Checking for file changes or loading disables future file change checks The worker only responds to reactivate the file change checks Upon parameter change, the new value is stored immediately so only one message is sent to the worker Removed the changing_state bool and let the sfizz internal lock protect the state
1107 lines
37 KiB
C
1107 lines
37 KiB
C
/*
|
|
SPDX-License-Identifier: ISC
|
|
|
|
Sfizz LV2 plugin
|
|
|
|
Copyright 2019-2020, 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.
|
|
|
|
Compiling this plugin statically against libsndfile implies distributing it
|
|
under the terms of the LGPL v3 license. See the LICENSE.md file for more
|
|
information. If you did not receive a LICENSE.md file, inform the current
|
|
maintainer.
|
|
|
|
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 <ardour/lv2_extensions.h>
|
|
|
|
#include <math.h>
|
|
#include <sfizz.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "atomic_compat.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 SFIZZ__preloadSize "http://sfztools.github.io/sfizz:preload_size"
|
|
#define SFIZZ__oversampling "http://sfztools.github.io/sfizz:oversampling"
|
|
// These ones are just for the worker
|
|
#define SFIZZ__logStatus "http://sfztools.github.io/sfizz:log_status"
|
|
#define SFIZZ__checkModification "http://sfztools.github.io/sfizz:check_modification"
|
|
|
|
#define CHANNEL_MASK 0x0F
|
|
#define MIDI_CHANNEL(byte) (byte & CHANNEL_MASK)
|
|
#define MIDI_STATUS(byte) (byte & ~CHANNEL_MASK)
|
|
#define PITCH_BUILD_AND_CENTER(first_byte, last_byte) (int)(((unsigned int)last_byte << 7) + (unsigned int)first_byte) - 8192
|
|
#define MAX_BLOCK_SIZE 8192
|
|
#define MAX_PATH_SIZE 1024
|
|
#define MAX_VOICES 512
|
|
#define DEFAULT_VOICES 64
|
|
#define DEFAULT_OVERSAMPLING SFIZZ_OVERSAMPLING_X1
|
|
#define DEFAULT_PRELOAD 8192
|
|
#define LOG_SAMPLE_COUNT 48000
|
|
#define UNUSED(x) (void)(x)
|
|
|
|
#ifndef NDEBUG
|
|
#define LV2_DEBUG(...) lv2_log_note(&self->logger, "[DEBUG] " __VA_ARGS__)
|
|
#else
|
|
#define LV2_DEBUG(...)
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
// Features
|
|
LV2_URID_Map *map;
|
|
LV2_URID_Unmap *unmap;
|
|
LV2_Worker_Schedule *worker;
|
|
LV2_Log_Log *log;
|
|
LV2_Midnam *midnam;
|
|
|
|
// Ports
|
|
const LV2_Atom_Sequence *control_port;
|
|
LV2_Atom_Sequence *notify_port;
|
|
float *output_buffers[2];
|
|
const float *volume_port;
|
|
const float *polyphony_port;
|
|
const float *oversampling_port;
|
|
const float *preload_port;
|
|
const float *freewheel_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;
|
|
LV2_URID sfizz_preload_size_uri;
|
|
LV2_URID sfizz_oversampling_uri;
|
|
LV2_URID sfizz_log_status_uri;
|
|
LV2_URID sfizz_check_modification_uri;
|
|
|
|
// Sfizz related data
|
|
sfizz_synth_t *synth;
|
|
bool expect_nominal_block_length;
|
|
char sfz_file_path[MAX_PATH_SIZE];
|
|
int num_voices;
|
|
unsigned int preload_size;
|
|
sfizz_oversampling_factor_t oversampling;
|
|
volatile bool check_modification;
|
|
int max_block_size;
|
|
int sample_counter;
|
|
float sample_rate;
|
|
atomic_int must_update_midnam;
|
|
} sfizz_plugin_t;
|
|
|
|
enum
|
|
{
|
|
SFIZZ_CONTROL = 0,
|
|
SFIZZ_NOTIFY = 1,
|
|
SFIZZ_LEFT = 2,
|
|
SFIZZ_RIGHT = 3,
|
|
SFIZZ_VOLUME = 4,
|
|
SFIZZ_POLYPHONY = 5,
|
|
SFIZZ_OVERSAMPLING = 6,
|
|
SFIZZ_PRELOAD = 7,
|
|
SFIZZ_FREEWHEELING = 8
|
|
};
|
|
|
|
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);
|
|
self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize);
|
|
self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling);
|
|
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
|
|
self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification);
|
|
}
|
|
|
|
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;
|
|
case SFIZZ_OVERSAMPLING:
|
|
self->oversampling_port = (const float *)data;
|
|
break;
|
|
case SFIZZ_PRELOAD:
|
|
self->preload_port = (const float *)data;
|
|
break;
|
|
case SFIZZ_FREEWHEELING:
|
|
self->freewheel_port = (const float *)data;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This function sets the sample rate in the self parameter but does not update it.
|
|
static void
|
|
sfizz_lv2_parse_sample_rate(sfizz_plugin_t* self, const LV2_Options_Option* opt)
|
|
{
|
|
if (opt->type == self->atom_float_uri)
|
|
{
|
|
// self->sample_rate = *(float*)opt->value;
|
|
LV2_DEBUG("Attempted to change the sample rate to %.2f (original was %.2f); ignored",
|
|
*(float*)opt->value,
|
|
self->sample_rate);
|
|
}
|
|
else if (opt->type == self->atom_int_uri)
|
|
{
|
|
// self->sample_rate = *(int*)opt->value;
|
|
LV2_DEBUG("Attempted to change the sample rate to %d (original was %.2f); ignored",
|
|
*(int*)opt->value,
|
|
self->sample_rate);
|
|
}
|
|
else
|
|
{
|
|
lv2_log_warning(&self->logger, "[sfizz] Got a sample rate but could not resolve the type of the atom\n");
|
|
if (self->unmap)
|
|
lv2_log_warning(&self->logger,
|
|
"[sfizz] Atom URI: %s\n",
|
|
self->unmap->unmap(self->unmap->handle, opt->type));
|
|
}
|
|
}
|
|
|
|
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 = NULL;
|
|
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->oversampling = DEFAULT_OVERSAMPLING;
|
|
self->preload_size = DEFAULT_PRELOAD;
|
|
self->check_modification = true;
|
|
self->sample_counter = 0;
|
|
|
|
// 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;
|
|
|
|
if (!strcmp((**f).URI, LV2_MIDNAM__update))
|
|
self->midnam = (**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)
|
|
{
|
|
sfizz_lv2_parse_sample_rate(self, opt);
|
|
}
|
|
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;
|
|
}
|
|
|
|
self->synth = sfizz_create_synth();
|
|
return (LV2_Handle)self;
|
|
}
|
|
|
|
static void
|
|
cleanup(LV2_Handle instance)
|
|
{
|
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
|
sfizz_free(self->synth);
|
|
free(self);
|
|
}
|
|
|
|
static void
|
|
activate(LV2_Handle instance)
|
|
{
|
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
|
sfizz_set_samples_per_block(self->synth, self->max_block_size);
|
|
sfizz_set_sample_rate(self->synth, self->sample_rate);
|
|
}
|
|
|
|
static void
|
|
deactivate(LV2_Handle instance)
|
|
{
|
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
|
sfizz_all_sound_off(self->synth);
|
|
}
|
|
|
|
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, (uint32_t)strlen(self->sfz_file_path));
|
|
lv2_atom_forge_pop(&self->forge, &frame);
|
|
}
|
|
|
|
|
|
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,
|
|
"[sfizz] Could not get the property from the patch object, aborting\n");
|
|
return;
|
|
}
|
|
|
|
if (property->type != self->atom_urid_uri)
|
|
{
|
|
lv2_log_error(&self->logger,
|
|
"[sfizz] 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, "[sfizz] 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[MAX_PATH_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;
|
|
self->worker->schedule_work(self->worker->handle, null_terminated_atom_size, sfz_file_path);
|
|
self->check_modification = false;
|
|
}
|
|
else
|
|
{
|
|
lv2_log_warning(&self->logger, "[sfizz] 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;
|
|
}
|
|
}
|
|
|
|
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:
|
|
if (msg[2] == 0)
|
|
goto noteoff; // 0 velocity note-ons should be forbidden but just in case...
|
|
|
|
sfizz_send_note_on(self->synth,
|
|
(int)ev->time.frames,
|
|
(int)msg[1],
|
|
msg[2]);
|
|
break;
|
|
case LV2_MIDI_MSG_NOTE_OFF: noteoff:
|
|
sfizz_send_note_off(self->synth,
|
|
(int)ev->time.frames,
|
|
(int)msg[1],
|
|
msg[2]);
|
|
break;
|
|
case LV2_MIDI_MSG_CONTROLLER:
|
|
sfizz_send_cc(self->synth,
|
|
(int)ev->time.frames,
|
|
(int)msg[1],
|
|
msg[2]);
|
|
break;
|
|
case LV2_MIDI_MSG_BENDER:
|
|
sfizz_send_pitch_wheel(self->synth,
|
|
(int)ev->time.frames,
|
|
PITCH_BUILD_AND_CENTER(msg[1], msg[2]));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sfizz_lv2_status_log(sfizz_plugin_t *self)
|
|
{
|
|
UNUSED(self);
|
|
// lv2_log_note(&self->logger, "[sfizz] Allocated buffers: %d\n", sfizz_get_num_buffers(self->synth));
|
|
// lv2_log_note(&self->logger, "[sfizz] Allocated bytes: %d bytes\n", sfizz_get_num_bytes(self->synth));
|
|
// lv2_log_note(&self->logger, "[sfizz] Active voices: %d\n", sfizz_get_num_active_voices(self->synth));
|
|
}
|
|
|
|
static void
|
|
sfizz_lv2_check_oversampling(sfizz_plugin_t* self)
|
|
{
|
|
sfizz_oversampling_factor_t oversampling = (sfizz_oversampling_factor_t)*self->oversampling_port;
|
|
if (oversampling != self->oversampling)
|
|
{
|
|
LV2_Atom_Int atom;
|
|
atom.atom.type = self->sfizz_oversampling_uri;
|
|
atom.atom.size = sizeof(int);
|
|
atom.body = oversampling;
|
|
if (self->worker->schedule_work(self->worker->handle,
|
|
lv2_atom_total_size((LV2_Atom *)&atom),
|
|
&atom) != LV2_WORKER_SUCCESS)
|
|
{
|
|
lv2_log_error(&self->logger, "[sfizz] There was an issue changing the oversampling factor\n");
|
|
}
|
|
self->oversampling = oversampling;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sfizz_lv2_check_preload_size(sfizz_plugin_t* self)
|
|
{
|
|
unsigned int preload_size = (int)*self->preload_port;
|
|
if (preload_size != self->preload_size)
|
|
{
|
|
LV2_Atom_Int atom;
|
|
atom.atom.type = self->sfizz_preload_size_uri;
|
|
atom.atom.size = sizeof(int);
|
|
atom.body = preload_size;
|
|
if (self->worker->schedule_work(self->worker->handle,
|
|
lv2_atom_total_size((LV2_Atom *)&atom),
|
|
&atom) != LV2_WORKER_SUCCESS)
|
|
{
|
|
lv2_log_error(&self->logger, "[sfizz] There was an issue changing the preload size\n");
|
|
}
|
|
self->preload_size = preload_size;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sfizz_lv2_check_num_voices(sfizz_plugin_t* self)
|
|
{
|
|
int num_voices = (int)*self->polyphony_port;
|
|
if (num_voices != self->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)
|
|
{
|
|
lv2_log_error(&self->logger, "[sfizz] There was an issue changing the number of voices\n");
|
|
}
|
|
self->num_voices = num_voices;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sfizz_lv2_check_freewheeling(sfizz_plugin_t* self)
|
|
{
|
|
if (*(self->freewheel_port) > 0)
|
|
{
|
|
sfizz_enable_freewheeling(self->synth);
|
|
}
|
|
else
|
|
{
|
|
sfizz_disable_freewheeling(self->synth);
|
|
}
|
|
}
|
|
|
|
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, "[sfizz] 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);
|
|
}
|
|
}
|
|
|
|
|
|
// Check and update parameters if needed
|
|
sfizz_lv2_check_freewheeling(self);
|
|
sfizz_set_volume(self->synth, *(self->volume_port));
|
|
sfizz_lv2_check_preload_size(self);
|
|
sfizz_lv2_check_oversampling(self);
|
|
sfizz_lv2_check_num_voices(self);
|
|
|
|
// Log the buffer usage
|
|
self->sample_counter += (int)sample_count;
|
|
if (self->sample_counter > LOG_SAMPLE_COUNT && self->check_modification)
|
|
{
|
|
LV2_Atom atom;
|
|
atom.size = 0;
|
|
#ifndef NDEBUG
|
|
atom.type = self->sfizz_log_status_uri;
|
|
if (!(self->worker->schedule_work(self->worker->handle,
|
|
lv2_atom_total_size((LV2_Atom *)&atom),
|
|
&atom) == LV2_WORKER_SUCCESS))
|
|
{
|
|
lv2_log_error(&self->logger, "[sfizz] There was an issue sending a logging message to the background worker\n");
|
|
}
|
|
#endif
|
|
atom.type = self->sfizz_check_modification_uri;
|
|
if ((self->worker->schedule_work(self->worker->handle,
|
|
lv2_atom_total_size((LV2_Atom *)&atom),
|
|
&atom) == LV2_WORKER_SUCCESS)) {
|
|
self->check_modification = false;
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] There was an issue sending a notice to check the modification of the SFZ file to the background worker\n");
|
|
}
|
|
self->sample_counter = 0;
|
|
}
|
|
|
|
// Render the block
|
|
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
|
|
|
|
if (self->midnam && atomic_exchange(&self->must_update_midnam, 0))
|
|
{
|
|
self->midnam->update(self->midnam->handle);
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
lv2_get_options(LV2_Handle instance, LV2_Options_Option *options)
|
|
{
|
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
|
LV2_DEBUG("[DEBUG] get_options called\n");
|
|
for (LV2_Options_Option *opt = options; opt->value; ++opt)
|
|
{
|
|
if (self->unmap) {
|
|
LV2_DEBUG("[DEBUG] Called for an option with key (subject): %s (%s) \n",
|
|
self->unmap->unmap(self->unmap->handle, opt->key),
|
|
self->unmap->unmap(self->unmap->handle, opt->subject));
|
|
}
|
|
|
|
if (opt->key == self->sample_rate_uri)
|
|
{
|
|
opt->type = self->atom_float_uri;
|
|
opt->size = sizeof(float);
|
|
opt->value = (void*)&self->sample_rate;
|
|
return LV2_OPTIONS_SUCCESS;
|
|
}
|
|
|
|
if (opt->key == self->max_block_length_uri || opt->key == self->nominal_block_length_uri)
|
|
{
|
|
opt->type = self->atom_int_uri;
|
|
opt->size = sizeof(int);
|
|
opt->value = (void*)&self->max_block_size;
|
|
return LV2_OPTIONS_SUCCESS;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
sfizz_lv2_parse_sample_rate(self, opt);
|
|
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, "[sfizz] 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, "[sfizz] 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 void
|
|
sfizz_lv2_update_file_info(sfizz_plugin_t* self, const char* file_path)
|
|
{
|
|
strcpy(self->sfz_file_path, file_path);
|
|
|
|
lv2_log_note(&self->logger, "[sfizz] File changed to: %s\n", self->sfz_file_path);
|
|
char *unknown_opcodes = sfizz_get_unknown_opcodes(self->synth);
|
|
if (unknown_opcodes)
|
|
{
|
|
lv2_log_note(&self->logger, "[sfizz] Unknown opcodes: %s\n", unknown_opcodes);
|
|
free(unknown_opcodes);
|
|
}
|
|
lv2_log_note(&self->logger, "[sfizz] Number of masters: %d\n", sfizz_get_num_masters(self->synth));
|
|
lv2_log_note(&self->logger, "[sfizz] Number of groups: %d\n", sfizz_get_num_groups(self->synth));
|
|
lv2_log_note(&self->logger, "[sfizz] Number of regions: %d\n", sfizz_get_num_regions(self->synth));
|
|
|
|
atomic_store(&self->must_update_midnam, 1);
|
|
}
|
|
|
|
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, "[sfizz] Restoring the file %s\n", (const char *)value);
|
|
if (sfizz_load_file(self->synth, (const char *)value))
|
|
{
|
|
sfizz_lv2_update_file_info(self, (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, "[sfizz] Restoring the number of voices to %d\n", num_voices);
|
|
sfizz_set_num_voices(self->synth, num_voices);
|
|
self->num_voices = num_voices;
|
|
}
|
|
}
|
|
|
|
value = retrieve(handle, self->sfizz_preload_size_uri, &size, &type, &val_flags);
|
|
if (value)
|
|
{
|
|
unsigned int preload_size = *(const unsigned int *)value;
|
|
if (preload_size != self->preload_size)
|
|
{
|
|
lv2_log_note(&self->logger, "[sfizz] Restoring the preload size to %d\n", preload_size);
|
|
sfizz_set_preload_size(self->synth, preload_size);
|
|
self->preload_size = preload_size;
|
|
}
|
|
}
|
|
|
|
value = retrieve(handle, self->sfizz_oversampling_uri, &size, &type, &val_flags);
|
|
if (value)
|
|
{
|
|
sfizz_oversampling_factor_t oversampling = *(const sfizz_oversampling_factor_t *)value;
|
|
if (oversampling != self->oversampling)
|
|
{
|
|
lv2_log_note(&self->logger, "[sfizz] Restoring the oversampling to %d\n", oversampling);
|
|
sfizz_set_oversampling_factor(self->synth, oversampling);
|
|
self->oversampling = oversampling;
|
|
}
|
|
}
|
|
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);
|
|
|
|
// Save the preload size
|
|
store(handle,
|
|
self->sfizz_preload_size_uri,
|
|
&self->preload_size,
|
|
sizeof(unsigned int),
|
|
self->atom_int_uri,
|
|
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
|
|
|
|
// Save the preload size
|
|
store(handle,
|
|
self->sfizz_oversampling_uri,
|
|
&self->oversampling,
|
|
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, "[sfizz] Ignoring empty data in the worker thread\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);
|
|
if (sfizz_load_file(self->synth, sfz_file_path)) {
|
|
sfizz_lv2_update_file_info(self, sfz_file_path);
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] Error with %s; no file should be loaded\n", sfz_file_path);
|
|
}
|
|
|
|
// Reactivate checking for file changes
|
|
LV2_Atom check_modification_atom;
|
|
check_modification_atom.size = 0;
|
|
check_modification_atom.type = self->sfizz_check_modification_uri;
|
|
respond(handle, lv2_atom_total_size(&check_modification_atom), &check_modification_atom);
|
|
}
|
|
else if (atom->type == self->sfizz_num_voices_uri)
|
|
{
|
|
const int num_voices = *(const int *)LV2_ATOM_BODY_CONST(atom);
|
|
if (sfizz_get_num_voices(self->synth) == num_voices) {
|
|
return LV2_WORKER_SUCCESS; // Nothing to do
|
|
}
|
|
|
|
sfizz_set_num_voices(self->synth, num_voices);
|
|
if (sfizz_get_num_voices(self->synth) == num_voices) {
|
|
lv2_log_note(&self->logger, "[sfizz] Number of voices changed to: %d\n", num_voices);
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] Error changing the number of voices\n");
|
|
}
|
|
}
|
|
else if (atom->type == self->sfizz_preload_size_uri)
|
|
{
|
|
const unsigned int preload_size = *(const unsigned int *)LV2_ATOM_BODY_CONST(atom);
|
|
if (sfizz_get_preload_size(self->synth) == preload_size)
|
|
return LV2_WORKER_SUCCESS; // Nothing to do
|
|
|
|
sfizz_set_preload_size(self->synth, preload_size);
|
|
if (sfizz_get_preload_size(self->synth) == preload_size) {
|
|
lv2_log_note(&self->logger, "[sfizz] Preload size changed to: %d\n", preload_size);
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] Error changing the preload size\n");
|
|
}
|
|
}
|
|
else if (atom->type == self->sfizz_oversampling_uri)
|
|
{
|
|
const sfizz_oversampling_factor_t oversampling =
|
|
*(const sfizz_oversampling_factor_t *)LV2_ATOM_BODY_CONST(atom);
|
|
if (sfizz_get_oversampling_factor(self->synth) == oversampling) {
|
|
return LV2_WORKER_SUCCESS; // Nothing to do
|
|
}
|
|
|
|
sfizz_set_oversampling_factor(self->synth, oversampling);
|
|
if (sfizz_get_oversampling_factor(self->synth) == oversampling) {
|
|
lv2_log_note(&self->logger, "[sfizz] Oversampling changed to: %d\n", oversampling);
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] Error changing the oversampling\n");
|
|
}
|
|
}
|
|
else if (atom->type == self->sfizz_log_status_uri)
|
|
{
|
|
sfizz_lv2_status_log(self);
|
|
}
|
|
else if (atom->type == self->sfizz_check_modification_uri)
|
|
{
|
|
if (sfizz_should_reload_file(self->synth))
|
|
{
|
|
lv2_log_note(&self->logger,
|
|
"[sfizz] File %s seems to have been updated, reloading\n",
|
|
self->sfz_file_path);
|
|
if (sfizz_load_file(self->synth, self->sfz_file_path)) {
|
|
sfizz_lv2_update_file_info(self, self->sfz_file_path);
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] Error with %s; no file should be loaded\n", self->sfz_file_path);
|
|
}
|
|
}
|
|
respond(handle, size, data); // reactivate file checking
|
|
}
|
|
else
|
|
{
|
|
lv2_log_error(&self->logger, "[sfizz] Got an unknown atom in work\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;
|
|
}
|
|
|
|
// 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_check_modification_uri) {
|
|
self->check_modification = true; // check changes
|
|
} else {
|
|
lv2_log_error(&self->logger, "[sfizz] Got an unexpected atom in work response\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 char *
|
|
midnam_model(LV2_Handle instance)
|
|
{
|
|
char *model = malloc(64);
|
|
if (!model)
|
|
return NULL;
|
|
|
|
sprintf(model, "Sfizz LV2:%p", instance);
|
|
return model;
|
|
}
|
|
|
|
static char *
|
|
midnam_export(LV2_Handle instance)
|
|
{
|
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
|
|
|
char *model = midnam_model(instance);
|
|
if (!model)
|
|
return NULL;
|
|
|
|
char *xml = sfizz_export_midnam(self->synth, model);
|
|
free(model);
|
|
return xml;
|
|
}
|
|
|
|
static void
|
|
midnam_free(char *string)
|
|
{
|
|
free(string);
|
|
}
|
|
|
|
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};
|
|
static const LV2_Midnam_Interface midnam = {midnam_export, midnam_model, midnam_free};
|
|
|
|
// 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;
|
|
else if (!strcmp(uri, LV2_MIDNAM__interface))
|
|
return &midnam;
|
|
|
|
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;
|
|
}
|
|
}
|