Merge pull request #776 from jpcima/lv2-parameters

Present the controllers as LV2 parameters
This commit is contained in:
JP Cimalando 2021-04-06 01:00:19 +02:00 committed by GitHub
commit 539fbf6442
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 450 additions and 83 deletions

View file

@ -40,3 +40,59 @@ else()
set(LV2PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib/lv2" CACHE STRING
"Install destination for LV2 bundle [default: ${CMAKE_INSTALL_PREFIX}/lib/lv2]")
endif()
include(StringUtility)
function(sfizz_lv2_generate_controllers_ttl FILE)
file(WRITE "${FILE}" "# LV2 parameters for SFZ controllers
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sfizz: <${LV2PLUGIN_URI}#> .
")
math(EXPR _j "${SFIZZ_NUM_CCS}-1")
foreach(_i RANGE "${_j}")
string_left_pad(_i "${_i}" 3 0)
file(APPEND "${FILE}" "
sfizz:cc${_i}
a lv2:Parameter ;
rdfs:label \"Controller ${_i}\" ;
rdfs:range atom:Float")
if(_i LESS 128)
math(EXPR _digit1 "${_i}>>4")
math(EXPR _digit2 "${_i}&15")
string(SUBSTRING "0123456789ABCDEF" "${_digit1}" 1 _digit1)
string(SUBSTRING "0123456789ABCDEF" "${_digit2}" 1 _digit2)
file(APPEND "${FILE}" " ;
midi:binding \"B0${_digit1}${_digit2}00\"^^midi:MidiEvent .
")
else()
file(APPEND "${FILE}" " .
")
endif()
endforeach()
file(APPEND "${FILE}" "
<${LV2PLUGIN_URI}>
a lv2:Plugin ;
")
file(APPEND "${FILE}" " patch:readable sfizz:cc000")
foreach(_i RANGE 1 "${_j}")
string_left_pad(_i "${_i}" 3 0)
file(APPEND "${FILE}" ", sfizz:cc${_i}")
endforeach()
file(APPEND "${FILE}" " ;
")
file(APPEND "${FILE}" " patch:writable sfizz:cc000")
foreach(_i RANGE 1 "${_j}")
string_left_pad(_i "${_i}" 3 0)
file(APPEND "${FILE}" ", sfizz:cc${_i}")
endforeach()
file(APPEND "${FILE}" " .
")
endfunction()

11
cmake/StringUtility.cmake Normal file
View file

@ -0,0 +1,11 @@
# SPDX-License-Identifier: BSD-2-Clause
function(string_left_pad VAR INPUT LENGTH FILLCHAR)
set(_output "${INPUT}")
string(LENGTH "${_output}" _length)
while(_length LESS "${LENGTH}")
set(_output "${FILLCHAR}${_output}")
string(LENGTH "${_output}" _length)
endwhile()
set("${VAR}" "${_output}" PARENT_SCOPE)
endfunction()

View file

@ -306,6 +306,12 @@ void Editor::close()
}
}
void Editor::sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args)
{
Impl& impl = *impl_;
impl.sendQueuedOSC(path, sig, args);
}
void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
{
switch (id) {
@ -476,12 +482,18 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
else if (editIdIsKeyswitchLabel(id)) {
updateSWLastLabel(keyswitchLabelForEditId(id), v.to_string().c_str());
}
else if (editIdIsCC(id)) {
updateCCValue(unsigned(ccForEditId(id)), v.to_float());
}
else if (editIdIsCCUsed(id)) {
updateCCUsed(ccUsedForEditId(id), v.to_float() != 0);
bool used = v.to_float() != 0;
updateCCUsed(ccUsedForEditId(id), used);
// TODO(jpc) remove value requests, when implementing CC automation
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", ccUsedForEditId(id));
sendQueuedOSC(pathBuf, "", nullptr);
if (used) {
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", ccUsedForEditId(id));
sendQueuedOSC(pathBuf, "", nullptr);
}
}
else if (editIdIsCCDefault(id)) {
updateCCDefaultValue(ccDefaultForEditId(id), v.to_float());
@ -1610,13 +1622,8 @@ void Editor::Impl::updateMemoryUsed(uint64_t mem)
void Editor::Impl::performCCValueChange(unsigned cc, float value)
{
// TODO(jpc) CC as parameters and automation
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", cc);
sfizz_arg_t args[1];
args[0].f = value;
sendQueuedOSC(pathBuf, "f", args);
EditorController& ctrl = *ctrl_;
ctrl.uiSendValue(editIdForCC(int(cc)), value);
}
void Editor::Impl::performCCBeginEdit(unsigned cc)

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include <sfizz_message.h>
#include <memory>
class EditorController;
@ -24,6 +25,9 @@ public:
void open(CFrame& frame);
void close();
// TODO(jpc) remove me after doing parameter automations
void sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args);
private:
struct Impl;
std::unique_ptr<Impl> impl_;

View file

@ -82,6 +82,9 @@ if(SFIZZ_USE_VCPKG OR SFIZZ_STATIC_DEPENDENCIES OR CMAKE_CXX_COMPILER_ID MATCHES
file(COPY "lgpl-3.0.txt" DESTINATION ${PROJECT_BINARY_DIR})
endif()
# Generate controllers.ttl
sfizz_lv2_generate_controllers_ttl("${PROJECT_BINARY_DIR}/controllers.ttl")
# Copy resource files into the bundle
set(LV2_RESOURCES
DefaultInstrument.sfz

View file

@ -5,7 +5,7 @@
<@LV2PLUGIN_URI@>
a lv2:Plugin ;
lv2:binary <Contents/Binary/@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ;
rdfs:seeAlso <@PROJECT_NAME@.ttl> .
rdfs:seeAlso <@PROJECT_NAME@.ttl>, <controllers.ttl> .
@LV2PLUGIN_IF_ENABLE_UI@<@LV2PLUGIN_URI@#ui>
@LV2PLUGIN_IF_ENABLE_UI@ a ui:@LV2_UI_TYPE@ ;

View file

@ -42,6 +42,8 @@
#include <stdlib.h>
#include <string.h>
#include <memory>
#define CHANNEL_MASK 0x0F
#define MIDI_CHANNEL(byte) (byte & CHANNEL_MASK)
#define MIDI_STATUS(byte) (byte & ~CHANNEL_MASK)
@ -201,6 +203,9 @@ connect_port(LV2_Handle instance,
case SFIZZ_NOTIFY:
self->notify_port = (LV2_Atom_Sequence *)data;
break;
case SFIZZ_AUTOMATE:
self->automate_port = (LV2_Atom_Sequence *)data;
break;
case SFIZZ_LEFT:
self->output_buffers[0] = (float *)data;
break;
@ -320,11 +325,12 @@ sfizz_lv2_receive_message(void* data, int delay, const char* path, const char* s
if (osc_size > OSC_TEMP_SIZE)
return;
LV2_Atom_Forge* forge = &self->forge_notify;
bool write_ok =
lv2_atom_forge_frame_time(&self->forge, 0) &&
lv2_atom_forge_atom(&self->forge, osc_size, self->sfizz_osc_blob_uri) &&
lv2_atom_forge_raw(&self->forge, osc_temp, osc_size);
lv2_atom_forge_pad(&self->forge, osc_size);
lv2_atom_forge_frame_time(forge, 0) &&
lv2_atom_forge_atom(forge, osc_size, self->sfizz_osc_blob_uri) &&
lv2_atom_forge_raw(forge, osc_temp, osc_size);
lv2_atom_forge_pad(forge, osc_size);
(void)write_ok;
}
@ -426,7 +432,8 @@ instantiate(const LV2_Descriptor *descriptor,
sfizz_lv2_map_required_uris(self);
// Initialize the forge
lv2_atom_forge_init(&self->forge, self->map);
lv2_atom_forge_init(&self->forge_notify, self->map);
lv2_atom_forge_init(&self->forge_automate, self->map);
lv2_atom_forge_init(&self->forge_secondary, self->map);
// Check the options for the block size and sample rate parameters
@ -476,6 +483,10 @@ instantiate(const LV2_Descriptor *descriptor,
return NULL;
}
self->ccmap = sfizz_lv2_ccmap_create(self->map);
self->cc_current = new float[sfz::config::numCCs]();
self->ccauto = new absl::optional<float>[sfz::config::numCCs];
self->synth = sfizz_create_synth();
self->client = sfizz_create_client(self);
self->synth_mutex = spin_mutex_create();
@ -501,6 +512,9 @@ cleanup(LV2_Handle instance)
spin_mutex_destroy(self->synth_mutex);
sfizz_delete_client(self->client);
sfizz_free(self->synth);
delete[] self->ccauto;
delete[] self->cc_current;
sfizz_lv2_ccmap_free(self->ccmap);
delete self;
}
@ -521,24 +535,42 @@ deactivate(LV2_Handle instance)
}
static void
sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_URID urid, const char *path)
sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_Atom_Forge* forge, LV2_URID urid, const char *path)
{
LV2_Atom_Forge_Frame frame;
bool write_ok =
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, urid) &&
lv2_atom_forge_key(&self->forge, self->patch_value_uri) &&
lv2_atom_forge_path(&self->forge, path, (uint32_t)strlen(path));
lv2_atom_forge_frame_time(forge, 0) &&
lv2_atom_forge_object(forge, &frame, 0, self->patch_set_uri) &&
lv2_atom_forge_key(forge, self->patch_property_uri) &&
lv2_atom_forge_urid(forge, urid) &&
lv2_atom_forge_key(forge, self->patch_value_uri) &&
lv2_atom_forge_path(forge, path, (uint32_t)strlen(path));
if (write_ok)
lv2_atom_forge_pop(&self->forge, &frame);
lv2_atom_forge_pop(forge, &frame);
}
static void
sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, const LV2_Atom_Object *obj)
sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_Atom_Forge* forge, unsigned cc, float value)
{
LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc));
LV2_Atom_Forge_Frame frame;
bool write_ok =
lv2_atom_forge_frame_time(forge, 0) &&
lv2_atom_forge_object(forge, &frame, 0, self->patch_set_uri) &&
lv2_atom_forge_key(forge, self->patch_property_uri) &&
lv2_atom_forge_urid(forge, urid) &&
lv2_atom_forge_key(forge, self->patch_value_uri) &&
lv2_atom_forge_float(forge, value);
if (write_ok)
lv2_atom_forge_pop(forge, &frame);
}
static void
sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Object *obj)
{
const LV2_Atom *property = NULL;
lv2_atom_object_get(obj, self->patch_property_uri, &property, 0);
@ -575,7 +607,15 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, const LV2_Atom_Object *obj)
char body[MAX_PATH_SIZE];
} sfizz_path_atom_buffer_t;
if (key == self->sfizz_sfz_file_uri)
int cc = sfizz_lv2_ccmap_unmap(self->ccmap, key);
if (cc != -1) {
if (atom->type == self->atom_float_uri && atom->size == sizeof(float)) {
float value = *(const float *)LV2_ATOM_BODY_CONST(atom);
sfizz_send_hdcc(self->synth, delay, cc, value);
self->cc_current[cc] = value;
}
}
else if (key == self->sfizz_sfz_file_uri)
{
LV2_Atom_Forge *forge = &self->forge_secondary;
sfizz_path_atom_buffer_t buffer;
@ -629,12 +669,14 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
(int)msg[1],
msg[2]);
break;
case LV2_MIDI_MSG_CONTROLLER:
// Note(jpc) CC must be mapped by host, not handled here.
// See LV2 midi:binding.
/*case LV2_MIDI_MSG_CONTROLLER:
sfizz_send_cc(self->synth,
(int)ev->time.frames,
(int)msg[1],
msg[2]);
break;
break;*/
case LV2_MIDI_MSG_CHANNEL_PRESSURE:
sfizz_send_aftertouch(self->synth,
(int)ev->time.frames,
@ -770,7 +812,7 @@ static void
run(LV2_Handle instance, uint32_t sample_count)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
assert(self->control_port && self->notify_port);
assert(self->control_port && self->notify_port && self->automate_port);
if (!spin_mutex_trylock(self->synth_mutex))
{
@ -779,13 +821,18 @@ run(LV2_Handle instance, uint32_t sample_count)
return;
}
// Set up forge to write directly to notify output port.
// Set up dedicated forges to write on their respective ports.
const size_t notify_capacity = self->notify_port->atom.size;
lv2_atom_forge_set_buffer(&self->forge, (uint8_t *)self->notify_port, notify_capacity);
lv2_atom_forge_set_buffer(&self->forge_notify, (uint8_t *)self->notify_port, notify_capacity);
const size_t automate_capacity = self->automate_port->atom.size;
lv2_atom_forge_set_buffer(&self->forge_automate, (uint8_t *)self->automate_port, automate_capacity);
// Start a sequence in the notify output port.
// Start sequences in the respective output ports.
LV2_Atom_Forge_Frame notify_frame;
if (!lv2_atom_forge_sequence_head(&self->forge, &notify_frame, 0))
if (!lv2_atom_forge_sequence_head(&self->forge_notify, &notify_frame, 0))
assert(false);
LV2_Atom_Forge_Frame automate_frame;
if (!lv2_atom_forge_sequence_head(&self->forge_automate, &automate_frame, 0))
assert(false);
LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev)
@ -796,9 +843,10 @@ run(LV2_Handle instance, uint32_t sample_count)
if (ev->body.type == self->atom_object_uri || ev->body.type == self->atom_blank_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);
sfizz_lv2_handle_atom_object(self, delay, obj);
}
else if (obj->body.otype == self->patch_get_uri)
{
@ -806,16 +854,25 @@ run(LV2_Handle instance, uint32_t sample_count)
lv2_atom_object_get(obj, self->patch_property_uri, &property, 0);
if (!property) // Send the full state
{
sfizz_lv2_send_file_path(self, self->sfizz_sfz_file_uri, self->sfz_file_path);
sfizz_lv2_send_file_path(self, self->sfizz_scala_file_uri, self->scala_file_path);
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_sfz_file_uri, self->sfz_file_path);
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_scala_file_uri, self->scala_file_path);
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc)
sfizz_lv2_send_controller(self, &self->forge_notify, cc, self->cc_current[cc]);
}
else if (property->body == self->sfizz_sfz_file_uri)
{
sfizz_lv2_send_file_path(self, self->sfizz_sfz_file_uri, self->sfz_file_path);
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_sfz_file_uri, self->sfz_file_path);
}
else if (property->body == self->sfizz_scala_file_uri)
{
sfizz_lv2_send_file_path(self, self->sfizz_scala_file_uri, self->scala_file_path);
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_scala_file_uri, self->scala_file_path);
}
else
{
int cc = sfizz_lv2_ccmap_unmap(self->ccmap, property->body);
if (cc != -1)
sfizz_lv2_send_controller(self, &self->forge_notify, unsigned(cc), self->cc_current[cc]);
}
}
else if (obj->body.otype == self->time_position_uri)
@ -949,17 +1006,29 @@ run(LV2_Handle instance, uint32_t sample_count)
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
// Request OSC updates
sfizz_send_message(self->synth, self->client, 0, "/cc/changed~", "", nullptr);
sfizz_send_message(self->synth, self->client, 0, "/sw/last/current", "", nullptr);
spin_mutex_unlock(self->synth_mutex);
if (self->midnam && self->must_update_midnam.exchange(0))
{
self->midnam->update(self->midnam->handle);
}
lv2_atom_forge_pop(&self->forge, &notify_frame);
if (self->have_ccauto)
{
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
absl::optional<float> value = self->ccauto[cc];
if (value) {
sfizz_lv2_send_controller(self, &self->forge_automate, cc, *value);
self->ccauto[cc] = absl::nullopt;
}
}
self->have_ccauto = false;
}
spin_mutex_unlock(self->synth_mutex);
lv2_atom_forge_pop(&self->forge_notify, &notify_frame);
lv2_atom_forge_pop(&self->forge_automate, &automate_frame);
}
static uint32_t
@ -1063,6 +1132,7 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self)
{
const std::string blob = getDescriptionBlob(self->synth);
// Update description blob that UI can fetch, thread-safely
uint32_t size = uint32_t(blob.size());
uint8_t *data = new uint8_t[size];
memcpy(data, blob.data(), size);
@ -1075,6 +1145,18 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self)
self->sfz_blob_mutex->unlock();
delete[] old_data;
//
const InstrumentDescription desc = parseDescriptionBlob(blob);
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
if (desc.ccUsed.test(cc)) {
// Mark all the used CCs for automation with default values
self->ccauto[cc] = desc.ccDefault[cc];
self->have_ccauto = true;
// Update the current CCs
self->cc_current[cc] = desc.ccDefault[cc];
}
}
}
static bool
@ -1220,6 +1302,17 @@ restore(LV2_Handle instance,
self->oversampling = oversampling;
}
// Collect all CC values present in the state
std::unique_ptr<absl::optional<float>[]> cc_values(
new absl::optional<float>[sfz::config::numCCs]);
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc));
value = retrieve(handle, urid, &size, &type, &val_flags);
if (value && type == self->atom_float_uri)
cc_values[cc] = *(const float *)value;
}
// Sync the parameters to the synth
spin_mutex_lock(self->synth_mutex);
@ -1258,6 +1351,20 @@ restore(LV2_Handle instance,
lv2_log_note(&self->logger, "[sfizz] Restoring the oversampling to %d\n", self->oversampling);
sfizz_set_oversampling_factor(self->synth, self->oversampling);
// Override default automation values with these from the state file
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
absl::optional<float> value = cc_values[cc];
if (value) {
// Set CC in the synth
sfizz_send_hdcc(self->synth, 0, int(cc), *value);
// Mark CCs for automation with state values
self->ccauto[cc] = *value;
self->have_ccauto = true;
// Update the current CCs
self->cc_current[cc] = *value;
}
}
spin_mutex_unlock(self->synth_mutex);
return status;
@ -1327,7 +1434,7 @@ save(LV2_Handle instance,
&self->num_voices,
sizeof(int),
self->atom_int_uri,
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
LV2_STATE_IS_POD);
// Save the preload size
store(handle,
@ -1335,7 +1442,7 @@ save(LV2_Handle instance,
&self->preload_size,
sizeof(unsigned int),
self->atom_int_uri,
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
LV2_STATE_IS_POD);
// Save the preload size
store(handle,
@ -1343,7 +1450,25 @@ save(LV2_Handle instance,
&self->oversampling,
sizeof(int),
self->atom_int_uri,
LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
LV2_STATE_IS_POD);
// Save the CCs (used only)
self->sfz_blob_mutex->lock();
const InstrumentDescription desc = parseDescriptionBlob(
absl::string_view((const char*)self->sfz_blob_data, self->sfz_blob_size));
self->sfz_blob_mutex->unlock();
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
if (desc.ccUsed.test(cc)) {
LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc));
store(handle,
urid,
&self->cc_current[cc],
sizeof(float),
self->atom_float_uri,
LV2_STATE_IS_POD);
}
}
return LV2_STATE_SUCCESS;
}

View file

@ -104,29 +104,38 @@ midnam:update a lv2:Feature .
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message, <@LV2PLUGIN_URI@:OSCBlob> ;
lv2:designation lv2:control ;
lv2:index 1 ;
lv2:symbol "notify" ;
lv2:name "Notify",
"Notification"@fr ;
rsz:minimumSize 65536 ;
] , [
a lv2:AudioPort, lv2:OutputPort ;
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message ;
lv2:designation lv2:control ;
lv2:index 2 ;
lv2:symbol "automate" ;
lv2:name "Automate",
"Automatisation"@fr ;
rsz:minimumSize 65536 ;
] , [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 3 ;
lv2:symbol "out_left" ;
lv2:name "Left Output",
"Sortie gauche"@fr ,
"Uscita Sinistra"@it
] , [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 3 ;
lv2:index 4 ;
lv2:symbol "out_right" ;
lv2:name "Right Output",
"Sortie droite"@fr ,
"Uscita Destra"@it
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 4 ;
lv2:index 5 ;
lv2:symbol "volume" ;
lv2:name "Volume" ;
lv2:default 0.0 ;
@ -135,7 +144,7 @@ midnam:update a lv2:Feature .
units:unit units:db
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 5 ;
lv2:index 6 ;
lv2:symbol "num_voices" ;
lv2:name "Polyphony",
"Polyphonie"@fr ,
@ -180,7 +189,7 @@ midnam:update a lv2:Feature .
] ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 6 ;
lv2:index 7 ;
lv2:symbol "oversampling" ;
lv2:name "Internal oversampling factor",
"Facteur de suréchantillonnage interne"@fr ,
@ -215,7 +224,7 @@ midnam:update a lv2:Feature .
] ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 7 ;
lv2:index 8 ;
lv2:symbol "preload_size" ;
lv2:name "Preload size",
"Taille préchargée"@fr ,
@ -258,7 +267,7 @@ midnam:update a lv2:Feature .
] ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 8 ;
lv2:index 9 ;
lv2:symbol "freewheeling" ;
lv2:name "Freewheeling",
"En roue libre (freewheeling)"@fr ,
@ -270,7 +279,7 @@ midnam:update a lv2:Feature .
lv2:maximum 1 ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 9 ;
lv2:index 10 ;
lv2:symbol "scala_root_key" ;
lv2:name "Scala root key",
"Tonalité de base Scala"@fr ,
@ -283,7 +292,7 @@ midnam:update a lv2:Feature .
units:unit units:midiNote
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 10 ;
lv2:index 11 ;
lv2:symbol "tuning_frequency" ;
lv2:name "Tuning frequency",
"Fréquence d'accordage"@fr ,
@ -295,7 +304,7 @@ midnam:update a lv2:Feature .
units:unit units:hz
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 11 ;
lv2:index 12 ;
lv2:symbol "stretched_tuning" ;
lv2:name "Stretched tuning",
"Accordage étiré"@fr ,
@ -307,7 +316,7 @@ midnam:update a lv2:Feature .
units:unit units:coef
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 12 ;
lv2:index 13 ;
lv2:symbol "active_voices" ;
lv2:name "Active voices",
"Voix utilisées"@fr ;
@ -318,7 +327,7 @@ midnam:update a lv2:Feature .
lv2:maximum 256 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 13 ;
lv2:index 14 ;
lv2:symbol "num_curves" ;
lv2:name "Number of curves",
"Nombre de courbes"@fr ;
@ -329,7 +338,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 14 ;
lv2:index 15 ;
lv2:symbol "num_masters" ;
lv2:name "Number of masters",
"Nombre de maîtres"@fr ;
@ -340,7 +349,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 15 ;
lv2:index 16 ;
lv2:symbol "num_groups" ;
lv2:name "Number of groups",
"Nombre de groupes"@fr ;
@ -351,7 +360,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 16 ;
lv2:index 17 ;
lv2:symbol "num_regions" ;
lv2:name "Number of regions",
"Nombre de régions"@fr ;
@ -362,7 +371,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 17 ;
lv2:index 18 ;
lv2:symbol "num_samples" ;
lv2:name "Number of samples",
"Nombre d'échantillons"@fr ;
@ -371,4 +380,6 @@ midnam:update a lv2:Feature .
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 65535 ;
] .
] ;
rdfs:seeAlso <controllers.ttl> .

View file

@ -49,22 +49,23 @@ 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,
SFIZZ_SCALA_ROOT_KEY = 9,
SFIZZ_TUNING_FREQUENCY = 10,
SFIZZ_STRETCH_TUNING = 11,
SFIZZ_ACTIVE_VOICES = 12,
SFIZZ_NUM_CURVES = 13,
SFIZZ_NUM_MASTERS = 14,
SFIZZ_NUM_GROUPS = 15,
SFIZZ_NUM_REGIONS = 16,
SFIZZ_NUM_SAMPLES = 17,
SFIZZ_AUTOMATE = 2,
SFIZZ_LEFT = 3,
SFIZZ_RIGHT = 4,
SFIZZ_VOLUME = 5,
SFIZZ_POLYPHONY = 6,
SFIZZ_OVERSAMPLING = 7,
SFIZZ_PRELOAD = 8,
SFIZZ_FREEWHEELING = 9,
SFIZZ_SCALA_ROOT_KEY = 10,
SFIZZ_TUNING_FREQUENCY = 11,
SFIZZ_STRETCH_TUNING = 12,
SFIZZ_ACTIVE_VOICES = 13,
SFIZZ_NUM_CURVES = 14,
SFIZZ_NUM_MASTERS = 15,
SFIZZ_NUM_GROUPS = 16,
SFIZZ_NUM_REGIONS = 17,
SFIZZ_NUM_SAMPLES = 18,
};
// For use with instance-access
@ -85,3 +86,14 @@ struct sfizz_plugin_t;
bool sfizz_lv2_fetch_description(
sfizz_plugin_t *self, const int *serial,
uint8_t **descp, uint32_t *sizep, int *serialp);
// Mapping URID to CC and vice-versa
struct sfizz_lv2_ccmap;
sfizz_lv2_ccmap *sfizz_lv2_ccmap_create(LV2_URID_Map* map);
void sfizz_lv2_ccmap_free(sfizz_lv2_ccmap *ccmap);
LV2_URID sfizz_lv2_ccmap_map(const sfizz_lv2_ccmap *ccmap, int cc);
int sfizz_lv2_ccmap_unmap(const sfizz_lv2_ccmap *ccmap, LV2_URID urid);
struct sfizz_lv2_ccmap_delete {
void operator()(sfizz_lv2_ccmap* ccmap) const noexcept { sfizz_lv2_ccmap_free(ccmap); }
};

View file

@ -6,6 +6,7 @@
#include "sfizz_lv2.h"
#include "sfizz_lv2_plugin.h"
#include "sfizz/Config.h"
bool sfizz_lv2_fetch_description(
sfizz_plugin_t *self, const int *serial,
@ -29,3 +30,81 @@ bool sfizz_lv2_fetch_description(
return true;
}
struct sfizz_lv2_ccmap {
LV2_URID *cc_to_urid;
int *urid_to_cc;
LV2_URID min_cc_urid;
LV2_URID max_cc_urid;
};
sfizz_lv2_ccmap *
sfizz_lv2_ccmap_create(LV2_URID_Map* map)
{
// store the associations between CC number and parameter URID
// construct it trivially in the form of a tabulated perfect hash function
LV2_URID* cc_to_urid = new LV2_URID[sfz::config::numCCs];
int* urid_to_cc;
int urid_to_cc_size;
LV2_URID min_cc_urid {};
LV2_URID max_cc_urid {};
for (int cc = 0; cc < sfz::config::numCCs; ++cc) {
char name[256];
sprintf(name, SFIZZ_URI "#cc%03d", cc);
LV2_URID urid = map->map(map->handle, name);
if (cc == 0) {
min_cc_urid = urid;
max_cc_urid = urid;
}
else {
min_cc_urid = (urid < min_cc_urid) ? urid : min_cc_urid;
max_cc_urid = (urid > max_cc_urid) ? urid : max_cc_urid;
}
cc_to_urid[cc] = urid;
}
urid_to_cc_size = max_cc_urid - min_cc_urid + 1;
urid_to_cc = new int[urid_to_cc_size];
for (int i = 0; i < urid_to_cc_size; ++i)
urid_to_cc[i] = -1;
for (int cc = 0; cc < sfz::config::numCCs; ++cc) {
LV2_URID urid = cc_to_urid[cc];
urid_to_cc[urid - min_cc_urid] = cc;
}
sfizz_lv2_ccmap *self = new sfizz_lv2_ccmap;
self->cc_to_urid = cc_to_urid;
self->urid_to_cc = urid_to_cc;
self->min_cc_urid = min_cc_urid;
self->max_cc_urid = max_cc_urid;
return self;
}
void sfizz_lv2_ccmap_free(sfizz_lv2_ccmap *ccmap)
{
if (ccmap) {
delete[] ccmap->cc_to_urid;
delete[] ccmap->urid_to_cc;
delete ccmap;
}
}
LV2_URID sfizz_lv2_ccmap_map(const sfizz_lv2_ccmap *ccmap, int cc)
{
LV2_URID urid = 0;
if (cc >= 0 && cc < sfz::config::numCCs)
urid = ccmap->cc_to_urid[cc];
return urid;
}
int sfizz_lv2_ccmap_unmap(const sfizz_lv2_ccmap *ccmap, LV2_URID urid)
{
int cc = -1;
if (urid >= ccmap->min_cc_urid && urid <= ccmap->max_cc_urid)
cc = ccmap->urid_to_cc[urid - ccmap->min_cc_urid];
return cc;
}

View file

@ -11,6 +11,7 @@
#include <sfizz.h>
#include <stdbool.h>
#include <stdint.h>
#include <absl/types/optional.h>
#include <atomic>
#include <mutex>
@ -31,6 +32,7 @@ struct sfizz_plugin_t
// Ports
const LV2_Atom_Sequence *control_port {};
LV2_Atom_Sequence *notify_port {};
LV2_Atom_Sequence *automate_port {};
float *output_buffers[2] {};
const float *volume_port {};
const float *polyphony_port {};
@ -48,7 +50,8 @@ struct sfizz_plugin_t
float *num_samples_port {};
// Atom forge
LV2_Atom_Forge forge {}; ///< Forge for writing atoms in run thread
LV2_Atom_Forge forge_notify {}; ///< Forge for writing notification atoms in run thread
LV2_Atom_Forge forge_automate {}; ///< Forge for writing automation atoms in run thread
LV2_Atom_Forge forge_secondary {}; ///< Forge for writing into other buffers
// Logger
@ -92,6 +95,9 @@ struct sfizz_plugin_t
LV2_URID time_beats_per_minute_uri {};
LV2_URID time_speed_uri {};
// CC parameters
sfizz_lv2_ccmap* ccmap {};
// Sfizz related data
sfizz_synth_t *synth {};
sfizz_client_t *client {};
@ -108,6 +114,7 @@ struct sfizz_plugin_t
int sample_counter {};
float sample_rate {};
std::atomic<int> must_update_midnam {};
volatile bool must_automate_cc {};
// Current instrument description
std::mutex *sfz_blob_mutex {};
@ -115,6 +122,14 @@ struct sfizz_plugin_t
const uint8_t *volatile sfz_blob_data {};
volatile uint32_t sfz_blob_size {};
// Current CC values in the synth (synchronized by `synth_mutex`)
// updated by hdcc or file load
float *cc_current {};
// CC queued for automation on next run(). (synchronized by `synth_mutex`)
absl::optional<float>* ccauto {};
volatile bool have_ccauto {};
// Timing data
int bar {};
double bar_beat {};

View file

@ -102,6 +102,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
LV2_Atom_Forge atom_forge;
LV2_URID atom_event_transfer_uri;
LV2_URID atom_object_uri;
LV2_URID atom_float_uri;
LV2_URID atom_path_uri;
LV2_URID atom_urid_uri;
LV2_URID midi_event_uri;
@ -112,6 +113,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
LV2_URID sfizz_sfz_file_uri;
LV2_URID sfizz_scala_file_uri;
LV2_URID sfizz_osc_blob_uri;
std::unique_ptr<sfizz_lv2_ccmap, sfizz_lv2_ccmap_delete> ccmap;
uint8_t osc_temp[OSC_TEMP_SIZE];
alignas(LV2_Atom) uint8_t atom_temp[ATOM_TEMP_SIZE];
@ -201,6 +203,7 @@ instantiate(const LV2UI_Descriptor *descriptor,
lv2_atom_forge_init(forge, map);
self->atom_event_transfer_uri = map->map(map->handle, LV2_ATOM__eventTransfer);
self->atom_object_uri = map->map(map->handle, LV2_ATOM__Object);
self->atom_float_uri = map->map(map->handle, LV2_ATOM__Float);
self->atom_path_uri = map->map(map->handle, LV2_ATOM__Path);
self->atom_urid_uri = map->map(map->handle, LV2_ATOM__URID);
self->midi_event_uri = map->map(map->handle, LV2_MIDI__MidiEvent);
@ -211,6 +214,7 @@ instantiate(const LV2UI_Descriptor *descriptor,
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__tuningfile);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
self->ccmap.reset(sfizz_lv2_ccmap_create(map));
// set up the resource path
// * on Linux, this is determined by going 2 folders back from the SO path
@ -348,7 +352,15 @@ port_event(LV2UI_Handle ui,
const LV2_URID prop_uri = reinterpret_cast<const LV2_Atom_URID *>(prop)->body;
auto *value_body = reinterpret_cast<const char *>(LV2_ATOM_BODY_CONST(value));
if (prop_uri == self->sfizz_sfz_file_uri && value->type == self->atom_path_uri) {
int cc = sfizz_lv2_ccmap_unmap(self->ccmap.get(), prop_uri);
if (cc != -1) {
if (value->type == self->atom_float_uri) {
float ccvalue = *reinterpret_cast<const float*>(value_body);
self->uiReceiveValue(editIdForCC(cc), ccvalue);
}
}
else if (prop_uri == self->sfizz_sfz_file_uri && value->type == self->atom_path_uri) {
std::string path(value_body, strnlen(value_body, value->size));
self->uiReceiveValue(EditId::SfzFile, path);
}
@ -525,6 +537,22 @@ void sfizz_ui_t::uiSendValue(EditId id, const EditValue& v)
}
};
auto sendController = [this](LV2_URID property, float value) {
LV2_Atom_Forge *forge = &atom_forge;
LV2_Atom_Forge_Frame frame;
auto *atom = reinterpret_cast<const LV2_Atom *>(atom_temp);
lv2_atom_forge_set_buffer(forge, atom_temp, sizeof(atom_temp));
if (lv2_atom_forge_object(forge, &frame, 0, patch_set_uri) &&
lv2_atom_forge_key(forge, patch_property_uri) &&
lv2_atom_forge_urid(forge, property) &&
lv2_atom_forge_key(forge, patch_value_uri) &&
lv2_atom_forge_float(forge, value))
{
lv2_atom_forge_pop(forge, &frame);
write(con, SFIZZ_CONTROL, lv2_atom_total_size(atom), atom_event_transfer_uri, atom);
}
};
switch (id) {
case EditId::Volume:
sendFloat(SFIZZ_VOLUME, v.to_float());
@ -554,6 +582,11 @@ void sfizz_ui_t::uiSendValue(EditId id, const EditValue& v)
sendPath(sfizz_scala_file_uri, v.to_string());
break;
default:
if (editIdIsCC(id)) {
int cc = ccForEditId(id);
LV2_URID urid = sfizz_lv2_ccmap_map(ccmap.get(), cc);
sendController(urid, v.to_float());
}
break;
}
}

View file

@ -329,7 +329,18 @@ void SfizzVstEditor::processNoteEventQueue()
///
void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
{
if (id == EditId::SfzFile)
if (editIdIsCC(id)) {
int cc = ccForEditId(id);
// TODO(jpc) CC as parameters and automation
if (Editor* editor = editor_.get()) {
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", cc);
sfizz_arg_t args[1];
args[0].f = v.to_float();
editor->sendQueuedOSC(pathBuf, "f", args);
}
}
else if (id == EditId::SfzFile)
loadSfzFile(v.to_string());
else if (id == EditId::ScalaFile)
loadScalaFile(v.to_string());