From 9d3e365f2e8445552fd38bb2f5869670ea7b08d0 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 2 Apr 2021 04:05:14 +0200 Subject: [PATCH] Instrument description in VST and LV2 --- plugins/CMakeLists.txt | 2 +- .../common/plugin/InstrumentDescription.cpp | 23 ++- plugins/common/plugin/InstrumentDescription.h | 22 +++ plugins/lv2/CMakeLists.txt | 2 + plugins/lv2/sfizz.cpp | 175 ++++-------------- plugins/lv2/sfizz_lv2.h | 40 ++++ plugins/lv2/sfizz_lv2_common.cpp | 31 ++++ plugins/lv2/sfizz_lv2_plugin.h | 131 +++++++++++++ plugins/lv2/sfizz_ui.cpp | 45 +++++ plugins/lv2/sfizz_ui.ttl.in | 3 +- plugins/vst/SfizzVstController.cpp | 56 +++--- plugins/vst/SfizzVstController.h | 4 +- plugins/vst/SfizzVstEditor.cpp | 17 +- plugins/vst/SfizzVstProcessor.cpp | 52 ++++-- plugins/vst/SfizzVstProcessor.h | 5 +- plugins/vst/SfizzVstUpdates.h | 43 +++-- src/sfizz/SynthMessaging.cpp | 22 +++ 17 files changed, 464 insertions(+), 209 deletions(-) create mode 100644 plugins/lv2/sfizz_lv2_common.cpp create mode 100644 plugins/lv2/sfizz_lv2_plugin.h diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index ad2c4871..4e0d1600 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -7,7 +7,7 @@ target_include_directories(plugins-common PUBLIC "common") target_link_libraries(plugins-common PUBLIC sfizz::spin_mutex PUBLIC sfizz::filesystem absl::strings - PRIVATE sfizz::internal) + PRIVATE sfizz::internal sfizz::sfizz) add_library(sfizz::plugins-common ALIAS plugins-common) if((SFIZZ_LV2 AND SFIZZ_LV2_UI) OR SFIZZ_VST) diff --git a/plugins/common/plugin/InstrumentDescription.cpp b/plugins/common/plugin/InstrumentDescription.cpp index 98865bfb..e104cc10 100644 --- a/plugins/common/plugin/InstrumentDescription.cpp +++ b/plugins/common/plugin/InstrumentDescription.cpp @@ -93,6 +93,11 @@ std::string getDescriptionBlob(sfizz_synth_t* handle) } }); + synth.sendMessage(*client, 0, "/num_regions", "", nullptr); + synth.sendMessage(*client, 0, "/num_groups", "", nullptr); + synth.sendMessage(*client, 0, "/num_masters", "", nullptr); + synth.sendMessage(*client, 0, "/num_curves", "", nullptr); + synth.sendMessage(*client, 0, "/num_samples", "", nullptr); synth.sendMessage(*client, 0, "/key/slots", "", nullptr); synth.sendMessage(*client, 0, "/sw/last/slots", "", nullptr); synth.sendMessage(*client, 0, "/cc/slots", "", nullptr); @@ -125,7 +130,17 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob) }; // - if (Messages::matchOSC("/key/slots", path, indices) && !strcmp(sig, "b")) + if (Messages::matchOSC("/num_regions", path, indices) && !strcmp(sig, "i")) + desc.numRegions = uint32_t(args[0].i); + else if (Messages::matchOSC("/num_groups", path, indices) && !strcmp(sig, "i")) + desc.numGroups = uint32_t(args[0].i); + else if (Messages::matchOSC("/num_masters", path, indices) && !strcmp(sig, "i")) + desc.numMasters = uint32_t(args[0].i); + else if (Messages::matchOSC("/num_curves", path, indices) && !strcmp(sig, "i")) + desc.numCurves = uint32_t(args[0].i); + else if (Messages::matchOSC("/num_samples", path, indices) && !strcmp(sig, "i")) + desc.numSamples = uint32_t(args[0].i); + else if (Messages::matchOSC("/key/slots", path, indices) && !strcmp(sig, "b")) copyArgToBitSpan(args[0], desc.keyUsed.span()); else if (Messages::matchOSC("/sw/last/slots", path, indices) && !strcmp(sig, "b")) copyArgToBitSpan(args[0], desc.keyswitchUsed.span()); @@ -151,6 +166,12 @@ std::ostream& operator<<(std::ostream& os, const InstrumentDescription& desc) { os << "instrument:\n"; + os << " regions: " << desc.numRegions << "\n"; + os << " groups: " << desc.numGroups << "\n"; + os << " masters: " << desc.numMasters << "\n"; + os << " curves: " << desc.numCurves << "\n"; + os << " samples: " << desc.numSamples << "\n"; + os << " keys:\n"; for (unsigned i = 0; i < 128; ++i) { if (desc.keyUsed.test(i)) { diff --git a/plugins/common/plugin/InstrumentDescription.h b/plugins/common/plugin/InstrumentDescription.h index 9caf5ede..88478a2d 100644 --- a/plugins/common/plugin/InstrumentDescription.h +++ b/plugins/common/plugin/InstrumentDescription.h @@ -13,7 +13,15 @@ #include struct sfizz_synth_t; +/** + * @brief Description of user-interactible elements of the SFZ instrument + */ struct InstrumentDescription { + uint32_t numRegions {}; + uint32_t numGroups {}; + uint32_t numMasters {}; + uint32_t numCurves {}; + uint32_t numSamples {}; BitArray<128> keyUsed {}; BitArray<128> keyswitchUsed {}; BitArray ccUsed {}; @@ -23,7 +31,21 @@ struct InstrumentDescription { std::array ccDefault {}; }; +/** + * @brief Produce a description of the currently loaded instrument in the synth, + * in the form of a concatenation of OSC messages. + * + * This form is a message transmissible over binary channels. + */ std::string getDescriptionBlob(sfizz_synth_t* handle); + +/** + * @brief Extract the information from the OSC blob and rearrange it in a + * structured form. + */ InstrumentDescription parseDescriptionBlob(absl::string_view blob); +/** + * @brief Display the description in human-readable format. + */ std::ostream& operator<<(std::ostream& os, const InstrumentDescription& desc); diff --git a/plugins/lv2/CMakeLists.txt b/plugins/lv2/CMakeLists.txt index ff97e859..3f9a7247 100644 --- a/plugins/lv2/CMakeLists.txt +++ b/plugins/lv2/CMakeLists.txt @@ -20,12 +20,14 @@ source_group("Turtle Files" FILES ) add_library(${LV2PLUGIN_PRJ_NAME} MODULE ${PROJECT_NAME}.cpp + ${PROJECT_NAME}_lv2_common.cpp ${LV2PLUGIN_TTL_SRC_FILES}) target_link_libraries(${LV2PLUGIN_PRJ_NAME} PRIVATE sfizz::sfizz sfizz::import sfizz::plugins-common) if(SFIZZ_LV2_UI) add_library(${LV2PLUGIN_PRJ_NAME}_ui MODULE ${PROJECT_NAME}_ui.cpp + ${PROJECT_NAME}_lv2_common.cpp vstgui_helpers.h vstgui_helpers.cpp) target_link_libraries(${LV2PLUGIN_PRJ_NAME}_ui PRIVATE sfizz::editor sfizz::vstgui sfizz::plugins-common) diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index 4ba2503c..edf486bf 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -33,38 +33,15 @@ */ #include "sfizz_lv2.h" +#include "sfizz_lv2_plugin.h" #include "sfizz/import/ForeignInstrument.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include +#include "plugin/InstrumentDescription.h" #include -#include -#include #include #include -#include - #define CHANNEL_MASK 0x0F #define MIDI_CHANNEL(byte) (byte & CHANNEL_MASK) #define MIDI_STATUS(byte) (byte & ~CHANNEL_MASK) @@ -77,122 +54,12 @@ #define LOG_SAMPLE_COUNT 48000 #define UNUSED(x) (void)(x) -#define DEFAULT_SCALA_FILE "Contents/Resources/DefaultScale.scl" -#define DEFAULT_SFZ_FILE "Contents/Resources/DefaultInstrument.sfz" -// This assumes that the longest path is the default sfz file; if not, change it -#define MAX_BUNDLE_PATH_SIZE (MAX_PATH_SIZE - sizeof(DEFAULT_SFZ_FILE)) - #ifndef NDEBUG #define LV2_DEBUG(...) lv2_log_note(&self->logger, "[DEBUG] " __VA_ARGS__) #else #define LV2_DEBUG(...) #endif -struct sfizz_plugin_t -{ - // 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 {}; - const float *scala_root_key_port {}; - const float *tuning_frequency_port {}; - const float *stretch_tuning_port {}; - float *active_voices_port {}; - float *num_curves_port {}; - float *num_masters_port {}; - float *num_groups_port {}; - float *num_regions_port {}; - float *num_samples_port {}; - - // Atom forge - LV2_Atom_Forge forge {}; ///< Forge for writing atoms in run thread - LV2_Atom_Forge forge_secondary {}; ///< Forge for writing into other buffers - - // 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_blank_uri {}; - LV2_URID atom_float_uri {}; - LV2_URID atom_double_uri {}; - LV2_URID atom_int_uri {}; - LV2_URID atom_long_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_scala_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 {}; - LV2_URID sfizz_active_voices_uri {}; - LV2_URID sfizz_osc_blob_uri {}; - LV2_URID time_position_uri {}; - LV2_URID time_bar_uri {}; - LV2_URID time_bar_beat_uri {}; - LV2_URID time_beat_unit_uri {}; - LV2_URID time_beats_per_bar_uri {}; - LV2_URID time_beats_per_minute_uri {}; - LV2_URID time_speed_uri {}; - - // Sfizz related data - sfizz_synth_t *synth {}; - sfizz_client_t *client {}; - spin_mutex_t *synth_mutex {}; - bool expect_nominal_block_length {}; - char sfz_file_path[MAX_PATH_SIZE] {}; - char scala_file_path[MAX_PATH_SIZE] {}; - int num_voices {}; - unsigned int preload_size {}; - sfizz_oversampling_factor_t oversampling {}; - float stretch_tuning {}; - volatile bool check_modification {}; - int max_block_size {}; - int sample_counter {}; - float sample_rate {}; - std::atomic must_update_midnam {}; - - // Timing data - int bar {}; - double bar_beat {}; - int beats_per_bar {}; - int beat_unit {}; - double bpm_tempo {}; - double speed {}; - - // Paths - char bundle_path[MAX_BUNDLE_PATH_SIZE] {}; - - // OSC - uint8_t osc_temp[OSC_TEMP_SIZE] {}; -}; - enum { SFIZZ_TIMEINFO_POSITION = 1 << 0, @@ -201,6 +68,14 @@ enum SFIZZ_TIMEINFO_SPEED = 1 << 3, }; +/// +static bool +sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path); + +static bool +sfizz_lv2_load_scala_file(sfizz_plugin_t *self, const char *file_path); + +/// static void sfizz_lv2_state_free_path(LV2_State_Free_Path_Handle handle, char *path) @@ -607,11 +482,10 @@ instantiate(const LV2_Descriptor *descriptor, sfizz_set_broadcast_callback(self->synth, &sfizz_lv2_receive_message, self); sfizz_set_receive_callback(self->client, &sfizz_lv2_receive_message); - sfizz_lv2_get_default_sfz_path(self, self->sfz_file_path, MAX_PATH_SIZE); - sfizz_lv2_get_default_scala_path(self, self->scala_file_path, MAX_PATH_SIZE); + self->sfz_blob_mutex = new std::mutex; - sfizz_load_file(self->synth, self->sfz_file_path); - sfizz_load_scala_file(self->synth, self->scala_file_path); + sfizz_lv2_load_file(self, self->sfz_file_path); + sfizz_lv2_load_scala_file(self, self->scala_file_path); sfizz_lv2_update_timeinfo(self, 0, ~0); @@ -622,6 +496,8 @@ static void cleanup(LV2_Handle instance) { sfizz_plugin_t *self = (sfizz_plugin_t *)instance; + delete[] self->sfz_blob_data; + delete self->sfz_blob_mutex; spin_mutex_destroy(self->synth_mutex); sfizz_delete_client(self->client); sfizz_free(self->synth); @@ -1172,6 +1048,25 @@ sfizz_lv2_update_file_info(sfizz_plugin_t* self, const char *file_path) self->must_update_midnam.store(1); } +static void +sfizz_lv2_update_sfz_info(sfizz_plugin_t *self) +{ + const std::string blob = getDescriptionBlob(self->synth); + + uint32_t size = uint32_t(blob.size()); + uint8_t *data = new uint8_t[size]; + memcpy(data, blob.data(), size); + + self->sfz_blob_mutex->lock(); + self->sfz_blob_serial += 1; + const uint8_t *old_data = self->sfz_blob_data; + self->sfz_blob_data = data; + self->sfz_blob_size = size; + self->sfz_blob_mutex->unlock(); + + delete[] old_data; +} + static bool sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path) { @@ -1197,7 +1092,7 @@ sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path) status = sfizz_load_string(self->synth, virtual_path.c_str(), sfz_text.c_str()); } - /// + sfizz_lv2_update_sfz_info(self); sfizz_lv2_update_file_info(self, file_path); return status; } diff --git a/plugins/lv2/sfizz_lv2.h b/plugins/lv2/sfizz_lv2.h index e8662cec..68cb03be 100644 --- a/plugins/lv2/sfizz_lv2.h +++ b/plugins/lv2/sfizz_lv2.h @@ -6,6 +6,27 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + #define MAX_PATH_SIZE 1024 #define ATOM_TEMP_SIZE 8192 #define OSC_TEMP_SIZE 8192 @@ -45,3 +66,22 @@ enum SFIZZ_NUM_REGIONS = 16, SFIZZ_NUM_SAMPLES = 17, }; + +// For use with instance-access +struct sfizz_plugin_t; + +/** + * @brief Fetch a copy of the current description, if more recent than what the + * version we already have. + * + * @param self The LV2 plugin + * @param serial The optional serial number to compare against + * @param desc The memory zone which receives the copy + * @param sizep The memory zone which receives the size + * @param serialp The memory zone which receives the new serial + * @return if serial is not null and its value isn't older, false + * otherwise, true, and the pointer returned in descp must be freed with delete[] after use + */ +bool sfizz_lv2_fetch_description( + sfizz_plugin_t *self, const int *serial, + uint8_t **descp, uint32_t *sizep, int *serialp); diff --git a/plugins/lv2/sfizz_lv2_common.cpp b/plugins/lv2/sfizz_lv2_common.cpp new file mode 100644 index 00000000..ea6562bb --- /dev/null +++ b/plugins/lv2/sfizz_lv2_common.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz_lv2.h" +#include "sfizz_lv2_plugin.h" + +bool sfizz_lv2_fetch_description( + sfizz_plugin_t *self, const int *serial, + uint8_t **descp, uint32_t *sizep, int *serialp) +{ + // must be thread-safe + + if (serial && self->sfz_blob_serial == *serial) + return false; + + self->sfz_blob_mutex->lock(); + uint32_t size = self->sfz_blob_size; + uint8_t *data = new uint8_t[size]; + int new_serial = self->sfz_blob_serial; + memcpy(data, self->sfz_blob_data, size); + self->sfz_blob_mutex->unlock(); + + *descp = data; + *sizep = size; + *serialp = new_serial; + + return true; +} diff --git a/plugins/lv2/sfizz_lv2_plugin.h b/plugins/lv2/sfizz_lv2_plugin.h new file mode 100644 index 00000000..0d05a00b --- /dev/null +++ b/plugins/lv2/sfizz_lv2_plugin.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once + +#include "sfizz_lv2.h" +#include +#include +#include +#include +#include +#include + +#define DEFAULT_SCALA_FILE "Contents/Resources/DefaultScale.scl" +#define DEFAULT_SFZ_FILE "Contents/Resources/DefaultInstrument.sfz" +// This assumes that the longest path is the default sfz file; if not, change it +#define MAX_BUNDLE_PATH_SIZE (MAX_PATH_SIZE - sizeof(DEFAULT_SFZ_FILE)) + +struct sfizz_plugin_t +{ + // 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 {}; + const float *scala_root_key_port {}; + const float *tuning_frequency_port {}; + const float *stretch_tuning_port {}; + float *active_voices_port {}; + float *num_curves_port {}; + float *num_masters_port {}; + float *num_groups_port {}; + float *num_regions_port {}; + float *num_samples_port {}; + + // Atom forge + LV2_Atom_Forge forge {}; ///< Forge for writing atoms in run thread + LV2_Atom_Forge forge_secondary {}; ///< Forge for writing into other buffers + + // 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_blank_uri {}; + LV2_URID atom_float_uri {}; + LV2_URID atom_double_uri {}; + LV2_URID atom_int_uri {}; + LV2_URID atom_long_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_scala_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 {}; + LV2_URID sfizz_active_voices_uri {}; + LV2_URID sfizz_osc_blob_uri {}; + LV2_URID time_position_uri {}; + LV2_URID time_bar_uri {}; + LV2_URID time_bar_beat_uri {}; + LV2_URID time_beat_unit_uri {}; + LV2_URID time_beats_per_bar_uri {}; + LV2_URID time_beats_per_minute_uri {}; + LV2_URID time_speed_uri {}; + + // Sfizz related data + sfizz_synth_t *synth {}; + sfizz_client_t *client {}; + spin_mutex_t *synth_mutex {}; + bool expect_nominal_block_length {}; + char sfz_file_path[MAX_PATH_SIZE] {}; + char scala_file_path[MAX_PATH_SIZE] {}; + int num_voices {}; + unsigned int preload_size {}; + sfizz_oversampling_factor_t oversampling {}; + float stretch_tuning {}; + volatile bool check_modification {}; + int max_block_size {}; + int sample_counter {}; + float sample_rate {}; + std::atomic must_update_midnam {}; + + // Current instrument description + std::mutex *sfz_blob_mutex {}; + volatile int sfz_blob_serial {}; + const uint8_t *volatile sfz_blob_data {}; + volatile uint32_t sfz_blob_size {}; + + // Timing data + int bar {}; + double bar_beat {}; + int beats_per_bar {}; + int beat_unit {}; + double bpm_tempo {}; + double speed {}; + + // Paths + char bundle_path[MAX_BUNDLE_PATH_SIZE] {}; + + // OSC + uint8_t osc_temp[OSC_TEMP_SIZE] {}; +}; diff --git a/plugins/lv2/sfizz_ui.cpp b/plugins/lv2/sfizz_ui.cpp index 47faf8a4..8a061ea7 100644 --- a/plugins/lv2/sfizz_ui.cpp +++ b/plugins/lv2/sfizz_ui.cpp @@ -37,6 +37,7 @@ #include "editor/Editor.h" #include "editor/EditorController.h" #include "editor/EditIds.h" +#include "plugin/InstrumentDescription.h" #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +88,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface { LV2_URID_Unmap *unmap = nullptr; LV2UI_Resize *resize = nullptr; LV2UI_Touch *touch = nullptr; + sfizz_plugin_t *plugin = nullptr; FrameHolder uiFrame; std::unique_ptr editor; #if LINUX @@ -112,6 +115,9 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface { uint8_t osc_temp[OSC_TEMP_SIZE]; alignas(LV2_Atom) uint8_t atom_temp[ATOM_TEMP_SIZE]; + int sfz_serial = 0; + bool valid_sfz_serial = false; + protected: void uiSendValue(EditId id, const EditValue& v) override; void uiBeginSend(EditId id) override; @@ -178,12 +184,18 @@ instantiate(const LV2UI_Descriptor *descriptor, self->touch = (LV2UI_Touch*)(**f).data; else if (!strcmp((**f).URI, LV2_UI__parent)) parentWindowId = (**f).data; + else if (!strcmp((**f).URI, LV2_INSTANCE_ACCESS_URI)) + self->plugin = (sfizz_plugin_t *)(**f).data; } // The map feature is required if (!map || !unmap) return nullptr; + // The instance-access feature is required + if (!self->plugin) + return nullptr; + LV2_Atom_Forge *forge = &self->atom_forge; lv2_atom_forge_init(forge, map); self->atom_event_transfer_uri = map->map(map->handle, LV2_ATOM__eventTransfer); @@ -358,11 +370,44 @@ port_event(LV2UI_Handle ui, (void)buffer_size; } +static void +sfizz_ui_update_description(const InstrumentDescription& desc) +{ + // TODO(jpc) perform editor updates here + + //std::cerr << desc << std::endl; +} + +static void +sfizz_ui_check_sfz_update(sfizz_ui_t *self) +{ + uint8_t *data = nullptr; + uint32_t size = 0; + int new_serial = 0; + const int *serial = self->valid_sfz_serial ? &self->sfz_serial : nullptr; + + bool update = sfizz_lv2_fetch_description( + self->plugin, serial, &data, &size, &new_serial); + + if (update) { + std::unique_ptr cleanup(data); + self->sfz_serial = new_serial; + self->valid_sfz_serial = true; + + const InstrumentDescription desc = parseDescriptionBlob( + absl::string_view(reinterpret_cast(data), size)); + sfizz_ui_update_description(desc); + } +} + static int idle(LV2UI_Handle ui) { sfizz_ui_t *self = (sfizz_ui_t *)ui; + // check if there are news regarding the current SFZ + sfizz_ui_check_sfz_update(self); + #if LINUX self->runLoop->execIdle(); #else diff --git a/plugins/lv2/sfizz_ui.ttl.in b/plugins/lv2/sfizz_ui.ttl.in index b5fb3470..06ee2e31 100644 --- a/plugins/lv2/sfizz_ui.ttl.in +++ b/plugins/lv2/sfizz_ui.ttl.in @@ -11,4 +11,5 @@ lv2:optionalFeature ui:parent ; lv2:optionalFeature ui:touch ; lv2:requiredFeature urid:map ; - lv2:requiredFeature urid:unmap . + lv2:requiredFeature urid:unmap ; + lv2:requiredFeature . diff --git a/plugins/vst/SfizzVstController.cpp b/plugins/vst/SfizzVstController.cpp index 2c3cb320..e695e6ba 100644 --- a/plugins/vst/SfizzVstController.cpp +++ b/plugins/vst/SfizzVstController.cpp @@ -22,8 +22,8 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) // create update objects oscUpdate_ = Steinberg::owned(new OSCUpdate); noteUpdate_ = Steinberg::owned(new NoteUpdate); - sfzPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateSfz)); - scalaPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateScala)); + sfzUpdate_ = Steinberg::owned(new SfzUpdate); + scalaUpdate_ = Steinberg::owned(new ScalaUpdate); processorStateUpdate_ = Steinberg::owned(new ProcessorStateUpdate); playStateUpdate_ = Steinberg::owned(new PlayStateUpdate); @@ -211,10 +211,8 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream) setParam(kPidTuningFrequency, s.tuningFrequency); setParam(kPidStretchedTuning, s.stretchedTuning); - sfzPathUpdate_->setPath(s.sfzFile); - sfzPathUpdate_->deferUpdate(); - scalaPathUpdate_->setPath(s.scalaFile); - scalaPathUpdate_->deferUpdate(); + scalaUpdate_->setPath(s.scalaFile); + scalaUpdate_->deferUpdate(); return kResultTrue; } @@ -230,35 +228,47 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message) const char* id = message->getMessageID(); Vst::IAttributeList* attr = message->getAttributes(); - if (!strcmp(id, "LoadedSfz")) { + /// + auto stringFromBinaryAttribute = [attr](const char* id, absl::string_view& string) -> tresult { const void* data = nullptr; uint32 size = 0; - result = attr->getBinary("File", data, size); + tresult result = attr->getBinary(id, data, size); + if (result == kResultTrue) + string = absl::string_view(reinterpret_cast(data), size); + return result; + }; + /// + if (!strcmp(id, "LoadedSfz")) { + absl::string_view sfzFile; + absl::string_view sfzDescriptionBlob; + + result = stringFromBinaryAttribute("File", sfzFile); if (result != kResultTrue) return result; - std::string sfzFile(static_cast(data), size); - processorStateUpdate_->access([&sfzFile](SfizzVstState& state) { - state.sfzFile = sfzFile; + result = stringFromBinaryAttribute("Description", sfzDescriptionBlob); + if (result != kResultTrue) + return result; + + processorStateUpdate_->access([sfzFile](SfizzVstState& state) { + state.sfzFile = std::string(sfzFile); }); - sfzPathUpdate_->setPath(std::move(sfzFile)); - sfzPathUpdate_->deferUpdate(); + sfzUpdate_->setPath(std::string(sfzFile), std::string(sfzDescriptionBlob)); + sfzUpdate_->deferUpdate(); } else if (!strcmp(id, "LoadedScala")) { - const void* data = nullptr; - uint32 size = 0; - result = attr->getBinary("File", data, size); + absl::string_view scalaFile; + result = stringFromBinaryAttribute("File", scalaFile); if (result != kResultTrue) return result; - std::string scalaFile(static_cast(data), size); - processorStateUpdate_->access([&scalaFile](SfizzVstState& state) { - state.scalaFile = scalaFile; + processorStateUpdate_->access([scalaFile](SfizzVstState& state) { + state.scalaFile = std::string(scalaFile); }); - scalaPathUpdate_->setPath(std::move(scalaFile)); - scalaPathUpdate_->deferUpdate(); + scalaUpdate_->setPath(std::string(scalaFile)); + scalaUpdate_->deferUpdate(); } else if (!strcmp(id, "NotifiedPlayState")) { const void* data = nullptr; @@ -314,8 +324,8 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) return nullptr; std::vector continuousUpdates; - continuousUpdates.push_back(sfzPathUpdate_); - continuousUpdates.push_back(scalaPathUpdate_); + continuousUpdates.push_back(sfzUpdate_); + continuousUpdates.push_back(scalaUpdate_); continuousUpdates.push_back(playStateUpdate_); for (uint32 i = 0, n = parameters.getParameterCount(); i < n; ++i) continuousUpdates.push_back(parameters.getParameterByIndex(i)); diff --git a/plugins/vst/SfizzVstController.h b/plugins/vst/SfizzVstController.h index 83e94eff..4d4192df 100644 --- a/plugins/vst/SfizzVstController.h +++ b/plugins/vst/SfizzVstController.h @@ -47,8 +47,8 @@ public: protected: Steinberg::IPtr oscUpdate_; Steinberg::IPtr noteUpdate_; - Steinberg::IPtr sfzPathUpdate_; - Steinberg::IPtr scalaPathUpdate_; + Steinberg::IPtr sfzUpdate_; + Steinberg::IPtr scalaUpdate_; Steinberg::IPtr processorStateUpdate_; Steinberg::IPtr playStateUpdate_; Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {}; diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 6b19f0c7..c9c83b47 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -188,17 +188,14 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) } } - if (FilePathUpdate* update = FCast(changedUnknown)) { + if (SfzUpdate* update = FCast(changedUnknown)) { const std::string path = update->getPath(); - switch (update->getType()) { - case kFilePathUpdateSfz: - uiReceiveValue(EditId::SfzFile, path); - break; - case kFilePathUpdateScala: - uiReceiveValue(EditId::ScalaFile, path); - break; - } - return; + uiReceiveValue(EditId::SfzFile, path); + } + + if (ScalaUpdate* update = FCast(changedUnknown)) { + const std::string path = update->getPath(); + uiReceiveValue(EditId::ScalaFile, path); } if (ProcessorStateUpdate* update = FCast(changedUnknown)) { diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 764e50fb..31e673c7 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -10,6 +10,7 @@ #include "SfizzVstParameters.h" #include "SfizzFileScan.h" #include "sfizz/import/ForeignInstrument.h" +#include "plugin/InstrumentDescription.h" #include "base/source/fstreamer.h" #include "pluginterfaces/vst/ivstevents.h" #include "pluginterfaces/vst/ivstparameterchanges.h" @@ -97,7 +98,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) _synth->setBroadcastCallback(onMessage, this); _currentStretchedTuning = 0.0; - loadSfzFileOrDefault(*_synth, {}); + loadSfzFileOrDefault({}); _synth->tempo(0, 0.5); _timeSigNumerator = 4; @@ -121,6 +122,19 @@ tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts); } +tresult PLUGIN_API SfizzVstProcessor::connect(IConnectionPoint* other) +{ + tresult result = AudioEffect::connect(other); + if (result != kResultTrue) + return result; + + // when controller connects, send these messages that we couldn't earlier + if (_loadedSfzMessage) + sendMessage(_loadedSfzMessage); + + return kResultTrue; +} + tresult PLUGIN_API SfizzVstProcessor::setState(IBStream* stream) { SfizzVstState s; @@ -173,7 +187,7 @@ void SfizzVstProcessor::syncStateToSynth() if (!synth) return; - loadSfzFileOrDefault(*synth, _state.sfzFile); + loadSfzFileOrDefault(_state.sfzFile); synth->setVolume(_state.volume); synth->setNumVoices(_state.numVoices); synth->setOversamplingFactor(1 << _state.oversamplingLog2); @@ -556,13 +570,8 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) std::unique_lock lock(_processMutex); _state.sfzFile.assign(static_cast(data), size); - loadSfzFileOrDefault(*_synth, _state.sfzFile); + loadSfzFileOrDefault(_state.sfzFile); lock.unlock(); - - Steinberg::OPtr reply { allocateMessage() }; - reply->setMessageID("LoadedSfz"); - reply->getAttributes()->setBinary("File", _state.sfzFile.data(), _state.sfzFile.size()); - sendMessage(reply); } else if (!std::strcmp(id, "LoadScala")) { const void* data = nullptr; @@ -614,8 +623,10 @@ void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char* } } -void SfizzVstProcessor::loadSfzFileOrDefault(sfz::Sfizz& synth, const std::string& filePath) +void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath) { + sfz::Sfizz& synth = *_synth; + if (!filePath.empty()) { const sfz::InstrumentFormatRegistry& formatRegistry = sfz::InstrumentFormatRegistry::getInstance(); const sfz::InstrumentFormat* format = formatRegistry.getMatchingFormat(filePath); @@ -628,8 +639,22 @@ void SfizzVstProcessor::loadSfzFileOrDefault(sfz::Sfizz& synth, const std::strin synth.loadSfzString(virtualPath, sfzText); } } - else + else { synth.loadSfzString("default.sfz", defaultSfzText); + } + + const std::string desc = getDescriptionBlob(synth.handle()); + + Steinberg::OPtr message { allocateMessage() }; + message->setMessageID("LoadedSfz"); + Vst::IAttributeList* attrs = message->getAttributes(); + attrs->setBinary("File", filePath.data(), filePath.size()); + attrs->setBinary("Description", desc.data(), desc.size()); + + // sending can fail if controller is not connected yet, so keep it around + _loadedSfzMessage = message; + + sendMessage(message); } void SfizzVstProcessor::doBackgroundWork() @@ -706,12 +731,7 @@ void SfizzVstProcessor::doBackgroundIdle() if (_synth->shouldReloadFile()) { fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n"); std::lock_guard lock(_processMutex); - loadSfzFileOrDefault(*_synth, _state.sfzFile); - - Steinberg::OPtr reply { allocateMessage() }; - reply->setMessageID("LoadedSfz"); - reply->getAttributes()->setBinary("File", _state.sfzFile.data(), _state.sfzFile.size()); - sendMessage(reply); + loadSfzFileOrDefault(_state.sfzFile); } if (_synth->shouldReloadScala()) { fprintf(stderr, "[Sfizz] scala file has changed, reloading\n"); diff --git a/plugins/vst/SfizzVstProcessor.h b/plugins/vst/SfizzVstProcessor.h index f34e1b4d..f7d73f2c 100644 --- a/plugins/vst/SfizzVstProcessor.h +++ b/plugins/vst/SfizzVstProcessor.h @@ -26,6 +26,8 @@ public: tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) override; + tresult PLUGIN_API connect(IConnectionPoint* other) override; + tresult PLUGIN_API setState(IBStream* stream) override; tresult PLUGIN_API getState(IBStream* stream) override; void syncStateToSynth(); @@ -49,6 +51,7 @@ public: private: // synth state. acquire processMutex before accessing std::unique_ptr _synth; + Steinberg::IPtr _loadedSfzMessage; bool _isActive = false; SfizzVstState _state; float _currentStretchedTuning = 0; @@ -59,7 +62,7 @@ private: void receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args); // misc - static void loadSfzFileOrDefault(sfz::Sfizz& synth, const std::string& filePath); + void loadSfzFileOrDefault(const std::string& filePath); // note event tracking std::array _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change diff --git a/plugins/vst/SfizzVstUpdates.h b/plugins/vst/SfizzVstUpdates.h index 145e39df..716ff6e5 100644 --- a/plugins/vst/SfizzVstUpdates.h +++ b/plugins/vst/SfizzVstUpdates.h @@ -67,21 +67,42 @@ private: }; /** - * @brief Update which notifies a change of file path pseudo-parameter - * The message ID is used to indicate which path it is. + * @brief Update which notifies a change of SFZ file. */ -class FilePathUpdate : public Steinberg::FObject { +class SfzUpdate : public Steinberg::FObject { public: - explicit FilePathUpdate(int32 type) - : type_(type) + void setPath(std::string newPath, std::string newDescription) { + std::lock_guard lock(mutex_); + path_ = std::move(newPath); + description_ = std::move(newDescription); } - int32 getType() const noexcept + std::string getPath() const { - return type_; + std::lock_guard lock(mutex_); + return path_; } + std::string getDescription() const + { + std::lock_guard lock(mutex_); + return description_; + } + + OBJ_METHODS(SfzUpdate, FObject) + +private: + std::string path_; + std::string description_; + mutable std::mutex mutex_; +}; + +/** + * @brief Update which notifies a change of scala file. + */ +class ScalaUpdate : public Steinberg::FObject { +public: void setPath(std::string newPath) { std::lock_guard lock(mutex_); @@ -94,19 +115,13 @@ public: return path_; } - OBJ_METHODS(FilePathUpdate, FObject) + OBJ_METHODS(ScalaUpdate, FObject) private: - int32 type_ {}; std::string path_; mutable std::mutex mutex_; }; -enum { - kFilePathUpdateSfz, - kFilePathUpdateScala, -}; - /** * @brief Update which indicates the processor status. */ diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 0b4f2922..940bb2a9 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -39,6 +39,28 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co //---------------------------------------------------------------------- + MATCH("/num_regions", "") { + client.receive<'i'>(delay, path, int(impl.layers_.size())); + } break; + + MATCH("/num_groups", "") { + client.receive<'i'>(delay, path, impl.numGroups_); + } break; + + MATCH("/num_masters", "") { + client.receive<'i'>(delay, path, impl.numMasters_); + } break; + + MATCH("/num_curves", "") { + client.receive<'i'>(delay, path, int(impl.resources_.curves.getNumCurves())); + } break; + + MATCH("/num_samples", "") { + client.receive<'i'>(delay, path, int(impl.resources_.filePool.getNumPreloadedSamples())); + } break; + + //---------------------------------------------------------------------- + MATCH("/key/slots", "") { const BitArray<128>& keys = impl.keySlots_; sfizz_blob_t blob { keys.data(), static_cast(keys.byte_size()) };