diff --git a/cmake/LV2Config.cmake b/cmake/LV2Config.cmake index ab7a1652..b9bd2791 100644 --- a/cmake/LV2Config.cmake +++ b/cmake/LV2Config.cmake @@ -40,3 +40,46 @@ 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: . +@prefix lv2: . +@prefix patch: . +@prefix rdfs: . +@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 . +") + 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() diff --git a/cmake/StringUtility.cmake b/cmake/StringUtility.cmake new file mode 100644 index 00000000..3e8b4614 --- /dev/null +++ b/cmake/StringUtility.cmake @@ -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}") + string(PREPEND _output "${FILLCHAR}") + string(LENGTH "${_output}" _length) + endwhile() + set("${VAR}" "${_output}" PARENT_SCOPE) +endfunction() diff --git a/plugins/lv2/CMakeLists.txt b/plugins/lv2/CMakeLists.txt index 3f9a7247..2417e263 100644 --- a/plugins/lv2/CMakeLists.txt +++ b/plugins/lv2/CMakeLists.txt @@ -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 diff --git a/plugins/lv2/manifest.ttl.in b/plugins/lv2/manifest.ttl.in index ab407d33..3c5a130c 100644 --- a/plugins/lv2/manifest.ttl.in +++ b/plugins/lv2/manifest.ttl.in @@ -5,7 +5,7 @@ <@LV2PLUGIN_URI@> a lv2:Plugin ; lv2:binary ; - rdfs:seeAlso <@PROJECT_NAME@.ttl> . + rdfs:seeAlso <@PROJECT_NAME@.ttl>, . @LV2PLUGIN_IF_ENABLE_UI@<@LV2PLUGIN_URI@#ui> @LV2PLUGIN_IF_ENABLE_UI@ a ui:@LV2_UI_TYPE@ ; diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index a0c94a0b..deb6ee73 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -329,6 +329,65 @@ sfizz_lv2_receive_message(void* data, int delay, const char* path, const char* s (void)write_ok; } +static void +sfizz_lv2_setup_cc_parameters(sfizz_plugin_t* self) +{ + // 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 = self->map->map(self->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; + } + + 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; +} + +static int +sfizz_lv2_get_cc_from_parameter_urid(const sfizz_plugin_t* self, LV2_URID urid) +{ + int cc = -1; + if (urid >= self->min_cc_urid && urid <= self->max_cc_urid) + cc = self->urid_to_cc[urid - self->min_cc_urid]; + return cc; +} + +static LV2_URID +sfizz_lv2_get_parameter_urid_from_cc(const sfizz_plugin_t* self, int cc) +{ + return self->cc_to_urid[cc]; +} + static LV2_Handle instantiate(const LV2_Descriptor *descriptor, double rate, @@ -476,6 +535,8 @@ instantiate(const LV2_Descriptor *descriptor, return NULL; } + sfizz_lv2_setup_cc_parameters(self); + self->synth = sfizz_create_synth(); self->client = sfizz_create_client(self); self->synth_mutex = spin_mutex_create(); @@ -501,6 +562,8 @@ cleanup(LV2_Handle instance) spin_mutex_destroy(self->synth_mutex); sfizz_delete_client(self->client); sfizz_free(self->synth); + delete[] self->cc_to_urid; + delete[] self->urid_to_cc; delete self; } @@ -575,7 +638,14 @@ 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_get_cc_from_parameter_urid(self, key); + if (cc != -1) { + if (atom->type == self->atom_float_uri && atom->size == sizeof(float)) { + float value = *(const float *)LV2_ATOM_BODY_CONST(atom); + // TODO: CC parameter arrived + } + } + else if (key == self->sfizz_sfz_file_uri) { LV2_Atom_Forge *forge = &self->forge_secondary; sfizz_path_atom_buffer_t buffer; @@ -796,6 +866,7 @@ 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); diff --git a/plugins/lv2/sfizz.ttl.in b/plugins/lv2/sfizz.ttl.in index 45494016..38e67814 100644 --- a/plugins/lv2/sfizz.ttl.in +++ b/plugins/lv2/sfizz.ttl.in @@ -371,4 +371,6 @@ midnam:update a lv2:Feature . lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 65535 ; - ] . + ] ; + + rdfs:seeAlso . diff --git a/plugins/lv2/sfizz_lv2_plugin.h b/plugins/lv2/sfizz_lv2_plugin.h index 0d05a00b..638fb106 100644 --- a/plugins/lv2/sfizz_lv2_plugin.h +++ b/plugins/lv2/sfizz_lv2_plugin.h @@ -92,6 +92,12 @@ struct sfizz_plugin_t LV2_URID time_beats_per_minute_uri {}; LV2_URID time_speed_uri {}; + // CC parameters + LV2_URID* cc_to_urid {}; + int* urid_to_cc {}; + LV2_URID min_cc_urid {}; + LV2_URID max_cc_urid {}; + // Sfizz related data sfizz_synth_t *synth {}; sfizz_client_t *client {};