Anatomía de un complemento LV2

Introducción

LV2 es un estándar abierto para crear complementos de efectos de sonido. Se cree que está destinado principalmente a Linux, aunque no existen restricciones para su uso en otros sistemas. Antes de eso, ya existían dos estándares similares en Linux: LADSPA y DSSI. El primero de ellos estaba destinado principalmente a procesar señales de audio y prácticamente no podía funcionar con datos MIDI. El segundo, por el contrario, fue concebido como un estándar para sintetizadores virtuales.





El nombre LV2 en sí es una abreviatura de LADSPA versión 2 , es una versión nueva y mejorada del estándar. A diferencia de sus predecesores, le permite procesar datos de audio, transmisiones midi, crear cualquier interfaz de usuario e intercambiar cualquier dato con la aplicación host. El estándar también admite un mecanismo de extensión. Gracias a esto, LV2 puede ofrecer una serie de características adicionales: un conjunto de preajustes "de fábrica", estado de guardado, registro. En teoría, el usuario puede crear sus propios complementos. La documentación detallada con ejemplos se encuentra en http://lv2plug.in





Organización

Seguramente muchos están familiarizados con el popular estándar VST. En su caso, el complemento y los recursos asociados suelen estar contenidos en una única biblioteca de enlaces dinámicos (archivo DLL). Casi siempre se utiliza más de un archivo en el estándar LV2. El estándar usa el concepto de paquete . No he podido averiguar si existe algún equivalente en ruso para este término. Un paquete es un directorio en el sistema de archivos donde se colocan todos los archivos relacionados con este complemento. Según la definición de la documentación: "LV2 Bundle es el directorio que contiene el archivo manifest.ttl en el nivel superior" .Es habitual nombrar directorios de modo que su nombre coincida con el nombre del complemento, por ejemplo, amsynth.lv2 o triceratops.lv2, pero se permite cualquier nombre. Ubicación de la ruta de los paquetes especificada en la variable del sistema LV2_PATH (ya sea definida directamente en la configuración de la aplicación host). Se pueden ubicar varios complementos en un paquete a la vez.





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.





La sección más importante que forma el sonido se encuentra dentro del bucle en la función run (). El estado de la variable soundOn indica lo que se escribirá en el puerto de salida: onda sinusoidal o ceros. (En realidad, usar currentSample para guardar la posición actual es incorrecto. Tarde o temprano se desbordará y aparecerán roturas en la onda sinusoidal. Pero para la demostración, funcionará así).





Enlaces




All Articles