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).