Add OSC and plugin-side communication

This commit is contained in:
Jean Pierre Cimalando 2020-10-19 02:58:01 +02:00
parent 0e33784307
commit 722225f9f2
27 changed files with 1232 additions and 59 deletions

View file

@ -89,6 +89,7 @@ SFIZZ_SOURCES = \
src/sfizz/Logger.cpp \
src/sfizz/LFO.cpp \
src/sfizz/LFODescription.cpp \
src/sfizz/Messaging.cpp \
src/sfizz/MidiState.cpp \
src/sfizz/OpcodeCleanup.cpp \
src/sfizz/Opcode.cpp \
@ -112,6 +113,7 @@ SFIZZ_SOURCES = \
src/sfizz/simd/HelpersAVX.cpp \
src/sfizz/Smoothers.cpp \
src/sfizz/Synth.cpp \
src/sfizz/SynthMessaging.cpp \
src/sfizz/Tuning.cpp \
src/sfizz/utility/SpinMutex.cpp \
src/sfizz/Voice.cpp \

View file

@ -42,6 +42,7 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL
src/editor/utility/vstgui_after.h
src/editor/utility/vstgui_before.h)
target_include_directories(sfizz_editor PUBLIC "src")
target_link_libraries(sfizz_editor PUBLIC sfizz_messaging)
target_link_libraries(sfizz_editor PRIVATE sfizz-vstgui)
target_link_libraries(sfizz_editor PUBLIC absl::strings)
if(APPLE)

View file

@ -100,6 +100,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener {
SPiano* piano_ = nullptr;
void uiReceiveValue(EditId id, const EditValue& v) override;
void uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args) override;
void createFrameContents();
@ -318,6 +319,11 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
}
}
void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args)
{
// TODO handle the message...
}
void Editor::Impl::createFrameContents()
{
CViewContainer* mainView;

View file

@ -6,6 +6,7 @@
#pragma once
#include "EditValue.h"
#include <sfizz_message.h>
#include <absl/strings/string_view.h>
#include <string>
#include <cstdint>
@ -20,6 +21,7 @@ public:
virtual void uiBeginSend(EditId id) = 0;
virtual void uiEndSend(EditId id) = 0;
virtual void uiSendMIDI(const uint8_t* msg, uint32_t len) = 0;
virtual void uiSendMessage(const char* path, const char* sig, const sfizz_arg_t* args) = 0;
class Receiver;
void decorate(Receiver* r) { r_ = r; }
@ -27,10 +29,12 @@ public:
public:
virtual ~Receiver() {}
virtual void uiReceiveValue(EditId id, const EditValue& v) = 0;
virtual void uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args) = 0;
};
// called by DSP
void uiReceiveValue(EditId id, const EditValue& v);
void uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args);
private:
Receiver* r_ = nullptr;
@ -41,3 +45,9 @@ inline void EditorController::uiReceiveValue(EditId id, const EditValue& v)
if (r_)
r_->uiReceiveValue(id, v);
}
inline void EditorController::uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args)
{
if (r_)
r_->uiReceiveMessage(path, sig, args);
}

View file

@ -148,6 +148,7 @@ typedef struct
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;
@ -158,6 +159,7 @@ typedef struct
// Sfizz related data
sfizz_synth_t *synth;
sfizz_client_t *client;
bool expect_nominal_block_length;
char sfz_file_path[MAX_PATH_SIZE];
char scala_file_path[MAX_PATH_SIZE];
@ -181,6 +183,9 @@ typedef struct
// Paths
char bundle_path[MAX_BUNDLE_PATH_SIZE];
// OSC
uint8_t osc_temp[OSC_TEMP_SIZE];
} sfizz_plugin_t;
enum
@ -236,6 +241,7 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self)
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
self->time_position_uri = map->map(map->handle, LV2_TIME__Position);
self->time_bar_uri = map->map(map->handle, LV2_TIME__bar);
self->time_bar_beat_uri = map->map(map->handle, LV2_TIME__barBeat);
@ -423,6 +429,28 @@ sfizz_lv2_update_timeinfo(sfizz_plugin_t *self, int delay, int updates)
sfizz_send_playback_state(self->synth, delay, self->speed > 0);
}
static void
sfizz_lv2_receive_message(void* data, int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
(void)delay;
sfizz_plugin_t *self = (sfizz_plugin_t *)data;
// transmit to UI as OSC blob
uint8_t *osc_temp = self->osc_temp;
uint32_t osc_size = sfizz_prepare_message(osc_temp, OSC_TEMP_SIZE, path, sig, args);
if (osc_size > OSC_TEMP_SIZE)
return;
bool write_ok =
lv2_atom_forge_frame_time(&self->forge, 0) &&
lv2_atom_forge_atom(&self->forge, osc_size, self->sfizz_osc_blob_uri) &&
lv2_atom_forge_raw(&self->forge, osc_temp, osc_size);
lv2_atom_forge_pad(&self->forge, osc_size);
(void)write_ok;
}
static LV2_Handle
instantiate(const LV2_Descriptor *descriptor,
double rate,
@ -570,6 +598,9 @@ instantiate(const LV2_Descriptor *descriptor,
}
self->synth = sfizz_create_synth();
self->client = sfizz_create_client(self);
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(instance, self->sfz_file_path, MAX_PATH_SIZE);
sfizz_lv2_get_default_scala_path(instance, self->scala_file_path, MAX_PATH_SIZE);
@ -586,6 +617,7 @@ static void
cleanup(LV2_Handle instance)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
sfizz_delete_client(self->client);
sfizz_free(self->synth);
free(self);
}
@ -952,12 +984,22 @@ run(LV2_Handle instance, uint32_t sample_count)
self->unmap->unmap(self->unmap->handle, obj->body.otype));
continue;
}
// Got an atom that is a MIDI event
}
else if (ev->body.type == self->midi_event_uri)
{
// Got an atom that is a MIDI event
sfizz_lv2_process_midi_event(self, ev);
}
else if (ev->body.type == self->sfizz_osc_blob_uri)
{
// Got an atom that is a OSC event
const char *path;
const char *sig;
const sfizz_arg_t *args;
uint8_t buffer[1024];
if (sfizz_extract_message(LV2_ATOM_BODY_CONST(&ev->body), ev->body.size, buffer, sizeof(buffer), &path, &sig, &args) > 0)
sfizz_send_message(self->synth, self->client, (int)ev->time.frames, path, sig, args);
}
}

View file

@ -11,6 +11,7 @@
@prefix pprop: <http://lv2plug.in/ns/ext/port-props#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rsz: <http://lv2plug.in/ns/ext/resize-port#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix time: <http://lv2plug.in/ns/ext/time#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
@ -92,21 +93,23 @@ midnam:update a lv2:Feature .
lv2:port [
a lv2:InputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message, midi:MidiEvent, time:Position ;
atom:supports patch:Message, midi:MidiEvent, time:Position, <@LV2PLUGIN_URI@:OSCBlob> ;
lv2:designation lv2:control ;
lv2:index 0 ;
lv2:symbol "control" ;
lv2:name "Control",
"Contrôle"@fr ;
rsz:minimumSize 65536 ;
] , [
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message ;
atom:supports patch:Message, <@LV2PLUGIN_URI@:OSCBlob> ;
lv2:designation lv2:control ;
lv2:index 1 ;
lv2:symbol "notify" ;
lv2:name "Notify",
"Notification"@fr ;
rsz:minimumSize 65536 ;
] , [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 2 ;

View file

@ -7,6 +7,8 @@
#pragma once
#define MAX_PATH_SIZE 1024
#define ATOM_TEMP_SIZE 8192
#define OSC_TEMP_SIZE 8192
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
#define SFIZZ_UI_URI "http://sfztools.github.io/sfizz#ui"
@ -19,6 +21,8 @@
// These ones are just for the worker
#define SFIZZ__logStatus SFIZZ_URI ":" "log_status"
#define SFIZZ__checkModification SFIZZ_URI ":" "check_modification"
// OSC atoms
#define SFIZZ__OSCBlob SFIZZ_URI ":" "OSCBlob"
enum
{

View file

@ -107,12 +107,17 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
LV2_URID patch_value_uri;
LV2_URID sfizz_sfz_file_uri;
LV2_URID sfizz_scala_file_uri;
LV2_URID sfizz_osc_blob_uri;
uint8_t osc_temp[OSC_TEMP_SIZE];
alignas(LV2_Atom) uint8_t atom_temp[ATOM_TEMP_SIZE];
protected:
void uiSendValue(EditId id, const EditValue& v) override;
void uiBeginSend(EditId id) override;
void uiEndSend(EditId id) override;
void uiSendMIDI(const uint8_t* msg, uint32_t len) override;
void uiSendMessage(const char* path, const char* sig, const sfizz_arg_t* args) override;
private:
void uiTouch(EditId id, bool t);
@ -192,6 +197,7 @@ instantiate(const LV2UI_Descriptor *descriptor,
self->patch_value_uri = map->map(map->handle, LV2_PATCH__value);
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__tuningfile);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
// set up the resource path
// * on Linux, this is determined by going 2 folders back from the SO path
@ -335,6 +341,14 @@ port_event(LV2UI_Handle ui,
}
}
}
else if (atom->type == self->sfizz_osc_blob_uri) {
const char *path;
const char *sig;
const sfizz_arg_t *args;
uint8_t buffer[1024];
if (sfizz_extract_message(LV2_ATOM_BODY_CONST(atom), atom->size, buffer, sizeof(buffer), &path, &sig, &args) > 0)
self->uiReceiveMessage(path, sig, args);
}
}
(void)buffer_size;
@ -422,9 +436,8 @@ void sfizz_ui_t::uiSendValue(EditId id, const EditValue& v)
auto sendPath = [this](LV2_URID property, const std::string& value) {
LV2_Atom_Forge *forge = &atom_forge;
LV2_Atom_Forge_Frame frame;
alignas(LV2_Atom) uint8_t buffer[MAX_PATH_SIZE + 512];
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
lv2_atom_forge_set_buffer(forge, (uint8_t *)&buffer, sizeof(buffer));
auto *atom = reinterpret_cast<const LV2_Atom *>(atom_temp);
lv2_atom_forge_set_buffer(forge, atom_temp, sizeof(atom_temp));
if (lv2_atom_forge_object(forge, &frame, 0, patch_set_uri) &&
lv2_atom_forge_key(forge, patch_property_uri) &&
lv2_atom_forge_urid(forge, property) &&
@ -514,12 +527,30 @@ void sfizz_ui_t::uiTouch(EditId id, bool t)
void sfizz_ui_t::uiSendMIDI(const uint8_t* msg, uint32_t len)
{
LV2_Atom_Forge *forge = &atom_forge;
alignas(LV2_Atom) uint8_t buffer[512];
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
lv2_atom_forge_set_buffer(forge, (uint8_t *)&buffer, sizeof(buffer));
auto *atom = reinterpret_cast<const LV2_Atom *>(atom_temp);
lv2_atom_forge_set_buffer(forge, atom_temp, sizeof(atom_temp));
if (lv2_atom_forge_atom(forge, len, midi_event_uri) &&
lv2_atom_forge_write(forge, msg, len))
{
write(con, SFIZZ_CONTROL, lv2_atom_total_size(atom), atom_event_transfer_uri, atom);
}
}
void sfizz_ui_t::uiSendMessage(const char* path, const char* sig, const sfizz_arg_t* args)
{
uint8_t *osc_temp = this->osc_temp;
uint32_t osc_size = sfizz_prepare_message(osc_temp, OSC_TEMP_SIZE, path, sig, args);
if (osc_size > OSC_TEMP_SIZE)
return;
LV2_Atom_Forge *forge = &atom_forge;
auto *atom = reinterpret_cast<const LV2_Atom *>(atom_temp);
lv2_atom_forge_set_buffer(forge, atom_temp, sizeof(atom_temp));
if (lv2_atom_forge_atom(forge, osc_size, sfizz_osc_blob_uri) &&
lv2_atom_forge_raw(forge, osc_temp, osc_size))
{
write(con, SFIZZ_CONTROL, lv2_atom_total_size(atom), atom_event_transfer_uri, atom);
}
}

View file

@ -148,6 +148,7 @@ set (SFIZZ_SOURCES
sfizz/PowerFollower.cpp
sfizz/FlexEGDescription.cpp
sfizz/FlexEnvelope.cpp
sfizz/SynthMessaging.cpp
sfizz/modulations/ModId.cpp
sfizz/modulations/ModKey.cpp
sfizz/modulations/ModKeyHash.cpp
@ -206,6 +207,7 @@ set (SFIZZ_PARSER_SOURCES
set (SFIZZ_PARSER_OTHER sfizz/OpcodeCleanup.re)
source_group ("Other Files" FILES ${SFIZZ_PARSER_OTHER})
# Sfizz parser library
add_library (sfizz_parser STATIC)
target_sources (sfizz_parser PRIVATE
${SFIZZ_PARSER_HEADERS} ${SFIZZ_PARSER_SOURCES} ${SFIZZ_PARSER_OTHER})
@ -213,6 +215,20 @@ target_include_directories (sfizz_parser PUBLIC sfizz)
target_include_directories (sfizz_parser PUBLIC external)
target_link_libraries (sfizz_parser PUBLIC absl::strings PRIVATE absl::flat_hash_map)
# OSC messaging library
set (SFIZZ_MESSAGING_HEADERS
sfizz/Messaging.h
sfizz_message.h)
set (SFIZZ_MESSAGING_SOURCES
sfizz/Messaging.cpp)
add_library (sfizz_messaging STATIC)
target_sources (sfizz_messaging PRIVATE
${SFIZZ_MESSAGING_HEADERS} ${SFIZZ_MESSAGING_SOURCES})
target_include_directories (sfizz_messaging PUBLIC ".")
target_link_libraries (sfizz_messaging PUBLIC absl::strings)
# Sfizz static library
add_library(sfizz_static STATIC)
target_sources(sfizz_static PRIVATE
@ -220,8 +236,8 @@ target_sources(sfizz_static PRIVATE
target_include_directories (sfizz_static PUBLIC .)
target_include_directories (sfizz_static PUBLIC external)
target_link_libraries (sfizz_static PUBLIC absl::strings absl::span)
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads st_audiofile sfizz-pugixml sfizz-spline sfizz-tunings sfizz-kissfft sfizz-cpuid sfizz-jsl sfizz-atomic)
set_target_properties (sfizz_static PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp")
target_link_libraries (sfizz_static PRIVATE sfizz_parser sfizz_messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz-pugixml sfizz-spline sfizz-tunings sfizz-kissfft sfizz-cpuid sfizz-jsl sfizz-atomic)
set_target_properties (sfizz_static PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h")
if(SFIZZ_USE_SNDFILE)
target_compile_definitions (sfizz_static PUBLIC SFIZZ_USE_SNDFILE=1)
target_link_libraries (sfizz_static PUBLIC st_audiofile)
@ -251,7 +267,7 @@ if (SFIZZ_SHARED)
${SFIZZ_HEADERS} ${SFIZZ_SOURCES} ${FAUST_FILES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp)
target_include_directories (sfizz_shared PRIVATE .)
target_include_directories (sfizz_shared PRIVATE external)
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads st_audiofile sfizz-pugixml sfizz-spline sfizz-tunings sfizz-kissfft sfizz-cpuid sfizz-jsl sfizz-atomic)
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser sfizz_messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz-pugixml sfizz-spline sfizz-tunings sfizz-kissfft sfizz-cpuid sfizz-jsl sfizz-atomic)
if(SFIZZ_USE_SNDFILE)
target_compile_definitions (sfizz_shared PUBLIC SFIZZ_USE_SNDFILE=1)
target_link_libraries (sfizz_shared PUBLIC st_audiofile)
@ -263,7 +279,7 @@ if (SFIZZ_SHARED)
target_compile_definitions (sfizz_shared PRIVATE "SFIZZ_ENABLE_RELEASE_ASSERT=1")
endif()
target_compile_definitions(sfizz_shared PRIVATE SFIZZ_EXPORT_SYMBOLS)
set_target_properties (sfizz_shared PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR} OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp")
set_target_properties (sfizz_shared PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR} OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h")
sfizz_enable_lto_if_needed(sfizz_shared)
sfizz_enable_fast_math(sfizz_shared)

View file

@ -10,6 +10,7 @@
*/
#pragma once
#include "sfizz_message.h"
#include <stddef.h>
#include <stdbool.h>
@ -731,6 +732,79 @@ SFIZZ_EXPORTED_API int sfizz_get_cc_label_number(sfizz_synth_t* synth, int label
*/
SFIZZ_EXPORTED_API const char * sfizz_get_cc_label_text(sfizz_synth_t* synth, int label_index);
/**
* @addtogroup Messaging
* @{
*/
/**
* @brief Client for communicating with the synth engine in either direction
* @since 0.6.0
*/
typedef struct sfizz_client_t sfizz_client_t;
/**
* @brief Create a new messaging client
* @since 0.6.0
*
* @param data The opaque data pointer which is passed to the receiver.
* @return The new client.
*/
SFIZZ_EXPORTED_API sfizz_client_t* sfizz_create_client(void* data);
/**
* @brief Destroy a messaging client
* @since 0.6.0
*
* @param client The client.
*/
SFIZZ_EXPORTED_API void sfizz_delete_client(sfizz_client_t* client);
/**
* @brief Get the client data
* @since 0.6.0
*
* @param client The client.
* @return The client data.
*/
SFIZZ_EXPORTED_API void* sfizz_get_client_data(sfizz_client_t* client);
/**
* @brief Set the function which receives reply messages from the synth engine.
* @since 0.6.0
*
* @param client The client.
* @param receive The pointer to the receiving function.
*/
SFIZZ_EXPORTED_API void sfizz_set_receive_callback(sfizz_client_t* client, sfizz_receive_t* receive);
/**
* @brief Send a message to the synth engine
* @since 0.6.0
*
* @param synth The synth.
* @param client The client sending the message.
* @param delay The delay of the message in the block, in samples.
* @param path The OSC address pattern.
* @param sig The OSC type tag string.
* @param args The OSC arguments, whose number and format is determined the type tag string.
*/
SFIZZ_EXPORTED_API void sfizz_send_message(sfizz_synth_t* synth, sfizz_client_t* client, int delay, const char* path, const char* sig, const sfizz_arg_t* args);
/**
* @brief Set the function which receives broadcast messages from the synth engine.
* @since 0.6.0
*
* @param synth The synth.
* @param broadcast The pointer to the receiving function.
* @param data The opaque data pointer which is passed to the receiver.
*/
SFIZZ_EXPORTED_API void sfizz_set_broadcast_callback(sfizz_synth_t* synth, sfizz_receive_t* broadcast, void* data);
/**
* @}
*/
#ifdef __cplusplus
}
#endif

View file

@ -10,6 +10,7 @@
*/
#pragma once
#include "sfizz_message.h"
#include <string>
#include <utility>
#include <vector>
@ -28,6 +29,7 @@
namespace sfz
{
class Synth;
class Client;
/**
* @brief Main class.
*/
@ -565,7 +567,75 @@ public:
*/
const std::vector<std::pair<uint16_t, std::string>>& getCCLabels() const noexcept;
/**
* @addtogroup Messaging
* @{
*/
private:
struct ClientDeleter {
void operator()(Client *client) const noexcept;
};
public:
using ClientPtr = std::unique_ptr<Client, ClientDeleter>;
/**
* @brief Create a new messaging client
* @since 0.6.0
*
* @param data The opaque data pointer which is passed to the receiver.
* @return The new client.
*/
static ClientPtr createClient(void* data);
/**
* @brief Get the client data
* @since 0.6.0
*
* @param client The client.
* @return The client data.
*/
static void* getClientData(Client& client);
/**
* @brief Set the function which receives reply messages from the synth engine.
* @since 0.6.0
*
* @param client The client.
* @param receive The pointer to the receiving function.
*/
static void setReceiveCallback(Client& client, sfizz_receive_t* receive);
/**
* @brief Send a message to the synth engine
* @since 0.6.0
*
* @param client The client sending the message.
* @param delay The delay of the message in the block, in samples.
* @param path The OSC address pattern.
* @param sig The OSC type tag string.
* @param args The OSC arguments, whose number and format is determined the type tag string.
*/
void sendMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args);
/**
* @brief Set the function which receives broadcast messages from the synth engine.
* @since 0.6.0
*
* @param broadcast The pointer to the receiving function.
* @param data The opaque data pointer which is passed to the receiver.
*/
void setBroadcastCallback(sfizz_receive_t* broadcast, void* data);
/**
* @}
*/
private:
std::unique_ptr<sfz::Synth> synth;
};
using ClientPtr = Sfizz::ClientPtr;
}

367
src/sfizz/Messaging.cpp Normal file
View file

@ -0,0 +1,367 @@
// 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 "Messaging.h"
#include <absl/strings/string_view.h>
#include <algorithm>
#include <type_traits>
#include <cstring>
#ifdef __cplusplus
static_assert(
sizeof(sfizz_arg_t) == sizeof(int64_t) && alignof(sfizz_arg_t) == 8,
"The ABI stability check has failed.");
#endif
template <class T>
static T paddingSize(T count, unsigned align) {
unsigned mask = align - 1;
return (align - (count & mask)) & mask;
};
///
class OSCWriter {
public:
void setOutputBuffer(void* buffer, uint32_t capacity);
uint32_t writeMessage(const char* path, const char* sig, const sfizz_arg_t* args);
private:
uint32_t appendBytes(const void* src, uint32_t count);
uint32_t appendZeros(uint32_t count);
template <class T> uint32_t appendInteger(T integer);
uint32_t appendFloat(float f);
uint32_t appendDouble(float d);
private:
uint8_t* dstBuffer_ = nullptr;
uint32_t dstCapacity_ = 0;
};
class OSCReader {
public:
void setInputBuffer(const void* buffer, uint32_t capacity);
void setAllocationBuffer(void* buffer, uint32_t capacity);
int32_t extractMessage(const char** outPath, const char** outSig, const sfizz_arg_t** outArgs);
private:
template <class T> T* allocate(uint32_t count);
bool extractString(const char*& outStr, uint32_t& outLen);
template <class T> bool extractInteger(T& outValue);
bool extractFloat(float& f);
bool extractDouble(double& d);
private:
const uint8_t* srcBuffer_ = nullptr;
uint32_t srcCapacity_ = 0;
uint8_t* allocBuffer_ = nullptr;
uint32_t allocCapacity_ = 0;
};
///
extern "C" uint32_t sfizz_prepare_message(
void* buffer, uint32_t capacity,
const char* path, const char* sig, const sfizz_arg_t* args)
{
OSCWriter writer;
writer.setOutputBuffer(buffer, capacity);
return writer.writeMessage(path, sig, args);
}
extern "C" int32_t sfizz_extract_message(
const void* srcBuffer, uint32_t srcCapacity,
void* argsBuffer, uint32_t argsCapacity,
const char** outPath, const char** outSig, const sfizz_arg_t** outArgs)
{
OSCReader reader;
reader.setInputBuffer(srcBuffer, srcCapacity);
reader.setAllocationBuffer(argsBuffer, argsCapacity);
return reader.extractMessage(outPath, outSig, outArgs);
}
///
void OSCWriter::setOutputBuffer(void* buffer, uint32_t capacity)
{
dstBuffer_ = reinterpret_cast<uint8_t*>(buffer);
dstCapacity_ = capacity;
}
uint32_t OSCWriter::writeMessage(const char* path, const char* sig, const sfizz_arg_t* args)
{
uint32_t msglen = 0;
// write path, null byte, and 4byte padding
uint32_t pathlen = static_cast<uint32_t>(strlen(path));
msglen += appendBytes(path, pathlen + 1);
msglen += appendZeros(paddingSize(pathlen + 1, 4));
// write comma, signature, null byte, and 4byte padding
uint32_t siglen = static_cast<uint32_t>(strlen(sig));
msglen += appendBytes(",", 1);
msglen += appendBytes(sig, siglen + 1);
msglen += appendZeros(paddingSize(siglen + 2, 4));
// write arguments
for (uint32_t i = 0; i < siglen; ++i) {
switch (sig[i]) {
default:
return 0;
case 'i':
case 'c':
case 'r':
msglen += appendInteger(args[i].i);
break;
case 'm':
msglen += appendBytes(args[i].m, 4);
break;
case 'h':
msglen += appendInteger(args[i].h);
break;
case 'f':
msglen += appendFloat(args[i].f);
break;
case 'd':
msglen += appendDouble(args[i].d);
break;
case 's':
case 'S':
{
size_t len = strlen(args[i].s);
msglen += appendBytes(args[i].s, len + 1);
msglen += appendZeros(paddingSize(len + 1, 4));
}
break;
case 'b':
{
msglen += appendInteger(args[i].b->size);
msglen += appendBytes(args[i].b->data, args[i].b->size);
msglen += appendZeros(paddingSize(args[i].b->size, 4));
}
break;
case 'T':
case 'F':
case 'N':
case 'I':
break;
}
}
return msglen;
}
uint32_t OSCWriter::appendBytes(const void* src, uint32_t count)
{
uint32_t written = std::min(dstCapacity_, count);
memcpy(dstBuffer_, src, written);
dstBuffer_ += written;
dstCapacity_ -= written;
return count;
}
uint32_t OSCWriter::appendZeros(uint32_t count)
{
uint32_t written = std::min(dstCapacity_, count);
memset(dstBuffer_, '\0', written);
dstBuffer_ += written;
dstCapacity_ -= written;
return count;
}
template <class T> uint32_t OSCWriter::appendInteger(T integer)
{
using U = typename std::make_unsigned<T>::type;
const U uinteger = static_cast<U>(integer);
uint8_t data[sizeof(U)];
for (unsigned i = 0; i < sizeof(U); ++i) {
unsigned sh = 8 * (sizeof(U) - 1 - i);
data[i] = (uint8_t)((uinteger >> sh) & 0xff);
}
return appendBytes(data, sizeof(U));
}
uint32_t OSCWriter::appendFloat(float f)
{
union { float f; uint32_t i; } u;
u.f = f;
return appendInteger(u.i);
}
uint32_t OSCWriter::appendDouble(float d)
{
union { double d; uint64_t i; } u;
u.d = d;
return appendInteger(u.i);
}
///
void OSCReader::setInputBuffer(const void* buffer, uint32_t capacity)
{
srcBuffer_ = reinterpret_cast<const uint8_t*>(buffer);
srcCapacity_ = capacity;
}
void OSCReader::setAllocationBuffer(void* buffer, uint32_t capacity)
{
allocBuffer_ = reinterpret_cast<uint8_t*>(buffer);
allocCapacity_ = capacity;
}
int32_t OSCReader::extractMessage(const char** outPath, const char** outSig, const sfizz_arg_t** outArgs)
{
const uint8_t* const srcStart = srcBuffer_;
// read path, null byte
const char* path;
uint32_t pathlen;
if (!extractString(path, pathlen))
return 0;
if (outPath)
*outPath = path;
// read signature, null byte
const char* sig;
uint32_t siglen;
if (!extractString(sig, siglen) || sig[0] != ',')
return 0;
++sig;
--siglen;
if (outSig)
*outSig = sig;
// read arguments
sfizz_arg_t* args = allocate<sfizz_arg_t>(siglen);
if (!args)
return -1;
if (outArgs)
*outArgs = args;
for (uint32_t i = 0, n = siglen; i < n; ++i) {
switch (sig[i]) {
default:
return 0;
case 'i':
case 'c':
case 'r':
if (!extractInteger(args[i].i))
return 0;
break;
case 'm':
if (srcCapacity_ < 4)
return 0;
memcpy(args[i].m, srcBuffer_, 4);
srcBuffer_ += 4;
srcCapacity_ -= 4;
break;
case 'h':
if (!extractInteger(args[i].h))
return 0;
break;
case 'f':
if (!extractFloat(args[i].f))
return 0;
break;
case 'd':
if (!extractDouble(args[i].d))
return 0;
break;
case 's':
case 'S':
{
const char* str;
uint32_t len;
if (!extractString(str, len))
return 0;
args[i].s = str;
}
break;
case 'b':
{
sfizz_blob_t* blob = allocate<sfizz_blob_t>(1);
if (!blob)
return -1;
args[i].b = blob;
uint32_t len = blob->size;
if (!extractInteger(len))
return 0;
uint32_t padlen = len + paddingSize(len, 4);
if (srcCapacity_ < padlen)
return 0;
blob->data = srcBuffer_;
blob->size = len;
srcBuffer_ += padlen;
srcCapacity_ -= padlen;
}
break;
case 'T':
case 'F':
case 'N':
case 'I':
break;
}
}
return srcBuffer_ - srcStart;
}
template <class T> T* OSCReader::allocate(uint32_t count)
{
uintptr_t pad = paddingSize(reinterpret_cast<uintptr_t>(allocBuffer_), alignof(T));
uint32_t size = count * sizeof(T);
if (allocCapacity_ < pad + size)
return nullptr;
void* ptr = allocBuffer_ + pad;
allocBuffer_ += pad + size;
allocCapacity_ -= pad + size;
return reinterpret_cast<T *>(ptr);
}
bool OSCReader::extractString(const char*& outStr, uint32_t& outLen)
{
const char* str = reinterpret_cast<const char*>(srcBuffer_);
uint32_t len = static_cast<uint32_t>(strnlen(str, srcCapacity_));
if (len == srcCapacity_)
return false;
uint32_t padlen = len + 1 + paddingSize(len + 1, 4);
if (padlen > srcCapacity_)
return false;
srcBuffer_ += padlen;
srcCapacity_ -= padlen;
outStr = str;
outLen = len;
return true;
}
template <class T> bool OSCReader::extractInteger(T& outValue)
{
if (srcCapacity_ < sizeof(T))
return false;
using U = typename std::make_unsigned<T>::type;
U value = 0;
const uint8_t* src = reinterpret_cast<const uint8_t*>(srcBuffer_);
for (unsigned i = 0; i < sizeof(T); ++i)
value = (value << 8) | src[i];
srcBuffer_ += sizeof(T);
srcCapacity_ -= sizeof(T);
outValue = static_cast<T>(value);
return true;
};
bool OSCReader::extractFloat(float& f)
{
union { float f; uint32_t i; } u;
if (!extractInteger(u.i))
return false;
f = u.f;
return true;
}
bool OSCReader::extractDouble(double& d)
{
union { double d; uint64_t i; } u;
if (!extractInteger(u.i))
return false;
d = u.d;
return true;
}

31
src/sfizz/Messaging.h Normal file
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
#pragma once
#include "sfizz_message.h"
namespace sfz {
class Client {
public:
explicit Client(void* data) : data_(data) {}
void* getClientData() const { return data_; }
void setReceiveCallback(sfizz_receive_t* receive) { receive_ = receive; }
bool canReceive() const { return receive_ != nullptr; }
void receive(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
private:
void* data_ = nullptr;
sfizz_receive_t* receive_ = nullptr;
};
inline void Client::receive(int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
if (receive_)
receive_(data_, delay, path, sig, args);
}
} // namespace sfz

View file

@ -298,6 +298,10 @@ struct Synth::Impl final: public Parser::Listener {
fs::file_time_type modificationTime_ { };
std::array<float, config::numCCs> defaultCCValues_;
// Messaging
sfizz_receive_t* broadcastReceiver = nullptr;
void* broadcastData = nullptr;
};
Synth::Synth()
@ -1953,6 +1957,13 @@ std::bitset<config::numCCs> Synth::getUsedCCs() const noexcept
return used;
}
void sfz::Synth::setBroadcastCallback(sfizz_receive_t* broadcast, void* data)
{
Impl& impl = *impl_;
impl.broadcastReceiver = broadcast;
impl.broadcastData = data;
}
void Synth::Impl::updateUsedCCsFromRegion(std::bitset<config::numCCs>& usedCCs, const Region& region)
{
updateUsedCCsFromCCMap(usedCCs, region.offsetCC);

View file

@ -8,6 +8,7 @@
#include "AudioSpan.h"
#include "LeakDetector.h"
#include "Resources.h"
#include "Messaging.h"
#include "utility/NumericId.h"
#include "parser/Parser.h"
#include <ghc/fs_std.hpp>
@ -585,6 +586,27 @@ public:
*/
std::bitset<config::numCCs> getUsedCCs() const noexcept;
/**
* @brief Dispatch the incoming message to the synth engine
* @since 0.6.0
*
* @param client The client sending the message.
* @param delay The delay of the message in the block, in samples.
* @param path The OSC address pattern.
* @param sig The OSC type tag string.
* @param args The OSC arguments, whose number and format is determined the type tag string.
*/
void dispatchMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args);
/**
* @brief Set the function which receives broadcast messages from the synth engine.
* @since 0.6.0
*
* @param broadcast The pointer to the receiving function.
* @param data The opaque data pointer which is passed to the receiver.
*/
void setBroadcastCallback(sfizz_receive_t* broadcast, void* data);
private:
struct Impl;
std::unique_ptr<Impl> impl_;

View file

@ -0,0 +1,81 @@
// 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 "Synth.h"
#include "StringViewHelpers.h"
#include <absl/strings/ascii.h>
#include <cstring>
namespace sfz {
static constexpr unsigned maxIndices = 8;
static bool extractMessage(const char* pattern, const char* path, unsigned* indices);
static uint64_t hashMessagePath(const char* path, const char* sig);
void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
unsigned indices[maxIndices];
switch (hashMessagePath(path, sig)) {
#define MATCH(p, s) case hash(p "," s): \
if (extractMessage(p, path, indices) && !strcmp(sig, s))
MATCH("/hello", "") {
client.receive(delay, "/hello", "", nullptr);
break;
}
// TODO...
}
}
static bool extractMessage(const char* pattern, const char* path, unsigned* indices)
{
unsigned nthIndex = 0;
while (const char *endp = strchr(pattern, '&')) {
if (nthIndex == maxIndices)
return false;
size_t length = endp - pattern;
if (strncmp(pattern, path, length))
return false;
pattern += length;
path += length;
length = 0;
while (absl::ascii_isdigit(path[length]))
++length;
if (!absl::SimpleAtoi(absl::string_view(path, length), &indices[nthIndex++]))
return false;
pattern += 1;
path += length;
}
return !strcmp(path, pattern);
}
static uint64_t hashMessagePath(const char* path, const char* sig)
{
uint64_t h = Fnv1aBasis;
while (unsigned char c = *path++) {
if (!absl::ascii_isdigit(c))
h = hashByte(c, h);
else {
h = hashByte('&', h);
while (absl::ascii_isdigit(*path))
++path;
}
}
h = hashByte(',');
while (unsigned char c = *sig++)
h = hashByte(c, h);
return h;
}
} // namespace sfz

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "Synth.h"
#include "Messaging.h"
#include "sfizz.hpp"
#include "absl/memory/memory.h"
@ -305,3 +306,33 @@ const std::vector<std::pair<uint16_t, std::string>>& sfz::Sfizz::getCCLabels() c
{
return synth->getCCLabels();
}
void sfz::Sfizz::ClientDeleter::operator()(Client *client) const noexcept
{
delete client;
}
auto sfz::Sfizz::createClient(void* data) -> ClientPtr
{
return ClientPtr(new Client(data));
}
void* sfz::Sfizz::getClientData(Client& client)
{
return client.getClientData();
}
void sfz::Sfizz::setReceiveCallback(Client& client, sfizz_receive_t* receive)
{
client.setReceiveCallback(receive);
}
void sfz::Sfizz::sendMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
synth->dispatchMessage(client, delay, path, sig, args);
}
void sfz::Sfizz::setBroadcastCallback(sfizz_receive_t* broadcast, void* data)
{
synth->setBroadcastCallback(broadcast, data);
}

View file

@ -7,6 +7,7 @@
#include "Config.h"
#include "Macros.h"
#include "Synth.h"
#include "Messaging.h"
#include "sfizz.h"
#include <limits>
@ -429,6 +430,38 @@ const char * sfizz_get_cc_label_text(sfizz_synth_t* synth, int label_index)
return ccLabels[label_index].second.c_str();
}
sfizz_client_t* sfizz_create_client(void* data)
{
return reinterpret_cast<sfizz_client_t*>(new sfz::Client(data));
}
void sfizz_delete_client(sfizz_client_t* client)
{
delete reinterpret_cast<sfz::Client*>(client);
}
void* sfizz_get_client_data(sfizz_client_t* client)
{
return reinterpret_cast<sfz::Client*>(client)->getClientData();
}
void sfizz_set_receive_callback(sfizz_client_t* client, sfizz_receive_t* receive)
{
reinterpret_cast<sfz::Client*>(client)->setReceiveCallback(receive);
}
void sfizz_send_message(sfizz_synth_t* synth, sfizz_client_t* client, int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
auto* self = reinterpret_cast<sfz::Synth*>(synth);
self->dispatchMessage(*reinterpret_cast<sfz::Client*>(client), delay, path, sig, args);
}
void sfizz_set_broadcast_callback(sfizz_synth_t* synth, sfizz_receive_t* broadcast, void* data)
{
auto* self = reinterpret_cast<sfz::Synth*>(synth);
self->setBroadcastCallback(broadcast, data);
}
#ifdef __cplusplus
}
#endif

91
src/sfizz_message.h Normal file
View file

@ -0,0 +1,91 @@
// 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 <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @addtogroup Messaging
* @{
*/
/**
* @brief Representation of a binary blob in OSC format
* @since 0.6.0
*/
typedef struct {
const uint8_t* data;
uint32_t size;
} sfizz_blob_t;
/**
* @brief Representation of an argument of variant type in OSC format
* @since 0.6.0
*/
typedef union {
int32_t i;
int64_t h;
float f;
double d;
const char* s;
const sfizz_blob_t* b;
uint8_t m[4];
} sfizz_arg_t;
/**
* @brief Generic message receiving function
* @since 0.6.0
*/
typedef void (sfizz_receive_t)(void* data, int delay, const char* path, const char* sig, const sfizz_arg_t* args);
/**
* @brief Convert the message to OSC using the provided output buffer
* @since 0.6.0
*
* @param buffer The output buffer
* @param capacity The capacity of the buffer
* @param path The path
* @param sig The signature
* @param args The arguments
* @return The size necessary to store the converted message in
* entirety, <= capacity if the written message is valid.
*/
uint32_t sfizz_prepare_message(
void* buffer, uint32_t capacity,
const char* path, const char* sig, const sfizz_arg_t* args);
/**
* @brief Extract the contents of an OSC message
* @since 0.6.0
*
* @param srcBuffer The data of the OSC message
* @param srcCapacity The size of the OSC message
* @param argsBuffer A buffer where the function can allocate the arguments
* @param argsCapacity The capacity of the argument buffer
* @param outPath A pointer to the variable which receives the path
* @param outSig A pointer to the variable which receives the signature
* @param outArgs A pointer to the variable which receives the arguments
* @return On success, this is the number of bytes read.
* On failure, it is 0 if the OSC message is invalid,
* -1 if there was not enough buffer for the arguments.
*/
int32_t sfizz_extract_message(
const void* srcBuffer, uint32_t srcCapacity,
void* argsBuffer, uint32_t argsCapacity,
const char** outPath, const char** outSig, const sfizz_arg_t** outArgs);
/**
* @}
*/
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -41,6 +41,7 @@ set(SFIZZ_TEST_SOURCES
ConcurrencyT.cpp
ModulationsT.cpp
LFOT.cpp
MessagingT.cpp
DataHelpers.h
DataHelpers.cpp
)

95
tests/MessagingT.cpp Normal file
View file

@ -0,0 +1,95 @@
// 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/Messaging.h"
#include "catch2/catch.hpp"
#include <absl/types/span.h>
#include <cstring>
TEST_CASE("[Messaging] OSC message creation")
{
// http://opensoundcontrol.org/spec-1_0-examples
{
const char* path = "/oscillator/4/frequency";
const char* sig = "f";
sfizz_arg_t args[1];
args[0].f = 440.0f;
const uint8_t expected[] = {
0x2f, /* / */ 0x6f, /* o */ 0x73, /* s */ 0x63, /* c */
0x69, /* i */ 0x6c, /* l */ 0x6c, /* l */ 0x61, /* a */
0x74, /* t */ 0x6f, /* o */ 0x72, /* r */ 0x2f, /* / */
0x34, /* 4 */ 0x2f, /* / */ 0x66, /* f */ 0x72, /* r */
0x65, /* e */ 0x71, /* q */ 0x75, /* u */ 0x65, /* e */
0x6e, /* n */ 0x63, /* c */ 0x79, /* y */ 0x00,
0x2c, /* , */ 0x66, /* f */ 0x00, 0x00,
0x43, 0xdc, 0x00, 0x00,
};
uint32_t size = sfizz_prepare_message(nullptr, 0, path, sig, args);
REQUIRE(size == sizeof(expected));
uint8_t actual[sizeof(expected)];
size = sfizz_prepare_message(actual, sizeof(actual), path, sig, args);
REQUIRE(size == sizeof(expected));
REQUIRE(absl::MakeSpan(actual) == absl::MakeSpan(expected));
const char* path2;
const char* sig2;
const sfizz_arg_t* args2;
uint8_t buffer[256];
REQUIRE(sfizz_extract_message(actual, sizeof(actual), buffer, sizeof(buffer), &path2, &sig2, &args2) > 0);
REQUIRE(!strcmp(path, path2));
REQUIRE(!strcmp(sig, sig2));
REQUIRE(args[0].f == 440.0f);
}
{
const char* path = "/foo";
const char* sig = "iisff";
sfizz_arg_t args[5];
args[0].i = 1000;
args[1].i = -1;
args[2].s = "hello";
args[3].f = 1.234f;
args[4].f = 5.678f;
const uint8_t expected[] = {
0x2f, /* / */ 0x66, /* f */ 0x6f, /* o */ 0x6f, /* o */
0x00, 0x00, 0x00, 0x00,
0x2c, /* , */ 0x69, /* i */ 0x69, /* i */ 0x73, /* s */
0x66, /* f */ 0x66, /* f */ 0x00, 0x00,
0x00, 0x00, 0x03, 0xe8,
0xff, 0xff, 0xff, 0xff,
0x68, 0x65, 0x6c, 0x6c,
0x6f, 0x00, 0x00, 0x00,
0x3f, 0x9d, 0xf3, 0xb6,
0x40, 0xb5, 0xb2, 0x2d,
};
uint32_t size = sfizz_prepare_message(nullptr, 0, path, sig, args);
REQUIRE(size == sizeof(expected));
uint8_t actual[sizeof(expected)];
size = sfizz_prepare_message(actual, sizeof(actual), path, sig, args);
REQUIRE(size == sizeof(expected));
REQUIRE(absl::MakeSpan(actual) == absl::MakeSpan(expected));
const char* path2;
const char* sig2;
const sfizz_arg_t* args2;
uint8_t buffer[256];
REQUIRE(sfizz_extract_message(actual, sizeof(actual), buffer, sizeof(buffer), &path2, &sig2, &args2) > 0);
REQUIRE(!strcmp(path, path2));
REQUIRE(!strcmp(sig, sig2));
REQUIRE(args[0].i == 1000);
REQUIRE(args[1].i == -1);
REQUIRE(!strcmp(args[2].s, "hello"));
REQUIRE(args[3].f == 1.234f);
REQUIRE(args[4].f == 5.678f);
}
}

View file

@ -293,6 +293,24 @@ tresult SfizzVstController::notify(Vst::IMessage* message)
_playState = *static_cast<const SfizzPlayState*>(data);
}
else if (!strcmp(id, "ReceivedMessage")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Message", data, size);
if (result != kResultTrue)
return result;
const char* path;
const char* sig;
const sfizz_arg_t* args;
uint8_t buffer[1024];
if (sfizz_extract_message(data, size, buffer, sizeof(buffer), &path, &sig, &args) > 0) {
for (MessageListener* listener : _messageListeners)
listener->onMessageReceived(path, sig, args);
}
}
for (StateListener* listener : _stateListeners)
listener->onStateChanged();
@ -312,6 +330,18 @@ void SfizzVstController::removeSfizzStateListener(StateListener* listener)
_stateListeners.erase(it);
}
void SfizzVstController::addSfizzMessageListener(MessageListener* listener)
{
_messageListeners.push_back(listener);
}
void SfizzVstController::removeSfizzMessageListener(MessageListener* listener)
{
auto it = std::find(_messageListeners.begin(), _messageListeners.end(), listener);
if (it != _messageListeners.end())
_messageListeners.erase(it);
}
FUnknown* SfizzVstController::createInstance(void*)
{
return static_cast<Vst::IEditController*>(new SfizzVstController);

View file

@ -9,6 +9,7 @@
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "public.sdk/source/vst/vstparameters.h"
#include "vstgui/plugin-bindings/vst3editor.h"
#include <sfizz_message.h>
class SfizzVstState;
using namespace Steinberg;
@ -49,6 +50,9 @@ public:
struct StateListener {
virtual void onStateChanged() = 0;
};
struct MessageListener {
virtual void onMessageReceived(const char* path, const char* sig, const sfizz_arg_t* args) = 0;
};
const SfizzVstState& getSfizzState() const { return _state; }
SfizzVstState& getSfizzState() { return _state; }
@ -62,6 +66,9 @@ public:
void addSfizzStateListener(StateListener* listener);
void removeSfizzStateListener(StateListener* listener);
void addSfizzMessageListener(MessageListener* listener);
void removeSfizzMessageListener(MessageListener* listener);
///
static FUnknown* createInstance(void*);
@ -72,4 +79,5 @@ private:
SfizzUiState _uiState;
SfizzPlayState _playState {};
std::vector<StateListener*> _stateListeners;
std::vector<MessageListener*> _messageListeners;
};

View file

@ -16,15 +16,22 @@ using namespace VSTGUI;
static ViewRect sfizzUiViewRect { 0, 0, Editor::viewWidth, Editor::viewHeight };
enum {
kOscTempSize = 8192,
};
SfizzVstEditor::SfizzVstEditor(void *controller)
: VSTGUIEditor(controller, &sfizzUiViewRect)
: VSTGUIEditor(controller, &sfizzUiViewRect),
oscTemp_(new uint8_t[kOscTempSize])
{
getController()->addSfizzStateListener(this);
getController()->addSfizzMessageListener(this);
}
SfizzVstEditor::~SfizzVstEditor()
{
getController()->removeSfizzStateListener(this);
getController()->removeSfizzMessageListener(this);
}
bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& platformType)
@ -105,6 +112,11 @@ void SfizzVstEditor::onStateChanged()
updateStateDisplay();
}
void SfizzVstEditor::onMessageReceived(const char* path, const char* sig, const sfizz_arg_t* args)
{
uiReceiveMessage(path, sig, args);
}
///
void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
{
@ -192,6 +204,26 @@ void SfizzVstEditor::uiSendMIDI(const uint8_t* data, uint32_t len)
ctl->sendMessage(msg);
}
void SfizzVstEditor::uiSendMessage(const char* path, const char* sig, const sfizz_arg_t* args)
{
SfizzVstController* ctl = getController();
Steinberg::OPtr<Vst::IMessage> msg { ctl->allocateMessage() };
if (!msg) {
fprintf(stderr, "[Sfizz] UI could not allocate message\n");
return;
}
uint8_t* oscTemp = oscTemp_.get();
uint32_t oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args);
if (oscSize <= kOscTempSize) {
msg->setMessageID("OscMessage");
Vst::IAttributeList* attr = msg->getAttributes();
attr->setBinary("Data", oscTemp, oscSize);
ctl->sendMessage(msg);
}
}
///
void SfizzVstEditor::loadSfzFile(const std::string& filePath)
{

View file

@ -16,7 +16,10 @@ namespace VSTGUI { class RunLoop; }
using namespace Steinberg;
using namespace VSTGUI;
class SfizzVstEditor : public Vst::VSTGUIEditor, public SfizzVstController::StateListener, public EditorController {
class SfizzVstEditor : public Vst::VSTGUIEditor,
public SfizzVstController::StateListener,
public SfizzVstController::MessageListener,
public EditorController {
public:
explicit SfizzVstEditor(void *controller);
~SfizzVstEditor();
@ -35,12 +38,16 @@ public:
// SfizzVstController::StateListener
void onStateChanged() override;
// SfizzVstController::MessageListener
void onMessageReceived(const char* path, const char* sig, const sfizz_arg_t* args) override;
protected:
// EditorController
void uiSendValue(EditId id, const EditValue& v) override;
void uiBeginSend(EditId id) override;
void uiEndSend(EditId id) override;
void uiSendMIDI(const uint8_t* data, uint32_t len) override;
void uiSendMessage(const char* path, const char* sig, const sfizz_arg_t* args) override;
private:
void loadSfzFile(const std::string& filePath);
@ -55,4 +62,7 @@ private:
#if !defined(__APPLE__) && !defined(_WIN32)
SharedPointer<RunLoop> _runLoop;
#endif
// messaging
std::unique_ptr<uint8[]> oscTemp_;
};

View file

@ -24,10 +24,17 @@ static const char defaultSfzText[] =
"<region>sample=*sine" "\n"
"ampeg_attack=0.02 ampeg_release=0.1" "\n";
enum { kMidiEventMaximumSize = 4 };
enum {
kMidiEventMaximumSize = 4,
kOscTempSize = 8192,
};
static const char* kRingIdMidi = "Mid";
static const char* kRingIdOsc = "Osc";
SfizzVstProcessor::SfizzVstProcessor()
: _fifoToWorker(64 * 1024), _fifoMidiFromUi(64 * 1024)
: _fifoToWorker(64 * 1024), _fifoMessageFromUi(64 * 1024),
_oscTemp(new uint8_t[kOscTempSize])
{
setControllerClass(SfizzVstController::cid);
@ -56,6 +63,16 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
fprintf(stderr, "[sfizz] new synth\n");
_synth.reset(new sfz::Sfizz);
auto onMessage = +[](void* data, int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
auto *self = reinterpret_cast<SfizzVstProcessor*>(data);
self->receiveMessage(delay, path, sig, args);
};
_client = _synth->createClient(this);
_synth->setReceiveCallback(*_client, onMessage);
_synth->setBroadcastCallback(onMessage, this);
_currentStretchedTuning = 0.0;
loadSfzFileOrDefault(*_synth, {});
@ -210,7 +227,7 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
else
synth.disableFreeWheeling();
processMidiFromUi();
processMessagesFromUi();
if (Vst::IParameterChanges* pc = data.inputParameterChanges)
processControllerChanges(*pc);
@ -404,36 +421,61 @@ void SfizzVstProcessor::processEvents(Vst::IEventList& events)
}
}
void SfizzVstProcessor::processMidiFromUi()
void SfizzVstProcessor::processMessagesFromUi()
{
sfz::Sfizz& synth = *_synth;
sfz::Client& client = *_client;
Ring_Buffer& fifo = _fifoMessageFromUi;
RTMessage header;
for (uint32 size = 0; _fifoMidiFromUi.peek(size) &&
_fifoMidiFromUi.size_used() >= sizeof(size) + size; ) {
_fifoMidiFromUi.discard(sizeof(size));
while (fifo.peek(header) && fifo.size_used() >= sizeof(header) + header.size) {
fifo.discard(sizeof(header));
if (size > kMidiEventMaximumSize) {
_fifoMidiFromUi.discard(size);
continue;
if (header.type == kRingIdMidi) {
if (header.size > kMidiEventMaximumSize) {
fifo.discard(header.size);
continue;
}
uint8_t data[kMidiEventMaximumSize] = {};
fifo.get(data, header.size);
// interpret the MIDI message
switch (data[0] & 0xf0) {
case 0x80:
synth.noteOff(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0x90:
synth.noteOn(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0xb0:
synth.cc(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0xe0:
synth.pitchWheel(0, (data[2] << 7) + data[1] - 8192);
break;
}
}
else if (header.type == kRingIdOsc) {
uint8_t* oscTemp = _oscTemp.get();
uint8_t data[kMidiEventMaximumSize] = {};
_fifoMidiFromUi.get(data, size);
if (header.size > kOscTempSize) {
fifo.discard(header.size);
continue;
}
// interpret the MIDI message
switch (data[0] & 0xf0) {
case 0x80:
synth.noteOff(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0x90:
synth.noteOn(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0xb0:
synth.cc(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0xe0:
synth.pitchWheel(0, (data[2] << 7) + data[1] - 8192);
break;
fifo.get(oscTemp, header.size);
const char* path;
const char* sig;
const sfizz_arg_t* args;
uint8_t buffer[1024];
if (sfizz_extract_message(oscTemp, header.size, buffer, sizeof(buffer), &path, &sig, &args) > 0)
synth.sendMessage(client, 0, path, sig, args);
}
else {
assert(false);
return;
}
}
}
@ -494,12 +536,14 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Data", data, size);
if (size < kMidiEventMaximumSize) {
if (_fifoMidiFromUi.size_free() >= sizeof(size) + size) {
_fifoMidiFromUi.put(size);
_fifoMidiFromUi.put(reinterpret_cast<const uint8_t*>(data), size);
}
}
if (size < kMidiEventMaximumSize)
writeMessage(_fifoMessageFromUi, kRingIdMidi, data, size);
}
else if (!std::strcmp(id, "OscMessage")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Data", data, size);
writeMessage(_fifoMessageFromUi, kRingIdOsc, data, size);
}
return result;
@ -510,6 +554,14 @@ FUnknown* SfizzVstProcessor::createInstance(void*)
return static_cast<Vst::IAudioProcessor*>(new SfizzVstProcessor);
}
void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
uint8_t* oscTemp = _oscTemp.get();
uint32_t oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args);
if (oscSize <= kOscTempSize)
writeWorkerMessage("ReceiveMessage", oscTemp, oscSize);
}
void SfizzVstProcessor::loadSfzFileOrDefault(sfz::Sfizz& synth, const std::string& filePath)
{
if (!filePath.empty())
@ -563,6 +615,12 @@ void SfizzVstProcessor::doBackgroundWork()
notification->getAttributes()->setBinary("PlayState", &playState, sizeof(playState));
sendMessage(notification);
}
else if (!std::strcmp(id, "ReceiveMessage")) {
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("ReceivedMessage");
notification->getAttributes()->setBinary("Message", msg->payload<uint8_t>(), msg->size);
sendMessage(notification);
}
}
}
@ -585,16 +643,7 @@ void SfizzVstProcessor::stopBackgroundWork()
bool SfizzVstProcessor::writeWorkerMessage(const char* type, const void* data, uintptr_t size)
{
RTMessage header;
header.type = type;
header.size = size;
if (_fifoToWorker.size_free() < sizeof(header) + size)
return false;
_fifoToWorker.put(header);
_fifoToWorker.put(static_cast<const uint8*>(data), size);
return true;
return writeMessage(_fifoToWorker, type, data, size);
}
SfizzVstProcessor::RTMessagePtr SfizzVstProcessor::readWorkerMessage()
@ -631,6 +680,20 @@ bool SfizzVstProcessor::discardWorkerMessage()
return true;
}
bool SfizzVstProcessor::writeMessage(Ring_Buffer& fifo, const char* type, const void* data, uintptr_t size)
{
RTMessage header;
header.type = type;
header.size = size;
if (fifo.size_free() < sizeof(header) + size)
return false;
fifo.put(header);
fifo.put(static_cast<const uint8*>(data), size);
return true;
}
/*
Note(jpc) Generated at random with uuidgen.
Can't find docs on it... maybe it's to register somewhere?

View file

@ -35,7 +35,7 @@ public:
void processParameterChanges(Vst::IParameterChanges& pc);
void processControllerChanges(Vst::IParameterChanges& pc);
void processEvents(Vst::IEventList& events);
void processMidiFromUi();
void processMessagesFromUi();
static int convertVelocityFromFloat(float x);
tresult PLUGIN_API notify(Vst::IMessage* message) override;
@ -51,6 +51,11 @@ private:
SfizzVstState _state;
float _currentStretchedTuning = 0;
// client
sfz::ClientPtr _client;
std::unique_ptr<uint8_t[]> _oscTemp;
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);
@ -59,7 +64,7 @@ private:
volatile bool _workRunning = false;
Ring_Buffer _fifoToWorker;
RTSemaphore _semaToWorker;
Ring_Buffer _fifoMidiFromUi;
Ring_Buffer _fifoMessageFromUi;
std::mutex _processMutex;
// file modification periodic checker
@ -95,6 +100,9 @@ private:
// reader
RTMessagePtr readWorkerMessage();
bool discardWorkerMessage();
// generic
static bool writeMessage(Ring_Buffer& fifo, const char* type, const void* data, uintptr_t size);
};
//------------------------------------------------------------------------------