EinfĂĽhrung
LV2 ist ein offener Standard zum Erstellen von Soundeffekt-Plugins. Es wird angenommen, dass es hauptsächlich für Linux gedacht ist, obwohl es keine Einschränkungen für die Verwendung auf anderen Systemen gibt. Zuvor gab es in Linux bereits zwei ähnliche Standards - LADSPA und DSSI. Die erste war hauptsächlich für die Verarbeitung von Audiosignalen gedacht und konnte praktisch nicht mit MIDI-Daten arbeiten. Der zweite wurde im Gegenteil als Standard für virtuelle Synthesizer konzipiert.
Der Name LV2 selbst ist eine Abkürzung für LADSPA Version 2 , es ist eine neue, verbesserte Version des Standards. Im Gegensatz zu seinen Vorgängern können Sie damit Audiodaten und MIDI-Streams verarbeiten, eine beliebige Benutzeroberfläche erstellen und Daten mit der Host-Anwendung austauschen. Der Standard unterstützt auch einen Erweiterungsmechanismus. Dank dessen kann LV2 eine Reihe zusätzlicher Funktionen bieten: eine Reihe von "Werks" -Voreinstellungen, Speicherstatus, Protokollierung. Theoretisch kann der Benutzer seine eigenen Add-Ons erstellen. Eine ausführliche Dokumentation mit Beispielen finden Sie unter http://lv2plug.in
Organisation
Sicherlich kennen viele den beliebten VST-Standard. In seinem Fall sind das Plugin und die zugehörigen Ressourcen normalerweise in einer einzigen dynamischen Linkbibliothek (DLL-Datei) enthalten. Im LV2-Standard wird fast immer mehr als eine Datei verwendet. Der Standard verwendet das Konzept eines Bündels . Ich konnte nicht herausfinden, ob es für diesen Begriff ein russischsprachiges Äquivalent gibt. Ein Bundle ist ein Verzeichnis im Dateisystem, in dem alle mit diesem Plugin verbundenen Dateien abgelegt werden. Gemäß der Definition aus der Dokumentation: "LV2-Bundle ist das Verzeichnis, das die Datei manifest.ttl auf der obersten Ebene enthält . "Es ist üblich, Verzeichnisse so zu benennen, dass ihr Name mit dem Namen des Plugins übereinstimmt, z. B. amsynth.lv2 oder triceratops.lv2, aber alle Namen sind zulässig. Bündelt den in der Systemvariablen LV2_PATH angegebenen Pfadspeicherort (entweder direkt in den Einstellungen der Hostanwendung definiert). In einem Bundle können sich mehrere Plugins gleichzeitig befinden.
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.
Der wichtigste Abschnitt, der den Sound bildet, befindet sich innerhalb der Schleife in der Funktion run (). Der Status der soundOn-Variablen gibt an, was in den Ausgangsport geschrieben wird: Sinuswelle oder Nullen. (Tatsächlich ist es falsch, currentSample zum Speichern der aktuellen Position zu verwenden. Früher oder später läuft es über und es erscheinen Brüche in der Sinuswelle. Aber zur Demonstration funktioniert es einfach so.)