Add setters
This commit is contained in:
parent
ad9c857513
commit
3d7bcb4968
20 changed files with 3294 additions and 4039 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -16,3 +16,6 @@
|
||||||
[submodule "external/st_audiofile/thirdparty/wavpack"]
|
[submodule "external/st_audiofile/thirdparty/wavpack"]
|
||||||
path = external/st_audiofile/thirdparty/wavpack
|
path = external/st_audiofile/thirdparty/wavpack
|
||||||
url = https://github.com/dbry/WavPack.git
|
url = https://github.com/dbry/WavPack.git
|
||||||
|
[submodule "external/invoke.hpp"]
|
||||||
|
path = external/invoke.hpp
|
||||||
|
url = https://github.com/BlackMATov/invoke.hpp.git
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ endif()
|
||||||
|
|
||||||
# Set macOS compatibility level
|
# Set macOS compatibility level
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# If using C++17, check if aligned-new has runtime support on the platform;
|
# If using C++17, check if aligned-new has runtime support on the platform;
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,11 @@ else()
|
||||||
sfizz_add_vendor_abseil()
|
sfizz_add_vendor_abseil()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# C++14 std::invoke
|
||||||
|
# add_subdirectory("external/invoke.hpp")
|
||||||
|
add_library(invoke_hpp INTERFACE)
|
||||||
|
target_include_directories(invoke_hpp INTERFACE "external/invoke.hpp/headers")
|
||||||
|
|
||||||
# The jsl utility library for C++
|
# The jsl utility library for C++
|
||||||
add_library(sfizz_jsl INTERFACE)
|
add_library(sfizz_jsl INTERFACE)
|
||||||
add_library(sfizz::jsl ALIAS sfizz_jsl)
|
add_library(sfizz::jsl ALIAS sfizz_jsl)
|
||||||
|
|
|
||||||
10
common.mk
10
common.mk
|
|
@ -243,6 +243,10 @@ SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/atomic_queue/include
|
||||||
|
|
||||||
SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/filesystem/include
|
SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/filesystem/include
|
||||||
|
|
||||||
|
# invoke.hpp dependency
|
||||||
|
|
||||||
|
SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/invoke.hpp/headers
|
||||||
|
|
||||||
### Abseil dependency
|
### Abseil dependency
|
||||||
|
|
||||||
SFIZZ_C_FLAGS += -I$(SFIZZ_DIR)/external/abseil-cpp
|
SFIZZ_C_FLAGS += -I$(SFIZZ_DIR)/external/abseil-cpp
|
||||||
|
|
@ -293,7 +297,11 @@ SFIZZ_SOURCES += \
|
||||||
external/abseil-cpp/absl/synchronization/blocking_counter.cc \
|
external/abseil-cpp/absl/synchronization/blocking_counter.cc \
|
||||||
external/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc \
|
external/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc \
|
||||||
external/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc \
|
external/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc \
|
||||||
external/abseil-cpp/absl/synchronization/internal/waiter.cc \
|
external/abseil-cpp/absl/synchronization/internal/waiter_base.cc \
|
||||||
|
external/abseil-cpp/absl/synchronization/internal/win32_waiter.cc \
|
||||||
|
external/abseil-cpp/absl/synchronization/internal/stdcpp_waiter.cc \
|
||||||
|
external/abseil-cpp/absl/synchronization/internal/sem_waiter.cc \
|
||||||
|
external/abseil-cpp/absl/synchronization/internal/pthread_waiter.cc \
|
||||||
external/abseil-cpp/absl/synchronization/notification.cc \
|
external/abseil-cpp/absl/synchronization/notification.cc \
|
||||||
external/abseil-cpp/absl/synchronization/mutex.cc
|
external/abseil-cpp/absl/synchronization/mutex.cc
|
||||||
# absl::graphcycles_internal
|
# absl::graphcycles_internal
|
||||||
|
|
|
||||||
2
external/abseil-cpp
vendored
2
external/abseil-cpp
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit c2435f8342c2d0ed8101cb43adfd605fdc52dca2
|
Subproject commit ad73c6dc1a253203c1c8b529cda18f2138d49df0
|
||||||
1
external/invoke.hpp
vendored
Submodule
1
external/invoke.hpp
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 2c1eabc2e20ab02961f95c704ff0c0818671ddd1
|
||||||
|
|
@ -295,7 +295,7 @@ target_sources(sfizz_internal PRIVATE ${SFIZZ_HEADERS} ${SFIZZ_SOURCES} ${FAUST_
|
||||||
target_include_directories(sfizz_internal PUBLIC "." "sfizz")
|
target_include_directories(sfizz_internal PUBLIC "." "sfizz")
|
||||||
target_link_libraries(sfizz_internal
|
target_link_libraries(sfizz_internal
|
||||||
PUBLIC absl::strings absl::span sfizz::filesystem sfizz::atomic_queue sfizz::spin_mutex sfizz::bit_array sfizz::simde sfizz::hiir sfizz::jsl
|
PUBLIC absl::strings absl::span sfizz::filesystem sfizz::atomic_queue sfizz::spin_mutex sfizz::bit_array sfizz::simde sfizz::hiir sfizz::jsl
|
||||||
PRIVATE sfizz::parser sfizz::messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz::pugixml sfizz::spline sfizz::tunings sfizz::kissfft sfizz::cephes sfizz::cpuid sfizz::threadpool sfizz::atomic)
|
PRIVATE sfizz::parser sfizz::messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz::pugixml sfizz::spline sfizz::tunings sfizz::kissfft sfizz::cephes sfizz::cpuid sfizz::threadpool sfizz::atomic invoke_hpp)
|
||||||
if(SFIZZ_USE_SNDFILE)
|
if(SFIZZ_USE_SNDFILE)
|
||||||
target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1")
|
target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1")
|
||||||
target_link_libraries(sfizz_internal PUBLIC st_audiofile)
|
target_link_libraries(sfizz_internal PUBLIC st_audiofile)
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ FloatSpec loBipolar { -1.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
||||||
FloatSpec hiBipolar { 1.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
FloatSpec hiBipolar { 1.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
||||||
UInt16Spec ccNumber { 0, {0, config::numCCs}, 0 };
|
UInt16Spec ccNumber { 0, {0, config::numCCs}, 0 };
|
||||||
UInt16Spec smoothCC { 0, {0, 100}, kPermissiveUpperBound };
|
UInt16Spec smoothCC { 0, {0, 100}, kPermissiveUpperBound };
|
||||||
|
FloatSpec stepCC { 0.0f, {0.0f, 127.0f}, kPermissiveUpperBound };
|
||||||
UInt8Spec curveCC { 0, {0, 255}, 0 };
|
UInt8Spec curveCC { 0, {0, 255}, 0 };
|
||||||
UInt8Spec sustainCC { 64, {0, 127}, 0 };
|
UInt8Spec sustainCC { 64, {0, 127}, 0 };
|
||||||
UInt8Spec sostenutoCC { 66, {0, 127}, 0 };
|
UInt8Spec sostenutoCC { 66, {0, 127}, 0 };
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> hiPolyAftertouch;
|
extern const OpcodeSpec<float> hiPolyAftertouch;
|
||||||
extern const OpcodeSpec<uint16_t> ccNumber;
|
extern const OpcodeSpec<uint16_t> ccNumber;
|
||||||
extern const OpcodeSpec<uint8_t> curveCC;
|
extern const OpcodeSpec<uint8_t> curveCC;
|
||||||
|
extern const OpcodeSpec<float> stepCC;
|
||||||
extern const OpcodeSpec<uint16_t> smoothCC;
|
extern const OpcodeSpec<uint16_t> smoothCC;
|
||||||
extern const OpcodeSpec<uint8_t> sustainCC;
|
extern const OpcodeSpec<uint8_t> sustainCC;
|
||||||
extern const OpcodeSpec<uint8_t> sostenutoCC;
|
extern const OpcodeSpec<uint8_t> sostenutoCC;
|
||||||
|
|
|
||||||
|
|
@ -1757,10 +1757,7 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
||||||
p.curve = opcode.read(Default::curveCC);
|
p.curve = opcode.read(Default::curveCC);
|
||||||
break;
|
break;
|
||||||
case kOpcodeStepCcN:
|
case kOpcodeStepCcN:
|
||||||
{
|
p.step = spec.normalizeInput(opcode.read(Default::stepCC));
|
||||||
const OpcodeSpec<float> stepCC { 0.0f, {}, kPermissiveBounds };
|
|
||||||
p.step = spec.normalizeInput(opcode.read(stepCC));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case kOpcodeSmoothCcN:
|
case kOpcodeSmoothCcN:
|
||||||
p.smooth = opcode.read(Default::smoothCC);
|
p.smooth = opcode.read(Default::smoothCC);
|
||||||
|
|
|
||||||
|
|
@ -290,8 +290,8 @@ struct Region {
|
||||||
VelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel
|
VelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel
|
||||||
bool checkSustain { Default::checkSustain }; // sustain_sw
|
bool checkSustain { Default::checkSustain }; // sustain_sw
|
||||||
bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw
|
bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw
|
||||||
uint16_t sustainCC { Default::sustainCC }; // sustain_cc
|
uint8_t sustainCC { Default::sustainCC }; // sustain_cc
|
||||||
uint16_t sostenutoCC { Default::sostenutoCC }; // sustain_cc
|
uint8_t sostenutoCC { Default::sostenutoCC }; // sostenuto_cc
|
||||||
float sustainThreshold { Default::sustainThreshold }; // sustain_cc
|
float sustainThreshold { Default::sustainThreshold }; // sustain_cc
|
||||||
float sostenutoThreshold { Default::sostenutoThreshold }; // sustain_cc
|
float sostenutoThreshold { Default::sostenutoThreshold }; // sustain_cc
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,20 +285,19 @@ inline CXX14_CONSTEXPR bool ccModulationIsPerVoice(int cc) {
|
||||||
namespace literals {
|
namespace literals {
|
||||||
inline float operator""_norm(unsigned long long int value)
|
inline float operator""_norm(unsigned long long int value)
|
||||||
{
|
{
|
||||||
if (value > 127)
|
return static_cast<float>(value) / 127.0f;
|
||||||
value = 127;
|
|
||||||
|
|
||||||
return normalize7Bits(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float operator""_norm(long double value)
|
inline float operator""_norm(long double value)
|
||||||
{
|
{
|
||||||
if (value < 0)
|
return static_cast<float>(value) / 127.0f;
|
||||||
value = 0;
|
}
|
||||||
if (value > 127)
|
inline float operator""_bend(unsigned long long int value)
|
||||||
value = 127;
|
{
|
||||||
|
return static_cast<float>(value) / 8191.0f;
|
||||||
return normalize7Bits(value);
|
}
|
||||||
|
inline float operator""_bend(long double value)
|
||||||
|
{
|
||||||
|
return static_cast<float>(value) / 8191.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include "parser/Parser.h"
|
#include "parser/Parser.h"
|
||||||
#include <absl/algorithm/container.h>
|
#include <absl/algorithm/container.h>
|
||||||
#include <absl/memory/memory.h>
|
#include <absl/memory/memory.h>
|
||||||
|
#include <absl/strings/str_cat.h>
|
||||||
#include <absl/strings/str_replace.h>
|
#include <absl/strings/str_replace.h>
|
||||||
#include <absl/types/optional.h>
|
#include <absl/types/optional.h>
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
708
src/sfizz/SynthMessagingHelper.hpp
Normal file
708
src/sfizz/SynthMessagingHelper.hpp
Normal file
|
|
@ -0,0 +1,708 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CCMap.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Defaults.h"
|
||||||
|
#include "EQDescription.h"
|
||||||
|
#include "FileId.h"
|
||||||
|
#include "FilterDescription.h"
|
||||||
|
#include "FlexEGDescription.h"
|
||||||
|
#include "LFOCommon.h"
|
||||||
|
#include "LFODescription.h"
|
||||||
|
#include "Opcode.h"
|
||||||
|
#include "Range.h"
|
||||||
|
#include "SfzFilter.h"
|
||||||
|
#include "SfzHelpers.h"
|
||||||
|
#include "SynthPrivate.h"
|
||||||
|
#include "FilePool.h"
|
||||||
|
#include "Curve.h"
|
||||||
|
#include "MidiState.h"
|
||||||
|
#include "SynthConfig.h"
|
||||||
|
#include "TriggerEvent.h"
|
||||||
|
#include "SynthPrivate.h"
|
||||||
|
#include "utility/Size.h"
|
||||||
|
#include <type_traits>
|
||||||
|
#include <invoke.hpp/invoke.hpp>
|
||||||
|
|
||||||
|
namespace inv = invoke_hpp;
|
||||||
|
|
||||||
|
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(',', h);
|
||||||
|
while (unsigned char c = *sig++)
|
||||||
|
h = hashByte(c, h);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using IntegralNotBool = std::enable_if_t<std::is_integral<T>::value && !std::is_same<T, bool>::value>;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using BoolOrNotIntegral = std::enable_if_t<!std::is_integral<T>::value || std::is_same<T, bool>::value>;
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
static constexpr unsigned maxIndices = 8;
|
||||||
|
|
||||||
|
class MessagingHelper {
|
||||||
|
public:
|
||||||
|
MessagingHelper(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args, Synth::Impl& impl)
|
||||||
|
: client(client)
|
||||||
|
, impl(impl)
|
||||||
|
, delay(delay)
|
||||||
|
, path(path)
|
||||||
|
, sig(sig)
|
||||||
|
, request_args(args)
|
||||||
|
{
|
||||||
|
indices.reserve(maxIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ModParam { Depth, Curve, Smooth, Step };
|
||||||
|
|
||||||
|
bool match(const char* pattern, const char* sig)
|
||||||
|
{
|
||||||
|
indices.clear();
|
||||||
|
const char* path_ = this->path;
|
||||||
|
|
||||||
|
while (const char* endp = strchr(pattern, '&')) {
|
||||||
|
if (indices.size() == maxIndices)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t length = endp - pattern;
|
||||||
|
if (strncmp(pattern, path_, length) != 0)
|
||||||
|
return false;
|
||||||
|
pattern += length;
|
||||||
|
path_ += length;
|
||||||
|
|
||||||
|
length = 0;
|
||||||
|
while (absl::ascii_isdigit(path_[length]))
|
||||||
|
++length;
|
||||||
|
|
||||||
|
indices.push_back(0);
|
||||||
|
if (!absl::SimpleAtoi(absl::string_view(path_, length), &indices.back()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pattern += 1;
|
||||||
|
path_ += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !strcmp(path_, pattern) && !strcmp(this->sig, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the reply/reply2 overloads for the (almost) concrete types, that should
|
||||||
|
// translate to actual calls to the client.receive(...) method.
|
||||||
|
|
||||||
|
void reply(const std::string& value) { client.receive<'s'>(delay, path, value.data()); }
|
||||||
|
void reply(const char* value) { client.receive<'s'>(delay, path, value); }
|
||||||
|
// void reply(float value) { client.receive<'f'>(delay, path, value); }
|
||||||
|
void reply(const float& value) { client.receive<'f'>(delay, path, value); }
|
||||||
|
void reply(absl::nullopt_t) { client.receive<'N'>(delay, path, {}); }
|
||||||
|
|
||||||
|
void reply(const bool& value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
client.receive<'T'>(delay, path, {});
|
||||||
|
else
|
||||||
|
client.receive<'F'>(delay, path, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, typename = std::enable_if_t<std::is_integral<T>::value>>
|
||||||
|
void reply(const T& value)
|
||||||
|
{
|
||||||
|
if (sizeof(value) <= 4)
|
||||||
|
client.receive<'i'>(delay, path, static_cast<int>(value));
|
||||||
|
else
|
||||||
|
client.receive<'h'>(delay, path, static_cast<long int>(value));
|
||||||
|
}
|
||||||
|
template <size_t N>
|
||||||
|
void reply(const BitArray<N>& array)
|
||||||
|
{
|
||||||
|
sfizz_blob_t blob { array.data(), static_cast<uint32_t>(array.byte_size()) };
|
||||||
|
client.receive<'b'>(delay, path, &blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call reply but denormalizes the input if needed by the opcode spec
|
||||||
|
template <class T>
|
||||||
|
void reply(const T& value, const OpcodeSpec<T>& spec) { reply(spec.denormalizeInput(value)); }
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
BoolOrNotIntegral<T> set(T& target, const OpcodeSpec<T>& spec) { target = Opcode::read(spec, request_args[0].s); }
|
||||||
|
|
||||||
|
// sfizz specific types
|
||||||
|
void reply(const LFOWave& wave) { reply(static_cast<int>(wave)); }
|
||||||
|
void reply(const SelfMask& mode) { reply(mode == SelfMask::mask); }
|
||||||
|
void reply(const LoopMode& mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case LoopMode::no_loop: reply("no_loop"); break;
|
||||||
|
case LoopMode::loop_continuous: reply("loop_continuous"); break;
|
||||||
|
case LoopMode::loop_sustain: reply("loop_sustain"); break;
|
||||||
|
case LoopMode::one_shot: reply("one_shot"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const CrossfadeCurve& curve)
|
||||||
|
{
|
||||||
|
switch (curve) {
|
||||||
|
case CrossfadeCurve::gain: reply("gain"); break;
|
||||||
|
case CrossfadeCurve::power: reply("power"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const Trigger& mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case Trigger::attack: reply("attack"); break;
|
||||||
|
case Trigger::first: reply("first"); break;
|
||||||
|
case Trigger::legato: reply("legato"); break;
|
||||||
|
case Trigger::release: reply("release"); break;
|
||||||
|
case Trigger::release_key: reply("release_key"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const VelocityOverride& mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case VelocityOverride::current: reply("current"); break;
|
||||||
|
case VelocityOverride::previous: reply("previous"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const OffMode& mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case OffMode::fast: reply("fast"); break;
|
||||||
|
case OffMode::time: reply("time"); break;
|
||||||
|
case OffMode::normal: reply("normal"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const FilterType& type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case FilterType::kFilterLpf1p: reply("lpf_1p"); break;
|
||||||
|
case FilterType::kFilterHpf1p: reply("hpf_1p"); break;
|
||||||
|
case FilterType::kFilterLpf2p: reply("lpf_2p"); break;
|
||||||
|
case FilterType::kFilterHpf2p: reply("hpf_2p"); break;
|
||||||
|
case FilterType::kFilterBpf2p: reply("bpf_2p"); break;
|
||||||
|
case FilterType::kFilterBrf2p: reply("brf_2p"); break;
|
||||||
|
case FilterType::kFilterBpf1p: reply("bpf_1p"); break;
|
||||||
|
case FilterType::kFilterBrf1p: reply("brf_1p"); break;
|
||||||
|
case FilterType::kFilterApf1p: reply("apf_1p"); break;
|
||||||
|
case FilterType::kFilterLpf2pSv: reply("lpf_2p_sv"); break;
|
||||||
|
case FilterType::kFilterHpf2pSv: reply("hpf_2p_sv"); break;
|
||||||
|
case FilterType::kFilterBpf2pSv: reply("bpf_2p_sv"); break;
|
||||||
|
case FilterType::kFilterBrf2pSv: reply("brf_2p_sv"); break;
|
||||||
|
case FilterType::kFilterLpf4p: reply("lpf_4p"); break;
|
||||||
|
case FilterType::kFilterHpf4p: reply("hpf_4p"); break;
|
||||||
|
case FilterType::kFilterLpf6p: reply("lpf_6p"); break;
|
||||||
|
case FilterType::kFilterHpf6p: reply("hpf_6p"); break;
|
||||||
|
case FilterType::kFilterPink: reply("pink"); break;
|
||||||
|
case FilterType::kFilterLsh: reply("lsh"); break;
|
||||||
|
case FilterType::kFilterHsh: reply("hsh"); break;
|
||||||
|
case FilterType::kFilterPeq: reply("peq"); break;
|
||||||
|
case FilterType::kFilterBpf4p: reply("bpf_4p"); break;
|
||||||
|
case FilterType::kFilterBpf6p: reply("bpf_6p"); break;
|
||||||
|
case FilterType::kFilterNone: reply("none"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const EqType& type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case EqType::kEqNone: reply("none"); break;
|
||||||
|
case EqType::kEqPeak: reply("peak"); break;
|
||||||
|
case EqType::kEqLshelf: reply("lshelf"); break;
|
||||||
|
case EqType::kEqHshelf: reply("hshelf"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply(const TriggerEventType& type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case TriggerEventType::NoteOff: reply("note_off"); break;
|
||||||
|
case TriggerEventType::NoteOn: reply("note_on"); break;
|
||||||
|
case TriggerEventType::CC: reply("cc"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reply2 for pairs (Usually only for float/int ranges)
|
||||||
|
template <class T, typename = std::enable_if_t<std::is_integral<T>::value>>
|
||||||
|
void reply2(T value1, T value2)
|
||||||
|
{
|
||||||
|
sfizz_arg_t response_args[2];
|
||||||
|
if (sizeof(value1) <= 4) {
|
||||||
|
response_args[0].i = value1;
|
||||||
|
response_args[1].i = value2;
|
||||||
|
client.receive(delay, path, "ii", response_args);
|
||||||
|
} else {
|
||||||
|
response_args[0].h = value1;
|
||||||
|
response_args[1].h = value2;
|
||||||
|
client.receive(delay, path, "hh", response_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply2(float value1, float value2)
|
||||||
|
{
|
||||||
|
sfizz_arg_t response_args[2];
|
||||||
|
response_args[0].f = value1;
|
||||||
|
response_args[1].f = value2;
|
||||||
|
client.receive(delay, path, "ff", response_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(std::string& target) { target = request_args[0].s; }
|
||||||
|
void set(float& target, const OpcodeSpec<float>& spec) { target = Opcode::transform(spec, request_args[0].f); }
|
||||||
|
void set(float& target) { target = request_args[0].f; }
|
||||||
|
void set(LFOWave& target, const OpcodeSpec<LFOWave>& spec) { target = Opcode::transform(spec, request_args[0].i); }
|
||||||
|
void set(bool& target, const OpcodeSpec<bool>& spec)
|
||||||
|
{
|
||||||
|
if (sig[0] == 'T') {
|
||||||
|
target = true;
|
||||||
|
} else if (sig[0] == 'F') {
|
||||||
|
target = false;
|
||||||
|
} else {
|
||||||
|
target = Opcode::read(spec, request_args[0].s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
IntegralNotBool<T> set(T& target, const OpcodeSpec<T>& spec)
|
||||||
|
{
|
||||||
|
if (sizeof(target) <= 4)
|
||||||
|
target = Opcode::transform(spec, request_args[0].i);
|
||||||
|
else
|
||||||
|
target = Opcode::transform(spec, request_args[0].h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have some templated reply overloads that decay into "concrete" reply calls
|
||||||
|
// but add some logic (e.g. an optional can either reply the value, or send 'N' for
|
||||||
|
// null)
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(const absl::optional<T>& value, Args... args)
|
||||||
|
{
|
||||||
|
if (!value) {
|
||||||
|
client.receive<'N'>(delay, path, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply(*value, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(T* value, Args... args)
|
||||||
|
{
|
||||||
|
if (!value) {
|
||||||
|
client.receive<'N'>(delay, path, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply(*value, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void reply(const std::shared_ptr<T>& value) { reply(value.get()); }
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void reply(const UncheckedRange<T>& range) { reply2(range.getStart(), range.getEnd()); }
|
||||||
|
template <class T>
|
||||||
|
void reply(const UncheckedRange<T>& range, const OpcodeSpec<T>& startSpec, const OpcodeSpec<T>& endSpec)
|
||||||
|
{
|
||||||
|
reply2(startSpec.denormalizeInput(range.getStart()), endSpec.denormalizeInput(range.getEnd()));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
IntegralNotBool<T> set(UncheckedRange<T>& range, const OpcodeSpec<T>& startSpec, const OpcodeSpec<T>& endSpec)
|
||||||
|
{
|
||||||
|
if (sizeof(range.getStart()) <= 4) {
|
||||||
|
range.setStart(Opcode::transform(startSpec, request_args[0].i));
|
||||||
|
range.setEnd(Opcode::transform(endSpec, request_args[1].i));
|
||||||
|
} else {
|
||||||
|
range.setStart(Opcode::transform(startSpec, request_args[0].h));
|
||||||
|
range.setEnd(Opcode::transform(endSpec, request_args[1].h));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
BoolOrNotIntegral<T> set(UncheckedRange<T>& range, const OpcodeSpec<T>& startSpec, const OpcodeSpec<T>& endSpec)
|
||||||
|
{
|
||||||
|
if (sizeof(range.getStart()) <= 4) {
|
||||||
|
range.setStart(Opcode::transform(startSpec, request_args[0].f));
|
||||||
|
range.setEnd(Opcode::transform(endSpec, request_args[1].f));
|
||||||
|
} else {
|
||||||
|
range.setStart(Opcode::transform(startSpec, request_args[0].d));
|
||||||
|
range.setEnd(Opcode::transform(endSpec, request_args[1].d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
IntegralNotBool<T> set(UncheckedRange<T>& range)
|
||||||
|
{
|
||||||
|
if (sizeof(range.getStart()) <= 4) {
|
||||||
|
range.setStart(request_args[0].i);
|
||||||
|
range.setEnd(request_args[1].i);
|
||||||
|
} else {
|
||||||
|
range.setStart(request_args[0].h);
|
||||||
|
range.setEnd(request_args[1].h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
BoolOrNotIntegral<T> set(UncheckedRange<T>& range)
|
||||||
|
{
|
||||||
|
if (sizeof(range.getStart()) <= 4) {
|
||||||
|
range.setStart(request_args[0].f);
|
||||||
|
range.setEnd(request_args[1].f);
|
||||||
|
} else {
|
||||||
|
range.setStart(request_args[0].d);
|
||||||
|
range.setEnd(request_args[1].d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void reply(const absl::optional<T>& value, T&& def) { reply(value.value_or(def)); }
|
||||||
|
|
||||||
|
template <class T, class...Args>
|
||||||
|
void set(absl::optional<T>& member, Args&&...args)
|
||||||
|
{
|
||||||
|
if (sig[0] == 'N') {
|
||||||
|
member.reset();
|
||||||
|
} else if (member.has_value()) {
|
||||||
|
set(*member, std::forward<Args>(args)...);
|
||||||
|
} else {
|
||||||
|
set(member.emplace(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(const ModifierCurvePair<T>& modCurve, ModParam which, Args&&... args)
|
||||||
|
{
|
||||||
|
switch (which) {
|
||||||
|
case ModParam::Curve: reply(modCurve.curve); break;
|
||||||
|
default: reply(modCurve.modifier, std::forward<Args>(args)...); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void set(ModifierCurvePair<T>& modCurve, ModParam which, Args&&... args)
|
||||||
|
{
|
||||||
|
switch (which) {
|
||||||
|
case ModParam::Curve: modCurve.curve = request_args[0].i; break;
|
||||||
|
default: set(modCurve.modifier, args...); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
void reply(const ModKey::Parameters& params, ModParam which, Args&&... args)
|
||||||
|
{
|
||||||
|
switch (which) {
|
||||||
|
case ModParam::Depth: break;
|
||||||
|
case ModParam::Curve: reply(params.curve); break;
|
||||||
|
case ModParam::Smooth: reply(params.smooth); break;
|
||||||
|
case ModParam::Step: reply(params.step, std::forward<Args>(args)...); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(const CCMap<T>& map, Args... args) { reply(map, true, args...); }
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(const CCMap<T>& map, bool&& useDefault, Args... args)
|
||||||
|
{
|
||||||
|
if (useDefault)
|
||||||
|
reply(map.getWithDefault(indices.back()), args...);
|
||||||
|
else
|
||||||
|
reply(map.get(indices.back()), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void set(const CCMap<T>& map, Args... args)
|
||||||
|
{
|
||||||
|
set(map[indices.back()], args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(const EGDescription& eg, T EGDescription::*&& member, Args&&... args)
|
||||||
|
{
|
||||||
|
reply(eg.*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void set(EGDescription& eg, T EGDescription::*&& member, Args&&... args)
|
||||||
|
{
|
||||||
|
set(eg.*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void set(CCMap<T>& map, Args&&... args) { set(map[indices.back()], std::forward<Args>(args)...); }
|
||||||
|
|
||||||
|
// Below are all methods that will fetch various data structure elements (regions,
|
||||||
|
// egs, ...), check that they exist, and call an actual "concrete" reply
|
||||||
|
// implementation. They use pointer-to-member variables, which allow the compiler to
|
||||||
|
// dispatch to the correct logic. For example, if the pointer-to-member is
|
||||||
|
// `Region::*`, we aim at sending the value of a member of the Region
|
||||||
|
// struct. The first thing is to read the index of the region and fetch it from the
|
||||||
|
// sfizz data structure using `getRegion(...)`, and then apply the pointer-to-member
|
||||||
|
// to this particular region to send the value.
|
||||||
|
|
||||||
|
// Adding new dispatching overloads seemed simple enough to this point, although I
|
||||||
|
// haven't found a nice way to the same with pointer-to-member function.
|
||||||
|
// TODO: could do something with the c++14 compatible `invoke` maybe?
|
||||||
|
|
||||||
|
template <class T, class M, class... Args>
|
||||||
|
void set(T M::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
dispatch(
|
||||||
|
static_cast<void (MessagingHelper::*)(T&, Args && ...)>(&MessagingHelper::set),
|
||||||
|
member,
|
||||||
|
std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MSVC require this because it finds T M::* and T Voice::* ambiguous in the overload
|
||||||
|
// resolution of `reply`, so we "disable" the reply/set dispatching for Voice and
|
||||||
|
// TriggerEvent since they're read-only components.
|
||||||
|
template<class M>
|
||||||
|
using DispatchedType = typename std::enable_if<
|
||||||
|
!std::is_same<M, Voice>::value && !std::is_same<M, TriggerEvent>::value, void>::type;
|
||||||
|
|
||||||
|
template <class T, class M, class... Args>
|
||||||
|
DispatchedType<M> reply(T M::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
dispatch(
|
||||||
|
static_cast<void (MessagingHelper::*)(const T&, Args&&...)>(&MessagingHelper::reply),
|
||||||
|
member,
|
||||||
|
std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T FilterDescription::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion())
|
||||||
|
if (auto filter = getFilter(*region))
|
||||||
|
inv::invoke(std::forward<F>(f), this, (*filter).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T SynthConfig::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
inv::invoke(std::forward<F>(f), this, (impl.resources_.getSynthConfig()).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T LFODescription::Sub::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion())
|
||||||
|
if (auto lfo = getLFO(*region))
|
||||||
|
if (auto sub = getLFOSub(*lfo))
|
||||||
|
inv::invoke(std::forward<F>(f), this, (*sub).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T Region::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion())
|
||||||
|
inv::invoke(std::forward<F>(f), this, (*region).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T EQDescription::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion())
|
||||||
|
if (auto eq = getEQ(*region))
|
||||||
|
inv::invoke(std::forward<F>(f), this, (*eq).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T LFODescription::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion())
|
||||||
|
if (auto lfo = getLFO(*region))
|
||||||
|
inv::invoke(std::forward<F>(f), this, (*lfo).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class F, class... Args>
|
||||||
|
void dispatch(F&& f, T FlexEGPoint::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion())
|
||||||
|
if (auto eg = getEG(*region))
|
||||||
|
if (auto point = getEGPoint(*eg))
|
||||||
|
inv::invoke(std::forward<F>(f), this, (*point).*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to dispatch for voices, they are read-only for now
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(T TriggerEvent::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto voice = getVoice())
|
||||||
|
reply((*voice).getTriggerEvent().*member, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
void reply(T Voice::*member, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto voice = getVoice()) {
|
||||||
|
reply((*voice.*member)(std::forward<Args>(args)...)); // Voice only has callables
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
void reply(ModId id, ModParam param, Args&&... args)
|
||||||
|
{
|
||||||
|
if (auto region = getRegion()) {
|
||||||
|
int cc = static_cast<int>(indices.back());
|
||||||
|
switch (id) {
|
||||||
|
case ModId::FilCutoff:
|
||||||
|
case ModId::FilGain:
|
||||||
|
switch (param) {
|
||||||
|
case ModParam::Depth: reply(region->ccModDepth(cc, id, indices[1]), std::forward<Args>(args)...);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reply(region->ccModParameters(cc, id, indices[1]), param, std::forward<Args>(args)...);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
switch (param) {
|
||||||
|
case ModParam::Depth: reply(region->ccModDepth(cc, id), std::forward<Args>(args)...);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reply(region->ccModParameters(cc, id), param, std::forward<Args>(args)...);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and fetch elements from the sfizz data structures. By default, we kind of
|
||||||
|
// assume that regions/voices will be the first index, CCs will be the last, and
|
||||||
|
// EQ/Filter/.. will be in-between.
|
||||||
|
Region* getRegion(absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[0]);
|
||||||
|
if (idx >= impl.layers_.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
Layer& layer = *impl.layers_[idx];
|
||||||
|
return &layer.getRegion();
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterDescription* getFilter(Region& region, absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[1]);
|
||||||
|
if (region.filters.size() <= idx)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ®ion.filters[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
EQDescription* getEQ(Region& region, absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[1]);
|
||||||
|
if (region.equalizers.size() <= idx)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ®ion.equalizers[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
LFODescription* getLFO(Region& region, absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[1]);
|
||||||
|
if (region.lfos.size() <= idx)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ®ion.lfos[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
LFODescription::Sub* getLFOSub(LFODescription& lfo, absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
if (indices.size() == 2) {
|
||||||
|
if (lfo.sub.empty())
|
||||||
|
return {};
|
||||||
|
else
|
||||||
|
return &lfo.sub.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto idx = index.value_or(indices[2]);
|
||||||
|
if (lfo.sub.size() <= idx)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return &lfo.sub[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
FlexEGDescription* getEG(Region& region, absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[1]);
|
||||||
|
if (region.flexEGs.size() <= idx)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ®ion.flexEGs[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
FlexEGPoint* getEGPoint(FlexEGDescription& desc, absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[2]) + 1;
|
||||||
|
if (desc.points.size() <= idx)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return &desc.points[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
Voice* getVoice(absl::optional<unsigned> index = {})
|
||||||
|
{
|
||||||
|
const auto idx = index.value_or(indices[0]);
|
||||||
|
if (static_cast<int>(idx) >= impl.numVoices_)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto& voice = impl.voiceManager_[idx];
|
||||||
|
if (voice.isFree())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return &voice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers to get and check the values of the indices
|
||||||
|
template <class T = unsigned>
|
||||||
|
absl::optional<T> index(int i)
|
||||||
|
{
|
||||||
|
if (i <= ssize(indices))
|
||||||
|
return static_cast<T>(indices[i]);
|
||||||
|
|
||||||
|
return absl::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<int> sindex(int i) { return index<int>(i); }
|
||||||
|
|
||||||
|
absl::optional<int> checkCC(int i = 0)
|
||||||
|
{
|
||||||
|
auto cc = index(i);
|
||||||
|
return cc <= config::numCCs ? cc : absl::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<int> checkNote(int i = 0)
|
||||||
|
{
|
||||||
|
auto note = sindex(i);
|
||||||
|
return note <= 127 ? note : absl::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Client& client;
|
||||||
|
std::vector<unsigned> indices;
|
||||||
|
Synth::Impl& impl;
|
||||||
|
int delay;
|
||||||
|
const char* path;
|
||||||
|
const char* sig;
|
||||||
|
const sfizz_arg_t* request_args;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "utility/Debug.h"
|
#include "utility/Debug.h"
|
||||||
#include <absl/container/flat_hash_map.h>
|
#include <absl/container/flat_hash_map.h>
|
||||||
|
#include <absl/strings/str_cat.h>
|
||||||
#include <absl/strings/string_view.h>
|
#include <absl/strings/string_view.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
#include "absl/strings/string_view.h"
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
using namespace Catch::literals;
|
using namespace Catch::literals;
|
||||||
|
|
||||||
struct ParsingMocker: sfz::Parser::Listener
|
struct ParsingMocker: sfz::Parser::Listener
|
||||||
|
|
|
||||||
|
|
@ -4,119 +4,509 @@
|
||||||
// license. You should have receive a LICENSE.md file along with the code.
|
// 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
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#include "TestHelpers.h"
|
#include "SynthDiscussion.h"
|
||||||
#include "sfizz/Synth.h"
|
#include "SfzHelpers.h"
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
#include <stdexcept>
|
|
||||||
#include <absl/strings/str_cat.h>
|
|
||||||
#include <absl/strings/str_format.h>
|
|
||||||
using namespace Catch::literals;
|
using namespace Catch::literals;
|
||||||
using namespace sfz;
|
using namespace sfz;
|
||||||
|
using namespace sfz::literals;
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
TEST_CASE("[Set values] Pitch keycenter")
|
using OSC = OSCValueLess;
|
||||||
|
|
||||||
|
TEST_CASE("Set values", "[parsing][OSC]")
|
||||||
{
|
{
|
||||||
Synth synth;
|
SynthDiscussion d;
|
||||||
std::vector<std::string> messageList;
|
|
||||||
Client client(&messageList);
|
|
||||||
client.setReceiveCallback(&simpleMessageReceiver);
|
|
||||||
AudioBuffer<float> buffer { 2, 256 };
|
|
||||||
|
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
|
SECTION("Pitch keycenter") {
|
||||||
<region> sample=*sine pitch_keycenter=48
|
d.load(R"( <region> sample=*sine pitch_keycenter=48 )");
|
||||||
)");
|
REQUIRE( d.read<int32_t>("/region0/pitch_keycenter") == 48);
|
||||||
synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr);
|
REQUIRE( d.sendAndRead("/region0/pitch_keycenter", 60) == 60);
|
||||||
|
|
||||||
// Update value
|
|
||||||
sfizz_arg_t args;
|
|
||||||
args.i = 60;
|
|
||||||
synth.dispatchMessage(client, 1, "/region0/pitch_keycenter", "i", &args);
|
|
||||||
synth.renderBlock(buffer);
|
|
||||||
|
|
||||||
synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr);
|
|
||||||
std::vector<std::string> expected {
|
|
||||||
"/region0/pitch_keycenter,i : { 48 }",
|
|
||||||
"/region0/pitch_keycenter,i : { 60 }",
|
|
||||||
};
|
|
||||||
REQUIRE(messageList == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Set values] LFO wave")
|
SECTION("LFO Wave") {
|
||||||
{
|
d.load(R"( <region> sample=*sine lfo1_wave=5 lfo1_wave2=4 )");
|
||||||
Synth synth;
|
REQUIRE( d.read<int32_t>("/region0/lfo0/wave") == 5);
|
||||||
std::vector<std::string> messageList;
|
REQUIRE( d.read<int32_t>("/region0/lfo0/wave1") == 4);
|
||||||
Client client(&messageList);
|
REQUIRE( d.sendAndRead("/region0/lfo0/wave", 3) == 3);
|
||||||
client.setReceiveCallback(&simpleMessageReceiver);
|
REQUIRE( d.sendAndRead("/region0/lfo0/wave1", 2) == 2);
|
||||||
AudioBuffer<float> buffer { 2, 256 };
|
|
||||||
|
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
|
|
||||||
<region> sample=*sine lfo1_wave=5
|
|
||||||
)");
|
|
||||||
synth.dispatchMessage(client, 0, "/region0/lfo0/wave", "", nullptr);
|
|
||||||
|
|
||||||
// Update value
|
|
||||||
sfizz_arg_t args;
|
|
||||||
args.i = 2;
|
|
||||||
synth.dispatchMessage(client, 1, "/region0/lfo0/wave", "i", &args);
|
|
||||||
synth.renderBlock(buffer);
|
|
||||||
|
|
||||||
synth.dispatchMessage(client, 0, "/region0/lfo0/wave", "", nullptr);
|
|
||||||
std::vector<std::string> expected {
|
|
||||||
"/region0/lfo0/wave,i : { 5 }",
|
|
||||||
"/region0/lfo0/wave,i : { 2 }",
|
|
||||||
};
|
|
||||||
REQUIRE(messageList == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Set values] Filter type")
|
SECTION("Loop mode") {
|
||||||
{
|
d.load(R"( <region> sample=looped_flute.wav )");
|
||||||
Synth synth;
|
REQUIRE( d.read<std::string>("/region0/loop_mode") == "loop_continuous");
|
||||||
std::vector<std::string> messageList;
|
REQUIRE( d.sendAndRead<std::string>("/region0/loop_mode", "one_shot") == "one_shot");
|
||||||
Client client(&messageList);
|
|
||||||
client.setReceiveCallback(&simpleMessageReceiver);
|
|
||||||
AudioBuffer<float> buffer { 2, 256 };
|
|
||||||
|
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
|
|
||||||
<region> sample=*sine fil2_type=lpf_1p
|
|
||||||
)");
|
|
||||||
synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr);
|
|
||||||
|
|
||||||
// Update value
|
|
||||||
sfizz_arg_t args;
|
|
||||||
args.s = "hpf_2p";
|
|
||||||
synth.dispatchMessage(client, 1, "/region0/filter1/type", "s", &args);
|
|
||||||
synth.renderBlock(buffer);
|
|
||||||
|
|
||||||
synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr);
|
|
||||||
std::vector<std::string> expected {
|
|
||||||
"/region0/filter1/type,s : { lpf_1p }",
|
|
||||||
"/region0/filter1/type,s : { hpf_2p }",
|
|
||||||
};
|
|
||||||
REQUIRE(messageList == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Set values] Loop mode")
|
SECTION("Sample quality") {
|
||||||
{
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
Synth synth;
|
REQUIRE( d.read<int32_t>("/sample_quality") == 2);
|
||||||
std::vector<std::string> messageList;
|
REQUIRE( d.read<int32_t>("/oscillator_quality") == 1);
|
||||||
Client client(&messageList);
|
REQUIRE( d.read<int32_t>("/freewheeling_sample_quality") == 10);
|
||||||
client.setReceiveCallback(&simpleMessageReceiver);
|
REQUIRE( d.read<int32_t>("/freewheeling_oscillator_quality") == 3);
|
||||||
AudioBuffer<float> buffer { 2, 256 };
|
REQUIRE( d.sendAndRead("/sample_quality", 3) == 3);
|
||||||
|
REQUIRE( d.sendAndRead("/oscillator_quality", 2) == 2);
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
|
REQUIRE( d.sendAndRead("/freewheeling_sample_quality", 6) == 6);
|
||||||
<region> sample=looped_flute.wav
|
REQUIRE( d.sendAndRead("/freewheeling_oscillator_quality", 2) == 2);
|
||||||
)");
|
}
|
||||||
synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr);
|
|
||||||
|
SECTION("Sustain cancels release") {
|
||||||
// Update value
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
sfizz_arg_t args;
|
REQUIRE( d.read<OSC>("/sustain_cancels_release") == OSC::False );
|
||||||
args.s = "one_shot";
|
d.send("/sustain_cancels_release", true);
|
||||||
synth.dispatchMessage(client, 1, "/region0/loop_mode", "s", &args);
|
REQUIRE( d.read<OSC>("/sustain_cancels_release") == OSC::True );
|
||||||
synth.renderBlock(buffer);
|
d.send("/sustain_cancels_release", false);
|
||||||
|
REQUIRE( d.read<OSC>("/sustain_cancels_release") == OSC::False );
|
||||||
synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr);
|
d.send("/sustain_cancels_release", "on"s);
|
||||||
std::vector<std::string> expected {
|
REQUIRE( d.read<OSC>("/sustain_cancels_release") == OSC::True );
|
||||||
"/region0/loop_mode,s : { loop_continuous }",
|
}
|
||||||
"/region0/loop_mode,s : { one_shot }",
|
|
||||||
};
|
SECTION("Delay") {
|
||||||
REQUIRE(messageList == expected);
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/delay", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/delay_random", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/delay_cc1", 10.0f) == 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Offset") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/offset", 10) == 10);
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/offset_random", 10) == 10);
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/offset_cc1", 10) == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("End") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/end", 10) == 10);
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/end_cc1", 10) == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Count") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/count", 3) == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Loop range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<int64_t> v {13, 2000};
|
||||||
|
REQUIRE( d.sendAndReadAll<int64_t>("/region0/loop_range", v) == v);
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/loop_start_cc1", 10) == 10);
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/loop_end_cc1", 1000) == 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Loop count") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/loop_count", 3) == 3);
|
||||||
|
d.send("/region0/loop_count", nullptr);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/loop_count") == OSC::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Output") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/output", 3) == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Off by") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.read<OSC>("/region0/off_by") == OSC::None);
|
||||||
|
REQUIRE( d.sendAndRead<int64_t>("/region0/off_by", 2) == 2);
|
||||||
|
d.send("/region0/off_by", nullptr);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/off_by") == OSC::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Off mode") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/off_mode", "time") =="time");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/off_mode", "fast") =="fast");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Key range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<int32_t> v {5, 67};
|
||||||
|
REQUIRE( d.sendAndReadAll<int32_t>("/region0/key_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Off time") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/off_time", 0.1f) == 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Velocity range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/vel_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Bend range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_bend, 67_bend};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/bend_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Program range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<int32_t> v {2, 10};
|
||||||
|
REQUIRE( d.sendAndReadAll<int32_t>("/region0/program_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("CC range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/cc_range2", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Last keyswitch") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/sw_last", 24) == 24);
|
||||||
|
std::vector<int32_t> v {10, 15};
|
||||||
|
REQUIRE( d.sendAndReadAll<int32_t>("/region0/sw_last", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Keyswitch label") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/sw_label", "hello") == "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Keyswitch up") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/sw_up", 12) == 12);
|
||||||
|
d.send("/region0/sw_up", "c4"s);
|
||||||
|
REQUIRE( d.read<int32_t>("/region0/sw_up") == 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Keyswitch down") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/sw_down", 12) == 12);
|
||||||
|
d.send("/region0/sw_down", "c4"s);
|
||||||
|
REQUIRE( d.read<int32_t>("/region0/sw_down") == 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Keyswitch down") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/sw_previous", 12) == 12);
|
||||||
|
d.send("/region0/sw_previous", "c4"s);
|
||||||
|
REQUIRE( d.read<int32_t>("/region0/sw_previous") == 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Velocity override") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/sw_vel", "previous") == "previous");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Channel aftertouch range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/chanaft_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Poly aftertouch range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/polyaft_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("BPM range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5, 67};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/bpm_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Rand range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/rand_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Sequences") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/seq_length", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/seq_position", 2) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Trigger") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/trigger", "release") == "release");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Start CC range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/start_cc_range2", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Volume etc") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/volume", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pan", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/width", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/position", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amplitude", 10.0f) == 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Amp key something") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amp_keycenter", 48) == 48);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amp_keytrack", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amp_veltrack", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amp_veltrack_cc3", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amp_veltrack_curvecc3", 2) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Amp random") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/amp_random", 10.0f) == 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Crossfade key range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<int32_t> v {5, 67};
|
||||||
|
REQUIRE( d.sendAndReadAll<int32_t>("/region0/xfin_key_range", v) == v);
|
||||||
|
REQUIRE( d.sendAndReadAll<int32_t>("/region0/xfout_key_range", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Other crossfade range") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
std::vector<float> v {5_norm, 67_norm};
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/xfin_vel_range", v) == v);
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/xfout_vel_range", v) == v);
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/xfin_cc_range3", v) == v);
|
||||||
|
REQUIRE( d.sendAndReadAll<float>("/region0/xfout_cc_range3", v) == v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Crossfade curves") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/xf_keycurve", "power") == "power");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/xf_velcurve", "power") == "power");
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/xf_cccurve", "power") == "power");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Global amps and volumes curves") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/global_volume", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/master_volume", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/group_volume", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/global_amplitude", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/master_amplitude", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/group_amplitude", 10.0f) == 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Pitch and transpose") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/transpose", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch_random", 10.0f) == 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Pitch key something") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch_keycenter", 48) == 48);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch_keytrack", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch_veltrack", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch_veltrack_cc3", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitch_veltrack_curvecc3", 2) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Bends") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/bend_up", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/bend_down", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/bend_step", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/bend_smooth", 10) == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Ampeg") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_attack", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_delay", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_decay", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_hold", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_release", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_start", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_sustain", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_depth", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_attack_cc1", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_decay_cc2", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_delay_cc3", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_hold_cc4", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_release_cc5", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_sustain_cc6", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_start_cc7", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_attack_curvecc1", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_decay_curvecc2", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_delay_curvecc3", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_hold_curvecc4", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_release_curvecc5", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_sustain_curvecc6", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_start_curvecc7", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2attack", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2delay", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2decay", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2hold", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2release", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2sustain", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/ampeg_vel2depth", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/ampeg_dynamic") == OSC::False);
|
||||||
|
d.send("/region0/ampeg_dynamic", true);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/ampeg_dynamic") == OSC::True);
|
||||||
|
d.send("/region0/ampeg_dynamic", "off"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/ampeg_dynamic") == OSC::False);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Fileg") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_attack", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_delay", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_decay", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_hold", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_release", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_start", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_sustain", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_depth", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_attack_cc1", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_decay_cc2", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_delay_cc3", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_hold_cc4", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_release_cc5", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_sustain_cc6", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_start_cc7", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_attack_curvecc1", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_decay_curvecc2", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_delay_curvecc3", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_hold_curvecc4", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_release_curvecc5", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_sustain_curvecc6", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/fileg_start_curvecc7", 2) == 2);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/fileg_dynamic") == OSC::False);
|
||||||
|
d.send("/region0/fileg_dynamic", true);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/fileg_dynamic") == OSC::True);
|
||||||
|
d.send("/region0/fileg_dynamic", "off"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/fileg_dynamic") == OSC::False);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("PitchEG") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_attack", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_delay", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_decay", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_hold", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_release", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_start", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_sustain", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_depth", 2.0f) == 2.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_attack_cc1", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_decay_cc2", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_delay_cc3", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_hold_cc4", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_release_cc5", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_sustain_cc6", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_start_cc7", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_attack_curvecc1", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_decay_curvecc2", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_delay_curvecc3", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_hold_curvecc4", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_release_curvecc5", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_sustain_curvecc6", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead("/region0/pitcheg_start_curvecc7", 2) == 2);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/pitcheg_dynamic") == OSC::False);
|
||||||
|
d.send("/region0/pitcheg_dynamic", true);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/pitcheg_dynamic") == OSC::True);
|
||||||
|
d.send("/region0/pitcheg_dynamic", "off"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/pitcheg_dynamic") == OSC::False);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Note polyphony") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/note_polyphony", 3) == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("RT dead") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.read<OSC>("/region0/rt_dead") == OSC::False );
|
||||||
|
d.send("/region0/rt_dead", true);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/rt_dead") == OSC::True );
|
||||||
|
d.send("/region0/rt_dead", false);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/rt_dead") == OSC::False );
|
||||||
|
d.send("/region0/rt_dead", "on"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/rt_dead") == OSC::True );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Sustain/sostenuto") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sustain_sw") == OSC::True );
|
||||||
|
d.send("/region0/sustain_sw", false);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sustain_sw") == OSC::False );
|
||||||
|
d.send("/region0/sustain_sw", true);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sustain_sw") == OSC::True );
|
||||||
|
d.send("/region0/sustain_sw", "off"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sustain_sw") == OSC::False );
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sostenuto_sw") == OSC::True );
|
||||||
|
d.send("/region0/sostenuto_sw", false);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sostenuto_sw") == OSC::False );
|
||||||
|
d.send("/region0/sostenuto_sw", true);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sostenuto_sw") == OSC::True );
|
||||||
|
d.send("/region0/sostenuto_sw", "off"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/sostenuto_sw") == OSC::False );
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/sustain_cc", 23) == 23);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/sostenuto_cc", 23) == 23);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/sustain_lo", 0.1f) == 0.1f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/sostenuto_lo", 0.1f) == 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Note selfmask") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.read<OSC>("/region0/note_selfmask") == OSC::True );
|
||||||
|
d.send("/region0/note_selfmask", "off"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/note_selfmask") == OSC::False );
|
||||||
|
d.send("/region0/note_selfmask", "mask"s);
|
||||||
|
REQUIRE( d.read<OSC>("/region0/note_selfmask") == OSC::True );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Oscillator stuff") {
|
||||||
|
d.load(R"( <region> sample=kick.wav )");
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/oscillator_phase", 0.1f) == 0.1f);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/oscillator_quality", 2) == 2);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/oscillator_mode", 1) == 1);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/oscillator_multi", 5) == 5);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/oscillator_detune", 0.2f) == 0.2f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/oscillator_mod_depth", 0.2f) == 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Effect") {
|
||||||
|
d.load(R"( <region> sample=kick.wav effect1=10)");
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/effect1", 1.0f) == 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Filters") {
|
||||||
|
d.load(R"( <region> sample=kick.wav)");
|
||||||
|
REQUIRE(d.read<int32_t>("/region0/add_filter") == 0);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/filter0/cutoff", 100.0f) == 100.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/filter0/resonance", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/filter0/gain", 4.0f) == 4.0f);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/filter0/keycenter", 42) == 42);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/filter0/keytrack", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/filter0/veltrack", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/filter0/veltrack_cc1", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<int32_t>("/region0/filter0/veltrack_curvecc2", 3) == 3);
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/filter0/type", "lpf_2p") == "lpf_2p");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("EQs") {
|
||||||
|
d.load(R"( <region> sample=kick.wav)");
|
||||||
|
REQUIRE(d.read<int32_t>("/region0/add_eq") == 0);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eq0/gain", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eq0/bandwidth", 100.0f) == 100.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eq0/frequency", 500.0f) == 500.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eq0/vel2freq", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eq0/vel2gain", 10.0f) == 10.0f);
|
||||||
|
REQUIRE( d.sendAndRead<std::string>("/region0/eq0/type", "hshelf") == "hshelf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("EGs") {
|
||||||
|
d.load(R"( <region> sample=kick.wav)");
|
||||||
|
REQUIRE(d.read<int32_t>("/region0/add_eg") == 0);
|
||||||
|
REQUIRE(d.read<int32_t>("/region0/eg0/add_point") == 0);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eg0/point0/time", 1.0f) == 1.0f);
|
||||||
|
REQUIRE( d.sendAndRead<float>("/region0/eg0/point0/level", 0.5f) == 0.5f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
187
tests/SynthDiscussion.h
Normal file
187
tests/SynthDiscussion.h
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
// 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 <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <absl/strings/str_cat.h>
|
||||||
|
#include <absl/types/variant.h>
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "sfizz/Synth.h"
|
||||||
|
#include "sfizz_message.h"
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "utility/Size.h"
|
||||||
|
|
||||||
|
using namespace sfz;
|
||||||
|
constexpr int maxArgs = 8;
|
||||||
|
|
||||||
|
enum class OSCValueLess { True, False, None };
|
||||||
|
using OSCVariant = absl::variant<int32_t, int64_t, float, double, std::string, OSCValueLess>;
|
||||||
|
|
||||||
|
// template <class ... Args>
|
||||||
|
// struct vecToTuple
|
||||||
|
// {
|
||||||
|
// template <unsigned I>
|
||||||
|
// static void rewrap(std::tuple<Args...> &tup, const std::vector<OSCVariant>& variants)
|
||||||
|
// {
|
||||||
|
// std::get<I-1>(tup) = std::get<decltype(std::get<I-1>(tup))>(variants[I-1]);
|
||||||
|
// rewrap<I-1>(tup, variants);
|
||||||
|
// }
|
||||||
|
// template <>
|
||||||
|
// static void rewrap<0>(std::tuple<Args...> &, const std::vector<OSCVariant>&) { }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// template<int MaxArgs = 8>
|
||||||
|
struct SynthDiscussion
|
||||||
|
{
|
||||||
|
SynthDiscussion()
|
||||||
|
{
|
||||||
|
client.setReceiveCallback(&SynthDiscussion::receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void receiver(void* data, int, const char* path, const char* sig, const sfizz_arg_t* args)
|
||||||
|
{
|
||||||
|
SynthDiscussion* self = (SynthDiscussion*)data;
|
||||||
|
self->recv_path = path;
|
||||||
|
self->recv_sig = sig;
|
||||||
|
self->recv_args.clear();
|
||||||
|
for (int i = 0, n = self->recv_sig.size(); i < n; ++i) {
|
||||||
|
switch (self->recv_sig[i]) {
|
||||||
|
case 'i': self->recv_args.emplace_back(args[i].i); break;
|
||||||
|
case 'f': self->recv_args.emplace_back(args[i].f); break;
|
||||||
|
case 'd': self->recv_args.emplace_back(args[i].d); break;
|
||||||
|
case 'h': self->recv_args.emplace_back(args[i].h); break;
|
||||||
|
case 's': self->recv_args.emplace_back(args[i].s); break;
|
||||||
|
case 'T': self->recv_args.emplace_back(OSCValueLess::True); break;
|
||||||
|
case 'F': self->recv_args.emplace_back(OSCValueLess::False); break;
|
||||||
|
case 'N': self->recv_args.emplace_back(OSCValueLess::None); break;
|
||||||
|
default:
|
||||||
|
ASSERTFALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(absl::string_view sfz)
|
||||||
|
{
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/discussion.sfz", sfz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(absl::string_view path, int32_t value)
|
||||||
|
{
|
||||||
|
sent_args[0].i = value;
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "i", sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(absl::string_view path, int64_t value)
|
||||||
|
{
|
||||||
|
sent_args[0].h = value;
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "h", sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(absl::string_view path, float value)
|
||||||
|
{
|
||||||
|
sent_args[0].f = value;
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "f", sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(absl::string_view path, const std::string& value)
|
||||||
|
{
|
||||||
|
sent_args[0].s = value.c_str();
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "s", sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(absl::string_view path, std::nullptr_t)
|
||||||
|
{
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "N", nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(absl::string_view path, bool value)
|
||||||
|
{
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), value ? "T" : "F", nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> T read(absl::string_view path)
|
||||||
|
{
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "", nullptr);
|
||||||
|
return absl::get<T>(recv_args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool replied(absl::string_view path)
|
||||||
|
{
|
||||||
|
recv_path = "";
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "", nullptr);
|
||||||
|
return !recv_path.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> std::vector<T> readAll(absl::string_view path)
|
||||||
|
{
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), "", nullptr);
|
||||||
|
std::vector<T> returned;
|
||||||
|
for (const auto& arg: recv_args)
|
||||||
|
returned.push_back(absl::get<T>(arg));
|
||||||
|
|
||||||
|
return returned;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendAll(absl::string_view path, const std::vector<float>& value)
|
||||||
|
{
|
||||||
|
std::string signature(value.size(), 'f');
|
||||||
|
for (int i = 0; i < ssize(value) && i < maxArgs; ++i)
|
||||||
|
sent_args[i].f = value[i];
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), signature.c_str(), sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendAll(absl::string_view path, const std::vector<int32_t>& value)
|
||||||
|
{
|
||||||
|
std::string signature(value.size(), 'i');
|
||||||
|
for (int i = 0; i < ssize(value) && i < maxArgs; ++i)
|
||||||
|
sent_args[i].i = value[i];
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), signature.c_str(), sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendAll(absl::string_view path, const std::vector<int64_t>& value)
|
||||||
|
{
|
||||||
|
std::string signature(value.size(), 'h');
|
||||||
|
for (int i = 0; i < ssize(value) && i < maxArgs; ++i)
|
||||||
|
sent_args[i].h = value[i];
|
||||||
|
synth.dispatchMessage(client, 0, path.data(), signature.c_str(), sent_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T sendAndRead(absl::string_view path, T value)
|
||||||
|
{
|
||||||
|
send(path, value);
|
||||||
|
return read<T>(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
std::vector<T> sendAndReadAll(absl::string_view path, const std::vector<T>& value)
|
||||||
|
{
|
||||||
|
sendAll(path, value);
|
||||||
|
return readAll<T>(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatLast() const
|
||||||
|
{
|
||||||
|
std::string newMessage = absl::StrCat(recv_path, ",", recv_sig, " : { ");
|
||||||
|
for (unsigned i = 0, n = recv_sig.size(); i < n; ++i) {
|
||||||
|
absl::visit([&](auto&& arg){ absl::StrAppend(&newMessage, arg); }, recv_args[i]);
|
||||||
|
if (i == (n - 1))
|
||||||
|
absl::StrAppend(&newMessage, " }");
|
||||||
|
else
|
||||||
|
absl::StrAppend(&newMessage, ", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
Synth synth;
|
||||||
|
sfizz_arg_t sent_args[maxArgs];
|
||||||
|
std::vector<OSCVariant> recv_args;
|
||||||
|
std::string recv_path;
|
||||||
|
std::string recv_sig;
|
||||||
|
AudioBuffer<float> buffer { 2, 256 };
|
||||||
|
Client client { this };
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue