diff options
author | fundamental <mark.d.mccurry@gmail.com> | 2018-05-27 23:12:15 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2019-03-18 15:48:19 +0100 |
commit | 7b67c423a084898a2bc2f6569285550c0b6c3961 (patch) | |
tree | be1930a6475c6c3577aedd293db5795cf3cd1732 | |
parent | 18fb67838b5a2468e042cc13e422480ff552667c (diff) | |
download | lv2-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.adoc | 185 | ||||
-rw-r--r-- | doc/lv2_plugin_guide/guide.adoc | 622 | ||||
-rw-r--r-- | plugins/eg-amp.lv2/amp.c | 27 | ||||
-rw-r--r-- | plugins/eg-amp.lv2/amp.ttl | 4 |
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__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[] |