Generating a Pure Data (PD) Plugin

This section illustrates making a pd plugin using the Faust architecture file puredata.cpp, and Albert Gräf's faust2pd script (version included in the Planet CCRMA distribution). Familiarity with Pure Data (the pd program by Miller Puckette [65,66]) is assumed in this section. Also, the original faust2pd paper [31] contains the most complete description of faust2pd at the time of this writing.

Even if one prefers writing real-time signal-processing programs in C++, C, or assembly language,K.8the ability to generate user interfaces and plugins with Faust is compellingly useful.

To illustrate automatic generation of user-interface controls, we will add two ``numeric entry'' fields and one ``horizontal slider'' to our example of Fig.K.1. These controls will allow the plugin user to vary the center-frequency, bandwidth, and peak gain of the constant-peak-gain resonator in real time. A complete listing of cpgrui.dsp (``Constant-Peak-Gain Resonator with User Interface'') appears in Fig.K.7.

Figure K.7: Listing of cpgrui.dsp--a Faust program specifying a constant-peak-gain resonator with three user controls. Also shown are typical header declarations.

  declare name "Constant-Peak-Gain Resonator";
  declare author "Julius Smith";
  declare version "1.0";
  declare license "GPL";

  /* Controls */
  fr = nentry("frequency (Hz)", 1000, 20, 20000, 1);
  bw = nentry("bandwidth (Hz)", 100, 20, 20000, 10);
  g  = hslider("peak gain", 1, 0, 10, 0.01);

  /* Constants (Faust provides these in math.lib) */
  SR = fconstant(int fSamplingFreq, <math.h>);
  PI = 3.1415926535897932385;

  /* The resonator */
  process = firpart : + ~ feedback
  with {
    R = exp(0-PI*bw/SR); // pole radius [0 required]
    A = 2*PI*fr/SR;      // pole angle (radians)
    RR = R*R;
    firpart(x) = (x - x'') * (g) * ((1-RR)/2);
    // time-domain coeffs ASSUMING ONE PIPELINE DELAY:
    feedback(v) = 0 + 2*R*cos(A)*v - RR*v';

Generating the PD Plugin

The plugin may be compiled as follows:

  > faust -a puredata.cpp -o cpgrui-pd.cpp cpgrui.dsp
  > g++ -DPD -Wall -g -shared -Dmydsp=cpgrui \
        -o cpgrui~.pd_linux  cpgrui-pd.cpp
The first line uses faust to generate a compilable .cpp file, this time using the architecture file puredata.cpp which encapsulates the pd plugin API. The second line (which wraps) compiles cpgrui-pd.cpp to produce the dynamically loadable (binary) object file cpgrui~.pd_linux, which is our signal-processing plugin for pd. Such pd plugins are also called externals (externally compiled loadable modules). The filename extension ``.pd_linux'' indicates that the plugin was compiled on a Linux system.

Figure K.8 shows an example test patch,K.9 named cpgrui~-help.pd,K.10written (manually) for the generated plugin. By convention, the left inlet and outlet of a Faust-generated plugin correspond to control info and general-purpose messages. Any remaining inlets and outlets are signals.

Figure: Pure Data test patch cpgrui~-help.pd exercising features of the external pd plugin cpgrui~ generated by Faust using the puredata.cpp architecture file.

A simple ``bang'' message to the control-inlet of the plugin (sent by clicking on the ``button'' drawn as a circle-within-square in Fig.K.8), results in a list being sent to the control (left) outlet describing all plugin controls and their current state. The print object in Fig.K.8 prints the received list in the main pd console window. For our example, we obtain the following bang-response in the pd console:

  print: nentry /faust/bandwidth-Hz 100 100 20 20000 10
  print: nentry /faust/frequency-Hz 1000 1000 20 20000 1
  print: hslider /faust/peak-gain 1 1 0 10 0.01
These are the three controls we expected corresponding to the frequency, bandwidth, and gain of the resonator. However, note that the message-names generated for the controls have changed. In particular, spaces have been replaced by hyphens, and parentheses have been removed, to observe pd naming rules for messages [31].

Controls may be queried or set to new values in the plugin by sending the following pd messages:

  • frequency-Hz [newval]
  • bandwidth-Hz [newval]
  • peak-gain [newval]
The longer form of the control name printed in the pd console, e.g.,
/faust/peak-gain, is the complete ``fully qualified path'' that can be used to address controls within a hierarchy of nested controls and abstractions. For example, if we were to add the instance argument ``foo'' to the plugin (by changing the contents of the plugin box to ``cpgrui~ foo'' in Fig.K.8), then the path to the peak-gain control, for example, would become /foo/faust/peak-gain (see [31] and the Faust documentation for more details and examples).

In the test-patch of Fig.K.8, the various controls are exercised using pd message boxes. For example, the message ``peak-gain'' with no argument causes the plugin to print the current value of the peak-gain parameter on its control outlet. Messages with arguments, such as ``peak-gain 0.01'', set the parameter to the argument value without generating an output message. The slider and number-box output raw numbers, so they must be routed through a message-box in order to prepend the controller name (``peak-gain'' in this case).

The plugin input signal (second inlet) comes from a noise~ object in Fig.K.8, and the output signal (second outlet) is routed to both channels of the D/A converter (for center panning).

In addition to the requested controls, all plugins generated using the puredata.cpp architecture file respond to the boolean ``active'' message, which, when given a ``false'' argument such as 0, tells the plugin to bypass itself. This too is illustrated in Fig.K.8. Note that setting active to ``true'' at load time using a loadbangK.11 message is not necessary; the plugin defaults to the active state when loaded and initialized--no active message is needed. The loadbang in this patch also turns on pd audio computation for convenience.

Generating a PD Plugin-Wrapper Abstraction

The test patch of Fig.K.8 was constructed in pd by manually attaching user-interface elements to the left (control) inlet of the plugin. As is well described in [31], one can alternatively use the faust2pd script to generate a pd abstraction containing the plugin and its pd controllers. When this abstraction is loaded into pd, its controllers are brought out to the top level using the ``graph on parent'' mechanism in pd, as shown in Fig.K.10.

The faust2pd script works from the XML file generated by Faust using the -xml option:

  > faust -xml -a puredata.cpp -o cpgrui-pd.cpp cpgrui.dsp
  > faust2pd cpgrui.dsp.xml
Adding the -xml option results in generation of the file cpgrui.dsp.xml which is then used by faust2pd to generate cpgrui.pd. Type faust2pd -h (and read [31]) to learn more of the features and options of the faust2pd script.

The generated abstraction can be opened in pd as follows:

  > pd cpgrui.pd
Figure K.9 shows the result. As indicated by the inlet~ and outlet~ objects, the abstraction is designed to be used in place of the plugin. For this reason, we will refer to it henceforth as a plugin wrapper.

Figure K.9: Pure Data abstraction generated by faust2pd from the XML file emitted by Faust for the constant-peak-gain resonator (cpgrui.dsp).

Notice in Fig.K.9 that a plugin wrapper forwards its control messages (left-inlet messages) to the encapsulated plugin, as we would expect. However, it also forwards a copy of each control message to its control outlet. This convention facilitates making cascade chains of plugin-wrappers, as illustrated in faust2pd examples such as synth.pd.K.12

A PD Test Patch for the Plugin Wrapper

Figure K.10 shows pd patch developed (manually) to test the plugin wrapper generated by faust2pd. Compare this with Fig.K.8. Notice how the three controls are brought out to the plugin-wrapper object automatically using the ``graph on parent'' convention for pd abstractions with controllers. The bang button on the plugin resets all controls to their default values, and the toggle switch in the upper-right corner functions as a ``bypass'' switch (by sending the active message with appropriate argument). The previous mechanism of setting controls via message boxes to the control inlet still works, as illustrated. However, as shown in Fig.K.9 (or by opening the plugin-wrapper in pd), the control outlet simply receives a copy of everything sent to the control inlet. In particular, ``bang'' no longer prints a list of all controls and their settings, and controls cannot be queried.

Figure: Pure Data test patch (cpgrui-help.pd) for exercising the plugin-wrapper (cpgrui.pd) generated by faust2pd to control the faust-generated pd plugin (cpgrui~.pd_linux).

Next Section:
Generating a LADSPA Plugin via Faust
Previous Section:
A Look at the Generated C++ code