Anatomia de um plugin LV2

Introdução

LV2 é um padrão aberto para a criação de plug-ins de efeitos sonoros. Acredita-se que seja principalmente destinado ao Linux, embora não haja restrições ao seu uso em outros sistemas. Antes disso, já havia dois padrões semelhantes no Linux - LADSPA e DSSI. O primeiro deles era destinado principalmente para processamento de sinais de áudio e praticamente não funcionava com dados MIDI. O segundo, ao contrário, foi concebido como um padrão para sintetizadores virtuais.





O próprio nome LV2 é uma abreviatura para LADSPA versão 2 , é uma versão nova e aprimorada do padrão. Ao contrário de seus predecessores, ele permite processar dados de áudio, streams midi, criar qualquer interface de usuário e trocar quaisquer dados com o aplicativo host. O padrão também oferece suporte a um mecanismo de extensão. Graças a isso, LV2 pode oferecer uma série de recursos adicionais: um conjunto de predefinições de "fábrica", estado de salvamento, registro. Em teoria, o usuário pode criar seus próprios add-ons. A documentação detalhada com exemplos está localizada em http://lv2plug.in





Organização

Certamente muitos estão familiarizados com o popular padrão VST. Nesse caso, o plug-in e os recursos associados geralmente estão contidos em uma única biblioteca de vínculo dinâmico (arquivo DLL). Quase sempre mais de um arquivo é usado no padrão LV2. O padrão usa o conceito de pacote . Não consegui descobrir se existe algum equivalente em russo para este termo. Um pacote é um diretório no sistema de arquivos onde todos os arquivos relacionados a este plugin são colocados. De acordo com a definição da documentação: "Pacote LV2 é o diretório que contém o arquivo manifest.ttl no nível superior . "É comum nomear diretórios de modo que seus nomes coincidam com o nome do plugin, por exemplo amsynth.lv2 ou triceratops.lv2, mas quaisquer nomes são permitidos. Local do caminho dos pacotes especificado na variável do sistema LV2_PATH (qualquer um deles definido diretamente nas configurações do aplicativo host). Vários plug-ins podem ser localizados em um pacote de uma 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.





A seção mais importante que forma o som está localizada dentro do loop na função run (). O estado da variável soundOn indica o que será escrito na porta de saída: onda senoidal ou zeros. (Na verdade, usar currentSample para salvar a posição atual está errado. Mais cedo ou mais tarde ele irá transbordar e quebras aparecerão na onda senoidal. Mas para demonstração, funcionará exatamente assim).





Links




All Articles