Anatomy of an LV2 plugin

Introduction

LV2 is an open standard for creating sound effect plugins. It is believed to be primarily intended for the Linux operating system, although there are no restrictions to its use on other systems. Before that, there were already two similar standards in Linux - LADSPA and DSSI. The first of them was intended mainly for processing audio signals and practically could not work with MIDI data. The second, on the contrary, was conceived as a standard for virtual synthesizers.





The name LV2 itself is an abbreviation for LADSPA version 2 , it is a new, improved version of the standard. Unlike its predecessors, it allows you to process audio data, midi streams, create any user interface, and exchange any data with the host application. The standard also supports an extension mechanism. Thanks to this, LV2 can offer a number of additional features: a set of "factory" presets, saving state, logging. In theory, the user can create their own add-ons. Detailed documentation with examples is located at http://lv2plug.in





Organization

Surely many are familiar with the popular VST standard. In his case, the plugin and associated resources are usually contained within a single dynamic link library (DLL) file. More than one file is almost always used in the LV2 standard. The standard uses the concept of a bundle . I have not been able to find out if there is any Russian-language equivalent for this term. A bundle is a directory in the file system where all files related to this plugin are placed. According to the definition from the documentation: "LV2 Bundle is the directory containing the manifest.ttl file at the top level . "It is customary to name directories so that their name coincides with the name of the plugin, for example amsynth.lv2 or triceratops.lv2, but any names are allowed. Bundles path location specified in the system variable LV2_PATH (either defined directly in the settings of the host application). Several plugins can be located in one bundle at once.





URI. , , . URI , . , , . URI: ; URI . http://example.org/. lv2ls.





manifest.ttl , , . - , ( ). , manifest.ttl Turtle. ttl-, manifest.ttl ( ). , LV2.





(UI). , ( ) . , . . - . UI , . , .





- . ( ) . :





  • AudioPort โ€” . -. float.





  • ControlPort โ€” , . โ€” UI .





  • EventPort โ€” ( MIDI-)





  • CVPort โ€” (Control Voltage). ยซยป : (VCO), (VCF), (VCA)





ttl-. โ€” (index) (symbol), . . , , .





, , . ControlPort . -. , , .





LV2 โ€” . ( , , ), , . . , (, memcpy()). - Utilities Forge. , .





LV2- . , ( LV2_Descriptor).





  • instantiate() - , . , .





  • connect_port() - . , . void *. , run().





  • activate() - . , , , connect_port().





  • run() - . , . , , .





  • deactivate() - activate(). , run() activate(). .





  • cleanup() - . .





  • extension_data() - , . URI , .





, midi-, . example URI http://example.org





, manifest.ttl, -.





@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<http://example.org>
        a lv2:Plugin, lv2:InstrumentPlugin ;
        lv2:binary <example.so> ;
        rdfs:seeAlso <example.ttl> .
      
      



, . URI . 5 , ( https://lv2plug.in/ns/lv2core/lv2core.html). , example.ttl, .





:





@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 rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .

<http://example.org>
        a lv2:Plugin, lv2:InstrumentPlugin ;
        doap:name "Example" ;
        lv2:requiredFeature urid:map ;
        lv2:port [
                a lv2:InputPort, atom:AtomPort ;
                atom:bufferType atom:Sequence ;
                atom:supports atom:Sequence, midi:MidiEvent ;
                lv2:index 0 ;
                lv2:symbol "in_midi" ;
                lv2:name "Midi input" ;
        ], [
                a lv2:AudioPort, lv2:OutputPort ;
                lv2:index 1 ;
                lv2:symbol "out" ;
                lv2:name "Out"
        ] .
      
      



, , . . lv2:requiredFeature , ( optionalFeature). , , . requiredFeature instantiate(). , . / . ( , , , ).





13, . โ€” midi ( lv2:InputPort lv2:OutputPort). lv2:AudioPort , atom:AtomPort , Atom ( , ControlPort , ).





, . lv2:index lv2:symbol. , connect_port(), . ยซยป lv2:name. symbol . , .





:





#include <math.h>
#include <stdlib.h>
#include <stdbool.h>

#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/atom/util.h>
#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>

#define MURI "http://example.org"

enum Ports {
    IN_MIDI,
    OUT
};

typedef struct {
    LV2_Atom_Sequence *midiPort;
    float *outPort;
    int rate;
    bool soundOn;
    int currentSample;
    LV2_URID midiEvent;
} Plugin;


static LV2_Handle
instantiate(const LV2_Descriptor* descriptor,
            double rate,
            const char* bundle_path,
            const LV2_Feature* const* features) {

    Plugin *self = (Plugin *) malloc(sizeof(Plugin));
    self->rate = rate;
    self->currentSample = 0;
    self->soundOn = false;

    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;
        }
    }

    if (map == NULL) {
        return NULL;
    }
    self->midiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);

    return (LV2_Handle)self;
}

static void connect_port(LV2_Handle instance,
             uint32_t port,
             void* data) {

    Plugin *self = (Plugin *) instance;
    switch (port) {
        case IN_MIDI:
            self->midiPort = (LV2_Atom_Sequence*) data;
            break;
        case OUT:
            self->outPort = (float*) data;
            break;
    }
}

void processEvent(LV2_Atom_Event *event, Plugin *self) {
    if (event->body.type != self->midiEvent) {
        return;
    }

    const uint8_t* const msg = LV2_ATOM_BODY(&(event->body));
    LV2_Midi_Message_Type type = lv2_midi_message_type(msg);

    switch(type) {
        case LV2_MIDI_MSG_NOTE_ON:
            self->soundOn = true;
            break;
        case LV2_MIDI_MSG_NOTE_OFF:
            self->soundOn = false;
            break;
    }
}

static void run(LV2_Handle instance, uint32_t sample_count) {
    Plugin *self = (Plugin *) instance;

    LV2_ATOM_SEQUENCE_FOREACH(self->midiPort, event) {
        processEvent(event, self);
    }

    for (uint32_t i = 0; i < sample_count; i++) {
        if (self->soundOn) {
            self->outPort[i] = sinf(2 * M_PI * 440.0 * self->currentSample / self->rate);
        } else {
            self->outPort[i] = 0.0;
        }
        self->currentSample++;
    }
}

static void cleanup(LV2_Handle instance) {
    free(instance);
}

static const LV2_Descriptor descriptor = {
    MURI,
    instantiate,
    connect_port,
    NULL,
    run,
    NULL,
    cleanup,
    NULL
};

LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor(uint32_t index) {
    switch (index) {
        case 0:
            return &descriptor;
        default:
            return NULL;
    }
}
      
      



, , LV2_Descriptor lv2_descriptor(). URI , ยซ ยป. , - , NULL. lv2_descriptor() - , . . , .





, Plugin. , LV2 . โ€” LV2_Handle void *, - . โ€” instatntiate(). , . , . map, URI . midi- . LV2_MIDI__MidiEvent .





, , . connect_port , ttl- . ( ) , . Plugin.





, run, . sample_count โ€” , ( , , ). midi-, LV2_ATOM_TUPLE_FOREACH. , .





processEvent(). , midi-. , map . LV2_Atom_Event , LV2_ATOM_BODY. midi , ยซยป . . , soundOn Plugin.





The most important section that forms the sound is located inside the loop in the run () function. The state of the soundOn variable indicates what will be written to the output port: sine wave or zeros. (Actually, using currentSample to store the current position is wrong. Sooner or later it will overflow and breaks will appear in the sine wave. But for the sake of demonstration, it will do just that).





Links




All Articles