Add LV2
This commit is contained in:
parent
cf9330e387
commit
8ee911e218
4 changed files with 891 additions and 0 deletions
19
lv2/CMakeLists.txt
Normal file
19
lv2/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
cmake_minimum_required(VERSION 3.11)
|
||||
project(sfizz-lv2)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(sfizz REQUIRED IMPORTED_TARGET sfizz)
|
||||
|
||||
# Force add relocation to the flags globally. It's ugly but what can you do...
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
|
||||
# Export the compile_commands.json file
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
add_library(sfizz-lv2 SHARED sfizz.c)
|
||||
set_target_properties(sfizz-lv2 PROPERTIES PREFIX "")
|
||||
set_target_properties(sfizz-lv2 PROPERTIES OUTPUT_NAME "sfizz")
|
||||
target_link_libraries(sfizz-lv2 sfizz)
|
||||
if(UNIX)
|
||||
target_compile_options(sfizz-lv2 PRIVATE -Wextra -pedantic -Wall -Werror)
|
||||
endif()
|
||||
8
lv2/manifest.ttl
Normal file
8
lv2/manifest.ttl
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
|
||||
<http://sfztools.github.io/sfizz>
|
||||
a lv2:Plugin ;
|
||||
lv2:binary <sfizz.so> ;
|
||||
rdfs:seeAlso <sfizz.ttl> .
|
||||
|
||||
769
lv2/sfizz.c
Normal file
769
lv2/sfizz.c
Normal file
|
|
@ -0,0 +1,769 @@
|
|||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
95
lv2/sfizz.ttl
Normal file
95
lv2/sfizz.ttl
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
||||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
||||
@prefix bufsize: <http://lv2plug.in/ns/ext/buf-size#> .
|
||||
@prefix opts: <http://lv2plug.in/ns/ext/options#> .
|
||||
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
|
||||
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
|
||||
@prefix state: <http://lv2plug.in/ns/ext/state#> .
|
||||
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
|
||||
@prefix pprop: <http://lv2plug.in/ns/ext/port-props#> .
|
||||
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
|
||||
|
||||
<#config>
|
||||
a pg:Group ;
|
||||
lv2:symbol "config" ;
|
||||
lv2:name "Configuration" .
|
||||
|
||||
<http://sfztools.github.io/sfizz:sfzfile>
|
||||
a lv2:Parameter ;
|
||||
rdfs:label "SFZ file" ;
|
||||
rdfs:range atom:Path .
|
||||
|
||||
<http://sfztools.github.io/sfizz:numvoices>
|
||||
a lv2:Parameter ;
|
||||
rdfs:label "Polyphony" ;
|
||||
rdfs:range atom:Int .
|
||||
|
||||
<http://sfztools.github.io/sfizz>
|
||||
a doap:Project, lv2:Plugin, lv2:InstrumentPlugin;
|
||||
doap:name "Sfizz" ;
|
||||
lv2:requiredFeature urid:map, bufsize:boundedBlockLength, work:schedule;
|
||||
rdfs:comment "SFZ sampler" ;
|
||||
lv2:optionalFeature lv2:hardRTCapable, opts:options;
|
||||
lv2:extensionData opts:interface, state:interface, work:interface ;
|
||||
patch:writable <http://sfztools.github.io/sfizz:sfzfile> ;
|
||||
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 "Control"
|
||||
] , [
|
||||
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 "Notify" ;
|
||||
] , [
|
||||
a lv2:AudioPort , lv2:OutputPort ;
|
||||
lv2:index 2 ;
|
||||
lv2:symbol "out_left" ;
|
||||
lv2:name "Left Output"
|
||||
] , [
|
||||
a lv2:AudioPort , lv2:OutputPort ;
|
||||
lv2:index 3 ;
|
||||
lv2:symbol "out_right" ;
|
||||
lv2:name "Right Output"
|
||||
] , [
|
||||
a lv2:InputPort, lv2:ControlPort ;
|
||||
lv2:index 4 ;
|
||||
lv2:symbol "volume" ;
|
||||
lv2:name "Volume" ;
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -80.0 ;
|
||||
lv2:maximum 6.0;
|
||||
lv2:portProperty pprop:notAutomatic;
|
||||
units:unit units:db
|
||||
], [
|
||||
a lv2:InputPort, lv2:ControlPort ;
|
||||
lv2:index 5 ;
|
||||
lv2:symbol "num_voices" ;
|
||||
lv2:name "Polyphony" ;
|
||||
pg:group <#config>;
|
||||
lv2:portProperty pprop:notAutomatic ;
|
||||
lv2:portProperty pprop:expensive ;
|
||||
lv2:portProperty lv2:integer;
|
||||
lv2:portProperty lv2:enumeration ;
|
||||
lv2:default 64 ;
|
||||
lv2:minimum 8 ;
|
||||
lv2:maximum 256 ;
|
||||
lv2:scalePoint [ rdfs:label "8 voices"; rdf:value 8 ] ;
|
||||
lv2:scalePoint [ rdfs:label "16 voices"; rdf:value 16 ] ;
|
||||
lv2:scalePoint [ rdfs:label "32 voices"; rdf:value 32 ] ;
|
||||
lv2:scalePoint [ rdfs:label "64 voices"; rdf:value 64 ] ;
|
||||
lv2:scalePoint [ rdfs:label "128 voices"; rdf:value 128 ] ;
|
||||
lv2:scalePoint [ rdfs:label "256 voices"; rdf:value 256 ] ;
|
||||
].
|
||||
Loading…
Add table
Reference in a new issue