diff options
Diffstat (limited to 'libs/plugins/a-fluidsynth.lv2/a-fluidsynth.cc')
-rw-r--r-- | libs/plugins/a-fluidsynth.lv2/a-fluidsynth.cc | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.cc b/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.cc new file mode 100644 index 0000000000..4a91b31823 --- /dev/null +++ b/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.cc @@ -0,0 +1,791 @@ +/* a-fluidsynth -- simple & robust x-platform fluidsynth LV2 + * + * Copyright (C) 2016 Robin Gareus <robin@gareus.org> + * + * 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, or (at your option) + * any later version. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <math.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 + +#ifdef LV2_EXTENDED +#include "../../ardour/ardour/lv2_extensions.h" +#endif + +#include "fluidsynth.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/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> + +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_LAST +}; + +enum { + CMD_APPLY = 0, + CMD_FREE = 1, +}; + +typedef struct { + /* ports */ + const LV2_Atom_Sequence* control; + LV2_Atom_Sequence* notify; + + float* p_ports[FS_PORT_LAST]; + float v_ports[FS_PORT_LAST]; + + /* 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; + LV2_URID midi_MidiEvent; + LV2_URID patch_Get; + LV2_URID patch_Set; + LV2_URID patch_property; + LV2_URID patch_value; + LV2_URID afs_sf2file; + + /* lv2 extensions */ + LV2_Log_Log* log; + LV2_Log_Logger logger; + LV2_Worker_Schedule* schedule; + LV2_Atom_Forge forge; + LV2_Atom_Forge_Frame frame; + +#ifdef LV2_EXTENDED + LV2_Midnam* midnam; +#endif + + /* state */ + bool panic; + bool initialized; + bool inform_ui; + + char current_sf2_file_path[1024]; + char queue_sf2_file_path[1024]; + bool reinit_in_progress; // set in run, cleared in work_response + bool queue_reinit; // set in restore, cleared in work_response + + fluid_midi_event_t* fmidi_event; + +} AFluidSynth; + +/* ***************************************************************************** + * helpers + */ + +static bool +load_sf2 (AFluidSynth* self, const char* fn) +{ + const int synth_id = fluid_synth_sfload (self->synth, fn, 1); + + 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; + } + + int chn; + fluid_preset_t preset; + sfont->iteration_start (sfont); + for (chn = 0; sfont->iteration_next (sfont, &preset) && chn < 16; ++chn) { + fluid_synth_program_select (self->synth, chn, synth_id, + preset.get_banknum (&preset), preset.get_num (&preset)); + } + + if (chn == 0) { + return false; + } + + return true; +} + +static const LV2_Atom* +parse_patch_msg (AFluidSynth* self, const LV2_Atom_Object* obj) +{ + const LV2_Atom* property = NULL; + 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); + 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; + lv2_atom_forge_frame_time (&self->forge, 0); + x_forge_object (&self->forge, &frame, 1, self->patch_Set); + 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)); + + lv2_atom_forge_pop (&self->forge, &frame); +} + +static float +db_to_coeff (float db) +{ + if (db <= -80) { return 0; } + else if (db >= 20) { return 10; } + return powf (10.f, .05f * db); +} + +/* ***************************************************************************** + * 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; + + for (int i=0; features[i] != NULL; ++i) { + 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; +#ifdef LV2_EXTENDED + } else if (!strcmp (features[i]->URI, LV2_MIDNAM__update)) { + self->midnam = (LV2_Midnam*)features[i]->data; +#endif + } + } + + 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; + } + + /* 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.parallel-render", 1); + fluid_settings_setint (self->settings, "synth.threadsafe-api", 0); + + self->synth = new_fluid_synth (self->settings); + + if (!self->synth) { + lv2_log_error (&self->logger, "a-fluidsynth.lv2: cannot allocate Fluid Synth\n"); + delete_fluid_settings (self->settings); + free (self); + return NULL; + } + + fluid_synth_set_gain (self->synth, 1.0f); + fluid_synth_set_polyphony (self->synth, 32); + fluid_synth_set_sample_rate (self->synth, (float)rate); + + 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); + delete_fluid_settings (self->settings); + free (self); + return NULL; + } + + /* initialize plugin state */ + + self->panic = false; + self->inform_ui = false; + self->initialized = false; + self->reinit_in_progress = false; + self->queue_reinit = false; + + lv2_atom_forge_init (&self->forge, map); + + /* map URIDs */ + 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_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->afs_sf2file = map->map (map->handle, AFS_URN ":sf2file"); + + 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; + } + break; + } +} + +static void +deactivate (LV2_Handle instance) +{ + AFluidSynth* self = (AFluidSynth*)instance; + 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); + + 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)); + } 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, + *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, + 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]; + } + } + + uint32_t offset = 0; + + LV2_ATOM_SEQUENCE_FOREACH (self->control, ev) { + 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); + } + else if (obj->body.otype == self->patch_Set) { + const LV2_Atom* file_path = parse_patch_msg (self, obj); + if (file_path && !self->reinit_in_progress && !self->queue_reinit) { + const char *fn = (const char*)(file_path+1); + strncpy (self->queue_sf2_file_path, fn, 1023); + self->queue_sf2_file_path[1023] = '\0'; + self->reinit_in_progress = true; + int magic = 0x4711; + self->schedule->schedule_work (self->schedule->handle, sizeof (int), &magic); + } + } + } + else if (ev->body.type == self->midi_MidiEvent && self->initialized && !self->reinit_in_progress) { + if (ev->body.size > 3 || ev->time.frames >= n_samples) { + continue; + } + + if (ev->time.frames > offset) { + fluid_synth_write_float ( + 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); + } + + 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]); + } + } + + fluid_synth_handle_midi_event (self->synth, self->fmidi_event); + } + } + + if (self->queue_reinit && !self->reinit_in_progress) { + self->reinit_in_progress = true; + int magic = 0x4711; + self->schedule->schedule_work (self->schedule->handle, sizeof (int), &magic); + } + + /* inform the GUI */ + if (self->inform_ui) { + self->inform_ui = false; + inform_ui (self); +#ifdef LV2_EXTENDED + self->midnam->update (self->midnam->handle); +#endif + } + + if (n_samples > offset && self->initialized && !self->reinit_in_progress) { + fluid_synth_write_float ( + 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); + } +} + +static void cleanup (LV2_Handle instance) +{ + AFluidSynth* self = (AFluidSynth*)instance; + delete_fluid_synth (self->synth); + delete_fluid_settings (self->settings); + delete_fluid_midi_event (self->fmidi_event); + 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; + + if (size != sizeof (int)) { + 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; + // boostrap synth engine. + float l[1024]; + float r[1024]; + fluid_synth_write_float (self->synth, 1024, l, 0, 1, r, 0, 1); + } + + respond (handle, 1, ""); + return LV2_WORKER_SUCCESS; +} + +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); + } else { + self->current_sf2_file_path[0] = 0; + } + + self->reinit_in_progress = false; + self->inform_ui = true; + self->queue_reinit = false; + 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; + } + + LV2_State_Map_Path* map_path = NULL; + + for (int i = 0; features[i]; ++i) { + if (!strcmp (features[i]->URI, LV2_STATE__mapPath)) { + map_path = (LV2_State_Map_Path*) features[i]->data; + } + } + + 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, + apath, strlen (apath) + 1, + self->atom_Path, LV2_STATE_IS_POD); + + 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; + } + + size_t size; + uint32_t type; + uint32_t valflags; + + const void* value = retrieve (handle, self->afs_sf2file, &size, &type, &valflags); + if (value) { + strncpy (self->queue_sf2_file_path, (const char*) value, 1023); + self->queue_sf2_file_path[1023] = '\0'; + self->queue_reinit = true; + } + return LV2_STATE_SUCCESS; +} + +static char* xml_escape (const char* s) { + // crude, but hey + if (!s) { + return strdup (""); + } + char* tmp; + char* rv = strdup (s); + while ((tmp = strchr(rv, '"'))) { + *tmp = '\''; + } + while ((tmp = strchr(rv, '&'))) { + *tmp = '+'; + } + return rv; +} + +#ifdef LV2_EXTENDED +static char* +mn_file (LV2_Handle instance) +{ + AFluidSynth* self = (AFluidSynth*)instance; + char* rv = NULL; + char tmp[1024]; + + fluid_sfont_t* sfont = NULL; + if (self->initialized && !self->reinit_in_progress) { + sfont = fluid_synth_get_sfont (self->synth, 0); + } + // TODO collect program info during load_sf2(); + + rv = (char*) calloc (1, sizeof (char)); + +#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) + + pf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!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); + + + 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"); + pf (" <PatchBank Name=\"Patch Bank 1\">\n"); + pf (" <UsesPatchNameList Name=\"Programmes\"/>\n"); + pf (" </PatchBank>\n"); + pf (" </ChannelNameSet>\n"); + + + pf (" <PatchNameList Name=\"Programmes\">\n"); + if (sfont) { + fluid_preset_t preset; + sfont->iteration_start (sfont); + for (int num = 1; sfont->iteration_next (sfont, &preset); ++num) { + if (preset.get_banknum (&preset) != 0) { + continue; + } + char* pn = xml_escape (preset.get_name (&preset)); + pf (" <Patch Number=\"%d\" Name=\"%s\" ProgramChange=\"%d\"/>\n", + num, pn, preset.get_num (&preset)); + free (pn); + } + } + pf (" </PatchNameList>\n"); + + pf (" <ControlNameList Name=\"Controls\">\n"); + pf (" <Control Type=\"7bit\" Number=\"7\" Name=\"Channel Volume\"/>\n"); + pf (" <Control Type=\"7bit\" Number=\"10\" Name=\"Pan\"/>\n"); + pf (" <Control Type=\"7bit\" Number=\"39\" Name=\"Channel Volume (Fine)\"/>\n"); + pf (" <Control Type=\"7bit\" Number=\"42\" Name=\"Pan (Fine)\"/>\n"); + pf (" <Control Type=\"7bit\" Number=\"64\" Name=\"Damper Pedal (Sustain)\"/>\n"); + pf (" <Control Type=\"7bit\" Number=\"66\" Name=\"Sostenuto\"/>\n"); + pf (" </ControlNameList>\n"); + + + pf ( + " </MasterDeviceNames>\n" + "</MIDINameDocument>" + ); + + return rv; +} + +static char* +mn_model (LV2_Handle instance) +{ + AFluidSynth* self = (AFluidSynth*)instance; + char* rv = (char*) malloc (64 * sizeof (char)); + snprintf (rv, 64, "%s:%p", AFS_URN, (void*) self); + rv[63] = 0; + return rv; +} + +static void +mn_free (char* v) +{ + free (v); +} +#endif + +static const void* +extension_data (const char* uri) +{ + if (!strcmp (uri, LV2_WORKER__interface)) { + static const LV2_Worker_Interface worker = { work, work_response, NULL }; + return &worker; + } + else if (!strcmp (uri, LV2_STATE__interface)) { + static const LV2_State_Interface state = { save, restore }; + return &state; + } +#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 + return NULL; +} + +static const LV2_Descriptor descriptor = { + AFS_URN, + instantiate, + connect_port, + NULL, + run, + deactivate, + cleanup, + extension_data +}; + +#undef LV2_SYMBOL_EXPORT +#ifdef _WIN32 +# define LV2_SYMBOL_EXPORT __declspec(dllexport) +#else +# define LV2_SYMBOL_EXPORT __attribute__ ((visibility ("default"))) +#endif +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor (uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} |