aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfundamental <mark.d.mccurry@gmail.com>2018-05-27 23:12:15 -0400
committerDavid Robillard <d@drobilla.net>2019-03-18 15:48:19 +0100
commit7b67c423a084898a2bc2f6569285550c0b6c3961 (patch)
treebe1930a6475c6c3577aedd293db5795cf3cd1732
parent18fb67838b5a2468e042cc13e422480ff552667c (diff)
downloadlv2-7b67c423a084898a2bc2f6569285550c0b6c3961.tar.xz
Add initial WIP for more user friendly docs
See May 2018 lv2-devel discussion for some of the motivations for the different style of documentation.
-rw-r--r--doc/lv2_host_guide/lv2-host-perspective.adoc185
-rw-r--r--doc/lv2_plugin_guide/guide.adoc622
-rw-r--r--plugins/eg-amp.lv2/amp.c27
-rw-r--r--plugins/eg-amp.lv2/amp.ttl4
4 files changed, 834 insertions, 4 deletions
diff --git a/doc/lv2_host_guide/lv2-host-perspective.adoc b/doc/lv2_host_guide/lv2-host-perspective.adoc
new file mode 100644
index 0000000..c890c54
--- /dev/null
+++ b/doc/lv2_host_guide/lv2-host-perspective.adoc
@@ -0,0 +1,185 @@
+Introduction to LV2, a Plugin Host Perspective
+==============================================
+:toc:
+
+Intro
+-----
+
+Hello there, if you're reading this document then you're likely trying to figure
+out how to work with existing LV2 plugins and understanding some of the details
+of the LV2 spec.
+This document assumes some cursory knowledge about prior plugin standards
+including LADSPA(link)/DSSI(link)/VST(link)/etc.
+At the end of reading this guide you should be comfortable with setting up a
+basic host which can process audio, MIDI, and control events using several of
+the LV2 utility libraries in C.
+If you're looking for a C++ implementation, we'll briefly explain how the
+information within this guide can be translated at the end of this article.
+
+For the most part in this guide we'll refer to the official API specifications
+for more clarifications, though several code examples will come from one of
+several simple hosts including jalv(link) which is the reference implementation,
+lv2file(link) (a simple wav file processor), lv2apply(), lv2ls(link), and ...
+
+What can LV2 plugins do?
+------------------------
+
+talk about the raw basics of audio synthesis, audio effects, midi effects, and
+GUIs.
+Have a brief mention of extensions, but do *NOT* talk about them until later.
+
+Perhaps mention the existance of .ttl files here and say xxx is abstracted away
+by yyy.
+
+
+The LV2 Ecosystem
+-----------------
+
+If you're searching around for libraries related to LV2 you may come across a
+wide variety of them.
+This can include lilv(links), raul(for), suil(every), serd(one), sord(of the),
+LV2 toolkit(libraries), and a few others.
+Fortunately building a basic host is not that difficult and will primarily build
+off of lilv.
+
+Talk about the other components as need be
+
+How can you load a LV2 plugin?
+------------------------------
+
+So, now that we know that we're using lilv, how can we load a simple plugin.
+For this let's try to load one of the basic sample plugins 'eg-amp'.
+Plugins within LV2 are loaded based upon URIs, so in the case of the 'eg-amp'
+plugin LV2 names it 'http://lv2plug.in/plugins/eg-amp'.
+(perhaps a brief footnote on why this is the case, but nothing more)
+
+First let's verify that this plugin is installed on your system
+
+--------------------------------------------------------------------------------
+void list_plugins(void)
+{
+ LilvWorld *world = lilv_world_new();
+ lilv_world_load_all(world);
+ const LilvPlugins *plugins=lilv_world_get_all_plugins(lilvworld);
+
+ LilvIter *plug_itr = lilv_plugins_begin(plugins);
+ for(int i=0; i<lilv_plugins_size(plugins); ++i) {
+ LilvPlugin *plugin = lilv_plugins_get(plugins, plug_itr);
+ LilvNode *uri_node = lilv_plugin_get_uri(plugin);
+ const char *uri_str = lilv_node_as_uri(uri_node);
+ printf("plugin.uri = %s\n", uri_str);
+ plug_itr = lilv_plugins_next(plugins, plug_itr);
+ }
+
+ some_teardown_code_that_I_didn_t_bother_to_implement();
+}
+
+int main()
+{
+ list_plugins();
+}
+--------------------------------------------------------------------------------
+
+You can compile this snippet via XXX.
+If you need some help getting the dependencies setup then please see appendix A
+(this appendix should describe setup steps for the popular dev distros which at
+the moment are (IMO) Ubuntu & Arch).
+This code will open the lilv library and iterate through all the plugins
+available to the LV2 host.
+As long as you can see 'http://lv2plug.in/plugins/eg-amp' in the output, then
+we're all ready to go to the next step, putting a few samples into the plugin.
+
+
+--------------------------------------------------------------------------------
+LilvPlugin *get_the_plugin(void) {}
+
+xxx *create_plugin_instance(plugin, ...) {}
+
+//Activate instance
+
+//instance run
+
+//Cleanup
+
+--------------------------------------------------------------------------------
+
+At this point we've created a plugin instance, activated it, and run it, but
+how do we send data to it and get the results?
+
+That brings us to the concept of LV2 ports.
+
+- Input
+- Output
+- Audio
+- Control (we'll get to this later)
+- Etc (we'll get to this later)
+
+--------------------------------------------------------------------------------
+void connect_input_output_buffer() {...}
+--------------------------------------------------------------------------------
+
+With this connection in place we can connect the input an output data from the
+host:
+
+--------------------------------------------------------------------------------
+//feed in dummy data before processing
+
+//read out the same dummy data (assuming that eg-amp defaults to unison gain)
+--------------------------------------------------------------------------------
+
+Great, we now have a plugin running and processing data within our trivial host
+
+
+How do you control a LV2 plugin?
+--------------------------------
+
+This brings us to how you can interact with control ports, ...
+
+
+LV2 Extensions
+--------------
+
+The core of LV2 does A, B, & C, but by design the specification makes it
+possible for hosts to implement extensions that the plugins can make use of.
+A common one is X.
+Let's walk through the implementation of this extension and what happens when
+plugin hosts have the functionality and when they do not have it.
+For this example we're going to use the X plugin which you can download from
+http://...
+
+Where to go from here?
+----------------------
+
+At this point in the guide you should have a rough idea as to what components
+are needed for a LV2 host and how to assemble them.
+There's still plenty more that can be done with LV2 and knowing about the
+available resources can make this a much easier process.
+
+- http://lv2plug.in/api_spec_url
+- http://the_link_to_liblilv
+
+Example hosts:
+
+- jalv - recommended for learning a,b,c
+- lv2file - largely discussed within this guide
+- ardour - see link to small part of the git repo to understand how X is used in
+ a large host as well as A,B,C to understand a few extensions
+
+Other interesting projects:
+
+
+- LV2 mailing list
+- LV2 IRC chat
+- etc
+
+
+
+Appendix A: LV2 dependency setup
+--------------------------------
+
+Apt get yada yada
+Compiler tweak abc,
+
+pacman xxx
+
+
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...
diff --git a/plugins/eg-amp.lv2/amp.c b/plugins/eg-amp.lv2/amp.c
index c3ba279..433a9d4 100644
--- a/plugins/eg-amp.lv2/amp.c
+++ b/plugins/eg-amp.lv2/amp.c
@@ -36,17 +36,21 @@
file is a good convention to follow. If this URI does not match that used
in the data files, the host will fail to load the plugin.
*/
+// tag::AMP_URI[]
#define AMP_URI "http://lv2plug.in/plugins/eg-amp"
+// end::AMP_URI[]
/**
In code, ports are referred to by index. An enumeration of port indices
should be defined for readability.
*/
+// tag::PortIndex[]
typedef enum {
AMP_GAIN = 0,
AMP_INPUT = 1,
AMP_OUTPUT = 2
} PortIndex;
+// end::PortIndex[]
/**
Every plugin defines a private structure for the plugin instance. All data
@@ -54,12 +58,13 @@ typedef enum {
every instance method. In this simple plugin, only port buffers need to be
stored, since there is no additional instance data.
*/
+// tag::Amp[]
typedef struct {
- // Port buffers
- const float* gain;
- const float* input;
- float* output;
+ const float* gain; // Gain control input
+ const float* input; // Audio input
+ float* output; // Audio output
} Amp;
+// end::Amp[]
/**
The `instantiate()` function is called by the host to create a new plugin
@@ -71,6 +76,7 @@ typedef struct {
This function is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.
*/
+// tag::instantiate[]
static LV2_Handle
instantiate(const LV2_Descriptor* descriptor,
double rate,
@@ -81,6 +87,7 @@ instantiate(const LV2_Descriptor* descriptor,
return (LV2_Handle)amp;
}
+// end::instantiate[]
/**
The `connect_port()` method is called by the host to connect a particular
@@ -90,6 +97,7 @@ instantiate(const LV2_Descriptor* descriptor,
This method is in the ``audio'' threading class, and is called in the same
context as run().
*/
+// tag::connect_port[]
static void
connect_port(LV2_Handle instance,
uint32_t port,
@@ -109,6 +117,7 @@ connect_port(LV2_Handle instance,
break;
}
}
+// end::connect_port[]
/**
The `activate()` method is called by the host to initialise and prepare the
@@ -124,6 +133,8 @@ activate(LV2_Handle instance)
{
}
+// tag::run[]
+
/** Define a macro for converting a gain in dB to a coefficient. */
#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f)
@@ -149,6 +160,8 @@ run(LV2_Handle instance, uint32_t n_samples)
}
}
+// end::run[]
+
/**
The `deactivate()` method is the counterpart to `activate()`, and is called by
the host after running the plugin. It indicates that the host will not call
@@ -171,11 +184,13 @@ deactivate(LV2_Handle instance)
This method is in the ``instantiation'' threading class, so no other
methods on this instance will be called concurrently with it.
*/
+// tag::cleanup[]
static void
cleanup(LV2_Handle instance)
{
free(instance);
}
+// end::cleanup[]
/**
The `extension_data()` function returns any extension data supported by the
@@ -193,6 +208,8 @@ extension_data(const char* uri)
return NULL;
}
+// tag::descriptor[]
+
/**
Every plugin must define an `LV2_Descriptor`. It is best to define
descriptors statically to avoid leaking memory and non-portable shared
@@ -228,3 +245,5 @@ lv2_descriptor(uint32_t index)
default: return NULL;
}
}
+
+// end::descriptor[]
diff --git a/plugins/eg-amp.lv2/amp.ttl b/plugins/eg-amp.lv2/amp.ttl
index 9f522a1..87fd76a 100644
--- a/plugins/eg-amp.lv2/amp.ttl
+++ b/plugins/eg-amp.lv2/amp.ttl
@@ -2,11 +2,13 @@
# `manifest.ttl`. This is done so the host only needs to scan the relatively
# small `manifest.ttl` files to quickly discover all plugins.
+# tag::prefixes[]
@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#> .
+# end::prefixes[]
# First the type of the plugin is described. All plugins must explicitly list
# `lv2:Plugin` as a type. A more specific type should also be given, where
@@ -36,6 +38,7 @@
"Просто Усилитель"@ru ;
doap:license <http://opensource.org/licenses/isc> ;
lv2:optionalFeature lv2:hardRTCapable ;
+# tag::ports[]
lv2:port [
# Every port must have at least two types, one that specifies direction
# (lv2:InputPort or lv2:OutputPort), and another to describe the data type.
@@ -88,3 +91,4 @@
lv2:symbol "out" ;
lv2:name "Out"
] .
+# end::ports[]