Merge pull request #767 from jpcima/onetime-instrument-info

Onetime instrument info
This commit is contained in:
JP Cimalando 2021-04-04 00:33:09 +02:00 • committed by GitHub
commit e7290b142d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 935 additions and 392 deletions

View file

@ -1,10 +1,13 @@
add_library(plugins-common STATIC EXCLUDE_FROM_ALL
"common/plugin/MessageUtils.h"
"common/plugin/MessageUtils.cpp")
"common/plugin/MessageUtils.cpp"
"common/plugin/InstrumentDescription.h"
"common/plugin/InstrumentDescription.cpp")
target_include_directories(plugins-common PUBLIC "common")
target_link_libraries(plugins-common
PUBLIC sfizz::spin_mutex
PUBLIC sfizz::filesystem absl::strings)
PUBLIC sfizz::filesystem absl::strings
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

@ -0,0 +1,213 @@
// 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 "InstrumentDescription.h"
#include "MessageUtils.h"
#include "sfizz.hpp"
#include <absl/strings/str_cat.h>
#include <algorithm>
#include <memory>
#include <iostream>
#include <cstring>
#include <cstdint>
template <class... Args>
static void bufferedStrCat(std::string* buffer, const Args&... args)
{
buffer->clear();
absl::StrAppend(buffer, args...);
}
std::string getDescriptionBlob(sfizz_synth_t* handle)
{
std::string blob;
blob.reserve(128 * 1024);
std::vector<char> msgbuf;
msgbuf.resize(1024);
std::string pathbuf;
pathbuf.reserve(256);
struct ClientData {
sfz::Sfizz* synth = nullptr;
sfz::Client* client = nullptr;
std::string* blob = nullptr;
std::vector<char>* msgbuf = nullptr;
std::string* pathbuf = nullptr;
};
sfz::Sfizz synth(handle);
ClientData cdata;
sfz::ClientPtr client = synth.createClient(&cdata);
cdata.synth = &synth;
cdata.client = client.get();
cdata.blob = &blob;
cdata.msgbuf = &msgbuf;
cdata.pathbuf = &pathbuf;
synth.setReceiveCallback(*client, [](void* data, int, const char* path, const char* sig, const sfizz_arg_t* args) {
ClientData& cdata = *reinterpret_cast<ClientData*>(data);
unsigned indices[8];
///
uint32_t msglen = sfizz_prepare_message(cdata.msgbuf->data(), cdata.msgbuf->size(), path, sig, args);
if (msglen > cdata.msgbuf->size()) {
cdata.msgbuf->resize(msglen);
sfizz_prepare_message(cdata.msgbuf->data(), cdata.msgbuf->size(), path, sig, args);
}
cdata.blob->append(cdata.msgbuf->data(), msglen);
///
if (Messages::matchOSC("/key/slots", path, indices) && !strcmp(sig, "b")) {
ConstBitSpan bits(args[0].b->data, 8 * args[0].b->size);
for (unsigned key = 0; key < 128 && key < bits.bit_size(); ++key) {
if (bits.test(key)) {
bufferedStrCat(cdata.pathbuf, "/key", key, "/label");
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
}
}
}
else if (Messages::matchOSC("/sw/last/slots", path, indices) && !strcmp(sig, "b")) {
ConstBitSpan bits(args[0].b->data, 8 * args[0].b->size);
for (unsigned key = 0; key < 128 && key < bits.bit_size(); ++key) {
if (bits.test(key)) {
bufferedStrCat(cdata.pathbuf, "/sw/last/", key, "/label");
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
}
}
}
else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b")) {
ConstBitSpan bits(args[0].b->data, 8 * args[0].b->size);
for (unsigned cc = 0; cc < sfz::config::numCCs && cc < bits.bit_size(); ++cc) {
if (bits.test(cc)) {
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/label");
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/default");
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
}
}
}
});
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, "/root_path", "", nullptr);
synth.sendMessage(*client, 0, "/image", "", nullptr);
synth.sendMessage(*client, 0, "/key/slots", "", nullptr);
synth.sendMessage(*client, 0, "/sw/last/slots", "", nullptr);
synth.sendMessage(*client, 0, "/cc/slots", "", nullptr);
blob.shrink_to_fit();
return blob;
}
InstrumentDescription parseDescriptionBlob(absl::string_view blob)
{
InstrumentDescription desc;
const uint8_t* src = reinterpret_cast<const uint8_t*>(blob.data());
uint32_t srcSize = blob.size();
char buffer[1024];
const char* path;
const char* sig;
const sfizz_arg_t* args;
int32_t byteCount;
while ((byteCount = sfizz_extract_message(src, srcSize, buffer, sizeof(buffer), &path, &sig, &args)) > 0) {
unsigned indices[8];
///
auto copyArgToBitSpan = [](const sfizz_arg_t& arg, BitSpan bits)
{
size_t size = std::min<size_t>(bits.byte_size(), arg.b->size);
memcpy(bits.data(), arg.b->data, size);
};
//
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("/root_path", path, indices) && !strcmp(sig, "s"))
desc.rootPath = args[0].s;
else if (Messages::matchOSC("/image", path, indices) && !strcmp(sig, "s"))
desc.image = args[0].s;
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());
else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b"))
copyArgToBitSpan(args[0], desc.ccUsed.span());
else if (Messages::matchOSC("/key&/label", path, indices) && !strcmp(sig, "s"))
desc.keyLabel[indices[0]] = args[0].s;
else if (Messages::matchOSC("/sw/last/&/label", path, indices) && !strcmp(sig, "s"))
desc.keyswitchLabel[indices[0]] = args[0].s;
else if (Messages::matchOSC("/cc&/label", path, indices) && !strcmp(sig, "s"))
desc.ccLabel[indices[0]] = args[0].s;
else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f"))
desc.ccDefault[indices[0]] = args[0].f;
src += byteCount;
srcSize -= byteCount;
}
return desc;
}
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 << " root_path: " << desc.rootPath << "\n";
os << " image: " << desc.image << "\n";
os << " keys:\n";
for (unsigned i = 0; i < 128; ++i) {
if (desc.keyUsed.test(i)) {
os << " - number: " << i << "\n";
if (!desc.keyLabel[i].empty())
os << " label: " << desc.keyLabel[i].c_str() << "\n";
}
}
os << " keyswitches:\n";
for (unsigned i = 0; i < 128; ++i) {
if (desc.keyswitchUsed.test(i)) {
os << " - number: " << i << "\n";
if (!desc.keyswitchLabel[i].empty())
os << " label: " << desc.keyswitchLabel[i].c_str() << "\n";
}
}
os << " cc:\n";
for (unsigned i = 0; i < sfz::config::numCCs; ++i) {
if (desc.ccUsed.test(i)) {
os << " - number: " << i << "\n";
os << " default: " << desc.ccDefault[i] << "\n";
if (!desc.ccLabel[i].empty())
os << " label: " << desc.ccLabel[i].c_str() << "\n";
}
}
return os;
}

View file

@ -0,0 +1,53 @@
// 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/Config.h"
#include "sfizz/utility/bit_array/BitArray.h"
#include <absl/strings/string_view.h>
#include <string>
#include <array>
#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 {};
std::string rootPath;
std::string image;
BitArray<128> keyUsed {};
BitArray<128> keyswitchUsed {};
BitArray<sfz::config::numCCs> ccUsed {};
std::array<std::string, 128> keyLabel {};
std::array<std::string, 128> keyswitchLabel {};
std::array<std::string, sfz::config::numCCs> ccLabel {};
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

@ -22,11 +22,19 @@ enum class EditId : int {
UserFilesDir,
FallbackFilesDir,
//
Key0,
KeyLast = Key0 + 128 - 1,
#define KEY_RANGE(Name) Name##0, Name##Last = Name##0 + 128 - 1
#define CC_RANGE(Name) Name##0, Name##Last = Name##0 + sfz::config::numCCs - 1
//
Controller0,
ControllerLast = Controller0 + sfz::config::numCCs - 1,
KEY_RANGE(Key),
CC_RANGE(Controller),
//
KEY_RANGE(KeyUsed),
KEY_RANGE(KeyLabel),
KEY_RANGE(KeyswitchUsed),
KEY_RANGE(KeyswitchLabel),
CC_RANGE(ControllerUsed),
CC_RANGE(ControllerDefault),
CC_RANGE(ControllerLabel),
//
UINumCurves,
UINumMasters,
@ -35,6 +43,11 @@ enum class EditId : int {
UINumPreloadedSamples,
UINumActiveVoices,
UIActivePanel,
//
BackgroundImage,
//
#undef KEY_RANGE
#undef CC_RANGE
};
struct EditRange {
@ -48,29 +61,33 @@ struct EditRange {
static EditRange get(EditId id);
};
inline EditId editIdForCC(int cc)
{
return EditId(int(EditId::Controller0) + cc);
}
inline int ccForEditId(EditId id)
{
return int(id) - int(EditId::Controller0);
}
inline bool editIdIsCC(EditId id)
{
return int(id) >= int(EditId::Controller0) &&
int(id) <= int(EditId::ControllerLast);
}
#define DEFINE_EDIT_ID_RANGE_HELPERS(type, Type, IdPrefix) \
inline bool editIdIs##Type(EditId id) \
{ \
return int(id) >= int(EditId::IdPrefix##0) && \
int(id) <= int(EditId::IdPrefix##Last); \
} \
inline EditId editIdFor##Type(int value) \
{ \
EditId id = EditId(int(EditId::IdPrefix##0) + value); \
assert(editIdIs##Type(id)); \
return id; \
} \
inline int type##ForEditId(EditId id) \
{ \
assert(editIdIs##Type(id)); \
return int(id) - int(EditId::IdPrefix##0); \
}
inline EditId editIdForKey(int key)
{
return EditId(int(EditId::Key0) + key);
}
inline int keyForEditId(EditId id)
{
return int(id) - int(EditId::Key0);
}
inline bool editIdIsKey(EditId id)
{
return int(id) >= int(EditId::Key0) && int(id) <= int(EditId::KeyLast);
}
// defines editIdForCC, ccForEditId, editIdIsCC, etc..
DEFINE_EDIT_ID_RANGE_HELPERS(cc, CC, Controller)
DEFINE_EDIT_ID_RANGE_HELPERS(key, Key, Key)
DEFINE_EDIT_ID_RANGE_HELPERS(keyUsed, KeyUsed, KeyUsed)
DEFINE_EDIT_ID_RANGE_HELPERS(keyLabel, KeyLabel, KeyLabel)
DEFINE_EDIT_ID_RANGE_HELPERS(keyswitchUsed, KeyswitchUsed, KeyswitchUsed)
DEFINE_EDIT_ID_RANGE_HELPERS(keyswitchLabel, KeyswitchLabel, KeyswitchLabel)
DEFINE_EDIT_ID_RANGE_HELPERS(ccUsed, CCUsed, ControllerUsed)
DEFINE_EDIT_ID_RANGE_HELPERS(ccDefault, CCDefault, ControllerDefault)
DEFINE_EDIT_ID_RANGE_HELPERS(ccLabel, CCLabel, ControllerLabel)
#undef DEFINE_EDIT_ID_RANGE_HELPERS

View file

@ -200,6 +200,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener {
void updateKeyswitchNameLabel();
void updateKeyUsed(unsigned key, bool used);
void updateKeyLabel(unsigned key, const char* label);
void updateKeyswitchUsed(unsigned key, bool used);
void updateCCUsed(unsigned cc, bool used);
void updateCCValue(unsigned cc, float value);
@ -278,12 +279,6 @@ void Editor::open(CFrame& frame)
impl.oscSendQueueTimer_ = makeOwned<CVSTGUITimer>(
[this](CVSTGUITimer* timer) { impl_->tickOSCQueue(timer); },
oscSendInterval, false);
// request the whole Key and CC information
impl.sendQueuedOSC("/key/slots", "", nullptr);
impl.sendQueuedOSC("/sw/last/slots", "", nullptr);
impl.sendQueuedOSC("/cc/slots", "", nullptr);
impl.sendQueuedOSC("/image", "", nullptr);
}
void Editor::close()
@ -311,12 +306,6 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
const std::string& value = v.to_string();
currentSfzFile_ = value;
updateSfzFileLabel(value);
// request the whole Key and CC information
sendQueuedOSC("/key/slots", "", nullptr);
sendQueuedOSC("/sw/last/slots", "", nullptr);
sendQueuedOSC("/cc/slots", "", nullptr);
sendQueuedOSC("/image", "", nullptr);
}
break;
case EditId::Volume:
@ -454,6 +443,12 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
setActivePanel(value);
}
break;
case EditId::BackgroundImage:
{
const std::string& value = v.to_string();
updateBackgroundImage(value.c_str());
}
break;
default:
if (editIdIsKey(id)) {
const int key = keyForEditId(id);
@ -461,6 +456,31 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
if (SPiano* piano = piano_)
piano->setKeyValue(key, value);
}
else if (editIdIsKeyUsed(id)) {
updateKeyUsed(keyUsedForEditId(id), v.to_float() != 0);
}
else if (editIdIsKeyLabel(id)) {
updateKeyLabel(keyLabelForEditId(id), v.to_string().c_str());
}
else if (editIdIsKeyswitchUsed(id)) {
updateKeyswitchUsed(keyswitchUsedForEditId(id), v.to_float() != 0);
}
else if (editIdIsKeyswitchLabel(id)) {
updateSWLastLabel(keyswitchLabelForEditId(id), v.to_string().c_str());
}
else if (editIdIsCCUsed(id)) {
updateCCUsed(ccUsedForEditId(id), v.to_float() != 0);
// TODO(jpc) remove value requests, when implementing CC automation
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", ccUsedForEditId(id));
sendQueuedOSC(pathBuf, "", nullptr);
}
else if (editIdIsCCDefault(id)) {
updateCCDefaultValue(ccDefaultForEditId(id), v.to_float());
}
else if (editIdIsCCLabel(id)) {
updateCCLabel(ccLabelForEditId(id), v.to_string().c_str());
}
break;
}
}
@ -469,46 +489,7 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi
{
unsigned indices[8];
if (Messages::matchOSC("/key/slots", path, indices) && !strcmp(sig, "b")) {
size_t numBits = 8 * args[0].b->size;
ConstBitSpan bits { args[0].b->data, numBits };
for (unsigned key = 0; key < 128; ++key) {
bool used = key < numBits && bits.test(key);
updateKeyUsed(key, used);
}
}
else if (Messages::matchOSC("/sw/last/slots", path, indices) && !strcmp(sig, "b")) {
size_t numBits = 8 * args[0].b->size;
ConstBitSpan bits { args[0].b->data, numBits };
for (unsigned key = 0; key < 128; ++key) {
bool used = key < numBits && bits.test(key);
updateKeyswitchUsed(key, used);
if (used) {
char pathBuf[256];
sprintf(pathBuf, "/sw/last/%u/label", key);
sendQueuedOSC(pathBuf, "", nullptr);
}
}
sendQueuedOSC("/sw/last/current", "", nullptr);
}
else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b")) {
size_t numBits = 8 * args[0].b->size;
ConstBitSpan bits { args[0].b->data, numBits };
for (unsigned cc = 0; cc < numBits; ++cc) {
bool used = bits.test(cc);
updateCCUsed(cc, used);
if (used) {
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", cc);
sendQueuedOSC(pathBuf, "", nullptr);
sprintf(pathBuf, "/cc%u/default", cc);
sendQueuedOSC(pathBuf, "", nullptr);
sprintf(pathBuf, "/cc%u/label", cc);
sendQueuedOSC(pathBuf, "", nullptr);
}
}
}
else if (Messages::matchOSC("/cc/changed", path, indices) && !strcmp(sig, "b")) {
if (Messages::matchOSC("/cc/changed~", path, indices) && !strcmp(sig, "b")) {
size_t numBits = 8 * args[0].b->size;
ConstBitSpan bits { args[0].b->data, numBits };
for (unsigned cc = 0; cc < numBits; ++cc) {
@ -523,20 +504,11 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi
else if (Messages::matchOSC("/cc&/value", path, indices) && !strcmp(sig, "f")) {
updateCCValue(indices[0], args[0].f);
}
else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f")) {
updateCCDefaultValue(indices[0], args[0].f);
}
else if (Messages::matchOSC("/cc&/label", path, indices) && !strcmp(sig, "s")) {
updateCCLabel(indices[0], args[0].s);
}
else if (Messages::matchOSC("/sw/last/current", path, indices) && !strcmp(sig, "i")) {
updateSWLastCurrent(args[0].i);
}
else if (Messages::matchOSC("/sw/last/&/label", path, indices) && !strcmp(sig, "s")) {
updateSWLastLabel(indices[0], args[0].s);
}
else if (Messages::matchOSC("/image", path, indices) && !strcmp(sig, "s")) {
updateBackgroundImage(args[0].s);
else if (Messages::matchOSC("/sw/last/current", path, indices) && !strcmp(sig, "N")) {
updateSWLastCurrent(-1);
}
else if (Messages::matchOSC("/mem/buffers", path, indices) && !strcmp(sig, "h")) {
updateMemoryUsed(args[0].h);
@ -1151,12 +1123,6 @@ void Editor::Impl::changeSfzFile(const std::string& filePath)
ctrl_->uiSendValue(EditId::SfzFile, filePath);
currentSfzFile_ = filePath;
updateSfzFileLabel(filePath);
// request the whole Key and CC information
sendQueuedOSC("/key/slots", "", nullptr);
sendQueuedOSC("/sw/last/slots", "", nullptr);
sendQueuedOSC("/cc/slots", "", nullptr);
sendQueuedOSC("/image", "", nullptr);
}
void Editor::Impl::changeToNextSfzFile(long offset)
@ -1519,6 +1485,13 @@ void Editor::Impl::updateKeyUsed(unsigned key, bool used)
piano->setKeyUsed(key, used);
}
void Editor::Impl::updateKeyLabel(unsigned key, const char* label)
{
// TODO nothing done with this info currently
(void)key;
(void)label;
}
void Editor::Impl::updateKeyswitchUsed(unsigned key, bool used)
{
if (SPiano* piano = piano_)
@ -1574,10 +1547,7 @@ void Editor::Impl::updateSWLastLabel(unsigned sw, const char* label)
void Editor::Impl::updateBackgroundImage(const char* filepath)
{
const fs::path sfzFilePath = fs::u8path(currentSfzFile_);
const fs::path sfzDirPath = sfzFilePath.parent_path();
const fs::path imagePath = sfzDirPath / fs::u8path(filepath);
SharedPointer<CBitmap> bitmap = loadAnyFormatImage(imagePath);
SharedPointer<CBitmap> bitmap = loadAnyFormatImage(filepath);
if (!bitmap)
bitmap = owned(new CBitmap("background.png"));

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);
@ -1066,6 +942,10 @@ run(LV2_Handle instance, uint32_t sample_count)
// Render the block
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))
@ -1172,6 +1052,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 +1096,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,8 @@
#include <lv2/midi/midi.h>
#include <lv2/patch/patch.h>
#include <lv2/urid/urid.h>
#include <lv2/instance-access/instance-access.h>
#include <ghc/fs_std.hpp>
#include <string>
#include <memory>
#include <cstring>
@ -86,6 +89,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 +116,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 +185,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 +371,70 @@ port_event(LV2UI_Handle ui,
(void)buffer_size;
}
static void
sfizz_ui_update_description(sfizz_ui_t *self, const InstrumentDescription& desc)
{
self->uiReceiveValue(EditId::UINumCurves, desc.numCurves);
self->uiReceiveValue(EditId::UINumMasters, desc.numMasters);
self->uiReceiveValue(EditId::UINumGroups, desc.numGroups);
self->uiReceiveValue(EditId::UINumRegions, desc.numRegions);
self->uiReceiveValue(EditId::UINumPreloadedSamples, desc.numSamples);
const fs::path rootPath = fs::u8path(desc.rootPath);
const fs::path imagePath = rootPath / fs::u8path(desc.image);
self->uiReceiveValue(EditId::BackgroundImage, imagePath.u8string());
for (unsigned key = 0; key < 128; ++key) {
bool keyUsed = desc.keyUsed.test(key);
bool keyswitchUsed = desc.keyswitchUsed.test(key);
self->uiReceiveValue(editIdForKeyUsed(key), float(keyUsed));
self->uiReceiveValue(editIdForKeyswitchUsed(key), float(keyswitchUsed));
if (keyUsed)
self->uiReceiveValue(editIdForKeyLabel(key), desc.keyLabel[key]);
if (keyswitchUsed)
self->uiReceiveValue(editIdForKeyswitchLabel(key), desc.keyswitchLabel[key]);
}
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
bool ccUsed = desc.ccUsed.test(cc);
self->uiReceiveValue(editIdForCCUsed(cc), float(ccUsed));
if (ccUsed) {
self->uiReceiveValue(editIdForCCDefault(cc), desc.ccDefault[cc]);
self->uiReceiveValue(editIdForCCLabel(cc), desc.ccLabel[cc]);
}
}
}
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(self, 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,9 @@ 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);
sfzDescriptionUpdate_ = Steinberg::owned(new SfzDescriptionUpdate);
scalaUpdate_ = Steinberg::owned(new ScalaUpdate);
processorStateUpdate_ = Steinberg::owned(new ProcessorStateUpdate);
playStateUpdate_ = Steinberg::owned(new PlayStateUpdate);
@ -211,10 +212,10 @@ 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();
sfzUpdate_->setPath(s.sfzFile);
sfzUpdate_->deferUpdate();
scalaUpdate_->setPath(s.scalaFile);
scalaUpdate_->deferUpdate();
return kResultTrue;
}
@ -230,35 +231,49 @@ 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));
sfzUpdate_->deferUpdate();
sfzDescriptionUpdate_->setDescription(std::string(sfzDescriptionBlob));
sfzDescriptionUpdate_->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 +329,9 @@ 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(sfzDescriptionUpdate_);
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,9 @@ public:
protected:
Steinberg::IPtr<OSCUpdate> oscUpdate_;
Steinberg::IPtr<NoteUpdate> noteUpdate_;
Steinberg::IPtr<FilePathUpdate> sfzPathUpdate_;
Steinberg::IPtr<FilePathUpdate> scalaPathUpdate_;
Steinberg::IPtr<SfzUpdate> sfzUpdate_;
Steinberg::IPtr<SfzDescriptionUpdate> sfzDescriptionUpdate_;
Steinberg::IPtr<ScalaUpdate> scalaUpdate_;
Steinberg::IPtr<ProcessorStateUpdate> processorStateUpdate_;
Steinberg::IPtr<PlayStateUpdate> playStateUpdate_;
Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {};

View file

@ -11,10 +11,12 @@
#include "SfizzFileScan.h"
#include "editor/Editor.h"
#include "editor/EditIds.h"
#include "plugin/InstrumentDescription.h"
#include "IdleUpdateHandler.h"
#if !defined(__APPLE__) && !defined(_WIN32)
#include "X11RunLoop.h"
#endif
#include <ghc/fs_std.hpp>
using namespace VSTGUI;
@ -188,17 +190,48 @@ 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;
uiReceiveValue(EditId::SfzFile, path);
}
if (SfzDescriptionUpdate* update = FCast<SfzDescriptionUpdate>(changedUnknown)) {
const InstrumentDescription desc = parseDescriptionBlob(update->getDescription());
uiReceiveValue(EditId::UINumCurves, desc.numCurves);
uiReceiveValue(EditId::UINumMasters, desc.numMasters);
uiReceiveValue(EditId::UINumGroups, desc.numGroups);
uiReceiveValue(EditId::UINumRegions, desc.numRegions);
uiReceiveValue(EditId::UINumPreloadedSamples, desc.numSamples);
const fs::path rootPath = fs::u8path(desc.rootPath);
const fs::path imagePath = rootPath / fs::u8path(desc.image);
uiReceiveValue(EditId::BackgroundImage, imagePath.u8string());
for (unsigned key = 0; key < 128; ++key) {
bool keyUsed = desc.keyUsed.test(key);
bool keyswitchUsed = desc.keyswitchUsed.test(key);
uiReceiveValue(editIdForKeyUsed(int(key)), float(keyUsed));
uiReceiveValue(editIdForKeyswitchUsed(int(key)), float(keyswitchUsed));
if (keyUsed)
uiReceiveValue(editIdForKeyLabel(int(key)), desc.keyLabel[key]);
if (keyswitchUsed)
uiReceiveValue(editIdForKeyswitchLabel(int(key)), desc.keyswitchLabel[key]);
}
return;
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
bool ccUsed = desc.ccUsed.test(cc);
uiReceiveValue(editIdForCCUsed(int(cc)), float(ccUsed));
if (ccUsed) {
uiReceiveValue(editIdForCCDefault(int(cc)), desc.ccDefault[cc]);
uiReceiveValue(editIdForCCLabel(int(cc)), desc.ccLabel[cc]);
}
}
}
if (ScalaUpdate* update = FCast<ScalaUpdate>(changedUnknown)) {
const std::string path = update->getPath();
uiReceiveValue(EditId::ScalaFile, path);
}
if (ProcessorStateUpdate* update = FCast<ProcessorStateUpdate>(changedUnknown)) {
@ -216,11 +249,6 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
if (PlayStateUpdate* update = FCast<PlayStateUpdate>(changedUnknown)) {
const SfizzPlayState playState = update->getState();
uiReceiveValue(EditId::UINumCurves, playState.curves);
uiReceiveValue(EditId::UINumMasters, playState.masters);
uiReceiveValue(EditId::UINumGroups, playState.groups);
uiReceiveValue(EditId::UINumRegions, playState.regions);
uiReceiveValue(EditId::UINumPreloadedSamples, playState.preloadedSamples);
uiReceiveValue(EditId::UINumActiveVoices, playState.activeVoices);
return;
}

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"
@ -38,11 +39,10 @@ static const char* kRingIdOsc = "Osc";
static const char* kMsgIdSetNumVoices = "SetNumVoices";
static const char* kMsgIdSetOversampling = "SetOversampling";
static const char* kMsgIdSetPreloadSize = "SetPreloadSize";
static const char* kMsgIdNotifyPlayState = "NotifyPlayState";
static const char* kMsgIdReceiveMessage = "ReceiveMessage";
static const char* kMsgIdNoteEvents = "NoteEvents";
static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 500 };
static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 50 };
SfizzVstProcessor::SfizzVstProcessor()
: _fifoToWorker(64 * 1024), _fifoMessageFromUi(64 * 1024),
@ -97,7 +97,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 +121,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 +186,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);
@ -205,9 +218,6 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state)
if (state) {
synth->setSampleRate(processSetup.sampleRate);
synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock);
_playStateChangePeriod = static_cast<uint32>(50e-3 * processSetup.sampleRate);
startBackgroundWork();
} else {
stopBackgroundWork();
@ -272,19 +282,10 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
synth.renderBlock(outputs, numFrames, numChannels);
_playStateChangeCounter += numFrames;
if (_playStateChangeCounter > _playStateChangePeriod) {
_playStateChangeCounter %= _playStateChangePeriod;
SfizzPlayState playState {};
playState.curves = synth.getNumCurves();
playState.masters = synth.getNumMasters();
playState.groups = synth.getNumGroups();
playState.regions = synth.getNumRegions();
playState.preloadedSamples = synth.getNumPreloadedSamples();
playState.activeVoices = synth.getNumActiveVoices();
if (writeWorkerMessage(kMsgIdNotifyPlayState, &playState, sizeof(playState)))
_semaToWorker.post();
}
// Request OSC updates
sfz::Client& client = *_client;
synth.sendMessage(client, 0, "/cc/changed~", "", nullptr);
synth.sendMessage(client, 0, "/sw/last/current", "", nullptr);
//
std::pair<uint32, float> noteEvents[128];
@ -556,13 +557,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 +610,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 +626,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()
@ -638,6 +650,7 @@ void SfizzVstProcessor::doBackgroundWork()
bool haveDoneIdleWork = false;
Clock::time_point lastIdleWorkTime;
size_t idleCounter = 0;
for (;;) {
bool isNotified = _semaToWorker.timed_wait(kBackgroundIdleInterval.count());
@ -672,13 +685,6 @@ void SfizzVstProcessor::doBackgroundWork()
std::lock_guard<SpinMutex> lock(_processMutex);
_synth->setPreloadSize(value);
}
else if (id == kMsgIdNotifyPlayState) {
SfizzPlayState playState = *msg->payload<SfizzPlayState>();
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("NotifiedPlayState");
notification->getAttributes()->setBinary("PlayState", &playState, sizeof(playState));
sendMessage(notification);
}
else if (id == kMsgIdReceiveMessage) {
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("ReceivedMessage");
@ -694,29 +700,35 @@ void SfizzVstProcessor::doBackgroundWork()
Clock::time_point currentTime = Clock::now();
if (!haveDoneIdleWork || currentTime - lastIdleWorkTime > kBackgroundIdleInterval) {
doBackgroundIdle();
doBackgroundIdle(idleCounter++);
haveDoneIdleWork = true;
lastIdleWorkTime = currentTime;
}
}
}
void SfizzVstProcessor::doBackgroundIdle()
void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter)
{
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);
{
SfizzPlayState ps;
ps.activeVoices = _synth->getNumActiveVoices();
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("NotifiedPlayState");
notification->getAttributes()->setBinary("PlayState", &ps, sizeof(ps));
sendMessage(notification);
}
if (_synth->shouldReloadScala()) {
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");
std::lock_guard<SpinMutex> lock(_processMutex);
_synth->loadScalaFile(_state.scalaFile);
if (idleCounter % 10 == 0) {
if (_synth->shouldReloadFile()) {
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
std::lock_guard<SpinMutex> lock(_processMutex);
loadSfzFileOrDefault(_state.sfzFile);
}
if (_synth->shouldReloadScala()) {
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");
std::lock_guard<SpinMutex> lock(_processMutex);
_synth->loadScalaFile(_state.scalaFile);
}
}
}

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
@ -72,10 +75,6 @@ private:
Ring_Buffer _fifoMessageFromUi;
SpinMutex _processMutex;
// state notification periodic timer
uint32 _playStateChangeCounter = 0;
uint32 _playStateChangePeriod = 0;
// time info
int _timeSigNumerator = 0;
int _timeSigDenominator = 0;
@ -95,7 +94,7 @@ private:
// worker
void doBackgroundWork();
void doBackgroundIdle();
void doBackgroundIdle(size_t idleCounter);
void startBackgroundWork();
void stopBackgroundWork();
// writer

View file

@ -31,10 +31,5 @@ public:
};
struct SfizzPlayState {
uint32 curves;
uint32 masters;
uint32 groups;
uint32 regions;
uint32 preloadedSamples;
uint32 activeVoices;
};

View file

@ -67,21 +67,10 @@ 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)
{
}
int32 getType() const noexcept
{
return type_;
}
void setPath(std::string newPath)
{
std::lock_guard<std::mutex> lock(mutex_);
@ -94,17 +83,59 @@ public:
return path_;
}
OBJ_METHODS(FilePathUpdate, FObject)
OBJ_METHODS(SfzUpdate, FObject)
private:
int32 type_ {};
std::string path_;
mutable std::mutex mutex_;
};
enum {
kFilePathUpdateSfz,
kFilePathUpdateScala,
/**
* @brief Update which notifies a change of SFZ description.
*/
class SfzDescriptionUpdate : public Steinberg::FObject {
public:
void setDescription(std::string newDescription)
{
std::lock_guard<std::mutex> lock(mutex_);
description_ = std::move(newDescription);
}
std::string getDescription() const
{
std::lock_guard<std::mutex> lock(mutex_);
return description_;
}
OBJ_METHODS(SfzDescriptionUpdate, FObject)
private:
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_);
path_ = std::move(newPath);
}
std::string getPath() const
{
std::lock_guard<std::mutex> lock(mutex_);
return path_;
}
OBJ_METHODS(ScalaUpdate, FObject)
private:
std::string path_;
mutable std::mutex mutex_;
};
/**

View file

@ -30,6 +30,6 @@ clang-tidy \
-- -Iexternal/abseil-cpp -Iexternal/jsl/include -Iexternal/filesystem/include -Iexternal/atomic_queue/include -Iexternal/threadpool -Isrc/external/hiir -Isrc/external/pugixml/src \
-Iexternal/st_audiofile/src -Iexternal/st_audiofile/thirdparty/dr_libs \
-Isrc/sfizz -Isrc -Isrc/sfizz/utility/bit_array -Isrc/sfizz/utility/spin_mutex -Isrc/external/spline -Isrc/external/cpuid/src -Iexternal/simde \
-Iplugins/vst -Iplugins/vst/external/VST_SDK/VST3_SDK -Iplugins/editor/external/vstgui4 -Iplugins/vst/external/ring_buffer \
-Iplugins/common -Iplugins/vst -Iplugins/vst/external/VST_SDK/VST3_SDK -Iplugins/editor/external/vstgui4 -Iplugins/vst/external/ring_buffer \
-Iplugins/editor/src \
-DNDEBUG -std=c++17

View file

@ -246,10 +246,10 @@ void Synth::Impl::clear()
effectBuses_[0]->setSampleRate(sampleRate_);
effectBuses_[0]->clearInputs(samplesPerBlock_);
resources_.clear();
rootPath_.clear();
numGroups_ = 0;
numMasters_ = 0;
currentSwitch_ = absl::nullopt;
currentSwitchChanged_ = true;
defaultPath_ = "";
image_ = "";
resources_.midiState.reset();
@ -258,6 +258,7 @@ void Synth::Impl::clear()
clearCCLabels();
currentUsedCCs_.clear();
changedCCsThisCycle_.clear();
changedCCsLastCycle_.clear();
clearKeyLabels();
keySlots_.clear();
swLastSlots_.clear();
@ -547,7 +548,11 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
void Synth::Impl::finalizeSfzLoad()
{
resources_.filePool.setRootDirectory(parser_.originalDirectory());
const fs::path& rootDirectory = parser_.originalDirectory();
resources_.filePool.setRootDirectory(rootDirectory);
// a string representation used for OSC purposes
rootPath_ = rootDirectory.u8string();
size_t currentRegionIndex = 0;
size_t currentRegionCount = layers_.size();
@ -790,8 +795,6 @@ void Synth::Impl::finalizeSfzLoad()
swLastSlots_.set(key);
}
}
// resend current keyswitch
currentSwitchChanged_ = true;
}
bool Synth::loadScalaFile(const fs::path& path)
@ -1017,25 +1020,9 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
// Advance the clock to the end of cycle
bc.endCycle();
// Send the set of changed CCs
Client broadcaster = impl.getBroadcaster();
const BitArray<config::numCCs>& changedCCs = impl.changedCCsThisCycle_;
const int finalFrameNumber = int(numFrames - 1);
if (broadcaster.canReceive()) {
sfizz_blob_t blob { changedCCs.data(), static_cast<uint32_t>(changedCCs.byte_size()) };
broadcaster.receive<'b'>(finalFrameNumber, "/cc/changed", &blob);
}
// Update sets of changed CCs
impl.changedCCsLastCycle_ = impl.changedCCsThisCycle_;
impl.changedCCsThisCycle_.clear();
// Send the changed keyswitch
if (impl.currentSwitchChanged_) {
if (broadcaster.canReceive()) {
int32_t value = -1;
if (impl.currentSwitch_)
value = *impl.currentSwitch_;
broadcaster.receive<'i'>(finalFrameNumber, "/sw/last/current", value);
}
impl.currentSwitchChanged_ = false;
}
{ // Clear events and advance midi time
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
@ -1135,7 +1122,6 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex
layer->keySwitched_ = false;
}
currentSwitch_ = noteNumber;
currentSwitchChanged_ = true;
}
for (Layer* layer : lastKeyswitchLists_[noteNumber])

View file

@ -39,13 +39,35 @@ 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()) };
client.receive<'b'>(delay, path, &blob);
} break;
MATCH("/key%d/label", "") {
MATCH("/key&/label", "") {
if (indices[0] >= 128)
break;
const std::string* label = impl.getKeyLabel(indices[0]);
@ -54,10 +76,16 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
//----------------------------------------------------------------------
MATCH("/root_path", "") {
client.receive<'s'>(delay, path, impl.rootPath_.c_str());
} break;
MATCH("/image", "") {
client.receive<'s'>(delay, path, impl.image_.c_str());
} break;
//----------------------------------------------------------------------
MATCH("/sw/last/slots", "") {
const BitArray<128>& switches = impl.swLastSlots_;
sfizz_blob_t blob { switches.data(), static_cast<uint32_t>(switches.byte_size()) };
@ -65,10 +93,10 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
} break;
MATCH("/sw/last/current", "") {
int32_t value = -1;
if (impl.currentSwitch_)
value = *impl.currentSwitch_;
client.receive<'i'>(delay, path, value);
client.receive<'i'>(delay, path, *impl.currentSwitch_);
else
client.receive<'N'>(delay, path, {});
} break;
MATCH("/sw/last/&/label", "") {
@ -112,6 +140,18 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
client.receive<'s'>(delay, path, label ? label->c_str() : "");
} break;
MATCH("/cc/changed", "") {
const BitArray<config::numCCs>& changedCCs = impl.changedCCsThisCycle_;
sfizz_blob_t blob { changedCCs.data(), static_cast<uint32_t>(changedCCs.byte_size()) };
client.receive<'b'>(delay, path, &blob);
} break;
MATCH("/cc/changed~", "") {
const BitArray<config::numCCs>& changedCCs = impl.changedCCsLastCycle_;
sfizz_blob_t blob { changedCCs.data(), static_cast<uint32_t>(changedCCs.byte_size()) };
client.receive<'b'>(delay, path, &blob);
} break;
//----------------------------------------------------------------------
MATCH("/mem/buffers", "") {

View file

@ -240,7 +240,6 @@ struct Synth::Impl final: public Parser::Listener {
// Set as sw_default if present in the file
absl::optional<uint8_t> currentSwitch_;
bool currentSwitchChanged_ = true;
std::vector<std::string> unknownOpcodes_;
using RegionViewVector = std::vector<Region*>;
using LayerViewVector = std::vector<Layer*>;
@ -279,6 +278,9 @@ struct Synth::Impl final: public Parser::Listener {
// Singletons passed as references to the voices
Resources resources_;
// Root path
std::string rootPath_;
// Control opcodes
std::string defaultPath_ { "" };
std::string image_ { "" };
@ -315,6 +317,7 @@ struct Synth::Impl final: public Parser::Listener {
std::array<float, config::numCCs> defaultCCValues_;
BitArray<config::numCCs> currentUsedCCs_;
BitArray<config::numCCs> changedCCsThisCycle_;
BitArray<config::numCCs> changedCCsLastCycle_;
// Messaging
sfizz_receive_t* broadcastReceiver = nullptr;