diff options
Diffstat (limited to 'plugins/eg-midigate.lv2')
-rw-r--r-- | plugins/eg-midigate.lv2/README.txt | 10 | ||||
-rw-r--r-- | plugins/eg-midigate.lv2/manifest.ttl.in | 10 | ||||
-rw-r--r-- | plugins/eg-midigate.lv2/midigate.c | 240 | ||||
-rw-r--r-- | plugins/eg-midigate.lv2/midigate.ttl | 56 | ||||
l--------- | plugins/eg-midigate.lv2/waf | 1 | ||||
-rw-r--r-- | plugins/eg-midigate.lv2/wscript | 61 |
6 files changed, 378 insertions, 0 deletions
diff --git a/plugins/eg-midigate.lv2/README.txt b/plugins/eg-midigate.lv2/README.txt new file mode 100644 index 0000000..8f4a0f0 --- /dev/null +++ b/plugins/eg-midigate.lv2/README.txt @@ -0,0 +1,10 @@ +== MIDI Gate == + +This plugin demonstrates: + + * Receiving MIDI input + + * Processing audio based on MIDI events with sample accuracy + + * Supporting MIDI programs which the host can control/automate, or present a + user interface for with human readable labels diff --git a/plugins/eg-midigate.lv2/manifest.ttl.in b/plugins/eg-midigate.lv2/manifest.ttl.in new file mode 100644 index 0000000..d32f1dc --- /dev/null +++ b/plugins/eg-midigate.lv2/manifest.ttl.in @@ -0,0 +1,10 @@ +# The manifest.ttl file follows the same template as the previous example. + +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://lv2plug.in/plugins/eg-midigate> + a lv2:Plugin ; + lv2:binary <midigate@LIB_EXT@> ; + rdfs:seeAlso <midigate.ttl> . diff --git a/plugins/eg-midigate.lv2/midigate.c b/plugins/eg-midigate.lv2/midigate.c new file mode 100644 index 0000000..70901f6 --- /dev/null +++ b/plugins/eg-midigate.lv2/midigate.c @@ -0,0 +1,240 @@ +/* + Copyright 2013-2016 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lv2/atom/atom.h" +#include "lv2/atom/util.h" +#include "lv2/core/lv2.h" +#include "lv2/core/lv2_util.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/urid/urid.h" + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MIDIGATE_URI "http://lv2plug.in/plugins/eg-midigate" + +typedef enum { + MIDIGATE_CONTROL = 0, + MIDIGATE_IN = 1, + MIDIGATE_OUT = 2 +} PortIndex; + +typedef struct { + // Port buffers + const LV2_Atom_Sequence* control; + const float* in; + float* out; + + // Features + LV2_URID_Map* map; + LV2_Log_Logger logger; + + struct { + LV2_URID midi_MidiEvent; + } uris; + + unsigned n_active_notes; + unsigned program; // 0 = normal, 1 = inverted +} Midigate; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + Midigate* self = (Midigate*)calloc(1, sizeof(Midigate)); + if (!self) { + return NULL; + } + + // Scan host features for URID map + const char* missing = lv2_features_query( + features, + LV2_LOG__log, &self->logger.log, false, + LV2_URID__map, &self->map, true, + NULL); + lv2_log_logger_set_map(&self->logger, self->map); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); + free(self); + return NULL; + } + + self->uris.midi_MidiEvent = self->map->map( + self->map->handle, LV2_MIDI__MidiEvent); + + return (LV2_Handle)self; +} + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Midigate* self = (Midigate*)instance; + + switch ((PortIndex)port) { + case MIDIGATE_CONTROL: + self->control = (const LV2_Atom_Sequence*)data; + break; + case MIDIGATE_IN: + self->in = (const float*)data; + break; + case MIDIGATE_OUT: + self->out = (float*)data; + break; + } +} + +static void +activate(LV2_Handle instance) +{ + Midigate* self = (Midigate*)instance; + self->n_active_notes = 0; + self->program = 0; +} + +/** + A function to write a chunk of output, to be called from run(). If the gate + is high, then the input will be passed through for this chunk, otherwise + silence is written. +*/ +static void +write_output(Midigate* self, uint32_t offset, uint32_t len) +{ + const bool active = (self->program == 0) + ? (self->n_active_notes > 0) + : (self->n_active_notes == 0); + if (active) { + memcpy(self->out + offset, self->in + offset, len * sizeof(float)); + } else { + memset(self->out + offset, 0, len * sizeof(float)); + } +} + +/** + This plugin works through the cycle in chunks starting at offset zero. The + +offset+ represents the current time within this this cycle, so + the output from 0 to +offset+ has already been written. + + MIDI events are read in a loop. In each iteration, the number of active + notes (on note on and note off) or the program (on program change) is + updated, then the output is written up until the current event time. Then + +offset+ is updated and the next event is processed. After the loop the + final chunk from the last event to the end of the cycle is emitted. + + There is currently no standard way to describe MIDI programs in LV2, so the + host has no way of knowing that these programs exist and should be presented + to the user. A future version of LV2 will address this shortcoming. + + This pattern of iterating over input events and writing output along the way + is a common idiom for writing sample accurate output based on event input. + + Note that this simple example simply writes input or zero for each sample + based on the gate. A serious implementation would need to envelope the + transition to avoid aliasing. +*/ +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Midigate* self = (Midigate*)instance; + uint32_t offset = 0; + + LV2_ATOM_SEQUENCE_FOREACH(self->control, ev) { + 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: + if (self->n_active_notes > 0) { + --self->n_active_notes; + } + break; + case LV2_MIDI_MSG_CONTROLLER: + if (msg[1] == LV2_MIDI_CTL_ALL_NOTES_OFF) { + self->n_active_notes = 0; + } + break; + case LV2_MIDI_MSG_PGM_CHANGE: + if (msg[1] == 0 || msg[1] == 1) { + self->program = msg[1]; + } + break; + default: break; + } + } + + write_output(self, offset, ev->time.frames - offset); + offset = (uint32_t)ev->time.frames; + } + + write_output(self, offset, sample_count - offset); +} + +/** + We have no resources to free on deactivation. + Note that the next call to activate will re-initialise the state, namely + self->n_active_notes, so there is no need to do so here. +*/ +static void +deactivate(LV2_Handle instance) +{} + +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + This plugin also has no extension data to return. +*/ +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +static const LV2_Descriptor descriptor = { + MIDIGATE_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; + } +} diff --git a/plugins/eg-midigate.lv2/midigate.ttl b/plugins/eg-midigate.lv2/midigate.ttl new file mode 100644 index 0000000..e14a329 --- /dev/null +++ b/plugins/eg-midigate.lv2/midigate.ttl @@ -0,0 +1,56 @@ +# The same set of namespace prefixes with two additions for LV2 extensions this +# plugin uses: atom and urid. + +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix midi: <http://lv2plug.in/ns/ext/midi#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . + +<http://lv2plug.in/plugins/eg-midigate> + a lv2:Plugin ; + doap:name "Example MIDI Gate" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:project <http://lv2plug.in/ns/lv2> ; + lv2:requiredFeature urid:map ; + lv2:optionalFeature lv2:hardRTCapable ; +# This plugin has three ports. There is an audio input and output as before, +# as well as a new AtomPort. An AtomPort buffer contains an Atom, which is a +# generic container for any type of data. In this case, we want to receive +# MIDI events, so the (mandatory) +atom:bufferType+ is atom:Sequence, which is +# a series of events with time stamps. +# +# Events themselves are also generic and can contain any type of data, but in +# this case we are only interested in MIDI events. The (optional) +# +atom:supports+ property describes which event types are supported. Though +# not required, this information should always be given so the host knows what +# types of event it can expect the plugin to understand. +# +# The (optional) +lv2:designation+ of this port is +lv2:control+, which +# indicates that this is the "main" control port where the host should send +# events it expects to configure the plugin, in this case changing the MIDI +# program. This is necessary since it is possible to have several MIDI input +# ports, though typically it is best to have one. + 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" + ] , [ + 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" + ] . diff --git a/plugins/eg-midigate.lv2/waf b/plugins/eg-midigate.lv2/waf new file mode 120000 index 0000000..59a1ac9 --- /dev/null +++ b/plugins/eg-midigate.lv2/waf @@ -0,0 +1 @@ +../../waf
\ No newline at end of file diff --git a/plugins/eg-midigate.lv2/wscript b/plugins/eg-midigate.lv2/wscript new file mode 100644 index 0000000..b6a85bb --- /dev/null +++ b/plugins/eg-midigate.lv2/wscript @@ -0,0 +1,61 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import re + +# Variables for 'waf dist' +APPNAME = 'eg-midigate.lv2' +VERSION = '1.0.0' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_c') + opt.load('lv2') + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + + if not autowaf.is_child(): + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2') + +def build(bld): + bundle = 'eg-midigate.lv2' + + # Make a pattern for shared objects without the 'lib' prefix + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + # Build manifest.ttl by substitution (for portable lib extension) + bld(features = 'subst', + source = 'manifest.ttl.in', + target = '%s/%s' % (bundle, 'manifest.ttl'), + install_path = '${LV2DIR}/%s' % bundle, + LIB_EXT = module_ext) + + # Copy other data files to build bundle (build/eg-midigate.lv2) + for i in ['midigate.ttl']: + bld(features = 'subst', + is_copy = True, + source = i, + target = '%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle) + + # Use LV2 headers from parent directory if building as a sub-project + includes = None + if autowaf.is_child: + includes = '../..' + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'midigate.c', + name = 'midigate', + target = '%s/midigate' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = 'LV2', + includes = includes) + obj.env.cshlib_PATTERN = module_pat |