aboutsummaryrefslogtreecommitdiffstats
path: root/doc/lv2_plugin_guide
diff options
context:
space:
mode:
Diffstat (limited to 'doc/lv2_plugin_guide')
-rw-r--r--doc/lv2_plugin_guide/guide.adoc622
1 files changed, 622 insertions, 0 deletions
diff --git a/doc/lv2_plugin_guide/guide.adoc b/doc/lv2_plugin_guide/guide.adoc
new file mode 100644
index 0000000..df085a0
--- /dev/null
+++ b/doc/lv2_plugin_guide/guide.adoc
@@ -0,0 +1,622 @@
+Programming LV2 Plugins
+=======================
+:toc:
+:source-highlighter: pygments
+
+
+Introduction
+------------
+
+The link:http://lv2plug.in/ns[LV2 specification] is a large collection of documentations and extensions to
+the core LV2 API.
+On their own they're difficult to parse.
+In this document a collection of example plugins are presented to show how the
+components of LV2 fit together and demonstrate best practices when writing LV2
+plugins.
+
+Overall this guide should be considered a complement for the more complete LV2
+specification.
+The examples presented will correspond with those included in the LV2
+distribution and will be presented in order of increasing complexity.
+
+This includes:
+
+A simple amplifier:: Shows the basic LV2 skeleton, introduces control ports,
+provides basic .ttl file information
+A MIDI gate:: Shows midi processing, introduces LV2 atoms, introduces LV2
+extension API
+Fifths:: A more complex MIDI processor, explains ??? LV2 concept
+Metromome:: Explores timining information, explains ??? LV2 concept
+A trivial sampler:: Provides a MIDI waveform sampler, introduces the LV2 worker
+extension and extensions in general
+A GUI oscilloscope:: Discusses user interfaces in the context of the LV2
+ecosystem
+
+This manual encourages users to jump around, but recommends reading the LV2
+asides in each chapter in order to best understand how the LV2 specific
+concepts relate to each other.
+
+Building a basic amplifier
+--------------------------
+
+In this example we're going to build a simple amplifier plugin.
+There will be audio input, audio output, and a gain control.
+
+[ditaa]
+--------------------------------------------------------------------------------
+
+ Gain In
+ |
+ |
+ v
+ +-------------+
+ | |
+Audio In ----> | Amp | ------> Audio Out
+ | |
+ +-------------+
+--------------------------------------------------------------------------------
+
+
+Part 1: The code
+~~~~~~~~~~~~~~~~
+
+Let's start off by including the core LV2 header.
+
+[source, C]
+--------------------------------------------------------------------------------
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+--------------------------------------------------------------------------------
+
+The include path of LV2 headers reflect the URI of the specification they are a part of by design.
+This makes it easy to find detailed information about the API.
+This header is part of the "core" LV2 specification: http://lv2plug.in/ns/lv2core.
+
+LV2 plugins are are responsible for keeping track of what ports they have and what host-controlled data structures they're attached to.
+In our simple amplifier, these are the only variables stored in the plugin structure:
+
+[source, C]
+----
+include::../../plugins/eg-amp.lv2/amp.c[tags=Amp]
+----
+
+We also define an `enum` for their indices to keep the code readable:
+
+[source, C]
+----
+include::../../plugins/eg-amp.lv2/amp.c[tags=PortIndex]
+----
+
+With the data for our amplifier set up we need to implement the functions needed
+for the host to communicate with our plugin.
+These functions are summarized in the link:http://lv2plug.in/doc/html/group__lv2core.html#structLV2&lowbar;_Descriptor[LV2_Descriptor] which is passed to a
+host and it includes:
+
+//More details in later examples
+// Actually the LV2 spec for these should have a bunch more detail on each,
+// though perhaps it should still be integrated
+
+instantiate:: Allocate and initialize the plugin.
+connect_port:: Connect a port to a host-provided buffer.
+activate:: Reset the plugin to a default state and prepare it to run.
+run:: Process data for a given number of samples.
+deactivate:: Stop the plugin from running (counterpart to activate).
+cleanup:: Free plugin (counterpart to instantiate).
+extension_data:: Get data defined by LV2 extensions.
+
+This plugin does not need the `activate`, `deactivate`, or `extension_data` functions, which are optional and do not need to be defined if they are not needed.
+
+NOTE:: For this simple plugin we don't really need to care about when each one
+of these callbacks can be called. For more detailed information about what
+guarantees you have for when each callback may be invoked see XXX
+
+Since this plugin contains minimal state, the `instantiate` and `cleanup` functions simply allocate and free the `Amp` structure:
+
+[source, C]
+--------------------------------------------------------------------------------
+include::../../plugins/eg-amp.lv2/amp.c[tags=instantiate]
+--------------------------------------------------------------------------------
+
+[source, C]
+--------------------------------------------------------------------------------
+include::../../plugins/eg-amp.lv2/amp.c[tags=cleanup]
+--------------------------------------------------------------------------------
+
+Connecting ports provided by the LV2 host is somewhat more complex.
+Each call from the host will provide a pointer to a buffer for one of the ports.
+The plugin will need to store that pointer and either read from it or write to
+it depending upon if it's an input or an output.
+
+[source, C]
+----
+include::../../plugins/eg-amp.lv2/amp.c[tags=connect_port]
+----
+
+After the host has connected data to each one of the ports and activated the
+plugin then the amplfier LV2 plugin can run for a number of samples.
+
+[source, C]
+----
+include::../../plugins/eg-amp.lv2/amp.c[tags=run]
+----
+
+All that's left is providing the host with a descriptor containing pointers to
+all of these functions.
+Since multiple LV2 plugins can be contained in the same shared library this is
+done through the exported lv2_descriptor(link) function:
+
+[source, C]
+----
+include::../../plugins/eg-amp.lv2/amp.c[tags=descriptor]
+----
+
+Part 2: The specification
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Now that we have an amplifier plugin setup, you might expect that a host could
+load it and be ready to run.
+The code however doesn't define all of the details.
+For instance the code doesn't provide a list of the inputs and outputs of the
+plugin, so how does the host know about them?
+
+The answer is that LV2 uses data files to store this information.
+
+
+LV2 plugins are defined in two parts: code and data.
+The code is written in C, or any C compatible language such as C++.
+Static data is described separately in the human and machine friendly http://www.w3.org/TeamSubmission/turtle/[Turtle] syntax.
+
+Generally, the goal is to keep code minimal,
+and describe as much as possible in the static data.
+There are several advantages to this approach:
+
+ * Hosts can discover and inspect plugins without loading or executing any plugin code.
+ * Plugin data can be used from a wide range of generic tools like scripting languages and command line utilities.
+ * The standard data model allows the use of existing vocabularies to describe plugins and related information.
+ * The language is extensible, so authors may describe any data without requiring changes to the LV2 specification.
+ * Labels and documentation are translatable, and available to hosts for display in user interfaces.
+
+
+A .ttl file maps a collection of properties to values using a collection of
+URLs.
+Now let's build one for the amplifier called 'amp.ttl'
+
+We can start out by saying that the AMP_URL which we previously defined is 'a'
+plugin:
+
+[source, turtle]
+--------------------------------------------------------------------------------
+<http://lv2plug.in/plugins/eg-amp>
+ a http://lv2plug.in/ns/lv2core#Plugin .
+--------------------------------------------------------------------------------
+
+Since we're going to define a number of properties in the LV2 namspace, let's
+define a few macros to condense the file and since we're defining multiple
+properties on our plugin let's use the continuation ';' rather than the property
+terminator '.'.
+
+[source, turtle]
+--------------------------------------------------------------------------------
+include::../../plugins/eg-amp.lv2/amp.ttl[tags=prefixes]
+--------------------------------------------------------------------------------
+
+Now we can begin describing our plugin, by first stating that it is 'a' LV2 plugin:
+
+[source, turtle]
+--------------------------------------------------------------------------------
+<http://lv2plug.in/plugins/eg-amp>
+ a lv2:Plugin ;
+--------------------------------------------------------------------------------
+
+IMPORTANT: The URI used to identify the plugin in the data must match the one in the code, `AMP_URI` in this example.
+
+From here we can provide information to what project the plugin is associated
+with, a display name, a license, and even specify features of the plugin:
+[source, turtle]
+--------------------------------------------------------------------------------
+ lv2:project <http://lv2plug.in/ns/lv2> ;
+ doap:name "Simple Amplifier" ;
+ doap:license <http://opensource.org/licenses/isc> ;
+ lv2:optionalFeature lv2:hardRTCapable ;
+--------------------------------------------------------------------------------
+
+Now as per defining the inputs and outputs of the plugin, let's consider what
+information we need to specify.
+
+- Type of data
+- Input vs output
+- Name of the port for a host to display
+- Index in the C code
+- etc
+
+For the control port it could also be handy to state the default value, a
+minimum, maximum, and what sort of units are present.
+All of this information helps hosts display the state of the plugin.
+
+With these basics in mind we have the following port description:
+
+[source, turtle]
+--------------------------------------------------------------------------------
+include::../../plugins/eg-amp.lv2/amp.ttl[tags=ports]
+--------------------------------------------------------------------------------
+
+Part 3: The bundle
+~~~~~~~~~~~~~~~~~~
+
+Phew, that's a lot of setup, but we're almost there.
+All that's left is creating a bundle for the audio plugin and describing the
+contents in a manifest .ttl.
+What's a bundle exactly?
+Well a bundle is a directory that includes:
+
+- The plugin's executable library
+- One ttl file per plugin in the library to define ports and describe features
+ about the plugin
+- One manifest .ttl file providing metadata about what ttl files a host needs to
+ look at
+
+This manifest file makes searching through your plugin library a quick and
+efficient process for plugin hosts and as such you're expected to have a tiny
+manifest.
+For this project it is:
+
+[source, turtle]
+--------------------------------------------------------------------------------
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://lv2plug.in/plugins/eg-amp>
+ a lv2:Plugin ;
+ lv2:binary <amp.so> ;
+ rdfs:seeAlso <amp.ttl> .
+--------------------------------------------------------------------------------
+
+In that manifest we define that:
+
+- The bundle only contains one plugin
+- The URI of that plugin
+- The files that the host needs to use the plugin.
+
+
+Part 4: A recap
+~~~~~~~~~~~~~~~
+
+amp.c:
+[source, C]
+--------------------------------------------------------------------------------
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+#define AMP_URI "http://lv2plug.in/plugins.eg-amp"
+
+typedef struct {
+ const float *gain; //control input
+ const float *data_in; //audio input
+ float *data_out; //audio output
+} Amp;
+
+typedef enum {
+ AMP_GAIN,
+ AMP_INPUT,
+ AMP_OUTPUT
+} PortIndex;
+
+static void activate(LV2_Handle instance) {}
+static void deactivate(LV2_Handle instance) {}
+static const char *extension_data(const char *uri) { return NULL; }
+
+static LV2_Handle instantiate(const LV2_Descriptor *,
+double, const char *, const LV2_Features**) { return calloc(sizeof(Amp)); }
+static void cleanup(LV2_Handle instance) { free(instance) };
+
+static void
+connect_port(LV2_Handle instance,
+ uint32_t port,
+ void* data)
+{
+ Amp* amp = (Amp*)instance;
+
+ switch ((PortIndex)port) {
+ case AMP_GAIN: amp->gain = (const float*)data; break;
+ case AMP_INPUT: amp->input = (const float*)data; break;
+ case AMP_OUTPUT: amp->output = (float*)data; break;
+ }
+}
+
+#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f)
+
+static void
+run(LV2_Handle instance, uint32_t n_samples)
+{
+ const Amp* amp = (const Amp*)instance;
+
+ const float gain = *(amp->gain);
+ const float* const input = amp->input;
+ float* const output = amp->output;
+
+ const float coef = DB_CO(gain);
+
+ for (uint32_t i = 0; i < n_samples; ++i)
+ output[i] = input[i] * coef;
+}
+
+static const LV2_Descriptor descriptor = {
+ AMP_URI,
+ instantiate,
+ connect_port,
+ activate,
+ run,
+ deactivate,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0: return &descriptor;
+ default: return NULL;
+ }
+}
+--------------------------------------------------------------------------------
+
+amp.ttl:
+[source, turtle]
+--------------------------------------------------------------------------------
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+<http://lv2plug.in/plugins/eg-amp>
+ a lv2:Plugin ;
+ lv2:project <http://lv2plug.in/ns/lv2> ;
+ doap:name "Simple Amplifier" ;
+ doap:license <http://opensource.org/licenses/isc> ;
+ lv2:optionalFeature lv2:hardRTCapable ;
+ lv2:port [
+ a lv2:InputPort , lv2:ControlPort ;
+ lv2:index 0 ;
+ lv2:symbol "gain" ;
+ lv2:name "Gain"
+ lv2:default 0.0 ;
+ lv2:minimum -90.0 ;
+ lv2:maximum 24.0 ;
+ units:unit units:db ;
+ ] , [
+ a lv2:AudioPort , lv2:InputPort ;
+ lv2:index 1 ;
+ lv2:symbol "in" ;
+ lv2:name "In"
+ ] , [
+ a lv2:AudioPort , lv2:OutputPort ;
+ lv2:index 2 ;
+ lv2:symbol "out" ;
+ lv2:name "Out"
+ ] .
+--------------------------------------------------------------------------------
+
+manifest.ttl:
+[source, turtle]
+--------------------------------------------------------------------------------
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://lv2plug.in/plugins/eg-amp>
+ a lv2:Plugin ;
+ lv2:binary <amp.so> ;
+ rdfs:seeAlso <amp.ttl> .
+--------------------------------------------------------------------------------
+
+That's the first LV2 plugin example out of the way.
+In this chapter we introduced the code skeleton needed to run a LV2 plugin, the
+necessary supporting metadata files, and how they can be bundled together to
+form a distributable bundle.
+This is just the basics of the LV2 API and we'll start to get a bit more in
+depth with the next example which starts processing MIDI events.
+
+Building a MIDI Gate
+--------------------
+
+Now that the basic skeleton is out of the way, let's talk about a slightly more
+complex control data in the form of MIDI.
+Sticking with the same core functionality of an amplifier, let's build a new
+plugin that either outputs the input signal or outputs a sequence of zeros
+depending upon the current MIDI input.
+
+[ditaa]
+--------------------------------------------------------------------------------
+
+ MIDI (LV2 atom)
+ |
+ |
+ v
+ +-------------+
+ | |
+Audio In ----> | Gate | ------> Audio Out
+ | |
+ +-------------+
+--------------------------------------------------------------------------------
+
+
+Part 1: What's an Atom?
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Instead of starting with the code for this new plugin, let's take a look at how
+the '.ttl' files will differ.
+The manifest will be nearly identical once the plugin name is changed.
+The midigate.ttl file will feature a replacement for the old control port
+looking like:
+
+[source,turtle]
+--------------------------------------------------------------------------------
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:designation lv2:control ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control"
+ ] , [
+--------------------------------------------------------------------------------
+
+We've got a new type of port which features LV2 atoms.
+A http://lv2plug.in/ns/ext/atom[LV2 Atom] is essentially a flexible container
+for all sorts of different data. It could be floats, strings, arrays, etc.
+In this case we're specifying that it uses
+http://lv2plug.in/ns/ext/midi/[MIDI] by specifying 'lv2:supports'.
+Lastly we provide a hint to the host that the primary (and currently only)
+control input is this port by specifying the 'lv2:destination'.
+
+Part 1b: What's an Atom URID?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Both 'midi' and 'atom' are in the extension namespace of LV2, but they're
+generally considered part of the core set of features a host should provide.
+Going beyond them, there is the http://lv2plug.in/ns/ext/urid[URID] extension
+which makes using Atom ports a bit easier.
+So, let's include that as well:
+
+[source,turtle]
+--------------------------------------------------------------------------------
+ lv2:requiredFeature urid:map ;
+--------------------------------------------------------------------------------
+
+Part 2: Handling Atoms in C
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A large bit of this plugin is going to still use the same skeleton as the first
+example, so let's focus on what is different.
+To start off with, we're going to need headers for each one of the chunks of the
+API we're using:
+
+[source,C]
+--------------------------------------------------------------------------------
+#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
+#include "lv2/lv2plug.in/ns/ext/atom/util.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate"
+--------------------------------------------------------------------------------
+
+Next up we'll want to store our data which consists of:
+
+- The port bindings
+- A URID which identifies a MIDI event from other Atom events
+- The number of active notes
+
+[source, C]
+--------------------------------------------------------------------------------
+typedef struct {
+ const LV2_Atom_Sequence* control;
+ const float* in;
+ float* out;
+
+ LV2_URID midi_MidiEvent;
+
+ unsigned n_active_notes;
+} Midigate;
+--------------------------------------------------------------------------------
+
+Now in instantiate we can find the value of the MIDI event URID:
+
+
+--------------------------------------------------------------------------------
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* bundle_path,
+ const LV2_Feature* const* features)
+{
+ //Search the provided feature list for the requested URID mapper
+ LV2_URID_Map* map = NULL;
+ for (int i = 0; features[i]; ++i) {
+ if (!strcmp(features[i]->URI, LV2_URID__map)) {
+ map = (LV2_URID_Map*)features[i]->data;
+ break;
+ }
+ }
+
+ //A host MUST provide the required features
+ assert(map);
+
+ Midigate* self = (Midigate*)calloc(1, sizeof(Midigate));
+ //Store the URID for a midi event
+ self->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ return (LV2_Handle)self;
+}
+--------------------------------------------------------------------------------
+
+Now to handle the actual MIDI events.
+Within the run() function we can step through all of the events in the control
+port and handle them in-order.
+What this means for the output port is that we can process a chunk of samples up
+to the new MIDI event, update the number of notes, and then process the next
+chunk of data.
+
+[source, C]
+--------------------------------------------------------------------------------
+static void
+run(LV2_Handle instance, uint32_t sample_count)
+{
+ Midigate* self = (Midigate*)instance;
+ size_t offset = 0;
+
+ LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) {
+ write_output(self->out + offset, self->in + offset,
+ self->n_active_notes > 0,
+ ev->time.frames - offset);
+
+
+ if (ev->body.type == self->uris.midi_MidiEvent) {
+
+ const uint8_t* const msg = (const uint8_t*)(ev + 1);
+
+ switch (lv2_midi_message_type(msg)) {
+ case LV2_MIDI_MSG_NOTE_ON: ++self->n_active_notes; break;
+ case LV2_MIDI_MSG_NOTE_OFF: --self->n_active_notes; break;
+ }
+ }
+
+
+ offset = ev->time.frames;
+ }
+
+ write_output(self->out + offset, self->in + offset,
+ self->n_active_notes > 0,
+ sample_count - offset);
+}
+
+static void
+write_output(float *dst, const float *src, bool active, size_t len) {
+ if (active)
+ memcpy(dest, src, len * sizeof(float));
+ else
+ memset(dest, 0, len * sizeof(float));
+}
+--------------------------------------------------------------------------------
+
+Now the last bit of housekeeping.
+When we first initialize the plugin all of the data is zeroed out, but that
+isn't the case if the host deactivates the plugin and reruns the 'activate()'
+function.
+We need to preserve any connections, but wipe out any state in the plugin.
+For the MIDI gate this is just clearing the number of active notes
+
+[source, C]
+--------------------------------------------------------------------------------
+static void activate(LV2_Handle instance) {
+ Midigate* self = (Midigate*)instance;
+ self->n_active_notes = 0;
+}
+--------------------------------------------------------------------------------
+
+
+Part 3: MIDI Gate Wrapup
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+In review...