faust-lv2

Albert Gräf <Dr.Graef@t-online.de>, January 01, 2013

This project provides an LV2 plugin architecture for the Faust programming language. The package contains the Faust architecture and templates for the needed LV2 manifest (ttl) files, a few sample plugins written in Faust, and some generic waf build scripts for compiling the plugins.

faust-lv2 is free and open source software. The latest version of the software can be found at http://faust-lv2.googlecode.com.

Copying

Copyright (c) 2012 by Albert Gräf <Dr.Graef@t-online.de>

This document is available under the GNU Free Documentation License.

faust-lv2 is distributed under the GNU Lesser General Public License v3+. Please see the included COPYING and COPYING.LESSER files for details.

Overview

The aim of this project is to provide a full-featured LV2 implementation of Faust plugins which supports both audio and instrument plugins. It is the first (and, at the time of this writing, the only) LV2 implementation for Faust.

Faust is Grame’s functional signal processing language. LV2 is the new open-source audio and MIDI plugin standard for Linux and other Unix-like systems, successor of the venerable LADSPA standard.

The main items in the faust-lv2 package are the Faust architecture files lv2.cpp and lv2synth.cpp which are needed to compile LV2 plugins with the Faust compiler. These are now included in recent Faust versions, but you can also install them from the faust-lv2 package if necessary.

The package also includes some sample Faust dsps in the effects and synths directories, as well as some waf scripts showing how to automatize the build process.

Supported hosts

LV2 is supported by sequencing and DAW software such as Ardour and Qtractor. Standalone hosts such as jalv and zynjacku can be used to interface with other software which doesn’t support LV2 yet, such as Rosegarden.

The current release has been tested and is known to work with jalv, Ardour3 (from the Ardour svn repository) and Qtractor 0.4.8 and later (including the latest svn versions of Qtractor). At the time of this writing, the current version of zynjacku and the related lv2rack don’t appear to work, possibly due to some bugs or incompatibilities with the latest iteration of the LV2 standard. We recommend using hosts based on Dave Robillard’s LV2 host library lilv instead. Until lilv becomes part of the major Linux distributions, you will have to build it from source or check the community repositories of your Linux distribution for corresponding packages. Both Ardour3 and Qtractor can be set up to work with this library.

Please note that faust-lv2 does not support custom plugin GUIs in the current version. This may be provided in the future, but for the time being you’ll have to rely on the LV2 host to display a GUI for the control elements. Both Ardour and Qtractor do a reasonably good job at this. However, note that the hierarchical layout of GUI controls prescribed by the Faust source is lost in the generic plugin GUIs provided by LV2 hosts.

Features

Plugins created with lv2.cpp and lv2synth.cpp provide the following basic features:

  • Audio inputs and outputs of the Faust dsp are made available as LV2 audio input and output ports.
  • Faust controls are made available as LV2 control ports with the proper label, initial value, range and (if supported by the host) step size. Both “active” (input) and “passive” (output) Faust controls are supported and mapped to the corresponding LV2 input and output ports, but note that most LV2 hosts don’t provide access to LV2 control output ports (a.k.a. Faust passive controls) at this time.
  • If the dsp defines any controls with corresponding MIDI mappings (midi:ctrl attributes in the Faust source), the plugin also provides an LV2 MIDI input port and interprets incoming MIDI controller messages accordingly. (This feature can also be disabled by configuring the package with the --nomidicc option or by resetting the FAUST_MIDICC macro in the architecture files, which may be useful if it gets in the way of the host’s own MIDI mapping and automation facilities. Also note that in the lv2synth.cpp architecture certain MIDI controllers have a predefined meaning and hence cannot be assigned using the midi:ctrl attribute, see below.)
  • Plugin name, description, author and license information provided as metadata in the Faust source is translated to the corresponding fields in the LV2 manifest of the plugin. (This can also be disabled with the --nometa configure option or by resetting the FAUST_META macro in the architecture files, if you’d rather like to provide this information yourself by editing the static manifest.)

The architectures also recognize the following Faust control metadata and set up the LV2 control port properties accordingly. Note that some of these properties rely on extensions which may not be supported by all LV2 hosts. Also note that all of these can be disabled by configuring with --nometa. Please refer to the LV2 documentation for a closer description of these options.

  • The unit attribute (e.g., [unit:Hz]) in the Faust source is translated to a corresponding LV2 unit attribute. The host may then display this information in its GUI rendering of the plugin controls.

  • LV2 scale points can be set with the lv2:scalePoint (or lv2:scalepoint) attribute on the Faust side. The value of this attribute in the Faust source takes the form of a list of pairs of descriptive labels and corresponding values, for instance:

    toggle = button("trigger [lv2:scalepoint on 1 off 0]");
    

    The host may then display the given scale points with a descriptive label in its GUI.

  • The lv2:integer attribute in the Faust source is translated to the lv2:integer LV2 port property, so that the control may be shown as an integer-only field in the host’s GUI.

  • The lv2:hidden or lv2:notOnGUI attribute maps to the LV2 notOnGUI port property, so that hosts honoring this property may suppress the display of this control in their GUI.

In addition, plugins created with lv2synth.cpp provide the necessary logic to drive a polyphonic synth with automatic voice allocation. The desired maximum number of voices can be configured with the --nvoices option or by setting the NVOICES macro in the lv2synth.cpp file accordingly. The plugin will manage that many instances of the Faust dsp. The actual number of voices can be changed dynamically from 1 to NVOICES with a special Polyphony control provided by the plugin.

This kind of plugin always provides a MIDI input port and interprets incoming MIDI note and pitch bend messages, as well as a number of General MIDI standard controller and sysex messages, as detailed below. By default, the synth units have a pitch bend range of +/- 2 semitones (General MIDI default) and are tuned in equal temperament with A4 at 440 Hz. These defaults can be adjusted as needed using some of the controller and sysex messages described below.

  • The “all notes off” (123) and “all sounds off” (120) MIDI controllers stop sounding notes on the corresponding MIDI channel.
  • The “all controllers off” (121) MIDI controller resets the current RPN and data entry controllers on the corresponding MIDI channel (see below).
  • The registered parameters (RPNs) 0 (pitch bend range), 1 (channel fine tuning) and 2 (channel coarse tuning) can be used to set the pitch bend range and fine/coarse master tuning on the corresponding MIDI channel in the usual way, employing a combination of the RPN (101, 100) and data entry controller pairs (6 and 38, as well as 96 and 97). Please check the MIDI specification for details.
  • Universal realtime and non-realtime scale/octave tuning messages following the MIDI Tuning Standard (MTS), Section MIDI Tuning Scale/Octave Extensions, can be used to set the synth to a given octave-based tuning specified as cent offsets relative to equal temperament, which is repeated in every octave of the MIDI note range 0..127. Please check the notes below for further details.

Note

The general format of the supported MTS messages is as follows (using hexadecimal notation):

f0 7f/7e <device-id> 08 08/09 bb bb bb tt ... tt f7

Note that the f0 7f and f0 7e headers are used to denote a universal realtime and non-realtime sysex message, respectively, and the final f7 byte terminates the message. Both types of messages will take effect immediately, but the realtime form will also change the frequencies of already sounding notes. The device id can be any 7-bit value from 00 to 7f and will be ignored, so that the unit will always respond to these messages, no matter which device id is specified. The following 08 id denotes an MTS message, followed either by the 08 subid to denote 1-byte, or the 09 subid to denote 2-byte encoding (see below).

The lv2synth.cpp architecture keeps track of separate tunings for different MIDI channels. The three bb bytes together specify the bitmask of MIDI channels the message applies to, most significant byte first; the bitmask 03 7f 7f thus sets the tuning for all MIDI channels, while the bitmask 00 00 01 only affects the tuning of the first MIDI channel. (Note that bits 3..7 of the most significant byte aren’t used right now, but are reserved by MTS for future use, so you shouldn’t set these unless you know what you’re doing.)

The tt bytes specify the tuning itself, as a sequence of 12 tuning offsets for the notes C, C#, D, etc., thru B. In the one-byte encoding (subid 08), each tuning offset is a 7 bit value in the range 00..7f, with 00, 40 and 7f denoting -64, 0 and +63 cents, respectively. Thus equal temperament is specified using twelve 40 bytes, and a quarter comma meantone tuning could be denoted, e.g., as 4a 32 43 55 3d 4e 36 47 2f 40 51 39. The two-byte encoding (subid 09) works in a similar fashion, but provides both an extended range and better resolution. Here each tuning offset is specified as a 14 bit value encoded as two data bytes (most significant byte first), mapping the range 0..16384 to -100..+100 cents with the center value 8192 (40 00) denoting 0 cents. Please check the MMA’s MIDI Tuning Standard document for details.

Plugins created with lv2.cpp and lv2synth.cpp also support the dynamic manifest extension, so that all requisite information about the plugin’s name, author, ports, etc. can be included in the plugin module itself. (To provide better compatibility with current LV2 hosts, which usually don’t have this extension enabled, this feature isn’t used by default in the provided build scripts. But you can select it by configuring faust-lv2 with the --dyn-manifest option, see the corresponding remarks under Static and dynamic manifests for details.)

Build requirements

To compile this package (or any Faust source using the included LV2 architecture files), you’ll need the GNU C++ compiler and the Boost headers, as well as recent versions of the Faust compiler and LV2. Faust versions 0.9.46 and 0.9.58 and LV2 1.0 have been tested and are known to work. At the time of this writing, the latest Faust and LV2 don’t seem to be packaged in most Linux distributions yet, so chances are that you’ll have to install these from source or check the community repositories of your Linux distribution for up-to-date packages.

You’ll also need Python to run the waf scripts; this should be readily available on most systems. Note that no installation of waf itself is required, as the needed scripts are all included in the distribution.

Installation

Please review the build requirements above and check that you have all the necessary third-party software installed.

faust-lv2 uses waf as its build tool. For waf usage instructions and information about the available configuration options, run waf in the main source directory as follows:

./waf --help

To compile the sample Faust plugins, you’ll have to run the following waf commands:

./waf configure
./waf

The compiled LV2 plugins will end up in the build/lv2/faust.lv2 directory, ready to be run with your favourite LV2 host.

To install the architecture files and the manifest templates in the Faust library directory (/usr/local/lib/faust by default) and the sample plugins in the system-wide LV2 directory (/usr/local/lib/lv2), run waf as follows:

./waf install

(You need to do this as root, e.g., via sudo, if you’re installing into system directories. Package maintainers can add the --destdir option to install into a staging directory.)

The installation step is optional. If you just want to try the plugins with your favourite LV2 host after compilation, it should be enough to set your LV2_PATH environment variable (or configure your LV2 host) so that the host includes the build/lv2 directory in its LV2 plugin search path.

The installation prefix can be changed with the --prefix configure option, and you can also use the --faustdir and --lv2dir options to set the Faust and LV2 installation paths in a direct fashion. In addition, you can change the default name faust.lv2 of the plugin directory with the --plugindir option.

Moreover, it is possible to build and install the plugins in separate bundles (one per plugin) by configuring with the --split-bundles option. The plugins will then be found in separate directories named after the plugin (amp.lv2 for the amp.dsp plugin etc.) so that you can also cherrypick the plugins that you want.

Last but not least, the configure options --no-faust-install and --no-plugin-install can be used to skip the installation of the architecture and template files and the sample plugins, respectively.

For instance, if you’d like to install the plugins as separate bundles under /opt/lib/lv2 and skip the installation of the Faust-related files, you’d run the following:

./waf configure --no-faust-install --lv2dir=/opt/lib/lv2 --split-bundles
./waf
sudo ./waf install

Static and dynamic manifests

By default, faust-lv2 uses static “manifests” (ttl files generated at compile time, cf. The manifest) for the plugins; this offers the best compatibility with existing LV2 hosts.

It is also possible to compile the plugins for LV2’s dynamic manifest extension which allows most of the manifest to be included in the plugin module itself. This is selected with the --dyn-manifest configure option, i.e.:

./waf configure --dyn-manifest

This option considerably speeds up the build process, but requires that the LV2 host supports the dynamic manifest extension. Unfortunately, at present there doesn’t seem to be any LV2 host which includes this extension out of the box, hence this isn’t the default right now. If you want to go that route, we recommend using hosts based on the lilv library, such as Ardour3 and the latest Qtractor versions. You’ll also have to configure lilv with --dyn-manifest when building it in order to enable the dynamic manifest extension.

Using the LV2 architectures in your own Faust programs

Basic usage of the architecture files is as with any other Faust architecture, i.e., you simply specify the architecture that you want with Faust’s -a option. E.g., the following creates a plain (audio-only) LV2 plugin from the Faust source:

faust -a lv2.cpp -cn myplugin myplugin.dsp -o myplugin.cpp

Note the use of Faust’s -cn (class name) option. This isn’t strictly necessary, but it sets a reasonable default for the plugin URI which is used to identify the plugin (see below for a more detailed explanation of this). If you do not specify this option, Faust will use the default class name mydsp.

Likewise, the following command creates a polyphonic synth (instrument) plugin which takes MIDI input and generates audio output:

faust -a lv2synth.cpp -cn mysynth mysynth.dsp -o mysynth.cpp

To make this work, the Faust dsp must be able to function as a monophonic synth which provides controls named freq, gain and gate to set the pitch, velocity and gate of a note, respectively. Voice allocation is then handled automatically in lv2synth.cpp. Please check the Faust sources (.dsp files) in the synths folder included in the distribution for examples which show how to implement such synth units in Faust.

In either case, you should now have the C++ source of the plugin in the current directory. To create an actual LV2 plugin from the C++ source, you’ll have to go through the following steps:

  • compile the plugin source to a shared module
  • create an LV2 “manifest” for the plugin
  • create an LV2 “bundle” containing the plugin module and its manifest

In the following, we take a look at each of these steps in turn.

Compiling the plugin

First, the C++ source file for the plugin must be compiled to a shared module using your favourite C++ compiler. For instance, using gcc under Linux:

g++ -shared myplugin.cpp -o myplugin.so

Add -fPIC and other compilation options such as -O3 as needed. To make this work, you’ll need the LV2 framework, so that the required LV2 header files are available. In the case of an instrument plugin created with the lv2synth.cpp architecture, you’ll also need some of the header files of the Boost C++ library, so you should have at least the headers from that project installed as well.

To create a working plugin, you may also have to modify the URI of the plugin. As already mentioned, the URI (“uniform resource identifier”, cf. RFC3986 and RFC1630) serves as a global name which is used to identify the plugin and differentiate it from other installed LV2 plugins, hence this name must be unique. For instance, the URI might be the actual URL of the website where the plugin may be downloaded, or it may just be an abstract name using one of the available URI schemes.

In order to support dynamic manifests, the URI needs to be included in the plugin source. The LV2 architectures thus contain a definition for the plugin URI. By default the URI http://faust-lv2.googlecode.com/myplugin will be used, where myplugin is the actual class name of the plugin as set with Faust’s -cn option, as discussed at the beginning of this section.

There are some macro definitions which can be adjusted on the g++ command line to change the URI according to your needs:

  • URI_PREFIX specifies the prefix of the URI to which the class name of the plugin will be appended.
  • PLUGIN_URI specifies the entire URI. This overrides the URI no matter what URI_PREFIX value and class name have been specified.

These values must be given as double-quoted strings. For instance, if the class name myplugin has been set with the Faust -cn option, then it’s usually sufficient to just change the URI prefix, e.g., as follows:

g++ -shared -DURI_PREFIX='"http://somewhere.org/plugins"' \
  myplugin.cpp -o myplugin.so

If you really need to adjust the entire plugin URI, you can also compile the plugin with:

g++ -shared -DPLUGIN_URI='"http://somewhere.org/plugins/myplugin"' \
  myplugin.cpp -o myplugin.so

In either case, the plugin URI given here must match the URI specified in the manifest of the plugin, see The manifest below.

The manifest

With the steps described in the previous subsection the compilation of the plugin is now essentially complete. However, LV2 also requires that you prepare a manifest.ttl file for the plugin, as explained in the LV2 documentation. The manifest is read by LV2 hosts to discover which plugins are available and how they can be loaded. You could prepare the manifest files by hand, but for non-trivial plugins this will be very error-prone and thus is not recommended. Therefore faust-lv2 comes with some templates which can be used to create the manifest files in a more or less automatic fashion, as explained below.

A minimal manifest must at least include the URI of the plugin and the name of the binary (shared module) from which the plugin can be loaded. If your LV2 host supports the dynamic manifest extension (see the corresponding remarks under Static and dynamic manifests), this is in fact all that is needed, since the remaining information (human-readable name, author and license of the plugin, port information, etc.) can be provided by the plugin module itself.

Manifest files are so-called RDF files written in Turtle syntax, which customarily have the .ttl filename extension. You can find a template for a minimal manifest in the lv2-manifest-template.ttl file which is included in the faust-lv2 distribution. This file can be edited by hand or, e.g., with the sed program to replace the placeholders @uri@, @name@ and @dllext@ with the URI, basename and the (OS-specific) filename extension of the plugin module. For instance:

sed -e 's?@uri@?http://somewhere.org/plugins/myplugin?g' \
  -e 's?@name@?myplugin?g' -e 's?@dllext@?.so?g' \
  < lv2-manifest-template.ttl > manifest.ttl

The plugin URI must match what was set when compiling the plugin, see Compiling the plugin above. Here’s how the created manifest might look like:

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix dman: <http://lv2plug.in/ns/ext/dynmanifest#> .

<http://somewhere.org/plugins/myplugin/manifest>
    lv2:binary <myplugin.so> ;
    a dman:DynManifest .

This is all that’s needed for the dynamic manifest case. If your LV2 host does not support the dynamic manifest extension (most LV2 hosts currently don’t, at least not out of the box), a static manifest will be needed in the form of a ttl file which includes all the requisite information about the plugin. Instead of lv2-manifest-template.ttl you should use lv2-manifest-static-template.ttl in this case. Replacing the @uri@ etc. placeholders in this file works the same as above. This will give you something like:

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<http://somewhere.org/plugins/myplugin>
    a lv2:Plugin ;
    lv2:binary <myplugin.so> ;
    rdfs:seeAlso <myplugin.ttl> .

Note that the static manifest refers to an additional .ttl file with the same basename as the plugin module which contains all the additional information about plugin name, ports etc. You can create this file from the same C++ source as the plugin module itself, by compiling the source to an executable. This executable, when run, simply prints the dynamic manifest of the plugin to standard output, from where it can be redirected into the ttl file. After creating the ttl file, the executable is no longer needed and can be deleted. For instance:

g++ myplugin.cpp -o myplugin
./myplugin > myplugin.ttl
rm myplugin

You should add to the g++ command all options that may be needed to compile the plugin. In particular, the same symbol definitions which set the plugin URI will be needed as described under Compiling the plugin.

LV2 bundles

Finally, shared module and manifest are packaged together as an LV2 bundle, a directory which customarily has the .lv2 extension. For dynamic manifests, this can be done as follows:

mkdir myplugin.lv2
cp myplugin.so manifest.ttl myplugin.lv2

If you use a static manifest, as described in the previous subsection, you must also copy the generated ttl file to this directory:

cp myplugin.ttl myplugin.lv2

You can then set up LV2_PATH to point to the parent directory of the myplugin.lv2 folder, or copy the folder to some directory on your LV2_PATH to have the plugin recognized by your LV2 host.

Bundling of plugin libraries

If you have more than one Faust plugin, you can simply repeat the three steps described above for each plugin to be compiled. However, in this case you also have the option to create a single bundle from the entire plugin collection. To these ends, just concatenate all the manifest.ttl files of the individual plugins to one big manifest.ttl file. Then place this file along with all the plugin modules (and, in the case of static manifests, the corresponding .ttl file for each plugin) into a single folder. (This is also the way that the faust-lv2 waf scripts package the plugins unless the --split-bundles configure option is given, cf. Installation.)

For instance, suppose that we have three plugins amp, chorus and freeverb in corresponding .lv2 subdirectories, then we can bundle them together in a single folder, say, faust.lv2, as follows:

mkdir faust.lv2
for plugin in amp chorus freeverb; do
  cat $plugin.lv2/manifest.ttl >> faust.lv2/manifest.ttl
  cp $plugin.lv2/$plugin.so $plugin.lv2/$plugin.ttl faust.lv2
done

Automatizing the build process

The steps needed to build and install a working LV2 plugin or an entire plugin collection from Faust source can of course be automatized, using any build tool of your choice. In fact the entire faust-lv2 package is just one big example which illustrates how to do this with the waf build tool. Please have a look at the waf scripts (wscript, *.py) included in the faust-lv2 package which should give you an idea how this works.

You can also just use the existing infrastructure of the faust-lv2 package as is. To these ends, drop your Faust programs into the effects and synths folders and run ./waf to have them compiled. (Instrument plugins should go into the synths directory so that they are compiled with the lv2synth.cpp architecture, while ordinary audio plugins go into the effects folder and are compiled with the lv2.cpp architecture.)

Future work

While the LV2 plugin implementation of the Faust LV2 architectures is fully functional and reasonably complete already, there are ways in which they could be further improved. These will hopefully be addressed in future releases.

  • Add improvements for smoother playback. In particular, the polyphony control provided by lv2synth.cpp is fairly disruptive right now, as it simply resets all voices each time the control changes. Also, it might be useful to implement crossfading to prevent clicks when a plugin is activated or deactivated.
  • Add custom plugin GUIs which honor the hierarchical GUI layout defined in the Faust source. Corresponding code is readily available in other Faust architectures such as jack-gtk and jack-qt, but would need to be integrated with the LV2 architectures and the LV2 GUI extension.
  • Add support for the new LV2 Time extension, which provides transport information such as current position, tempo and meter to a plugin.
  • Implement MIDI output for passive Faust controls. It’s unclear if and how existing LV2 hosts would process such data, however, so there’s still some research to be done there.