An API for soft synth plugins with custom user interfaces. Version 1.0.
DSSI (pronounced "dizzy") is a plugin API for software instruments (soft synths) with user interfaces, permitting them to be hosted in-process by audio applications. DSSI can be thought of as LADSPA-for-instruments, or something comparable to VSTi.
The proposal consists of this RFC, which describes the background and defines part of the proposed standard, plus a documented header file (dssi.h) which defines the remainder. The distribution also contains a handful of example files including a complete host implementation and a small number of synth plugins. The API itself is licensed under the LGPL.
This proposal was constructed by Steve Harris (steve <at> plugin <dot> org <dot> uk), Chris Cannam (cannam@all-day-breakfast.com), and Sean Bolton (musound <at> jps <dot> net).
One of the barriers to the acceptance of Linux audio software as a direct alternative to mainstream sequencer applications for other platforms is the lack of a comparable way to operate software synth plugins. There is an immediate need for an API that permits synth plugins to be written to a simple standard and then used in a range of host applications.
This is an awkward situation, because it is hoped that the forthcoming GMPI initiative will comprehensively address the needs of soft synths in a flexible cross-platform way. But the requirement for Linux applications to be able to support simple hosted synths is here now, and GMPI is not. Thus, we need to provide a simple interim solution in a way that will prove compelling enough and easy enough to support now, yet not so universal as to supplant GMPI or any other comprehensive future proposal. Hence this RFC.
Because of the conservative nature of this proposal, the main requirements are:
There are of course ways to run MIDI soft synths on Linux already, as well as some other initiatives that address parts of the same problem as this one.
The above three parts of the proposal are documented thoroughly in the dssi.h header file.
Most DSSI hosts will be multi-threaded applications, and an ideal DSSI host would be able to take advantage of multiple processors. Developers of DSSI hosts and plugins must implement appropriate interprocess synchronization measures, which should be as minimal and efficient as possible while allowing safe multi-threaded operation. Therefore, a clear delineation of responsibility in this regard between host and plugin is needed.
(The same delineation is also necessary for LADSPA plugins and for other APIs such as VST, but it is often assumed or deduced from practical examples rather than documented.)
To this end, each of the DSSI or LADSPA API functions is assigned to one of three 'classes', and restrictions are placed on when a host may make simultaneous calls to these functions, based on which classes of functions are in use.
The three classes of function are:
This class contains functions that instantiate and set up plugins before they are run, and that clean up and disinstantiate when they are no longer to be used. They are:
-- activate() -- cleanup() -- deactivate() -- instantiate()
This class contains functions that control the behaviour of an active or running plugin, or return information about a plugin's state, yet (for real-time plugins) are not expected to run in real time. They are:
-- configure() -- get_midi_controller_for_port() -- get_program()
The remaining functions belong to the audio class:
-- connect_port() -- run() -- run_adding() -- run_synth() -- run_synth_adding() -- run_multiple_synths() -- run_multiple_synths_adding() -- select_program() -- set_run_adding_gain()
It is not the intent of these class divisions to associate the functions with particular host threads. That is, while some hosts may call control class functions from a 'control' thread, and audio class functions from an 'audio' thread, nothing in these rules requires that. As long as the restrictions on simultaneous execution of the functions are met, host applications may be structured in any way that makes sense. These rules follow.
The restrictions that a DSSI host must observe within each instance group are:
These restrictions apply to each 'instance group' within a running DSSI system. When a host is using run_multiple_synths() with a particular plugin, then the instance group includes all active instances of that same plugin. When a plugin does not support run_multiple_synths() (or when it supplies both run_synth() and run_multiple_synths() but the host chooses to use run_synth()), then each instance of the plugin is its own instance group.
Because a single DSSI library (typically a dynamically-loaded *.so file) may contain several different plugins, it is important to clarify that 'instances of the same plugin' refers to all instances of one particular plugin within the library (that is, all instances with the same label and *.so file). It does NOT refer to all instances drawn from the same *.so file.
Because it is permissible for a host to simultaneously call one control class function and one audio class function (per instance group), it is the responsibility of the plugin to ensure that its internal data structures are appropriately protected (with e.g. mutexes or multi-thread-safe queues).
In practice it may be quite common for one host thread to call a control class function while another thread continues to repeatedly call a run*() function. Where possible, a plugin should continue synthesis in the run*() function while the control class function executes, but in cases where resource contention cannot be overcome, it is permissible -- perhaps even expected -- that the run*() function stop generating sound and return only silence until the control function completes.
Finally, all of the audio context functions provided by a plugin must obey restrictions similar to those placed on 'hard real-time' LADSPA plugins: no malloc() or free(), no libraries other than libc or libm, and no blocking I/O. (Unless, of course, the plugin is not intended for real-time operation.)
A synth user interface is an executable program, not a part of the plugin or a separate shared object. A host may elect to start or stop the UI for a plugin at any time, starting and terminating the executable at will.
The UI and host communicate with one another using OSC, the OpenSound Control protocol. See
http://www.cnmat.berkeley.edu/OpenSoundControl/
OSC is a simple message-based protocol intended for communications among sound devices. DSSI does not mandate any particular implementation of OSC, but it does require that it be based on a UDP transport (OSC itself is transport-independent). The example code uses an implementation by Steve Harris called liblo ("Lite OSC") which can be obtained from
http://liblo.sourceforge.net/
Note that liblo is distributed under a different licence from DSSI and so might not be a legal option for certain DSSI implementations.
DSSI uses OSC in both directions between the host and UI. When a user changes a configure, program, or port value in the UI, it sends an OSC request to the host, which informs the plugin of the change; when an automated change occurs in the host, or a plugin's output control port changes, the host sends an update to the UI.
(The host does not send updates to the UI for configure, program, or port changes that the UI itself initiated; likewise the UI must not send changes back to the host that the host itself initiated. A host that supports multiple UIs per plugin instance should send each change to all UIs for the instance other than the UI that initiated it.)
Communications between the host and UI are deliberately as limited as possible. There is, for example, no way for a UI to query the available port names, values, ranges etc for a plugin. It's expected that the UI will either share some code with the plugin so that it knows these things already, or will itself also load the plugin DLL and query it directly.
The mechanism by which a host locates and starts the UI for a plugin is host-dependent, and this section is only a recommendation.
For a typical graphical host, trying to start a GUI for a plugin labelled PLUGIN found in a dll named MYPLUGINS.so in directory DIRECTORY, we would recommend as follows.
The host looks for a directory DIRECTORY/MYPLUGINS/. If found, it looks for executable files or symbolic links in that directory beginning with the string PLUGIN or MYPLUGINS and ending with a suffix separated by an underscore, e.g. PLUGIN_gui, MYPLUGINS_qt. If nothing so named is found, it should conclude that there is no suitable UI for this plugin (i.e. files with no underscores in their names should not be used -- this permits the plugin UI author to include supporting or config files in the same directory). Otherwise, the host should select from among the results according to suffix: for a Qt application prefer things ending in _qt, for GTK prefer _gtk, etc; as another example, the Rosegarden sequencer will prefer files ending in _rg to anything else, as it assumes such a file is probably a link to the packager's preferred matching GUI. If both PLUGIN_suffix and MYPLUGINS_suffix are found for the same suffix, the host should use the more specific PLUGIN_suffix.
Thus, for a plugin UI author: if creating a graphical UI for a plugin labelled PLUGIN in dll MYPLUGINS.so, we recommend creating a single executable called PLUGIN_gui (or PLUGIN_gtk, PLUGIN_qt, PLUGIN_fltk depending on toolkit, if so inclined) and installing it to a subdirectory called MYPLUGINS of the directory containing MYPLUGINS.so.
Once the host has found a suitable executable, it then starts it with a command line consisting of:
0
as normal (including
the full path, so that the UI may locate either the MYPLUGINS.so or
supporting files in the MYPLUGINS subdirectory, if need be.)
If the UI supports the show/hide mechanism (which any graphical UI should), then it should initially be in hidden state. The UI then requests an update, passing its own OSC URL and base path to the host; the host responds by sending the sample rate and current configure, program and control values (in that order). The host must then call show() on the UI and startup is complete.
An OSC method call consists of a path -- identifying the method being called -- and a sequence of typed arguments.
The DSSI host and UI are each expected to think of an arbitrary path to associate with each plugin instance, known as the "base path". This will presumably have some internal and/or diagnostic meaning: e.g. a host might use "/dssi/MYPLUGINS/PLUGIN.1" for the path to the first instance of plugin labelled PLUGIN in MYPLUGINS.so. Individual method calls are always made to a subpath of the base path, as detailed below.
Base paths are exchanged on startup: the host gives its path to the UI on the command line, the UI returns its own as the argument to an update call.
These are the methods the host may support:
And these are the methods the UI may support:
A LADSPA plugin must never change the values of its own input ports.
A DSSI plugin is allowed to do so when select_program is called. The host must re-read the input port values after calling select_program, if it wishes to keep an accurate record of them. (The host should not notify any extant UI of the new values -- the UI is notified of the program change, and that should be enough.)
LADSPA says "hosts can reinitialise a plugin instance by calling deactivate() and then activate(). In this case the plugin instance must reset all state information dependent on the history of the plugin instance except for any data locations provided by connect_port() and any gain set by set_run_adding_gain()."
This is slightly ambiguous when applied to DSSI plugins that have internal state that does not change as a matter of course through time, but that is based on settings made via port and program changes or MIDI controller data.
On activate(), a DSSI plugin should reset any internal state that changes over time and is not controlled by the host. Anything that is set by the host (configure data, program data, port data, or any internal values derived from these) should be left alone, or, in the case of data that change over time from host-provided values, reset to the values that were most recently set by the host.
(The intention is that a host should be able to silence a plugin by calling deactivate followed by activate, and should know that after the activate call, the plugin has been reset to a state that is entirely defined by its host-visible configuration.)
LADSPA defines a "Unique ID" value within a plugin descriptor, which is intended to provide a globally unique identifier for the plugin type. Plugin authors are expected to liaise with an unnamed central body to ensure that their plugin IDs are in fact unique.
This is a problematic concept. Without an official LADSPA organisation, it's not obvious how an author actually obtains a unique ID range. Some authors may wish to write plugins that may vary in number, either by automatically generating them or by providing wrappers for other sorts of plugin. For such plugins, it is impossible to guarantee that the "unique" ID is in fact unique.
DSSI host authors are strongly recommended to ignore the LADSPA "Unique ID" when handling DSSI plugins. Instead, they should identify a DSSI plugin by the DLL's .so name and the LADSPA "Label" (which should be unique within a single .so file). Hosts that do otherwise will inevitably experience subtle but disastrous failures for some existing and yet-to-be-written plugins, because of the practical impossibility of making the "Unique ID" actually unique.