Anatomie d'un plugin LV2

introduction

LV2 est un standard ouvert pour la création de plugins d'effets sonores. On pense qu'il est principalement destiné à Linux, bien qu'il n'y ait aucune restriction à son utilisation sur d'autres systèmes. Avant cela, il existait déjà deux standards similaires sous Linux - LADSPA et DSSI. Le premier d'entre eux était principalement destiné au traitement des signaux audio et ne pouvait pratiquement pas fonctionner avec des données MIDI. Le second, au contraire, a été conçu comme un standard pour les synthétiseurs virtuels.





Le nom LV2 lui-même est une abréviation de LADSPA version 2 , c'est une nouvelle version améliorée de la norme. Contrairement à ses prédécesseurs, il vous permet de traiter des données audio, des flux midi, de créer n'importe quelle interface utilisateur et d'échanger des données avec l'application hôte. La norme prend également en charge un mécanisme d'extension. Grâce à cela, LV2 peut offrir un certain nombre de fonctionnalités supplémentaires: un ensemble de préréglages «d'usine», la sauvegarde de l'état, la journalisation. En théorie, l'utilisateur peut créer ses propres modules complémentaires. Une documentation détaillée avec des exemples est disponible sur http://lv2plug.in





Organisation

Beaucoup connaissent sûrement la norme VST populaire. Dans son cas, le plugin et les ressources associées sont généralement contenus dans une seule bibliothèque de liens dynamiques (fichier DLL). Plus d'un fichier est presque toujours utilisé dans la norme LV2. La norme utilise le concept de bundle . Je n'ai pas été en mesure de savoir s'il existe un équivalent russe pour ce terme. Un bundle est un répertoire du système de fichiers où sont placés tous les fichiers liés à ce plugin. Selon la définition de la documentation: "LV2 Bundle est le répertoire contenant le fichier manifest.ttl au niveau supérieur . "Il est courant de nommer les répertoires de sorte que leur nom coïncide avec le nom du plugin, par exemple amsynth.lv2 ou triceratops.lv2, mais tous les noms sont autorisés. Emplacement du chemin des bundles spécifié dans la variable système LV2_PATH (soit défini directement dans les paramètres de l'application hôte). Plusieurs plugins peuvent être situés dans un seul bundle à la fois.





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 section la plus importante qui forme le son est située à l'intérieur de la boucle dans la fonction run (). L'état de la variable soundOn indique ce qui sera écrit sur le port de sortie: onde sinusoïdale ou zéros. (En fait, utiliser currentSample pour enregistrer la position actuelle est faux. Tôt ou tard, il débordera et des ruptures apparaîtront dans l'onde sinusoïdale. Mais pour la démonstration, cela fonctionnera comme ça).





Liens

  • Site officiel du projet





  • Atomes LV2: un modèle de données pour les plugins audio en temps réel








All Articles