Instrument description in VST and LV2

This commit is contained in:
Jean Pierre Cimalando 2021-04-02 04:05:14 +02:00
parent b272b1a3e3
commit 9d3e365f2e
17 changed files with 464 additions and 209 deletions

View file

@ -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)

View file

@ -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)) {

View file

@ -13,7 +13,15 @@
#include <iosfwd>
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<sfz::config::numCCs> ccUsed {};
@ -23,7 +31,21 @@ struct InstrumentDescription {
std::array<float, sfz::config::numCCs> 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);

View file

@ -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)

View file

@ -33,38 +33,15 @@
*/
#include "sfizz_lv2.h"
#include "sfizz_lv2_plugin.h"
#include "sfizz/import/ForeignInstrument.h"
#include <lv2/atom/atom.h>
#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 <lv2/time/time.h>
#include <ardour/lv2_extensions.h>
#include <spin_mutex.h>
#include "plugin/InstrumentDescription.h"
#include <math.h>
#include <sfizz.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <atomic>
#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<int> 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;
}

View file

@ -6,6 +6,27 @@
#pragma once
#include <lv2/atom/atom.h>
#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 <lv2/time/time.h>
#include <ardour/lv2_extensions.h>
#include <stdint.h>
#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);

View file

@ -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;
}

View file

@ -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 <spin_mutex.h>
#include <sfizz.h>
#include <stdbool.h>
#include <stdint.h>
#include <atomic>
#include <mutex>
#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<int> 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] {};
};

View file

@ -37,6 +37,7 @@
#include "editor/Editor.h"
#include "editor/EditorController.h"
#include "editor/EditIds.h"
#include "plugin/InstrumentDescription.h"
#include <lv2/ui/ui.h>
#include <lv2/atom/atom.h>
#include <lv2/atom/forge.h>
@ -44,6 +45,7 @@
#include <lv2/midi/midi.h>
#include <lv2/patch/patch.h>
#include <lv2/urid/urid.h>
#include <lv2/instance-access/instance-access.h>
#include <string>
#include <memory>
#include <cstring>
@ -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> 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<uint8_t[]> cleanup(data);
self->sfz_serial = new_serial;
self->valid_sfz_serial = true;
const InstrumentDescription desc = parseDescriptionBlob(
absl::string_view(reinterpret_cast<char*>(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

View file

@ -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 <http://lv2plug.in/ns/ext/instance-access> .

View file

@ -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<const char*>(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<const char *>(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<const char *>(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<FObject*> 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));

View file

@ -47,8 +47,8 @@ public:
protected:
Steinberg::IPtr<OSCUpdate> oscUpdate_;
Steinberg::IPtr<NoteUpdate> noteUpdate_;
Steinberg::IPtr<FilePathUpdate> sfzPathUpdate_;
Steinberg::IPtr<FilePathUpdate> scalaPathUpdate_;
Steinberg::IPtr<SfzUpdate> sfzUpdate_;
Steinberg::IPtr<ScalaUpdate> scalaUpdate_;
Steinberg::IPtr<ProcessorStateUpdate> processorStateUpdate_;
Steinberg::IPtr<PlayStateUpdate> playStateUpdate_;
Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {};

View file

@ -188,17 +188,14 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
}
}
if (FilePathUpdate* update = FCast<FilePathUpdate>(changedUnknown)) {
if (SfzUpdate* update = FCast<SfzUpdate>(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<ScalaUpdate>(changedUnknown)) {
const std::string path = update->getPath();
uiReceiveValue(EditId::ScalaFile, path);
}
if (ProcessorStateUpdate* update = FCast<ProcessorStateUpdate>(changedUnknown)) {

View file

@ -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<SpinMutex> lock(_processMutex);
_state.sfzFile.assign(static_cast<const char *>(data), size);
loadSfzFileOrDefault(*_synth, _state.sfzFile);
loadSfzFileOrDefault(_state.sfzFile);
lock.unlock();
Steinberg::OPtr<Vst::IMessage> 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<Vst::IMessage> 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<SpinMutex> lock(_processMutex);
loadSfzFileOrDefault(*_synth, _state.sfzFile);
Steinberg::OPtr<Vst::IMessage> 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");

View file

@ -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<sfz::Sfizz> _synth;
Steinberg::IPtr<Vst::IMessage> _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<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change

View file

@ -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<std::mutex> lock(mutex_);
path_ = std::move(newPath);
description_ = std::move(newDescription);
}
int32 getType() const noexcept
std::string getPath() const
{
return type_;
std::lock_guard<std::mutex> lock(mutex_);
return path_;
}
std::string getDescription() const
{
std::lock_guard<std::mutex> 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<std::mutex> 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.
*/

View file

@ -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<uint32_t>(keys.byte_size()) };