From 870cb197373cb70b28233b3a30e90a70e737dba5 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Wed, 24 Aug 2016 03:18:14 +0200 Subject: [PATCH] a-fluidsynth --- libs/plugins/a-fluidsynth.lv2/a-fluidsynth.c | 560 ++++++++++++++++++ .../a-fluidsynth.lv2/a-fluidsynth.ttl.in | 67 +++ libs/plugins/a-fluidsynth.lv2/manifest.ttl.in | 7 + libs/plugins/a-fluidsynth.lv2/wscript | 57 ++ wscript | 1 + 5 files changed, 692 insertions(+) create mode 100644 libs/plugins/a-fluidsynth.lv2/a-fluidsynth.c create mode 100644 libs/plugins/a-fluidsynth.lv2/a-fluidsynth.ttl.in create mode 100644 libs/plugins/a-fluidsynth.lv2/manifest.ttl.in create mode 100644 libs/plugins/a-fluidsynth.lv2/wscript diff --git a/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.c b/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.c new file mode 100644 index 0000000000..c6b743ed84 --- /dev/null +++ b/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.c @@ -0,0 +1,560 @@ +/* a-fluidsynth -- simple & robust x-platform fluidsynth LV2 + * + * Copyright (C) 2016 Robin Gareus + * + * 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 . + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include + +#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 + +#include "fluidsynth.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + FS_PORT_CONTROL = 0, + FS_PORT_NOTIFY, + FS_PORT_OUT_L, + FS_PORT_OUT_R +}; + +enum { + CMD_APPLY = 0, + CMD_FREE = 1, +}; + +typedef struct { + /* ports */ + const LV2_Atom_Sequence* control; + LV2_Atom_Sequence* notify; + float* output[2]; + + /* 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; + + /* 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 < 15; ++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); +} + +/* ***************************************************************************** + * 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; + } + } + + 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 = true; + 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; + case FS_PORT_OUT_L: + self->output[0] = (float*)data; + break; + case FS_PORT_OUT_R: + self->output[1] = (float*)data; + break; + } +} + +static void +activate (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->output[0], 0, n_samples * sizeof (float)); + memset (self->output[1], 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; + } + + 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->output[0][offset], 0, 1, + &self->output[1][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) { + 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); + } + + if (n_samples > offset && self->initialized && !self->reinit_in_progress) { + fluid_synth_write_float ( + self->synth, + n_samples - offset, + &self->output[0][offset], 0, 1, + &self->output[1][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); + 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; + self->reinit_in_progress = false; + self->queue_reinit = false; + self->inform_ui = true; + self->panic = true; + + if (self->initialized) { + strcpy (self->current_sf2_file_path, self->queue_sf2_file_path); + } else { + self->current_sf2_file_path[0] = 0; + } + 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 | LV2_STATE_IS_PORTABLE); + + 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, value, 1023); + self->queue_sf2_file_path[1023] = '\0'; + self->queue_reinit = true; + } + return LV2_STATE_SUCCESS; +} + +static const void* +extension_data (const char* uri) +{ + static const LV2_Worker_Interface worker = { work, work_response, NULL }; + static const LV2_State_Interface state = { save, restore }; + if (!strcmp (uri, LV2_WORKER__interface)) { + return &worker; + } + else if (!strcmp (uri, LV2_STATE__interface)) { + return &state; + } + return NULL; +} + +static const LV2_Descriptor descriptor = { + AFS_URN, + instantiate, + connect_port, + activate, + run, + NULL, + 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; + } +} diff --git a/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.ttl.in b/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.ttl.in new file mode 100644 index 0000000000..3363d18a6d --- /dev/null +++ b/libs/plugins/a-fluidsynth.lv2/a-fluidsynth.ttl.in @@ -0,0 +1,67 @@ +@prefix atom: . +@prefix doap: . +@prefix foaf: . +@prefix lv2: . +@prefix midi: . +@prefix patch: . +@prefix rdf: . +@prefix rdfs: . +@prefix state: . +@prefix unit: . +@prefix urid: . +@prefix work: . + + + a foaf:Person ; + foaf:name "Ardour Team" ; + foaf:homepage . + + + a lv2:Parameter ; + rdfs:label "SF2 File" ; + rdfs:range atom:Path . + + + a doap:Project, lv2:InstrumentPlugin, lv2:Plugin ; + + doap:name "a-Fluid Synth" ; + rdfs:comment "SF2 Synthesizer using Fluidsynth" ; + + doap:maintainer ; + doap:license ; + + lv2:microVersion 2 ; lv2:minorVersion 0 ; + + lv2:requiredFeature urid:map, work:schedule ; + lv2:extensionData work:interface, state:interface ; + lv2:optionalFeature lv2:hardRTCapable; + + patch:writable ; + + lv2:port [ + a lv2:InputPort, atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message, midi:MidiEvent; + lv2:designation lv2:control ; + lv2:index 0 ; + lv2:symbol "control" ; + lv2:name "Midi In" ; + ] , [ + a lv2:OutputPort, atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports patch:Message; + lv2:designation lv2:control ; + lv2:index 1 ; + lv2:symbol "notify" ; + lv2:name "UI Notifications" ; + ] , [ + a lv2:OutputPort, lv2:AudioPort ; + lv2:index 2 ; + lv2:symbol "outL" ; + lv2:name "Out Left" ; + ] , [ + a lv2:OutputPort, lv2:AudioPort ; + lv2:index 3 ; + lv2:symbol "outR" ; + lv2:name "Output Right" ; + ] . diff --git a/libs/plugins/a-fluidsynth.lv2/manifest.ttl.in b/libs/plugins/a-fluidsynth.lv2/manifest.ttl.in new file mode 100644 index 0000000000..39576ff5ad --- /dev/null +++ b/libs/plugins/a-fluidsynth.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: . +@prefix rdfs: . + + + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . diff --git a/libs/plugins/a-fluidsynth.lv2/wscript b/libs/plugins/a-fluidsynth.lv2/wscript new file mode 100644 index 0000000000..50f727e1cc --- /dev/null +++ b/libs/plugins/a-fluidsynth.lv2/wscript @@ -0,0 +1,57 @@ +#!/usr/bin/env python +import os +import re +import shutil +import waflib.extras.autowaf as autowaf +import waflib.Options as Options, waflib.Utils as Utils + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + autowaf.set_options(opt) + +def configure(conf): + conf.load('compiler_c') + autowaf.configure(conf) + if Options.options.lv2: + autowaf.check_pkg(conf, 'lv2', atleast_version='1.0.0', + uselib_store='LV2_1_0_0') + +def build(bld): + bundle = 'a-fluidsynth.lv2' + module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) + module_ext = module_pat[module_pat.rfind('.'):] + + if bld.is_defined ('HAVE_LV2'): + # Build RDF files + for i in ['manifest.ttl', 'a-fluidsynth.ttl']: + bld(features = 'subst', + source = i + '.in', + target = '../../LV2/%s/%s' % (bundle, i), + install_path = '${LV2DIR}/%s' % bundle, + chmod = Utils.O644, + LIB_EXT = module_ext) + + # Build plugin library + obj = bld(features = 'c cshlib', + source = 'a-fluidsynth.c', + name = 'a-fluidsynth', + cflags = [ '-fPIC', bld.env['compiler_flags_dict']['c99'] ], + includes = [ '../../ardour' ], + target = '../../LV2/%s/a-fluidsynth' % bundle, + install_path = '${LV2DIR}/%s' % bundle, + uselib = ['LIBFLUIDSYNTH'], + use = ['LV2_1_0_0'] + ) + + if bld.is_defined('USE_EXTERNAL_LIBS'): + obj.uselib.extend(['LIBFLUIDSYNTH']) + else: + obj.use.extend(['libfluidsynth_includes', 'libfluidsynth']) + + obj.env.cshlib_PATTERN = module_pat + obj.env.cxxshlib_PATTERN = module_pat + +# vi:set ts=4 sw=4 et: diff --git a/wscript b/wscript index a4ee4ab590..8edd38d176 100644 --- a/wscript +++ b/wscript @@ -224,6 +224,7 @@ children = [ 'libs/plugins/a-delay.lv2', 'libs/plugins/a-eq.lv2', 'libs/plugins/a-reverb.lv2', + 'libs/plugins/a-fluidsynth.lv2', 'gtk2_ardour', 'export', 'midi_maps',