sfizz/lv2/sfizz.c
2019-11-22 18:33:05 +01:00

769 lines
25 KiB
C

/*
Sfizz LV2 plugin
Copyright 2019, Paul Ferrand <paul@ferrand.cc>
This file was based on skeleton and example code from the LV2 plugin
distribution available at http://lv2plug.in/
The LV2 sample plugins have the following copyright and notice, which are
extended to the current work:
Copyright 2011-2016 David Robillard <d@drobilla.net>
Copyright 2011 Gabriel M. Beddingfield <gabriel@teuton.org>
Copyright 2011 James Morris <jwm.art.net@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <lv2/atom/forge.h>
#include <lv2/atom/util.h>
#include <lv2/buf-size/buf-size.h>
#include <lv2/core/lv2.h>
#include <lv2/core/lv2_util.h>
#include <lv2/midi/midi.h>
#include <lv2/options/options.h>
#include <lv2/parameters/parameters.h>
#include <lv2/patch/patch.h>
#include <lv2/state/state.h>
#include <lv2/urid/urid.h>
#include <lv2/worker/worker.h>
#include <lv2/log/logger.h>
#include <lv2/log/log.h>
#include <math.h>
#include <sfizz.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define DEFAULT_SFZ_FILE "/home/paul/Documents/AVL_Percussions/AVL_Drumkits_Percussion-1.0-Alt.sfz"
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
#define SFIZZ_PREFIX SFIZZ_URI "#"
#define SFIZZ__sfzFile "http://sfztools.github.io/sfizz:sfzfile"
#define SFIZZ__numVoices "http://sfztools.github.io/sfizz:numvoices"
#define CHANNEL_MASK 0x0F
#define MIDI_CHANNEL(byte) (byte & CHANNEL_MASK)
#define MIDI_STATUS(byte) (byte & ~CHANNEL_MASK)
#define MAX_BLOCK_SIZE 8192
#define MAX_PATH_SIZE 1024
#define MAX_VOICES 256
#define DEFAULT_VOICES 64
#define UNUSED(x) (void)(x)
typedef struct
{
// Features
LV2_URID_Map *map;
LV2_URID_Unmap *unmap;
LV2_Worker_Schedule *worker;
LV2_Log_Log *log;
// Ports
const LV2_Atom_Sequence *control_port;
LV2_Atom_Sequence *notify_port;
float *output_buffers[2];
const float *volume_port;
const float *polyphony_port;
// Atom forge
LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread
LV2_Atom_Forge_Frame notify_frame; ///< Cached for worker replies
// Logger
LV2_Log_Logger logger;
// URIs
LV2_URID midi_event_uri;
LV2_URID options_interface_uri;
LV2_URID max_block_length_uri;
LV2_URID nominal_block_length_uri;
LV2_URID sample_rate_uri;
LV2_URID atom_object_uri;
LV2_URID atom_float_uri;
LV2_URID atom_int_uri;
LV2_URID atom_urid_uri;
LV2_URID atom_path_uri;
LV2_URID patch_set_uri;
LV2_URID patch_get_uri;
LV2_URID patch_put_uri;
LV2_URID patch_property_uri;
LV2_URID patch_value_uri;
LV2_URID patch_body_uri;
LV2_URID state_changed_uri;
LV2_URID sfizz_sfz_file_uri;
LV2_URID sfizz_num_voices_uri;
// Sfizz related data
sfizz_synth_t *synth;
bool expect_nominal_block_length;
char sfz_file_path[MAX_PATH_SIZE];
int num_voices;
bool changing_voices;
int max_block_size;
float sample_rate;
} sfizz_plugin_t;
enum
{
SFIZZ_CONTROL = 0,
SFIZZ_NOTIFY = 1,
SFIZZ_LEFT = 2,
SFIZZ_RIGHT = 3,
SFIZZ_VOLUME = 4,
SFIZZ_POLYPHONY = 5
};
static void
sfizz_lv2_map_required_uris(sfizz_plugin_t *self)
{
LV2_URID_Map *map = self->map;
self->midi_event_uri = map->map(map->handle, LV2_MIDI__MidiEvent);
self->max_block_length_uri = map->map(map->handle, LV2_BUF_SIZE__maxBlockLength);
self->nominal_block_length_uri = map->map(map->handle, LV2_BUF_SIZE__nominalBlockLength);
self->sample_rate_uri = map->map(map->handle, LV2_PARAMETERS__sampleRate);
self->atom_float_uri = map->map(map->handle, LV2_ATOM__Float);
self->atom_int_uri = map->map(map->handle, LV2_ATOM__Int);
self->atom_path_uri = map->map(map->handle, LV2_ATOM__Path);
self->atom_urid_uri = map->map(map->handle, LV2_ATOM__URID);
self->atom_object_uri = map->map(map->handle, LV2_ATOM__Object);
self->patch_set_uri = map->map(map->handle, LV2_PATCH__Set);
self->patch_get_uri = map->map(map->handle, LV2_PATCH__Get);
self->patch_put_uri = map->map(map->handle, LV2_PATCH__Put);
self->patch_body_uri = map->map(map->handle, LV2_PATCH__body);
self->patch_property_uri = map->map(map->handle, LV2_PATCH__property);
self->patch_value_uri = map->map(map->handle, LV2_PATCH__value);
self->state_changed_uri = map->map(map->handle, LV2_STATE__StateChanged);
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
self->sfizz_num_voices_uri = map->map(map->handle, SFIZZ__numVoices);
}
static void
connect_port(LV2_Handle instance,
uint32_t port,
void *data)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
switch (port)
{
case SFIZZ_CONTROL:
self->control_port = (const LV2_Atom_Sequence *)data;
break;
case SFIZZ_NOTIFY:
self->notify_port = (LV2_Atom_Sequence *)data;
break;
case SFIZZ_LEFT:
self->output_buffers[0] = (float *)data;
break;
case SFIZZ_RIGHT:
self->output_buffers[1] = (float *)data;
break;
case SFIZZ_VOLUME:
self->volume_port = (const float *)data;
break;
case SFIZZ_POLYPHONY:
self->polyphony_port = (const float *)data;
break;
default:
break;
}
}
static LV2_Handle
instantiate(const LV2_Descriptor *descriptor,
double rate,
const char *path,
const LV2_Feature *const *features)
{
UNUSED(descriptor);
UNUSED(path);
LV2_Options_Option *options;
bool supports_bounded_block_size = false;
bool options_has_block_size = false;
bool supports_fixed_block_size = false;
// Allocate and initialise instance structure.
sfizz_plugin_t *self = (sfizz_plugin_t *)calloc(1, sizeof(sfizz_plugin_t));
if (!self)
return NULL;
// Set defaults
self->max_block_size = MAX_BLOCK_SIZE;
self->sample_rate = (float)rate;
self->expect_nominal_block_length = false;
self->sfz_file_path[0] = '\0';
self->num_voices = DEFAULT_VOICES;
self->changing_voices = false;
// Get the features from the host and populate the structure
for (const LV2_Feature *const *f = features; *f; f++)
{
// lv2_log_note(&self->logger, "Feature URI: %s\n", (**f).URI);
if (!strcmp((**f).URI, LV2_URID__map))
self->map = (**f).data;
if (!strcmp((**f).URI, LV2_URID__unmap))
self->unmap = (**f).data;
if (!strcmp((**f).URI, LV2_BUF_SIZE__boundedBlockLength))
supports_bounded_block_size = true;
if (!strcmp((**f).URI, LV2_BUF_SIZE__fixedBlockLength))
supports_fixed_block_size = true;
if (!strcmp((**f).URI, LV2_OPTIONS__options))
options = (**f).data;
if (!strcmp((**f).URI, LV2_WORKER__schedule))
self->worker = (**f).data;
if (!strcmp((**f).URI, LV2_LOG__log))
self->log = (**f).data;
}
// Setup the loggers
lv2_log_logger_init(&self->logger, self->map, self->log);
// The map feature is required
if (!self->map)
{
lv2_log_error(&self->logger, "Map feature not found, aborting...\n");
free(self);
return NULL;
}
// The worker feature is required
if (!self->worker)
{
lv2_log_error(&self->logger, "Worker feature not found, aborting...\n");
free(self);
return NULL;
}
// Map the URIs we will need
sfizz_lv2_map_required_uris(self);
// Initialize the forge
lv2_atom_forge_init(&self->forge, self->map);
// Check the options for the block size and sample rate parameters
if (options)
{
for (const LV2_Options_Option *opt = options; opt->value; ++opt)
{
if (opt->key == self->sample_rate_uri)
{
if (opt->type != self->atom_float_uri)
{
lv2_log_warning(&self->logger, "Got a sample rate but the type was wrong\n");
continue;
}
self->sample_rate = *(float *)opt->value;
}
else if (!self->expect_nominal_block_length && opt->key == self->max_block_length_uri)
{
if (opt->type != self->atom_int_uri)
{
lv2_log_warning(&self->logger, "Got a max block size but the type was wrong\n");
continue;
}
self->max_block_size = *(int *)opt->value;
options_has_block_size = true;
}
else if (opt->key == self->nominal_block_length_uri)
{
if (opt->type != self->atom_int_uri)
{
lv2_log_warning(&self->logger, "Got a nominal block size but the type was wrong\n");
continue;
}
self->max_block_size = *(int *)opt->value;
self->expect_nominal_block_length = true;
options_has_block_size = true;
}
}
}
else
{
lv2_log_warning(&self->logger,
"No option array was given upon instantiation; will use default values\n.");
}
// We need _some_ information on the block size
if (!supports_bounded_block_size && !supports_fixed_block_size && !options_has_block_size)
{
lv2_log_error(&self->logger,
"Bounded block size not supported and options gave no block size, aborting...\n");
free(self);
return NULL;
}
return (LV2_Handle)self;
}
static void
cleanup(LV2_Handle instance)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
free(self);
}
static void
activate(LV2_Handle instance)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
self->synth = sfizz_create_synth();
sfizz_set_samples_per_block(self->synth, self->max_block_size);
sfizz_set_sample_rate(self->synth, self->sample_rate);
if (self->sfz_file_path && strlen(self->sfz_file_path) > 0)
{
lv2_log_note(&self->logger, "Current file is: %s\n", self->sfz_file_path);
sfizz_load_file(self->synth, self->sfz_file_path);
}
}
static void
deactivate(LV2_Handle instance)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
sfizz_free(self->synth);
}
static void
sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, const LV2_Atom_Object *obj)
{
const LV2_Atom *property = NULL;
lv2_atom_object_get(obj, self->patch_property_uri, &property, 0);
if (!property)
{
lv2_log_error(&self->logger,
"[handle_object] Could not get the property from the patch object, aborting.\n");
return;
}
if (property->type != self->atom_urid_uri)
{
lv2_log_error(&self->logger,
"[handle_object] Atom type was not a URID, aborting.\n");
return;
}
const uint32_t key = ((const LV2_Atom_URID *)property)->body;
const LV2_Atom *atom = NULL;
lv2_atom_object_get(obj, self->patch_value_uri, &atom, 0);
if (!atom)
{
lv2_log_error(&self->logger, "[handle_object] Error retrieving the atom, aborting.\n");
if (self->unmap)
lv2_log_warning(&self->logger,
"Atom URI: %s\n",
self->unmap->unmap(self->unmap->handle, key));
return;
}
if (key == self->sfizz_sfz_file_uri)
{
const uint32_t original_atom_size = lv2_atom_total_size((const LV2_Atom *)atom);
const uint32_t null_terminated_atom_size = original_atom_size + 1;
char atom_buffer[null_terminated_atom_size];
memcpy(&atom_buffer, atom, original_atom_size);
atom_buffer[original_atom_size] = 0; // Null terminate the string for safety
LV2_Atom *sfz_file_path = (LV2_Atom *)&atom_buffer;
sfz_file_path->type = self->sfizz_sfz_file_uri;
// If the parameter is different from the current one we send it through
if (strcmp(self->sfz_file_path, LV2_ATOM_BODY_CONST(sfz_file_path)))
self->worker->schedule_work(self->worker->handle, null_terminated_atom_size, sfz_file_path);
lv2_log_note(&self->logger, "[handle_object] Received an SFZ file: %s\n", (char*)LV2_ATOM_BODY_CONST(sfz_file_path));
}
else
{
lv2_log_warning(&self->logger, "[handle_object] Unknown or unsupported object.\n");
if (self->unmap)
lv2_log_warning(&self->logger,
"Object URI: %s\n",
self->unmap->unmap(self->unmap->handle, key));
return;
}
// Pfiou, we got a valid parameter, so send it to the worker
}
static void
sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
{
const uint8_t *const msg = (const uint8_t *)(ev + 1);
switch (lv2_midi_message_type(msg))
{
case LV2_MIDI_MSG_NOTE_ON:
// lv2_log_note(&self->logger,
// "[process_midi] Received note on %d/%d at time %d\n", msg[0], msg[1], ev->time.frames);
sfizz_send_note_on(self->synth,
(int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]) + 1,
(int)msg[1],
msg[2]);
break;
case LV2_MIDI_MSG_NOTE_OFF:
// lv2_log_note(&self->logger,
// "[process_midi] Received note off %d/%d at time %d\n", msg[0], msg[1], ev->time.frames);
sfizz_send_note_off(self->synth,
(int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]) + 1,
(int)msg[1],
msg[2]);
break;
case LV2_MIDI_MSG_CONTROLLER:
// lv2_log_note(&self->logger,
// "[process_midi] Received CC %d/%d at time %d\n", msg[0], msg[1], ev->time.frames);
sfizz_send_cc(self->synth,
(int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]) + 1,
(int)msg[1],
msg[2]);
break;
default:
break;
}
}
static void sfizz_lv2_send_file_path(sfizz_plugin_t *self)
{
LV2_Atom_Forge_Frame frame;
lv2_atom_forge_frame_time(&self->forge, 0);
lv2_atom_forge_object(&self->forge, &frame, 0, self->patch_set_uri);
lv2_atom_forge_key(&self->forge, self->patch_property_uri);
lv2_atom_forge_urid(&self->forge, self->sfizz_sfz_file_uri);
lv2_atom_forge_key(&self->forge, self->patch_value_uri);
lv2_atom_forge_path(&self->forge, self->sfz_file_path, strlen(self->sfz_file_path));
lv2_atom_forge_pop(&self->forge, &frame);
}
static void
run(LV2_Handle instance, uint32_t sample_count)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
if (!self->control_port || !self->notify_port)
return;
// Set up forge to write directly to notify output port.
const size_t notify_capacity = self->notify_port->atom.size;
lv2_atom_forge_set_buffer(&self->forge, (uint8_t *)self->notify_port, notify_capacity);
// Start a sequence in the notify output port.
lv2_atom_forge_sequence_head(&self->forge, &self->notify_frame, 0);
LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev)
{
// If the received atom is an object/patch message
if (ev->body.type == self->atom_object_uri)
{
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
if (obj->body.otype == self->patch_set_uri)
{
sfizz_lv2_handle_atom_object(self, obj);
}
else if (obj->body.otype == self->patch_get_uri)
{
const LV2_Atom_URID *property = NULL;
lv2_atom_object_get(obj, self->patch_property_uri, &property, 0);
if (!property) // Send the full state
{
sfizz_lv2_send_file_path(self);
}
else if (property->body == self->sfizz_sfz_file_uri)
{
sfizz_lv2_send_file_path(self);
}
}
else
{
lv2_log_warning(&self->logger, "Got an Object atom but it was not supported.\n");
if (self->unmap)
lv2_log_warning(&self->logger,
"Object URI: %s\n",
self->unmap->unmap(self->unmap->handle, obj->body.otype));
continue;
}
// Got an atom that is a MIDI event
}
else if (ev->body.type == self->midi_event_uri)
{
sfizz_lv2_process_midi_event(self, ev);
}
}
float volume = *(self->volume_port);
sfizz_set_volume(self->synth, volume);
int num_voices = (int)*self->polyphony_port;
if (num_voices != self->num_voices && !self->changing_voices)
{
lv2_log_note(&self->logger, "[run] Number of voices changed to %d\n", num_voices);
LV2_Atom_Int num_voices_atom;
num_voices_atom.atom.type = self->sfizz_num_voices_uri;
num_voices_atom.atom.size = sizeof(int);
num_voices_atom.body = num_voices;
if (self->worker->schedule_work(self->worker->handle,
lv2_atom_total_size((LV2_Atom*)&num_voices_atom),
&num_voices_atom) == LV2_WORKER_SUCCESS)
{
self->changing_voices = true;
}
}
// Render the block
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
}
static uint32_t
lv2_get_options(LV2_Handle instance, LV2_Options_Option *options)
{
UNUSED(instance);
UNUSED(options);
// We have no options
return LV2_OPTIONS_ERR_UNKNOWN;
}
static uint32_t
lv2_set_options(LV2_Handle instance, const LV2_Options_Option *options)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
// Update the block size and sample rate as needed
for (const LV2_Options_Option *opt = options; opt->value; ++opt)
{
if (opt->key == self->sample_rate_uri)
{
if (opt->type != self->atom_float_uri)
{
lv2_log_warning(&self->logger, "Got a sample rate but the type was wrong\n");
continue;
}
self->sample_rate = *(float *)opt->value;
sfizz_set_sample_rate(self->synth, self->sample_rate);
}
else if (!self->expect_nominal_block_length && opt->key == self->max_block_length_uri)
{
if (opt->type != self->atom_int_uri)
{
lv2_log_warning(&self->logger, "Got a max block size but the type was wrong\n");
continue;
}
self->max_block_size = *(int *)opt->value;
sfizz_set_samples_per_block(self->synth, self->max_block_size);
}
else if (opt->key == self->nominal_block_length_uri)
{
if (opt->type != self->atom_int_uri)
{
lv2_log_warning(&self->logger, "Got a nominal block size but the type was wrong\n");
continue;
}
self->max_block_size = *(int *)opt->value;
sfizz_set_samples_per_block(self->synth, self->max_block_size);
}
}
return LV2_OPTIONS_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)
{
UNUSED(flags);
UNUSED(features);
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
// Fetch back the saved file path, if any
size_t size;
uint32_t type;
uint32_t val_flags;
const void *value;
value = retrieve(handle, self->sfizz_sfz_file_uri, &size, &type, &val_flags);
if (value)
{
lv2_log_note(&self->logger, "Restoring the file %s\n", (const char *)value);
if (sfizz_load_file(self->synth, (const char *)value))
strcpy(self->sfz_file_path, (const char *)value);
}
value = retrieve(handle, self->sfizz_num_voices_uri, &size, &type, &val_flags);
if (value)
{
int num_voices = *(const int *)value;
if (num_voices > 0 && num_voices <= MAX_VOICES && num_voices != self->num_voices)
{
lv2_log_note(&self->logger, "Restoring the number of voices to %d\n", num_voices);
sfizz_set_num_voices(self->synth, num_voices);
self->num_voices = num_voices;
}
}
return LV2_STATE_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)
{
UNUSED(flags);
UNUSED(features);
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
// Save the file path
store(handle,
self->sfizz_sfz_file_uri,
self->sfz_file_path,
strlen(self->sfz_file_path) + 1,
self->atom_path_uri,
LV2_STATE_IS_POD);
// Save the number of voices
store(handle,
self->sfizz_num_voices_uri,
&self->num_voices,
sizeof(int),
self->atom_int_uri,
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
return LV2_STATE_SUCCESS;
}
// This runs in a lower priority thread
static LV2_Worker_Status
work(LV2_Handle instance,
LV2_Worker_Respond_Function respond,
LV2_Worker_Respond_Handle handle,
uint32_t size,
const void *data)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
if (!data)
{
lv2_log_error(&self->logger, "[worker] Got an empty data.\n");
return LV2_WORKER_ERR_UNKNOWN;
}
const LV2_Atom *atom = (const LV2_Atom *)data;
if (atom->type == self->sfizz_sfz_file_uri)
{
const char *sfz_file_path = LV2_ATOM_BODY_CONST(atom);
lv2_log_note(&self->logger, "[work] Loading file: %s\n", sfz_file_path);
sfizz_load_file(self->synth, sfz_file_path);
}
else if (atom->type == self->sfizz_num_voices_uri)
{
const int num_voices = *(const int *)LV2_ATOM_BODY_CONST(atom);
lv2_log_note(&self->logger, "[work] Changing number of voices to: %d\n", num_voices);
sfizz_set_num_voices(self->synth, num_voices);
}
else
{
lv2_log_error(&self->logger, "[worker] Got an unknown atom.\n");
if (self->unmap)
lv2_log_error(&self->logger,
"URI: %s\n",
self->unmap->unmap(self->unmap->handle, atom->type));
return LV2_WORKER_ERR_UNKNOWN;
}
respond(handle, size, data);
return LV2_WORKER_SUCCESS;
}
// This runs in the audio thread
static LV2_Worker_Status
work_response(LV2_Handle instance,
uint32_t size,
const void *data)
{
UNUSED(size);
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
if (!data)
return LV2_WORKER_ERR_UNKNOWN;
const LV2_Atom *atom = (const LV2_Atom *)data;
if (atom->type == self->sfizz_sfz_file_uri)
{
const char *sfz_file_path = LV2_ATOM_BODY_CONST(atom);
strcpy(self->sfz_file_path, sfz_file_path);
lv2_log_note(&self->logger, "[work_response] File changed to: %s\n", self->sfz_file_path);
}
else if (atom->type == self->sfizz_num_voices_uri)
{
const int num_voices = *(const int *)LV2_ATOM_BODY_CONST(atom);
self->num_voices = num_voices;
self->changing_voices = false;
lv2_log_note(&self->logger, "[work_response] Number of voices changed to: %d\n", self->num_voices);
}
else
{
lv2_log_error(&self->logger, "[work_response] Got an unknown atom.\n");
if (self->unmap)
lv2_log_error(&self->logger,
"URI: %s\n",
self->unmap->unmap(self->unmap->handle, atom->type));
return LV2_WORKER_ERR_UNKNOWN;
}
return LV2_WORKER_SUCCESS;
}
static const void *
extension_data(const char *uri)
{
static const LV2_Options_Interface options = {lv2_get_options, lv2_set_options};
static const LV2_State_Interface state = {save, restore};
static const LV2_Worker_Interface worker = {work, work_response, NULL};
// Advertise the extensions we support
if (!strcmp(uri, LV2_OPTIONS__interface))
return &options;
else if (!strcmp(uri, LV2_STATE__interface))
return &state;
else if (!strcmp(uri, LV2_WORKER__interface))
return &worker;
return NULL;
}
static const LV2_Descriptor descriptor = {
SFIZZ_URI,
instantiate,
connect_port,
activate,
run,
deactivate,
cleanup,
extension_data};
LV2_SYMBOL_EXPORT
const LV2_Descriptor *
lv2_descriptor(uint32_t index)
{
switch (index)
{
case 0:
return &descriptor;
default:
return NULL;
}
}