13
0
livetrax/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.cc

1153 lines
33 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2016-2019 Robin Gareus <robin@gareus.org>
2016-08-23 21:18:14 -04:00
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
2016-08-23 21:18:14 -04:00
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2016-08-23 21:18:14 -04:00
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
2016-12-06 10:59:57 -05:00
#include <algorithm>
2021-05-21 12:29:38 -04:00
#include <cstring>
2016-12-06 10:59:57 -05:00
#include <map>
2021-05-21 12:29:38 -04:00
#include <string>
2016-12-06 10:59:57 -05:00
#include <vector>
2021-05-21 12:29:38 -04:00
#include <math.h>
2016-12-06 10:59:57 -05:00
#include <pthread.h>
2016-08-23 21:18:14 -04:00
#include <stdlib.h>
#define AFS_URN "urn:ardour:a-fluidsynth"
#ifdef HAVE_LV2_1_10_0
#define x_forge_object lv2_atom_forge_object
#else
#define x_forge_object lv2_atom_forge_blank
#endif
2016-12-06 09:56:36 -05:00
#ifdef LV2_EXTENDED
#include "../../ardour/ardour/lv2_extensions.h"
#endif
2016-08-23 21:18:14 -04:00
#include "fluidsynth.h"
#ifdef HAVE_LV2_1_18_6
#include <lv2/atom/atom.h>
#include <lv2/atom/forge.h>
#include <lv2/atom/util.h>
2023-05-06 12:28:35 -04:00
#include <lv2/core/lv2.h>
#include <lv2/log/logger.h>
#include <lv2/midi/midi.h>
#include <lv2/patch/patch.h>
#include <lv2/state/state.h>
#include <lv2/urid/urid.h>
#include <lv2/worker/worker.h>
#else
2016-08-23 21:18:14 -04:00
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
#include <lv2/lv2plug.in/ns/ext/atom/util.h>
#include <lv2/lv2plug.in/ns/ext/log/logger.h>
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
#include <lv2/lv2plug.in/ns/ext/state/state.h>
#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
2021-05-21 12:29:38 -04:00
#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
#endif
2016-08-23 21:18:14 -04:00
enum {
FS_PORT_CONTROL = 0,
FS_PORT_NOTIFY,
FS_PORT_OUT_L,
FS_PORT_OUT_R,
FS_OUT_GAIN,
FS_REV_ENABLE,
FS_REV_ROOMSIZE,
FS_REV_DAMPING,
FS_REV_WIDTH,
FS_REV_LEVEL,
FS_CHR_ENABLE,
FS_CHR_N,
FS_CHR_SPEED,
FS_CHR_DEPTH,
FS_CHR_LEVEL,
FS_CHR_TYPE,
FS_PORT_ENABLE,
FS_PORT_LAST
2016-08-23 21:18:14 -04:00
};
enum {
2021-05-21 12:29:38 -04:00
CMD_APPLY = 0,
CMD_FREE = 1,
2016-08-23 21:18:14 -04:00
};
2016-12-06 10:59:57 -05:00
struct BankProgram {
BankProgram (const std::string& n, int b, int p)
: name (n)
, bank (b)
, program (p)
{}
BankProgram (const BankProgram& other)
: name (other.name)
, bank (other.bank)
, program (other.program)
{}
std::string name;
2021-05-21 12:29:38 -04:00
int bank;
int program;
2016-12-06 10:59:57 -05:00
};
2021-05-21 12:29:38 -04:00
typedef std::vector<BankProgram> BPList;
typedef std::map<int, BPList> BPMap;
typedef std::map<int, BankProgram> BPState;
2016-12-06 10:59:57 -05:00
2016-08-23 21:18:14 -04:00
typedef struct {
/* ports */
const LV2_Atom_Sequence* control;
2021-05-21 12:29:38 -04:00
LV2_Atom_Sequence* notify;
float* p_ports[FS_PORT_LAST];
float v_ports[FS_PORT_LAST];
2016-08-23 21:18:14 -04:00
/* fluid synth */
fluid_settings_t* settings;
fluid_synth_t* synth;
int synthId;
/* lv2 URIDs */
LV2_URID atom_Blank;
LV2_URID atom_Object;
LV2_URID atom_URID;
LV2_URID atom_Path;
2021-02-12 08:16:07 -05:00
LV2_URID atom_Vector;
LV2_URID atom_Double;
2016-08-23 21:18:14 -04:00
LV2_URID midi_MidiEvent;
LV2_URID patch_Get;
LV2_URID patch_Set;
LV2_URID patch_property;
LV2_URID patch_value;
LV2_URID state_Changed;
2016-08-23 21:18:14 -04:00
LV2_URID afs_sf2file;
2021-02-12 08:16:07 -05:00
LV2_URID afs_tuning;
2016-08-23 21:18:14 -04:00
/* lv2 extensions */
LV2_Log_Log* log;
LV2_Log_Logger logger;
2021-05-21 12:29:38 -04:00
LV2_Worker_Schedule* schedule;
2016-08-23 21:18:14 -04:00
LV2_Atom_Forge forge;
LV2_Atom_Forge_Frame frame;
2016-08-23 21:18:14 -04:00
2016-12-06 09:56:36 -05:00
#ifdef LV2_EXTENDED
2021-05-21 12:29:38 -04:00
LV2_Midnam* midnam;
LV2_BankPatch* bankpatch;
BPMap presets;
2016-12-06 09:56:36 -05:00
#endif
2021-05-21 12:29:38 -04:00
pthread_mutex_t bp_lock;
2016-12-06 09:56:36 -05:00
2016-08-23 21:18:14 -04:00
/* state */
bool panic;
bool initialized;
bool inform_ui;
bool send_bankpgm;
2016-08-23 21:18:14 -04:00
char current_sf2_file_path[1024];
char queue_sf2_file_path[1024];
bool reinit_in_progress; // set in run, cleared in work_response
2021-05-21 12:29:38 -04:00
bool queue_reinit; // set in restore, cleared in work_response
2016-08-23 21:18:14 -04:00
2021-02-12 08:16:07 -05:00
bool queue_retune;
double queue_tuning[128];
BankProgram program_state[16];
2016-08-23 21:18:14 -04:00
fluid_midi_event_t* fmidi_event;
} AFluidSynth;
/* *****************************************************************************
* helpers
*/
2016-08-23 21:18:14 -04:00
static bool
load_sf2 (AFluidSynth* self, const char* fn)
{
const int synth_id = fluid_synth_sfload (self->synth, fn, 1);
2016-12-06 10:59:57 -05:00
#ifdef LV2_EXTENDED
pthread_mutex_lock (&self->bp_lock);
self->presets.clear ();
pthread_mutex_unlock (&self->bp_lock);
#endif
2016-08-23 21:18:14 -04:00
if (synth_id == FLUID_FAILED) {
return false;
}
fluid_sfont_t* const sfont = fluid_synth_get_sfont_by_id (self->synth, synth_id);
if (!sfont) {
return false;
}
2021-05-21 12:29:38 -04:00
int chn;
fluid_preset_t* preset;
fluid_sfont_iteration_start (sfont);
2016-12-06 10:59:57 -05:00
pthread_mutex_lock (&self->bp_lock);
for (chn = 0; (preset = fluid_sfont_iteration_next (sfont)); ++chn) {
2016-12-06 10:59:57 -05:00
if (chn < 16) {
fluid_synth_program_select (self->synth, chn, synth_id,
2021-05-21 12:29:38 -04:00
fluid_preset_get_banknum (preset), fluid_preset_get_num (preset));
2016-12-06 10:59:57 -05:00
}
#ifndef LV2_EXTENDED
2021-05-21 12:29:38 -04:00
else {
break;
}
2016-12-06 10:59:57 -05:00
#else
self->presets[fluid_preset_get_banknum (preset)].push_back (
2021-05-21 12:29:38 -04:00
BankProgram (
fluid_preset_get_name (preset),
fluid_preset_get_banknum (preset),
fluid_preset_get_num (preset)));
#endif // LV2_EXTENDED
2016-08-23 21:18:14 -04:00
}
2016-12-06 10:59:57 -05:00
pthread_mutex_unlock (&self->bp_lock);
2016-08-23 21:18:14 -04:00
if (chn == 0) {
return false;
}
return true;
}
static const LV2_Atom*
parse_patch_msg (AFluidSynth* self, const LV2_Atom_Object* obj)
{
2021-05-21 12:29:38 -04:00
const LV2_Atom* property = NULL;
2016-08-23 21:18:14 -04:00
const LV2_Atom* file_path = NULL;
if (obj->body.otype != self->patch_Set) {
return NULL;
}
lv2_atom_object_get (obj, self->patch_property, &property, 0);
if (!property || property->type != self->atom_URID) {
return NULL;
} else if (((const LV2_Atom_URID*)property)->body != self->afs_sf2file) {
return NULL;
}
lv2_atom_object_get (obj, self->patch_value, &file_path, 0);
2016-08-23 21:18:14 -04:00
if (!file_path || file_path->type != self->atom_Path) {
return NULL;
}
return file_path;
}
static void
inform_ui (AFluidSynth* self)
{
if (strlen (self->current_sf2_file_path) == 0) {
return;
}
LV2_Atom_Forge_Frame frame;
2016-08-23 21:18:14 -04:00
lv2_atom_forge_frame_time (&self->forge, 0);
x_forge_object (&self->forge, &frame, 1, self->patch_Set);
2016-08-23 21:18:14 -04:00
lv2_atom_forge_property_head (&self->forge, self->patch_property, 0);
lv2_atom_forge_urid (&self->forge, self->afs_sf2file);
lv2_atom_forge_property_head (&self->forge, self->patch_value, 0);
lv2_atom_forge_path (&self->forge, self->current_sf2_file_path, strlen (self->current_sf2_file_path));
2016-08-23 21:18:14 -04:00
lv2_atom_forge_pop (&self->forge, &frame);
2016-08-23 21:18:14 -04:00
}
static float
db_to_coeff (float db)
{
2021-05-21 12:29:38 -04:00
if (db <= -80) {
return 0;
} else if (db >= 20) {
return 10;
}
return powf (10.f, .05f * db);
}
2021-02-12 08:16:07 -05:00
static void
parse_mts (AFluidSynth* self, const uint8_t* data, uint32_t len)
{
2021-05-21 12:29:38 -04:00
assert (data[0] == 0xf0 && data[3] == 0x08 && len > 11);
2021-02-12 08:16:07 -05:00
if (data[4] == 0x01 && len == 408) {
/* bulk transfer
* 0xf0, 0x7e, -- non-realtime sysex
* 0xXX, -- target-id
* 0x08, 0x01, -- tuning, bulk dump reply
* 0x7X, -- tuning program number 0 to 127
* TEXT * 16 -- 16 chars name (zero padded)
* TUNE * 3 * 128 -- tuning for all notes (base, MSB, LSB)
* 0xXX -- checksum
* 0xf7 -- 408 bytes in total
*/
int prog = 0; // data[2]
2021-05-21 12:29:38 -04:00
int off = 22;
2021-02-12 08:16:07 -05:00
int key[128];
double pitch[128];
for (int i = 0; i < 128; ++i) {
2021-05-21 12:29:38 -04:00
const uint32_t note = data[off];
const uint32_t fract = (data[off + 1] << 7) | data[off + 2];
key[i] = i;
pitch[i] = note * 100.f + fract / 163.83;
2021-02-12 08:16:07 -05:00
off += 3;
}
if (data[off + 1] == 0xf7) {
int rv = fluid_synth_tune_notes (self->synth,
2021-05-21 12:29:38 -04:00
/* tuning bank */ 0,
/* tuning prog */ prog,
128, key, pitch,
/* apply */ 1);
2021-02-12 08:16:07 -05:00
if (rv == FLUID_OK) {
for (int c = 0; c < 16; ++c) {
fluid_synth_activate_tuning (self->synth, c, 0, prog, 0);
}
self->inform_ui = true; // StateChanged
}
}
} else if (data[4] == 0x02 && len == 12) {
/* single note tuning
* 0xf0, 0x7f, -- realtime sysex
* 0xXX, -- target-id
* 0x08, 0x02, -- tuning, note change request
* 0x7X, -- tuning program number 0 to 127
* 0x7X -- Note to re-tune
* base, -- semitone (MIDI note number to retune to, unit is 100 cents)
* cent_msb, -- MSB of fractional part (1/128 semitone = 100/128 cents = .78125 cent units)
* cent_lsb, -- LSB of fractional part (1/16384 semitone = 100/16384 cents = .0061 cent units
* 0xf7 -- 12 bytesin total
*/
2021-05-21 12:29:38 -04:00
const uint32_t note = data[8];
const uint32_t fract = (data[9] << 7) | data[10];
2021-02-12 08:16:07 -05:00
int prog = 0; // data[2]
int key = data[7];
double pitch = note * 100.f + fract / 163.83;
if (data[11] == 0xf7) {
int rv = fluid_synth_tune_notes (self->synth,
2021-05-21 12:29:38 -04:00
/* tuning bank */ 0,
/* tuning prog */ prog,
1, &key, &pitch,
/* apply */ 1);
2021-02-12 08:16:07 -05:00
if (rv == FLUID_OK) {
for (int c = 0; c < 16; ++c) {
fluid_synth_activate_tuning (self->synth, c, 0, prog, 0);
}
self->inform_ui = true; // StateChanged
}
}
}
}
2016-08-23 21:18:14 -04:00
/* *****************************************************************************
* LV2 Plugin
*/
static LV2_Handle
instantiate (const LV2_Descriptor* descriptor,
double rate,
const char* bundle_path,
const LV2_Feature* const* features)
{
AFluidSynth* self = (AFluidSynth*)calloc (1, sizeof (AFluidSynth));
if (!self) {
return NULL;
}
LV2_URID_Map* map = NULL;
2021-05-21 12:29:38 -04:00
for (int i = 0; features[i] != NULL; ++i) {
2016-08-23 21:18:14 -04:00
if (!strcmp (features[i]->URI, LV2_URID__map)) {
map = (LV2_URID_Map*)features[i]->data;
} else if (!strcmp (features[i]->URI, LV2_LOG__log)) {
self->log = (LV2_Log_Log*)features[i]->data;
} else if (!strcmp (features[i]->URI, LV2_WORKER__schedule)) {
self->schedule = (LV2_Worker_Schedule*)features[i]->data;
2016-12-06 09:56:36 -05:00
#ifdef LV2_EXTENDED
} else if (!strcmp (features[i]->URI, LV2_MIDNAM__update)) {
self->midnam = (LV2_Midnam*)features[i]->data;
} else if (!strcmp (features[i]->URI, LV2_BANKPATCH__notify)) {
self->bankpatch = (LV2_BankPatch*)features[i]->data;
2016-12-06 09:56:36 -05:00
#endif
2016-08-23 21:18:14 -04:00
}
}
lv2_log_logger_init (&self->logger, map, self->log);
if (!map) {
lv2_log_error (&self->logger, "a-fluidsynth.lv2: Host does not support urid:map\n");
free (self);
return NULL;
}
if (!self->schedule) {
lv2_log_error (&self->logger, "a-fluidsynth.lv2: Host does not support worker:schedule\n");
free (self);
return NULL;
}
#ifdef LV2_EXTENDED
if (!self->midnam) {
lv2_log_warning (&self->logger, "a-fluidsynth.lv2: Host does not support midnam:update\n");
}
if (!self->bankpatch) {
lv2_log_warning (&self->logger, "a-fluidsynth.lv2: Host does not support bankpatch:notify\n");
}
#endif
2016-08-23 21:18:14 -04:00
/* initialize fluid synth */
self->settings = new_fluid_settings ();
if (!self->settings) {
lv2_log_error (&self->logger, "a-fluidsynth.lv2: cannot allocate Fluid Settings\n");
free (self);
return NULL;
}
fluid_settings_setnum (self->settings, "synth.sample-rate", rate);
fluid_settings_setint (self->settings, "synth.threadsafe-api", 0);
fluid_settings_setstr (self->settings, "synth.midi-bank-select", "mma");
2016-08-23 21:18:14 -04:00
self->synth = new_fluid_synth (self->settings);
if (!self->synth) {
lv2_log_error (&self->logger, "a-fluidsynth.lv2: cannot allocate Fluid Synth\n");
2021-05-21 12:29:38 -04:00
delete_fluid_settings (self->settings);
2016-08-23 21:18:14 -04:00
free (self);
return NULL;
}
fluid_synth_set_gain (self->synth, 1.0f);
2020-07-12 00:33:34 -04:00
fluid_synth_set_polyphony (self->synth, 256);
2016-08-23 21:18:14 -04:00
fluid_synth_set_sample_rate (self->synth, (float)rate);
fluid_synth_set_reverb_on (self->synth, 0);
fluid_synth_set_chorus_on (self->synth, 0);
2016-08-23 21:18:14 -04:00
self->fmidi_event = new_fluid_midi_event ();
if (!self->fmidi_event) {
lv2_log_error (&self->logger, "a-fluidsynth.lv2: cannot allocate Fluid Event\n");
delete_fluid_synth (self->synth);
2021-05-21 12:29:38 -04:00
delete_fluid_settings (self->settings);
2016-08-23 21:18:14 -04:00
free (self);
return NULL;
}
/* initialize plugin state */
2016-12-06 10:59:57 -05:00
pthread_mutex_init (&self->bp_lock, NULL);
#ifdef LV2_EXTENDED
2021-05-21 12:29:38 -04:00
self->presets = BPMap ();
#endif
2021-05-21 12:29:38 -04:00
self->panic = false;
self->inform_ui = false;
self->send_bankpgm = true;
self->initialized = false;
2016-08-23 21:18:14 -04:00
self->reinit_in_progress = false;
2021-05-21 12:29:38 -04:00
self->queue_reinit = false;
self->queue_retune = false;
for (int chn = 0; chn < 16; ++chn) {
self->program_state[chn].program = -1;
}
2016-08-23 21:18:14 -04:00
lv2_atom_forge_init (&self->forge, map);
/* map URIDs */
2021-05-21 12:29:38 -04:00
self->atom_Blank = map->map (map->handle, LV2_ATOM__Blank);
self->atom_Object = map->map (map->handle, LV2_ATOM__Object);
self->atom_Path = map->map (map->handle, LV2_ATOM__Path);
self->atom_Vector = map->map (map->handle, LV2_ATOM__Vector);
self->atom_Double = map->map (map->handle, LV2_ATOM__Double);
self->atom_URID = map->map (map->handle, LV2_ATOM__URID);
self->midi_MidiEvent = map->map (map->handle, LV2_MIDI__MidiEvent);
self->patch_Get = map->map (map->handle, LV2_PATCH__Get);
self->patch_Set = map->map (map->handle, LV2_PATCH__Set);
self->patch_property = map->map (map->handle, LV2_PATCH__property);
self->patch_value = map->map (map->handle, LV2_PATCH__value);
self->state_Changed = map->map (map->handle, "http://lv2plug.in/ns/ext/state#StateChanged");
self->afs_sf2file = map->map (map->handle, AFS_URN ":sf2file");
self->afs_tuning = map->map (map->handle, AFS_URN ":tuning");
2016-08-23 21:18:14 -04:00
return (LV2_Handle)self;
}
static void
connect_port (LV2_Handle instance,
uint32_t port,
void* data)
{
AFluidSynth* self = (AFluidSynth*)instance;
switch (port) {
case FS_PORT_CONTROL:
self->control = (const LV2_Atom_Sequence*)data;
break;
case FS_PORT_NOTIFY:
self->notify = (LV2_Atom_Sequence*)data;
break;
default:
if (port < FS_PORT_LAST) {
self->p_ports[port] = (float*)data;
}
2016-08-23 21:18:14 -04:00
break;
}
}
static void
deactivate (LV2_Handle instance)
2016-08-23 21:18:14 -04:00
{
AFluidSynth* self = (AFluidSynth*)instance;
2021-05-21 12:29:38 -04:00
2016-08-23 21:18:14 -04:00
self->panic = true;
}
static void
run (LV2_Handle instance, uint32_t n_samples)
{
AFluidSynth* self = (AFluidSynth*)instance;
if (!self->control || !self->notify) {
return;
}
const uint32_t capacity = self->notify->atom.size;
lv2_atom_forge_set_buffer (&self->forge, (uint8_t*)self->notify, capacity);
lv2_atom_forge_sequence_head (&self->forge, &self->frame, 0);
2016-08-23 21:18:14 -04:00
const bool enabled = *self->p_ports[FS_PORT_ENABLE] > 0;
if (self->v_ports[FS_PORT_ENABLE] != *self->p_ports[FS_PORT_ENABLE]) {
if (self->initialized && !self->reinit_in_progress) {
fluid_synth_all_notes_off (self->synth, -1);
}
}
2016-08-23 21:18:14 -04:00
if (!self->initialized || self->reinit_in_progress) {
memset (self->p_ports[FS_PORT_OUT_L], 0, n_samples * sizeof (float));
memset (self->p_ports[FS_PORT_OUT_R], 0, n_samples * sizeof (float));
2016-08-23 21:18:14 -04:00
} else if (self->panic) {
fluid_synth_all_notes_off (self->synth, -1);
fluid_synth_all_sounds_off (self->synth, -1);
self->panic = false;
}
if (self->initialized && !self->reinit_in_progress) {
bool rev_change = false;
bool chr_change = false;
// TODO clamp values to ranges
if (self->v_ports[FS_OUT_GAIN] != *self->p_ports[FS_OUT_GAIN]) {
fluid_synth_set_gain (self->synth, db_to_coeff (*self->p_ports[FS_OUT_GAIN]));
}
if (self->v_ports[FS_REV_ENABLE] != *self->p_ports[FS_REV_ENABLE]) {
fluid_synth_set_reverb_on (self->synth, *self->p_ports[FS_REV_ENABLE] > 0 ? 1 : 0);
rev_change = true;
}
if (self->v_ports[FS_CHR_ENABLE] != *self->p_ports[FS_CHR_ENABLE]) {
fluid_synth_set_chorus_on (self->synth, *self->p_ports[FS_CHR_ENABLE] > 0 ? 1 : 0);
chr_change = true;
}
for (uint32_t p = FS_REV_ROOMSIZE; p <= FS_REV_LEVEL && !rev_change; ++p) {
if (self->v_ports[p] != *self->p_ports[p]) {
rev_change = true;
}
}
for (uint32_t p = FS_CHR_N; p <= FS_CHR_TYPE && !chr_change; ++p) {
if (self->v_ports[p] != *self->p_ports[p]) {
chr_change = true;
}
}
if (rev_change) {
fluid_synth_set_reverb (self->synth,
2021-05-21 12:29:38 -04:00
*self->p_ports[FS_REV_ROOMSIZE],
*self->p_ports[FS_REV_DAMPING],
*self->p_ports[FS_REV_WIDTH],
*self->p_ports[FS_REV_LEVEL]);
}
if (chr_change) {
fluid_synth_set_chorus (self->synth,
2021-05-21 12:29:38 -04:00
rintf (*self->p_ports[FS_CHR_N]),
db_to_coeff (*self->p_ports[FS_CHR_LEVEL]),
*self->p_ports[FS_CHR_SPEED],
*self->p_ports[FS_CHR_DEPTH],
(*self->p_ports[FS_CHR_TYPE] > 0) ? FLUID_CHORUS_MOD_SINE : FLUID_CHORUS_MOD_TRIANGLE);
}
for (uint32_t p = FS_OUT_GAIN; p < FS_PORT_LAST; ++p) {
self->v_ports[p] = *self->p_ports[p];
}
}
2016-08-23 21:18:14 -04:00
uint32_t offset = 0;
2021-05-21 12:29:38 -04:00
LV2_ATOM_SEQUENCE_FOREACH (self->control, ev)
{
2016-08-23 21:18:14 -04:00
const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body;
if (ev->body.type == self->atom_Blank || ev->body.type == self->atom_Object) {
if (obj->body.otype == self->patch_Get) {
self->inform_ui = false;
inform_ui (self);
2021-05-21 12:29:38 -04:00
} else if (obj->body.otype == self->patch_Set) {
2016-08-23 21:18:14 -04:00
const LV2_Atom* file_path = parse_patch_msg (self, obj);
if (file_path && !self->reinit_in_progress && !self->queue_reinit) {
2021-05-21 12:29:38 -04:00
const char* fn = (const char*)(file_path + 1);
2016-08-23 21:18:14 -04:00
strncpy (self->queue_sf2_file_path, fn, 1023);
self->queue_sf2_file_path[1023] = '\0';
2021-05-21 12:29:38 -04:00
self->reinit_in_progress = true;
int magic = 0x4711;
self->schedule->schedule_work (self->schedule->handle, sizeof (int), &magic);
2016-08-23 21:18:14 -04:00
}
}
2021-05-21 12:29:38 -04:00
} else if (ev->body.type == self->midi_MidiEvent) {
if (ev->time.frames >= n_samples || self->reinit_in_progress || !enabled) {
2021-02-12 08:16:07 -05:00
continue;
}
if (ev->body.size > 3) {
if (ev->body.size > 11) {
const uint8_t* const data = (const uint8_t*)(ev + 1);
2021-05-21 12:29:38 -04:00
if (data[0] == 0xf0 && (data[1] & 0x7e) == 0x7e && data[3] == 0x08) {
2021-02-12 08:16:07 -05:00
parse_mts (self, data, ev->body.size);
}
}
2016-08-23 21:18:14 -04:00
continue;
}
if (ev->time.frames > offset) {
fluid_synth_write_float (
2021-05-21 12:29:38 -04:00
self->synth,
ev->time.frames - offset,
&self->p_ports[FS_PORT_OUT_L][offset], 0, 1,
&self->p_ports[FS_PORT_OUT_R][offset], 0, 1);
2016-08-23 21:18:14 -04:00
}
offset = ev->time.frames;
const uint8_t* const data = (const uint8_t*)(ev + 1);
fluid_midi_event_set_type (self->fmidi_event, data[0] & 0xf0);
fluid_midi_event_set_channel (self->fmidi_event, data[0] & 0x0f);
if (ev->body.size > 1) {
fluid_midi_event_set_key (self->fmidi_event, data[1]);
}
if (ev->body.size > 2) {
if (0xe0 /*PITCH_BEND*/ == fluid_midi_event_get_type (self->fmidi_event)) {
fluid_midi_event_set_value (self->fmidi_event, 0);
fluid_midi_event_set_pitch (self->fmidi_event, ((data[2] & 0x7f) << 7) | (data[1] & 0x7f));
} else {
fluid_midi_event_set_value (self->fmidi_event, data[2]);
}
if (0xb0 /* CC */ == fluid_midi_event_get_type (self->fmidi_event)) {
int chn = fluid_midi_event_get_channel (self->fmidi_event);
assert (chn >= 0 && chn < 16);
if (data[1] == 0x00) {
self->program_state[chn].bank &= 0x7f;
2021-05-21 12:29:38 -04:00
self->program_state[chn].bank |= (data[2] & 0x7f) << 7;
}
if (data[1] == 0x20) {
self->program_state[chn].bank &= 0x3F80;
self->program_state[chn].bank |= data[2] & 0x7f;
}
}
}
if (ev->body.size == 2 && 0xc0 /* Pgm */ == fluid_midi_event_get_type (self->fmidi_event)) {
int chn = fluid_midi_event_get_channel (self->fmidi_event);
assert (chn >= 0 && chn < 16);
self->program_state[chn].program = data[1];
#ifdef LV2_EXTENDED
if (self->bankpatch) {
self->bankpatch->notify (self->bankpatch->handle, chn,
2021-05-21 12:29:38 -04:00
self->program_state[chn].bank,
self->program_state[chn].program < 0 ? 255 : self->program_state[chn].program);
}
#endif
2016-08-23 21:18:14 -04:00
}
2016-08-23 21:18:14 -04:00
fluid_synth_handle_midi_event (self->synth, self->fmidi_event);
}
}
if (self->queue_reinit && !self->reinit_in_progress) {
self->reinit_in_progress = true;
2021-05-21 12:29:38 -04:00
int magic = 0x4711;
self->schedule->schedule_work (self->schedule->handle, sizeof (int), &magic);
2016-08-23 21:18:14 -04:00
}
/* inform the GUI */
if (self->inform_ui) {
self->inform_ui = false;
/* emit stateChanged */
LV2_Atom_Forge_Frame frame;
2021-05-21 12:29:38 -04:00
lv2_atom_forge_frame_time (&self->forge, 0);
x_forge_object (&self->forge, &frame, 1, self->state_Changed);
lv2_atom_forge_pop (&self->forge, &frame);
/* send .sf2 filename */
2016-08-23 21:18:14 -04:00
inform_ui (self);
2016-12-06 09:56:36 -05:00
#ifdef LV2_EXTENDED
if (self->midnam)
self->midnam->update (self->midnam->handle);
2016-12-06 09:56:36 -05:00
#endif
2016-08-23 21:18:14 -04:00
}
#ifdef LV2_EXTENDED
if (self->send_bankpgm && self->bankpatch) {
self->send_bankpgm = false;
for (uint8_t chn = 0; chn < 16; ++chn) {
self->bankpatch->notify (self->bankpatch->handle, chn,
2021-05-21 12:29:38 -04:00
self->program_state[chn].bank,
self->program_state[chn].program < 0 ? 255 : self->program_state[chn].program);
}
}
#endif
2016-08-23 21:18:14 -04:00
if (n_samples > offset && self->initialized && !self->reinit_in_progress) {
fluid_synth_write_float (
2021-05-21 12:29:38 -04:00
self->synth,
n_samples - offset,
&self->p_ports[FS_PORT_OUT_L][offset], 0, 1,
&self->p_ports[FS_PORT_OUT_R][offset], 0, 1);
2016-08-23 21:18:14 -04:00
}
}
2021-05-21 12:29:38 -04:00
static void
cleanup (LV2_Handle instance)
2016-08-23 21:18:14 -04:00
{
AFluidSynth* self = (AFluidSynth*)instance;
delete_fluid_synth (self->synth);
delete_fluid_settings (self->settings);
delete_fluid_midi_event (self->fmidi_event);
2016-12-06 10:59:57 -05:00
pthread_mutex_destroy (&self->bp_lock);
2016-08-23 21:18:14 -04:00
free (self);
}
/* *****************************************************************************
* LV2 Extensions
*/
static LV2_Worker_Status
work (LV2_Handle instance,
LV2_Worker_Respond_Function respond,
LV2_Worker_Respond_Handle handle,
uint32_t size,
const void* data)
{
AFluidSynth* self = (AFluidSynth*)instance;
2021-05-21 12:29:38 -04:00
if (size != sizeof (int)) {
2016-08-23 21:18:14 -04:00
return LV2_WORKER_ERR_UNKNOWN;
}
int magic = *((const int*)data);
if (magic != 0x4711) {
return LV2_WORKER_ERR_UNKNOWN;
}
self->initialized = load_sf2 (self, self->queue_sf2_file_path);
if (self->initialized) {
fluid_synth_all_notes_off (self->synth, -1);
fluid_synth_all_sounds_off (self->synth, -1);
self->panic = false;
// bootstrap synth engine.
2016-08-24 11:51:15 -04:00
float l[1024];
float r[1024];
fluid_synth_write_float (self->synth, 1024, l, 0, 1, r, 0, 1);
}
2016-08-23 21:18:14 -04:00
respond (handle, 1, "");
return LV2_WORKER_SUCCESS;
}
2021-02-12 08:16:07 -05:00
struct VectorOfDouble {
2021-05-21 12:29:38 -04:00
LV2_Atom_Vector_Body vb;
double pitch[128];
2021-02-12 08:16:07 -05:00
};
2016-08-23 21:18:14 -04:00
static LV2_Worker_Status
work_response (LV2_Handle instance,
uint32_t size,
const void* data)
{
AFluidSynth* self = (AFluidSynth*)instance;
if (self->initialized) {
strcpy (self->current_sf2_file_path, self->queue_sf2_file_path);
for (int chn = 0; chn < 16; ++chn) {
if (self->program_state[chn].program < 0) {
continue;
}
/* cannot direcly call fluid_channel_set_bank_msb/fluid_channel_set_bank_lsb, use CCs */
fluid_midi_event_set_type (self->fmidi_event, 0xb0 /* CC */);
fluid_midi_event_set_channel (self->fmidi_event, chn);
fluid_midi_event_set_control (self->fmidi_event, 0x00); // BANK_SELECT_MSB
fluid_midi_event_set_value (self->fmidi_event, (self->program_state[chn].bank >> 7) & 0x7f);
fluid_synth_handle_midi_event (self->synth, self->fmidi_event);
fluid_midi_event_set_control (self->fmidi_event, 0x20); // BANK_SELECT_LSB
fluid_midi_event_set_value (self->fmidi_event, self->program_state[chn].bank & 0x7f);
fluid_synth_handle_midi_event (self->synth, self->fmidi_event);
fluid_synth_program_change (self->synth, chn, self->program_state[chn].program);
}
for (int chn = 0; chn < 16; ++chn) {
2021-05-21 12:29:38 -04:00
int sfid = 0;
int bank = 0;
int program = -1;
if (FLUID_OK == fluid_synth_get_program (self->synth, chn, &sfid, &bank, &program)) {
2021-05-21 12:29:38 -04:00
self->program_state[chn].bank = bank;
self->program_state[chn].program = program;
}
}
2021-02-12 08:16:07 -05:00
if (self->queue_retune) {
2021-05-21 12:29:38 -04:00
int rv = fluid_synth_activate_key_tuning (self->synth,
/* tuning bank */ 0,
/* tuning prog */ 0,
"ACE", self->queue_tuning, 0);
2021-02-12 08:16:07 -05:00
if (rv == FLUID_OK) {
for (int c = 0; c < 16; ++c) {
fluid_synth_activate_tuning (self->synth, c, 0, 0, 0);
}
}
}
2016-08-23 21:18:14 -04:00
} else {
self->current_sf2_file_path[0] = 0;
}
self->reinit_in_progress = false;
2021-05-21 12:29:38 -04:00
self->inform_ui = true;
self->send_bankpgm = true;
self->queue_retune = false;
self->queue_reinit = false;
2016-08-23 21:18:14 -04:00
return LV2_WORKER_SUCCESS;
}
static LV2_State_Status
save (LV2_Handle instance,
LV2_State_Store_Function store,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features)
{
AFluidSynth* self = (AFluidSynth*)instance;
if (strlen (self->current_sf2_file_path) == 0) {
return LV2_STATE_ERR_NO_PROPERTY;
}
2021-05-21 12:29:38 -04:00
LV2_State_Map_Path* map_path = NULL;
#ifdef LV2_STATE__freePath
LV2_State_Free_Path* free_path = NULL;
#endif
2016-08-23 21:18:14 -04:00
for (int i = 0; features[i]; ++i) {
if (!strcmp (features[i]->URI, LV2_STATE__mapPath)) {
2021-05-21 12:29:38 -04:00
map_path = (LV2_State_Map_Path*)features[i]->data;
2016-08-23 21:18:14 -04:00
}
#ifdef LV2_STATE__freePath
else if (!strcmp (features[i]->URI, LV2_STATE__freePath)) {
free_path = (LV2_State_Free_Path*)features[i]->data;
}
#endif
2016-08-23 21:18:14 -04:00
}
if (!map_path) {
return LV2_STATE_ERR_NO_FEATURE;
}
char* apath = map_path->abstract_path (map_path->handle, self->current_sf2_file_path);
store (handle, self->afs_sf2file,
2021-05-21 12:29:38 -04:00
apath, strlen (apath) + 1,
self->atom_Path, LV2_STATE_IS_POD);
#ifdef LV2_STATE__freePath
if (free_path) {
free_path->free_path (free_path->handle, apath);
} else
#endif
{
#ifndef _WIN32 // https://github.com/drobilla/lilv/issues/14
free (apath);
#endif
}
2021-02-12 08:16:07 -05:00
int tbank, tprog;
fluid_synth_tuning_iteration_start (self->synth);
if (0 != fluid_synth_tuning_iteration_next (self->synth, &tbank, &tprog)) {
VectorOfDouble vod;
vod.vb.child_type = self->atom_Double;
2021-05-21 12:29:38 -04:00
vod.vb.child_size = sizeof (double);
2021-02-12 08:16:07 -05:00
fluid_synth_tuning_dump (self->synth, tbank, tprog, NULL, 0, vod.pitch);
store (handle, self->afs_tuning,
2021-05-21 12:29:38 -04:00
(void*)&vod, sizeof (LV2_Atom_Vector_Body) + 128 * sizeof (double),
self->atom_Vector,
LV2_STATE_IS_POD);
2021-02-12 08:16:07 -05:00
}
2016-08-23 21:18:14 -04:00
return LV2_STATE_SUCCESS;
}
static LV2_State_Status
restore (LV2_Handle instance,
LV2_State_Retrieve_Function retrieve,
LV2_State_Handle handle,
uint32_t flags,
const LV2_Feature* const* features)
{
AFluidSynth* self = (AFluidSynth*)instance;
if (self->reinit_in_progress || self->queue_reinit) {
lv2_log_warning (&self->logger, "a-fluidsynth.lv2: sf2 load already queued.\n");
return LV2_STATE_ERR_UNKNOWN;
}
2021-05-21 12:29:38 -04:00
LV2_State_Map_Path* map_path = NULL;
#ifdef LV2_STATE__freePath
LV2_State_Free_Path* free_path = NULL;
#endif
for (int i = 0; features[i]; ++i) {
if (!strcmp (features[i]->URI, LV2_STATE__mapPath)) {
2021-05-21 12:29:38 -04:00
map_path = (LV2_State_Map_Path*)features[i]->data;
}
#ifdef LV2_STATE__freePath
2021-05-21 12:29:38 -04:00
else if (!strcmp (features[i]->URI, LV2_STATE__freePath)) {
free_path = (LV2_State_Free_Path*)features[i]->data;
}
#endif
}
if (!map_path) {
return LV2_STATE_ERR_NO_FEATURE;
}
2021-05-21 12:29:38 -04:00
size_t size;
uint32_t type;
uint32_t valflags;
2016-08-23 21:18:14 -04:00
2021-05-21 12:29:38 -04:00
const void* value = retrieve (handle, self->afs_sf2file, &size, &type, &valflags);
2021-02-12 08:16:07 -05:00
if (!value) {
return LV2_STATE_ERR_NO_PROPERTY;
}
2021-05-21 12:29:38 -04:00
char* apath = map_path->absolute_path (map_path->handle, (const char*)value);
2021-02-12 08:16:07 -05:00
strncpy (self->queue_sf2_file_path, apath, 1023);
self->queue_sf2_file_path[1023] = '\0';
2021-05-21 12:29:38 -04:00
self->queue_reinit = true;
#ifdef LV2_STATE__freePath
2021-02-12 08:16:07 -05:00
if (free_path) {
free_path->free_path (free_path->handle, apath);
} else
#endif
2021-02-12 08:16:07 -05:00
{
#ifndef _WIN32 // https://github.com/drobilla/lilv/issues/14
2021-02-12 08:16:07 -05:00
free (apath);
#endif
2016-08-23 21:18:14 -04:00
}
2021-02-12 08:16:07 -05:00
value = retrieve (handle, self->afs_tuning, &size, &type, &valflags);
2021-05-21 12:29:38 -04:00
if (value && size == sizeof (LV2_Atom_Vector_Body) + 128 * sizeof (double) && type == self->atom_Vector) {
memcpy (self->queue_tuning, LV2_ATOM_BODY (value), 128 * sizeof (double));
2021-02-12 08:16:07 -05:00
self->queue_retune = true;
}
2016-08-23 21:18:14 -04:00
return LV2_STATE_SUCCESS;
}
2021-05-21 12:29:38 -04:00
static std::string
xml_escape (const std::string& s)
2016-12-06 10:59:57 -05:00
{
std::string r (s);
2021-05-21 12:29:38 -04:00
std::replace (r.begin (), r.end (), '"', '\'');
2016-12-06 10:59:57 -05:00
size_t pos = 0;
2021-05-21 12:29:38 -04:00
while ((pos = r.find ("&", pos)) != std::string::npos) {
2016-12-06 10:59:57 -05:00
r.replace (pos, 1, "&amp;");
pos += 4;
}
return r;
2016-12-06 09:56:36 -05:00
}
#ifdef LV2_EXTENDED
static char*
mn_file (LV2_Handle instance)
{
AFluidSynth* self = (AFluidSynth*)instance;
2021-05-21 12:29:38 -04:00
char* rv = NULL;
char tmp[1024];
2016-12-06 09:56:36 -05:00
2021-05-21 12:29:38 -04:00
rv = (char*)calloc (1, sizeof (char));
2016-12-06 09:56:36 -05:00
2021-05-21 12:29:38 -04:00
#define pf(...) \
do { \
snprintf (tmp, sizeof (tmp), __VA_ARGS__); \
tmp[sizeof (tmp) - 1] = '\0'; \
rv = (char*)realloc (rv, strlen (rv) + strlen (tmp) + 1); \
strcat (rv, tmp); \
} while (0)
2016-12-06 09:56:36 -05:00
pf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2021-05-21 12:29:38 -04:00
"<!DOCTYPE MIDINameDocument PUBLIC \"-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN\" \"http://dev.midi.org/dtds/MIDINameDocument10.dtd\">\n"
"<MIDINameDocument>\n"
" <Author/>\n"
" <MasterDeviceNames>\n"
" <Manufacturer>Ardour Foundation</Manufacturer>\n"
" <Model>%s:%p</Model>\n",
AFS_URN, (void*)self);
2016-12-06 09:56:36 -05:00
pf (" <CustomDeviceMode Name=\"Default\">\n");
pf (" <ChannelNameSetAssignments>\n");
for (int c = 0; c < 16; ++c) {
pf (" <ChannelNameSetAssign Channel=\"%d\" NameSet=\"Presets\"/>\n", c + 1);
}
pf (" </ChannelNameSetAssignments>\n");
pf (" </CustomDeviceMode>\n");
// TODO collect used banks, std::set<> would be a nice here
pf (" <ChannelNameSet Name=\"Presets\">\n");
pf (" <AvailableForChannels>\n");
for (int c = 0; c < 16; ++c) {
pf (" <AvailableChannel Channel=\"%d\" Available=\"true\"/>\n", c + 1);
}
pf (" </AvailableForChannels>\n");
pf (" <UsesControlNameList Name=\"Controls\"/>\n");
2016-12-06 10:59:57 -05:00
int bn = 1;
pthread_mutex_lock (&self->bp_lock);
const BPMap& ps (self->presets);
pthread_mutex_unlock (&self->bp_lock);
for (BPMap::const_iterator i = ps.begin (); i != ps.end (); ++i, ++bn) {
pf (" <PatchBank Name=\"Patch Bank %d\">\n", i->first);
2021-05-21 12:29:38 -04:00
if (i->second.size () > 0) {
2017-01-30 18:43:43 -05:00
pf (" <MIDICommands>\n");
pf (" <ControlChange Control=\"0\" Value=\"%d\"/>\n", (i->first >> 7) & 127);
pf (" <ControlChange Control=\"32\" Value=\"%d\"/>\n", i->first & 127);
2017-01-30 18:43:43 -05:00
pf (" </MIDICommands>\n");
2016-12-06 10:59:57 -05:00
pf (" <PatchNameList>\n");
int n = 0;
2021-05-21 12:29:38 -04:00
for (BPList::const_iterator j = i->second.begin (); j != i->second.end (); ++j, ++n) {
2016-12-06 10:59:57 -05:00
pf (" <Patch Number=\"%d\" Name=\"%s\" ProgramChange=\"%d\"/>\n",
2021-05-21 12:29:38 -04:00
n, xml_escape (j->name).c_str (), j->program);
2016-12-06 09:56:36 -05:00
}
2016-12-06 10:59:57 -05:00
pf (" </PatchNameList>\n");
2016-12-06 09:56:36 -05:00
}
2016-12-06 10:59:57 -05:00
pf (" </PatchBank>\n");
2016-12-06 09:56:36 -05:00
}
2016-12-06 10:59:57 -05:00
pf (" </ChannelNameSet>\n");
2016-12-06 09:56:36 -05:00
pf (" <ControlNameList Name=\"Controls\">\n");
pf (" <Control Type=\"7bit\" Number=\"1\" Name=\"Modulation\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"2\" Name=\"Breath\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"5\" Name=\"Portamento Time\"/>\n");
2016-12-06 09:56:36 -05:00
pf (" <Control Type=\"7bit\" Number=\"7\" Name=\"Channel Volume\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"8\" Name=\"Stereo Balance\"/>\n");
2016-12-06 09:56:36 -05:00
pf (" <Control Type=\"7bit\" Number=\"10\" Name=\"Pan\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"11\" Name=\"Expression\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"37\" Name=\"Portamento Time (Fine)\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"64\" Name=\"Sustain On/Off\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"65\" Name=\"Portamento On/Off\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"66\" Name=\"Sostenuto On/Off\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"68\" Name=\"Legato On/Off\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"91\" Name=\"Reverb\"/>\n");
pf (" <Control Type=\"7bit\" Number=\"93\" Name=\"Chorus\"/>\n");
2016-12-06 09:56:36 -05:00
pf (" </ControlNameList>\n");
pf (
2021-05-21 12:29:38 -04:00
" </MasterDeviceNames>\n"
"</MIDINameDocument>");
2016-12-06 09:56:36 -05:00
2016-12-06 10:59:57 -05:00
//printf("-----\n%s\n------\n", rv);
2016-12-06 09:56:36 -05:00
return rv;
}
static char*
mn_model (LV2_Handle instance)
{
AFluidSynth* self = (AFluidSynth*)instance;
2021-05-21 12:29:38 -04:00
char* rv = (char*)malloc (64 * sizeof (char));
snprintf (rv, 64, "%s:%p", AFS_URN, (void*)self);
2016-12-06 09:56:36 -05:00
rv[63] = 0;
return rv;
}
static void
mn_free (char* v)
{
free (v);
}
#endif
2016-08-23 21:18:14 -04:00
static const void*
extension_data (const char* uri)
{
if (!strcmp (uri, LV2_WORKER__interface)) {
2016-12-06 09:56:36 -05:00
static const LV2_Worker_Interface worker = { work, work_response, NULL };
2016-08-23 21:18:14 -04:00
return &worker;
2021-05-21 12:29:38 -04:00
} else if (!strcmp (uri, LV2_STATE__interface)) {
static const LV2_State_Interface state = { save, restore };
2016-08-23 21:18:14 -04:00
return &state;
}
2016-12-06 09:56:36 -05:00
#ifdef LV2_EXTENDED
else if (!strcmp (uri, LV2_MIDNAM__interface)) {
static const LV2_Midnam_Interface midnam = { mn_file, mn_model, mn_free };
return &midnam;
}
#endif
2016-08-23 21:18:14 -04:00
return NULL;
}
static const LV2_Descriptor descriptor = {
AFS_URN,
instantiate,
connect_port,
NULL,
run,
deactivate,
2016-08-23 21:18:14 -04:00
cleanup,
extension_data
};
#undef LV2_SYMBOL_EXPORT
#ifdef _WIN32
2021-05-21 12:29:38 -04:00
# define LV2_SYMBOL_EXPORT __declspec(dllexport)
2016-08-23 21:18:14 -04:00
#else
2021-05-21 12:29:38 -04:00
# define LV2_SYMBOL_EXPORT __attribute__ ((visibility ("default")))
2016-08-23 21:18:14 -04:00
#endif
LV2_SYMBOL_EXPORT
const LV2_Descriptor*
lv2_descriptor (uint32_t index)
{
switch (index) {
2021-05-21 12:29:38 -04:00
case 0:
return &descriptor;
default:
return NULL;
2016-08-23 21:18:14 -04:00
}
}