From 171a5e508333505f7253a5f92fb7d64143ee3922 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 8 Nov 2020 23:59:28 +0100 Subject: [PATCH] Initial --- src/sfizz/BufferPool.h | 6 +- src/sfizz/CCMap.h | 17 + src/sfizz/Defaults.h | 4 +- src/sfizz/Messaging.h | 2 - src/sfizz/Messaging.hpp | 2 +- src/sfizz/Region.cpp | 28 + src/sfizz/Region.h | 20 +- src/sfizz/Synth.cpp | 5 +- src/sfizz/SynthMessaging.cpp | 1096 +++++++++- tests/CMakeLists.txt | 3 +- tests/DirectRegionT.cpp | 112 + tests/RegionT.cpp | 1894 ----------------- tests/RegionValueComputationsT.cpp | 114 +- tests/RegionValuesT.cpp | 3033 ++++++++++++++++++++++++++++ 14 files changed, 4390 insertions(+), 1946 deletions(-) create mode 100644 tests/DirectRegionT.cpp delete mode 100644 tests/RegionT.cpp create mode 100644 tests/RegionValuesT.cpp diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index b9971fcf..eea96b56 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -144,9 +144,9 @@ public: #ifndef NDEBUG ~BufferPool() { - DBG("Max buffers used: " << maxBuffersUsed); - DBG("Max index buffers used: " << maxIndexBuffersUsed); - DBG("Max stereo buffers used: " << maxStereoBuffersUsed); + // DBG("Max buffers used: " << maxBuffersUsed); + // DBG("Max index buffers used: " << maxIndexBuffersUsed); + // DBG("Max stereo buffers used: " << maxStereoBuffersUsed); } #endif diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index 4fcc13f8..8354ad64 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -9,6 +9,7 @@ #include "SfzHelpers.h" #include #include +#include namespace sfz { /** @@ -57,6 +58,22 @@ public: } } + /** + * @brief Returns the held object at the index, or a default value if not present + * + * @param index + * @return const ValueType& + */ + absl::optional get(int index) const noexcept + { + auto it = absl::c_lower_bound(container, index, CCDataComparator {}); + if (it == container.end() || it->cc != index) { + return {}; + } else { + return it->data; + } + } + /** * @brief Get the value at index or emplace a new one if not present * diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 290c1248..7ddfcf37 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -207,8 +207,8 @@ namespace Default constexpr Range pitchVeltrackRange { -12000, 12000 }; constexpr int transpose { 0 }; constexpr Range transposeRange { -127, 127 }; - constexpr int tune { 0 }; - constexpr Range tuneRange { -12000, 12000 }; // ±100 in SFZv1, more in ARIA + constexpr float tune { 0 }; + constexpr Range tuneRange { -12000, 12000 }; // ±100 in SFZv1, more in ARIA constexpr Range tuneCCRange { -12000, 12000 }; constexpr Range bendBoundRange { -12000, 12000 }; constexpr Range bendStepRange { 1, 1200 }; diff --git a/src/sfizz/Messaging.h b/src/sfizz/Messaging.h index 0f48587b..68839869 100644 --- a/src/sfizz/Messaging.h +++ b/src/sfizz/Messaging.h @@ -27,8 +27,6 @@ public: private: template sfizz_arg_t make_arg(OscDecayedType value); - -private: void* data_ = nullptr; sfizz_receive_t* receive_ = nullptr; }; diff --git a/src/sfizz/Messaging.hpp b/src/sfizz/Messaging.hpp index 587e671d..33c9127a 100644 --- a/src/sfizz/Messaging.hpp +++ b/src/sfizz/Messaging.hpp @@ -56,7 +56,7 @@ inline void Client::receive(int delay, const char* path, OscDecayedType... typedef struct Nothing {} type; \ typedef type decayed_type; \ static inline sfizz_arg_t make_arg(decayed_type v) { \ - sfizz_arg_t a; (void)v; return a; \ + sfizz_arg_t a {}; (void)v; return a; \ } \ } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index f131febf..0c0b84c7 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1936,3 +1936,31 @@ bool sfz::Region::disabled() const noexcept { return (sampleEnd == 0); } + +absl::optional sfz::Region::ccModDepth(int cc, ModId id) const noexcept +{ + const ModKey target = ModKey::createNXYZ(id, getId()); + for (const sfz::Region::Connection& conn : connections) { + if (conn.source.id() == sfz::ModId::Controller && conn.target == target) { + auto p = conn.source.parameters(); + if (p.cc == cc) + return conn.sourceDepth; + } + } + + return {}; +} + +absl::optional sfz::Region::ccModParameters(int cc, ModId id) const noexcept +{ + const ModKey target = ModKey::createNXYZ(id, getId()); + for (const sfz::Region::Connection& conn : connections) { + if (conn.source.id() == sfz::ModId::Controller && conn.target == target) { + auto p = conn.source.parameters(); + if (p.cc == cc) + return p; + } + } + + return {}; +} diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 932aecf4..384b090f 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -303,6 +303,24 @@ struct Region { */ bool disabled() const noexcept; + /** + * @brief Extract the source depth modifier for a given cc and id. + * + * @param cc + * @param id + * @return absl::optional + */ + absl::optional ccModDepth(int cc, ModId id) const noexcept; + + /** + * @brief Extract the parameters for a given modulation cc and id. + * + * @param cc + * @param id + * @return float + */ + absl::optional ccModParameters(int cc, ModId id) const noexcept; + const NumericId id; // Sound source: sample playback @@ -412,7 +430,7 @@ struct Region { float pitchRandom { Default::pitchRandom }; // pitch_random int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack int transpose { Default::transpose }; // transpose - int tune { Default::tune }; // tune + float tune { Default::tune }; // tune int bendUp { Default::bendUp }; int bendDown { Default::bendDown }; int bendStep { Default::bendStep }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 79651e4e..6a2e6e08 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -676,7 +676,10 @@ void Synth::Impl::finalizeSfzLoad() ++currentRegionIndex; } - DBG("Removing " << (regions_.size() - currentRegionCount) << " out of " << regions_.size() << " regions"); + if (currentRegionCount < regions_.size()) { + DBG("Removing " << (regions_.size() - currentRegionCount) + << " out of " << regions_.size() << " regions"); + } regions_.resize(currentRegionCount); // collect all CCs used in regions, with matrix not yet connected diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 9aef0213..4e3285fa 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -17,17 +17,1109 @@ 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) { + UNUSED(args); + Impl& impl = *impl_; unsigned indices[maxIndices]; switch (hashMessagePath(path, sig)) { #define MATCH(p, s) case hash(p "," s): \ if (extractMessage(p, path, indices) && !strcmp(sig, s)) + #define GET_REGION_OR_BREAK(idx) \ + if (idx >= impl.regions_.size()) \ + break; \ + const auto& region = *impl.regions_[idx]; + MATCH("/hello", "") { client.receive(delay, "/hello", "", nullptr); - break; - } + } break; + MATCH("/region&/delay", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.delay); + } break; + + MATCH("/region&/sample", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'s'>(delay, path, region.sampleId->filename().c_str()); + } break; + + MATCH("/region&/direction", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.sampleId->isReverse()) + client.receive<'s'>(delay, path, "reverse"); + else + client.receive<'s'>(delay, path, "forward"); + } break; + + MATCH("/region&/delay_random", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.delayRandom); + } break; + + MATCH("/region&/offset", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.offset); + } break; + + MATCH("/region&/offset_random", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.offsetRandom); + } break; + + MATCH("/region&/offset_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.offsetCC.getWithDefault(indices[1])); + } break; + + MATCH("/region&/end", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.sampleEnd); + } break; + + MATCH("/region&/enabled", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.disabled()) { + client.receive<'F'>(delay, path, {}); + } else { + client.receive<'T'>(delay, path, {}); + } + } break; + + MATCH("/region&/count", "") { + GET_REGION_OR_BREAK(indices[0]) + if (!region.sampleCount) { + client.receive<'N'>(delay, path, {}); + } else { + client.receive<'h'>(delay, path, *region.sampleCount); + } + } break; + + MATCH("/region&/loop_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].h = region.loopRange.getStart(); + args[1].h = region.loopRange.getEnd(); + client.receive(delay, path, "hh", args); + } break; + + MATCH("/region&/loop_mode", "") { + GET_REGION_OR_BREAK(indices[0]) + if (!region.loopMode) { + client.receive<'s'>(delay, path, "no_loop"); + break; + } + + switch (*region.loopMode) { + case SfzLoopMode::no_loop: + client.receive<'s'>(delay, path, "no_loop"); + break; + case SfzLoopMode::loop_continuous: + client.receive<'s'>(delay, path, "loop_continuous"); + break; + case SfzLoopMode::loop_sustain: + client.receive<'s'>(delay, path, "loop_sustain"); + break; + case SfzLoopMode::one_shot: + client.receive<'s'>(delay, path, "one_shot"); + break; + } + } break; + + MATCH("/region&/loop_crossfade", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.loopCrossfade); + } break; + + MATCH("/region&/group", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.group); + } break; + + MATCH("/region&/off_by", "") { + GET_REGION_OR_BREAK(indices[0]) + if (!region.offBy) { + client.receive<'N'>(delay, path, {}); + } else { + client.receive<'h'>(delay, path, *region.offBy); + } + } break; + + MATCH("/region&/off_mode", "") { + GET_REGION_OR_BREAK(indices[0]) + switch (region.offMode) { + case SfzOffMode::time: + client.receive<'s'>(delay, path, "time"); + break; + case SfzOffMode::normal: + client.receive<'s'>(delay, path, "normal"); + break; + case SfzOffMode::fast: + client.receive<'s'>(delay, path, "fast"); + break; + } + } break; + + MATCH("/region&/key_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].i = region.keyRange.getStart(); + args[1].i = region.keyRange.getEnd(); + client.receive(delay, path, "ii", args); + } break; + + MATCH("/region&/off_time", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.offTime); + } break; + + MATCH("/region&/pitch_keycenter", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.pitchKeycenter); + } break; + + MATCH("/region&/vel_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.velocityRange.getStart(); + args[1].f = region.velocityRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/bend_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.bendRange.getStart(); + args[1].f = region.bendRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/cc_range&", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + const auto& conditions = region.ccConditions.getWithDefault(indices[1]); + args[0].f = conditions.getStart(); + args[1].f = conditions.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/sw_last", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.lastKeyswitch) { + client.receive<'i'>(delay, path, *region.lastKeyswitch); + } else if (region.lastKeyswitchRange) { + sfizz_arg_t args[2]; + args[0].i = region.lastKeyswitchRange->getStart(); + args[1].i = region.lastKeyswitchRange->getEnd(); + client.receive(delay, path, "ii", args); + } else { + client.receive<'N'>(delay, path, {}); + } + + } break; + + MATCH("/region&/sw_label", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.keyswitchLabel) { + client.receive<'s'>(delay, path, region.keyswitchLabel->c_str()); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/sw_up", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.upKeyswitch) { + client.receive<'i'>(delay, path, *region.upKeyswitch); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/sw_down", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.downKeyswitch) { + client.receive<'i'>(delay, path, *region.downKeyswitch); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/sw_previous", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.previousKeyswitch) { + client.receive<'i'>(delay, path, *region.previousKeyswitch); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/sw_vel", "") { + GET_REGION_OR_BREAK(indices[0]) + switch (region.velocityOverride) { + case SfzVelocityOverride::current: + client.receive<'s'>(delay, path, "current"); + break; + case SfzVelocityOverride::previous: + client.receive<'s'>(delay, path, "previous"); + break; + } + } break; + + MATCH("/region&/chanaft_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].i = region.aftertouchRange.getStart(); + args[1].i = region.aftertouchRange.getEnd(); + client.receive(delay, path, "ii", args); + } break; + + MATCH("/region&/bpm_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.bpmRange.getStart(); + args[1].f = region.bpmRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/rand_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.randRange.getStart(); + args[1].f = region.randRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/seq_length", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.sequenceLength); + } break; + + MATCH("/region&/seq_position", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'h'>(delay, path, region.sequencePosition); + } break; + + MATCH("/region&/trigger", "") { + GET_REGION_OR_BREAK(indices[0]) + switch (region.trigger) { + case SfzTrigger::attack: + client.receive<'s'>(delay, path, "attack"); + break; + case SfzTrigger::first: + client.receive<'s'>(delay, path, "first"); + break; + case SfzTrigger::release: + client.receive<'s'>(delay, path, "release"); + break; + case SfzTrigger::release_key: + client.receive<'s'>(delay, path, "release_key"); + break; + case SfzTrigger::legato: + client.receive<'s'>(delay, path, "legato"); + break; + } + } break; + + MATCH("/region&/start_cc_range&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto trigger = region.ccTriggers.get(indices[1]); + if (trigger) { + sfizz_arg_t args[2]; + args[0].f = trigger->getStart(); + args[1].f = trigger->getEnd(); + client.receive(delay, path, "ff", args); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/volume", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.volume); + } break; + + MATCH("/region&/volume_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto value = region.ccModDepth(indices[1], ModId::Volume); + if (value) { + client.receive<'f'>(delay, path, *value); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/volume_stepcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Volume); + if (params) { + client.receive<'f'>(delay, path, params->step); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/volume_smoothcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Volume); + if (params) { + client.receive<'i'>(delay, path, params->smooth); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/volume_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Volume); + if (params) { + client.receive<'i'>(delay, path, params->curve); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/pan", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.pan * 100.0f); + } break; + + MATCH("/region&/pan_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto value = region.ccModDepth(indices[1], ModId::Pan); + if (value) { + client.receive<'f'>(delay, path, *value); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/pan_stepcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Pan); + if (params) { + client.receive<'f'>(delay, path, params->step); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/pan_smoothcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Pan); + if (params) { + client.receive<'i'>(delay, path, params->smooth); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/pan_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Pan); + if (params) { + client.receive<'i'>(delay, path, params->curve); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/width", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.width * 100.0f); + } break; + + MATCH("/region&/width_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto value = region.ccModDepth(indices[1], ModId::Width); + if (value) { + client.receive<'f'>(delay, path, *value); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/width_stepcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Width); + if (params) { + client.receive<'f'>(delay, path, params->step); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/width_smoothcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Width); + if (params) { + client.receive<'i'>(delay, path, params->smooth); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/width_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Width); + if (params) { + client.receive<'i'>(delay, path, params->curve); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/position", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.position * 100.0f); + } break; + + MATCH("/region&/position_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto value = region.ccModDepth(indices[1], ModId::Position); + if (value) { + client.receive<'f'>(delay, path, *value); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/position_stepcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Position); + if (params) { + client.receive<'f'>(delay, path, params->step); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/position_smoothcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Position); + if (params) { + client.receive<'i'>(delay, path, params->smooth); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/position_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Position); + if (params) { + client.receive<'i'>(delay, path, params->curve); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/amplitude", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitude * 100.0f); + } break; + + MATCH("/region&/amplitude_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto value = region.ccModDepth(indices[1], ModId::Amplitude); + if (value) { + client.receive<'f'>(delay, path, *value); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/amplitude_stepcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Amplitude); + if (params) { + client.receive<'f'>(delay, path, params->step); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/amplitude_smoothcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Amplitude); + if (params) { + client.receive<'i'>(delay, path, params->smooth); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/amplitude_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Amplitude); + if (params) { + client.receive<'i'>(delay, path, params->curve); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/amp_keycenter", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.ampKeycenter); + } break; + + MATCH("/region&/amp_keytrack", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.ampKeytrack); + } break; + + MATCH("/region&/amp_veltrack", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.ampVeltrack * 100.0f); + } break; + + MATCH("/region&/amp_random", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.ampRandom); + } break; + + MATCH("/region&/xfin_key_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].i = region.crossfadeKeyInRange.getStart(); + args[1].i = region.crossfadeKeyInRange.getEnd(); + client.receive(delay, path, "ii", args); + } break; + + MATCH("/region&/xfout_key_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].i = region.crossfadeKeyOutRange.getStart(); + args[1].i = region.crossfadeKeyOutRange.getEnd(); + client.receive(delay, path, "ii", args); + } break; + + MATCH("/region&/xfin_vel_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.crossfadeVelInRange.getStart(); + args[1].f = region.crossfadeVelInRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/xfout_vel_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.crossfadeVelOutRange.getStart(); + args[1].f = region.crossfadeVelOutRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + + MATCH("/region&/xfin_cc_range&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto range = region.crossfadeCCInRange.get(indices[1]); + if (range) { + sfizz_arg_t args[2]; + args[0].f = range->getStart(); + args[1].f = range->getEnd(); + client.receive(delay, path, "ff", args); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/xfout_cc_range&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto range = region.crossfadeCCOutRange.get(indices[1]); + if (range) { + sfizz_arg_t args[2]; + args[0].f = range->getStart(); + args[1].f = range->getEnd(); + client.receive(delay, path, "ff", args); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/xf_keycurve", "") { + GET_REGION_OR_BREAK(indices[0]) + switch (region.crossfadeKeyCurve) { + case SfzCrossfadeCurve::gain: + client.receive<'s'>(delay, path, "gain"); + break; + case SfzCrossfadeCurve::power: + client.receive<'s'>(delay, path, "power"); + break; + } + } break; + + MATCH("/region&/xf_velcurve", "") { + GET_REGION_OR_BREAK(indices[0]) + switch (region.crossfadeVelCurve) { + case SfzCrossfadeCurve::gain: + client.receive<'s'>(delay, path, "gain"); + break; + case SfzCrossfadeCurve::power: + client.receive<'s'>(delay, path, "power"); + break; + } + } break; + + MATCH("/region&/xf_cccurve", "") { + GET_REGION_OR_BREAK(indices[0]) + switch (region.crossfadeCCCurve) { + case SfzCrossfadeCurve::gain: + client.receive<'s'>(delay, path, "gain"); + break; + case SfzCrossfadeCurve::power: + client.receive<'s'>(delay, path, "power"); + break; + } + } break; + + MATCH("/region&/global_volume", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.globalVolume); + } break; + + MATCH("/region&/master_volume", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.masterVolume); + } break; + + MATCH("/region&/group_volume", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.groupVolume); + } break; + + MATCH("/region&/global_amplitude", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.globalAmplitude * 100.0f); + } break; + + MATCH("/region&/master_amplitude", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.masterAmplitude * 100.0f); + } break; + + MATCH("/region&/group_amplitude", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.groupAmplitude * 100.0f); + } break; + + MATCH("/region&/pitch_keytrack", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.pitchKeytrack); + } break; + + MATCH("/region&/pitch_veltrack", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.pitchVeltrack); + } break; + + MATCH("/region&/pitch_random", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.pitchRandom); + } break; + + MATCH("/region&/transpose", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.transpose); + } break; + + MATCH("/region&/tune", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.tune); + } break; + + MATCH("/region&/tune_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto value = region.ccModDepth(indices[1], ModId::Pitch); + if (value) { + client.receive<'f'>(delay, path, *value); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/tune_stepcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Pitch); + if (params) { + client.receive<'f'>(delay, path, params->step); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/tune_smoothcc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Pitch); + if (params) { + client.receive<'i'>(delay, path, params->smooth); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/tune_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto params = region.ccModParameters(indices[1], ModId::Pitch); + if (params) { + client.receive<'i'>(delay, path, params->curve); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/bend_up", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.bendUp); + } break; + + MATCH("/region&/bend_down", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.bendDown); + } break; + + MATCH("/region&/bend_step", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.bendStep); + } break; + + MATCH("/region&/bend_smooth", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.bendSmooth); + } break; + + MATCH("/region&/ampeg_attack", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.attack); + } break; + + MATCH("/region&/ampeg_delay", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.delay); + } break; + + MATCH("/region&/ampeg_decay", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.decay); + } break; + + MATCH("/region&/ampeg_hold", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.hold); + } break; + + MATCH("/region&/ampeg_release", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.release); + } break; + + MATCH("/region&/ampeg_start", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.start); + } break; + + MATCH("/region&/ampeg_sustain", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.amplitudeEG.sustain); + } break; + + MATCH("/region&/ampeg_depth", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.amplitudeEG.depth); + } break; + + MATCH("/region&/ampeg_vel&attack", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'f'>(delay, path, region.amplitudeEG.vel2attack); + } break; + + MATCH("/region&/ampeg_vel&delay", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'f'>(delay, path, region.amplitudeEG.vel2delay); + } break; + + MATCH("/region&/ampeg_vel&decay", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'f'>(delay, path, region.amplitudeEG.vel2decay); + } break; + + MATCH("/region&/ampeg_vel&hold", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'f'>(delay, path, region.amplitudeEG.vel2hold); + } break; + + MATCH("/region&/ampeg_vel&release", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'f'>(delay, path, region.amplitudeEG.vel2release); + } break; + + MATCH("/region&/ampeg_vel&sustain", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'f'>(delay, path, region.amplitudeEG.vel2sustain); + } break; + + MATCH("/region&/ampeg_vel&depth", "") { + GET_REGION_OR_BREAK(indices[0]) + if (indices[1] != 2) + break; + client.receive<'i'>(delay, path, region.amplitudeEG.vel2depth); + } break; + + MATCH("/region&/note_polyphony", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.notePolyphony) { + client.receive<'i'>(delay, path, *region.notePolyphony); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/note_selfmask", "") { + GET_REGION_OR_BREAK(indices[0]) + switch(region.selfMask) { + case SfzSelfMask::mask: + client.receive(delay, path, "T", nullptr); + break; + case SfzSelfMask::dontMask: + client.receive(delay, path, "F", nullptr); + break; + } + } break; + + MATCH("/region&/rt_dead", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.rtDead) { + client.receive(delay, path, "T", nullptr); + } else { + client.receive(delay, path, "F", nullptr); + } + } break; + + MATCH("/region&/sustain_sw", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.checkSustain) { + client.receive(delay, path, "T", nullptr); + } else { + client.receive(delay, path, "F", nullptr); + } + } break; + + MATCH("/region&/sostenuto_sw", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.checkSostenuto) { + client.receive(delay, path, "T", nullptr); + } else { + client.receive(delay, path, "F", nullptr); + } + } break; + + MATCH("/region&/sustain_cc", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.sustainCC); + } break; + + MATCH("/region&/sustain_lo", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.sustainThreshold); + } break; + + MATCH("/region&/oscillator_phase", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.oscillatorPhase); + } break; + + MATCH("/region&/effect&", "") { + GET_REGION_OR_BREAK(indices[0]) + auto effectIdx = indices[1]; + if (indices[1] == 0) + break; + + if (effectIdx < region.gainToEffect.size()) + client.receive<'f'>(delay, path, region.gainToEffect[effectIdx] * 100.0f); + } break; + + MATCH("/region&/ampeg_attack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + MATCH("/region&/ampeg_decay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + MATCH("/region&/ampeg_delay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + MATCH("/region&/ampeg_hold_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + MATCH("/region&/ampeg_release_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + MATCH("/region&/ampeg_start_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + MATCH("/region&/ampeg_sustain_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + float value = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, value); + } break; + + #define GET_FILTER_OR_BREAK(idx) \ + if (idx >= region.filters.size()) \ + break; \ + const auto& filter = region.filters[idx]; + + MATCH("/region&/filter&/cutoff", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + client.receive<'f'>(delay, path, filter.cutoff); + } break; + + MATCH("/region&/filter&/resonance", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + client.receive<'f'>(delay, path, filter.resonance); + } break; + + MATCH("/region&/filter&/gain", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + client.receive<'f'>(delay, path, filter.gain); + } break; + + MATCH("/region&/filter&/keycenter", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + client.receive<'i'>(delay, path, filter.keycenter); + } break; + + MATCH("/region&/filter&/keytrack", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + client.receive<'i'>(delay, path, filter.keytrack); + } break; + + MATCH("/region&/filter&/veltrack", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + client.receive<'i'>(delay, path, filter.veltrack); + } break; + + MATCH("/region&/filter&/type", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + switch (filter.type) { + case FilterType::kFilterLpf1p: client.receive<'s'>(delay, path, "lpf_1p"); break; + case FilterType::kFilterHpf1p: client.receive<'s'>(delay, path, "hpf_1p"); break; + case FilterType::kFilterLpf2p: client.receive<'s'>(delay, path, "lpf_2p"); break; + case FilterType::kFilterHpf2p: client.receive<'s'>(delay, path, "hpf_2p"); break; + case FilterType::kFilterBpf2p: client.receive<'s'>(delay, path, "bpf_2p"); break; + case FilterType::kFilterBrf2p: client.receive<'s'>(delay, path, "brf_2p"); break; + case FilterType::kFilterBpf1p: client.receive<'s'>(delay, path, "bpf_1p"); break; + case FilterType::kFilterBrf1p: client.receive<'s'>(delay, path, "brf_1p"); break; + case FilterType::kFilterApf1p: client.receive<'s'>(delay, path, "apf_1p"); break; + case FilterType::kFilterLpf2pSv: client.receive<'s'>(delay, path, "lpf_2p_sv"); break; + case FilterType::kFilterHpf2pSv: client.receive<'s'>(delay, path, "hpf_2p_sv"); break; + case FilterType::kFilterBpf2pSv: client.receive<'s'>(delay, path, "bpf_2p_sv"); break; + case FilterType::kFilterBrf2pSv: client.receive<'s'>(delay, path, "brf_2p_sv"); break; + case FilterType::kFilterLpf4p: client.receive<'s'>(delay, path, "lpf_4p"); break; + case FilterType::kFilterHpf4p: client.receive<'s'>(delay, path, "hpf_4p"); break; + case FilterType::kFilterLpf6p: client.receive<'s'>(delay, path, "lpf_6p"); break; + case FilterType::kFilterHpf6p: client.receive<'s'>(delay, path, "hpf_6p"); break; + case FilterType::kFilterPink: client.receive<'s'>(delay, path, "pink"); break; + case FilterType::kFilterLsh: client.receive<'s'>(delay, path, "lsh"); break; + case FilterType::kFilterHsh: client.receive<'s'>(delay, path, "hsh"); break; + case FilterType::kFilterPeq: client.receive<'s'>(delay, path, "peq"); break; + case FilterType::kFilterBpf4p: client.receive<'s'>(delay, path, "bpf_4p"); break; + case FilterType::kFilterBpf6p: client.receive<'s'>(delay, path, "bpf_6p"); break; + case FilterType::kFilterNone: client.receive<'s'>(delay, path, "none"); break; + } + } break; + + #undef GET_FILTER_OR_BREAK + + #define GET_EQ_OR_BREAK(idx) \ + if (idx >= region.equalizers.size()) \ + break; \ + const auto& eq = region.equalizers[idx]; + + MATCH("/region&/eq&/gain", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EQ_OR_BREAK(indices[1]) + client.receive<'f'>(delay, path, eq.gain); + } break; + + MATCH("/region&/eq&/bandwidth", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EQ_OR_BREAK(indices[1]) + client.receive<'f'>(delay, path, eq.bandwidth); + } break; + + MATCH("/region&/eq&/frequency", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EQ_OR_BREAK(indices[1]) + client.receive<'f'>(delay, path, eq.frequency); + } break; + + MATCH("/region&/eq&/vel&freq", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EQ_OR_BREAK(indices[1]) + if (indices[2] != 2) + break; + client.receive<'f'>(delay, path, eq.vel2frequency); + } break; + + MATCH("/region&/eq&/vel&gain", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EQ_OR_BREAK(indices[1]) + if (indices[2] != 2) + break; + client.receive<'f'>(delay, path, eq.vel2gain); + } break; + + MATCH("/region&/eq&/type", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EQ_OR_BREAK(indices[1]) + switch (eq.type) { + case EqType::kEqNone: client.receive<'s'>(delay, path, "none"); break; + case EqType::kEqPeak: client.receive<'s'>(delay, path, "peak"); break; + case EqType::kEqLowShelf: client.receive<'s'>(delay, path, "lshelf"); break; + case EqType::kEqHighShelf: client.receive<'s'>(delay, path, "hshelf"); break; + } + } break; + + #undef GET_EQ_OR_BREAK + + #undef GET_REGION_OR_BREAK + #undef MATCH // TODO... } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8e562df8..afee85a8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,8 @@ project(sfizz) set(SFIZZ_TEST_SOURCES - RegionT.cpp + DirectRegionT.cpp + RegionValuesT.cpp TestHelpers.h TestHelpers.cpp ParsingT.cpp diff --git a/tests/DirectRegionT.cpp b/tests/DirectRegionT.cpp new file mode 100644 index 00000000..2ae1d821 --- /dev/null +++ b/tests/DirectRegionT.cpp @@ -0,0 +1,112 @@ +// 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 "TestHelpers.h" +#include "sfizz/MidiState.h" +#include "sfizz/Region.h" +#include "sfizz/SfzHelpers.h" +#include "sfizz/modulations/ModId.h" +#include "sfizz/modulations/ModKey.h" +#include "catch2/catch.hpp" +#include +using namespace Catch::literals; +using namespace sfz::literals; +using namespace sfz; + +TEST_CASE("[Direct Region Tests] amp_velcurve") +{ + MidiState midiState; + Region region { 0, midiState }; + region.parseOpcode({ "amp_velcurve_6", "0.4" }); + REQUIRE(region.velocityPoints.back() == std::pair(6, 0.4f)); + region.parseOpcode({ "amp_velcurve_127", "-1.0" }); + REQUIRE(region.velocityPoints.back() == std::pair(127, 0.0f)); + region.parseOpcode({ "amp_velcurve_008", "0.3" }); + REQUIRE(region.velocityPoints.back() == std::pair(8, 0.3f)); + region.parseOpcode({ "amp_velcurve_064", "0.9" }); + REQUIRE(region.velocityPoints.back() == std::pair(64, 0.9f)); +} + +TEST_CASE("[Direct Region Tests] Release and release key") +{ + MidiState midiState; + Region region { 0, midiState }; + region.parseOpcode({ "lokey", "63" }); + region.parseOpcode({ "hikey", "65" }); + region.parseOpcode({ "sample", "*sine" }); + SECTION("Release key without sustain") + { + region.parseOpcode({ "trigger", "release_key" }); + midiState.ccEvent(0, 64, 0.0f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + } + SECTION("Release key with sustain") + { + region.parseOpcode({ "trigger", "release_key" }); + midiState.ccEvent(0, 64, 1.0f); + REQUIRE( !region.registerCC(64, 1.0f) ); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + } + + SECTION("Release without sustain") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 0.0f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + } + + SECTION("Release with sustain") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 1.0f); + midiState.noteOnEvent(0, 63, 0.5f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) ); + REQUIRE( region.delayedReleases.size() == 1 ); + std::vector> expected = { + { 63, 0.5f } + }; + REQUIRE( region.delayedReleases == expected ); + } + + SECTION("Release with sustain and 2 notes") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 1.0f); + midiState.noteOnEvent(0, 63, 0.5f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + midiState.noteOnEvent(0, 64, 0.6f); + REQUIRE( !region.registerNoteOn(64, 0.6f, 0.0f) ); + REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) ); + REQUIRE( !region.registerNoteOff(64, 0.2f, 0.0f) ); + REQUIRE( region.delayedReleases.size() == 2 ); + std::vector> expected = { + { 63, 0.5f }, + { 64, 0.6f } + }; + REQUIRE( region.delayedReleases == expected ); + } + + SECTION("Release with sustain and 2 notes but 1 outside") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 1.0f); + midiState.noteOnEvent(0, 63, 0.5f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + midiState.noteOnEvent(0, 66, 0.6f); + REQUIRE( !region.registerNoteOn(66, 0.6f, 0.0f) ); + REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) ); + REQUIRE( !region.registerNoteOff(66, 0.2f, 0.0f) ); + REQUIRE( region.delayedReleases.size() == 1 ); + std::vector> expected = { + { 63, 0.5f } + }; + REQUIRE( region.delayedReleases == expected ); + } +} diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp deleted file mode 100644 index 312d17a0..00000000 --- a/tests/RegionT.cpp +++ /dev/null @@ -1,1894 +0,0 @@ -// 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 "TestHelpers.h" -#include "sfizz/MidiState.h" -#include "sfizz/Region.h" -#include "sfizz/SfzHelpers.h" -#include "sfizz/modulations/ModId.h" -#include "sfizz/modulations/ModKey.h" -#include "catch2/catch.hpp" -#include -using namespace Catch::literals; -using namespace sfz::literals; -using namespace sfz; - -TEST_CASE("[Region] Parsing opcodes") -{ - MidiState midiState; - Region region { 0, midiState }; - - SECTION("sample") - { - REQUIRE(region.sampleId->filename() == ""); - region.parseOpcode({ "sample", "dummy.wav" }); - REQUIRE(region.sampleId->filename() == "dummy.wav"); - } - - SECTION("direction") - { - REQUIRE(!region.sampleId->isReverse()); - region.parseOpcode({ "direction", "reverse" }); - REQUIRE(region.sampleId->isReverse()); - region.parseOpcode({ "direction", "forward" }); - REQUIRE(!region.sampleId->isReverse()); - } - - SECTION("delay") - { - REQUIRE(region.delay == 0.0); - region.parseOpcode({ "delay", "1.0" }); - REQUIRE(region.delay == 1.0); - region.parseOpcode({ "delay", "-1.0" }); - REQUIRE(region.delay == 0.0); - region.parseOpcode({ "delay", "110.0" }); - REQUIRE(region.delay == 100.0); - } - - SECTION("delay_random") - { - REQUIRE(region.delayRandom == 0.0); - region.parseOpcode({ "delay_random", "1.0" }); - REQUIRE(region.delayRandom == 1.0); - region.parseOpcode({ "delay_random", "-1.0" }); - REQUIRE(region.delayRandom == 0.0); - region.parseOpcode({ "delay_random", "110.0" }); - REQUIRE(region.delayRandom == 100.0); - } - - SECTION("offset") - { - REQUIRE(region.offset == 0); - region.parseOpcode({ "offset", "1" }); - REQUIRE(region.offset == 1); - region.parseOpcode({ "offset", "-1" }); - REQUIRE(region.offset == 0); - } - SECTION("offset_cc") - { - REQUIRE(region.offsetCC.empty()); - region.parseOpcode({ "offset_cc1", "1" }); - REQUIRE(region.offsetCC.contains(1)); - REQUIRE(region.offsetCC[1] == 1); - region.parseOpcode({ "offset_cc2", "15420" }); - REQUIRE(region.offsetCC.contains(2)); - REQUIRE(region.offsetCC[2] == 15420); - region.parseOpcode({ "offset_cc2", "-1" }); - REQUIRE(region.offsetCC[2] == 0); - } - - - SECTION("offset_random") - { - REQUIRE(region.offsetRandom == 0); - region.parseOpcode({ "offset_random", "1" }); - REQUIRE(region.offsetRandom == 1); - region.parseOpcode({ "offset_random", "-1" }); - REQUIRE(region.offsetRandom == 0); - } - - SECTION("end") - { - region.parseOpcode({ "end", "184" }); - REQUIRE(region.sampleEnd == 184); - region.parseOpcode({ "end", "-1" }); - REQUIRE(region.disabled()); - region.parseOpcode({ "end", "2" }); - REQUIRE(!region.disabled()); - REQUIRE(region.sampleEnd == 2); - region.parseOpcode({ "end", "0" }); - REQUIRE(region.disabled()); - } - - SECTION("count") - { - REQUIRE(!region.sampleCount); - region.parseOpcode({ "count", "184" }); - REQUIRE(region.sampleCount); - REQUIRE(*region.sampleCount == 184); - region.parseOpcode({ "count", "-1" }); - REQUIRE(region.sampleCount); - REQUIRE(*region.sampleCount == 0); - } - - SECTION("loop_mode") - { - REQUIRE( !region.loopMode ); - region.parseOpcode({ "loop_mode", "no_loop" }); - REQUIRE(region.loopMode == SfzLoopMode::no_loop); - region.parseOpcode({ "loop_mode", "one_shot" }); - REQUIRE(region.loopMode == SfzLoopMode::one_shot); - region.parseOpcode({ "loop_mode", "loop_continuous" }); - REQUIRE(region.loopMode == SfzLoopMode::loop_continuous); - region.parseOpcode({ "loop_mode", "loop_sustain" }); - REQUIRE(region.loopMode == SfzLoopMode::loop_sustain); - } - - SECTION("loopmode") - { - REQUIRE( !region.loopMode ); - region.parseOpcode({ "loopmode", "no_loop" }); - REQUIRE(region.loopMode == SfzLoopMode::no_loop); - region.parseOpcode({ "loopmode", "one_shot" }); - REQUIRE(region.loopMode == SfzLoopMode::one_shot); - region.parseOpcode({ "loopmode", "loop_continuous" }); - REQUIRE(region.loopMode == SfzLoopMode::loop_continuous); - region.parseOpcode({ "loopmode", "loop_sustain" }); - REQUIRE(region.loopMode == SfzLoopMode::loop_sustain); - } - - SECTION("loop_end") - { - REQUIRE(region.loopRange == Range(0, 4294967295)); - region.parseOpcode({ "loop_end", "184" }); - REQUIRE(region.loopRange == Range(0, 184)); - region.parseOpcode({ "loop_end", "-1" }); - REQUIRE(region.loopRange == Range(0, 0)); - } - - SECTION("loop_start") - { - region.parseOpcode({ "loop_start", "184" }); - REQUIRE(region.loopRange == Range(184, 4294967295)); - region.parseOpcode({ "loop_start", "-1" }); - REQUIRE(region.loopRange == Range(0, 4294967295)); - } - - SECTION("loopend") - { - REQUIRE(region.loopRange == Range(0, 4294967295)); - region.parseOpcode({ "loopend", "184" }); - REQUIRE(region.loopRange == Range(0, 184)); - region.parseOpcode({ "loopend", "-1" }); - REQUIRE(region.loopRange == Range(0, 0)); - } - - SECTION("loopstart") - { - region.parseOpcode({ "loopstart", "184" }); - REQUIRE(region.loopRange == Range(184, 4294967295)); - region.parseOpcode({ "loopstart", "-1" }); - REQUIRE(region.loopRange == Range(0, 4294967295)); - } - - SECTION("loop_crossfade") - { - region.parseOpcode({ "loop_crossfade", "0.5" }); - REQUIRE(region.loopCrossfade == Approx(0.5f)); - region.parseOpcode({ "loop_crossfade", "0" }); - REQUIRE(region.loopCrossfade > 0); - } - - SECTION("group") - { - REQUIRE(region.group == 0); - region.parseOpcode({ "group", "5" }); - REQUIRE(region.group == 5); - region.parseOpcode({ "group", "-1" }); - REQUIRE(region.group == 0); - } - - SECTION("off_by") - { - REQUIRE(!region.offBy); - region.parseOpcode({ "off_by", "5" }); - REQUIRE(region.offBy); - REQUIRE(*region.offBy == 5); - region.parseOpcode({ "off_by", "-1" }); - REQUIRE(!region.offBy); - } - - SECTION("off_mode") - { - REQUIRE(region.offMode == SfzOffMode::fast); - region.parseOpcode({ "off_mode", "fast" }); - REQUIRE(region.offMode == SfzOffMode::fast); - region.parseOpcode({ "off_mode", "normal" }); - REQUIRE(region.offMode == SfzOffMode::normal); - region.parseOpcode({ "off_mode", "time" }); - REQUIRE(region.offMode == SfzOffMode::time); - } - - SECTION("off_time") - { - REQUIRE(region.offTime == 0.006f); - REQUIRE(region.offMode == SfzOffMode::fast); - region.parseOpcode({ "off_time", "0.1" }); - REQUIRE(region.offTime == 0.1f); - REQUIRE(region.offMode == SfzOffMode::time); - region.parseOpcode({ "off_time", "0" }); - REQUIRE(region.offTime == 0.0f); - region.parseOpcode({ "off_time", "0.1" }); - region.parseOpcode({ "off_time", "-1" }); - REQUIRE(region.offTime == 0.0f); - } - - SECTION("lokey, hikey, and key") - { - REQUIRE(region.keyRange == Range(0, 127)); - region.parseOpcode({ "lokey", "37" }); - REQUIRE(region.keyRange == Range(37, 127)); - region.parseOpcode({ "lokey", "c4" }); - REQUIRE(region.keyRange == Range(60, 127)); - region.parseOpcode({ "lokey", "128" }); - REQUIRE(region.keyRange == Range(127, 127)); - region.parseOpcode({ "lokey", "-3" }); - REQUIRE(region.keyRange == Range(0, 127)); - region.parseOpcode({ "hikey", "65" }); - REQUIRE(region.keyRange == Range(0, 65)); - region.parseOpcode({ "hikey", "c4" }); - REQUIRE(region.keyRange == Range(0, 60)); - region.parseOpcode({ "hikey", "-1" }); - REQUIRE(region.keyRange == Range(0, 0)); - region.parseOpcode({ "hikey", "128" }); - REQUIRE(region.keyRange == Range(0, 127)); - region.parseOpcode({ "key", "26" }); - REQUIRE(region.keyRange == Range(26, 26)); - REQUIRE(region.pitchKeycenter == 26); - region.parseOpcode({ "key", "-26" }); - REQUIRE(region.keyRange == Range(0, 0)); - REQUIRE(region.pitchKeycenter == 0); - region.parseOpcode({ "key", "234" }); - REQUIRE(region.keyRange == Range(127, 127)); - REQUIRE(region.pitchKeycenter == 127); - region.parseOpcode({ "key", "c4" }); - REQUIRE(region.keyRange == Range(60, 60)); - REQUIRE(region.pitchKeycenter == 60); - } - - SECTION("lovel, hivel") - { - REQUIRE(region.velocityRange == Range(0_norm, 127_norm)); - region.parseOpcode({ "lovel", "37" }); - REQUIRE(region.velocityRange == Range(37_norm, 127_norm)); - region.parseOpcode({ "lovel", "128" }); - REQUIRE(region.velocityRange == Range(127_norm, 127_norm)); - region.parseOpcode({ "lovel", "-3" }); - REQUIRE(region.velocityRange == Range(0_norm, 127_norm)); - region.parseOpcode({ "hivel", "65" }); - REQUIRE(region.velocityRange == Range(0_norm, 65_norm)); - region.parseOpcode({ "hivel", "-1" }); - REQUIRE(region.velocityRange == Range(0_norm, 0_norm)); - region.parseOpcode({ "hivel", "128" }); - REQUIRE(region.velocityRange == Range(0_norm, 127_norm)); - } - - SECTION("lobend, hibend") - { - REQUIRE(region.bendRange == Range(-1.0f, 1.0f)); - region.parseOpcode({ "lobend", "400" }); - REQUIRE(region.bendRange.getStart() == Approx(normalizeBend(400))); - REQUIRE(region.bendRange.getEnd() == 1.0_a); - region.parseOpcode({ "lobend", "-128" }); - REQUIRE(region.bendRange.getStart() == Approx(normalizeBend(-128))); - REQUIRE(region.bendRange.getEnd() == 1.0_a); - region.parseOpcode({ "lobend", "-10000" }); - REQUIRE(region.bendRange == Range(-1.0f, 1.0f)); - region.parseOpcode({ "hibend", "13" }); - REQUIRE(region.bendRange.getStart() == -1.0_a); - REQUIRE(region.bendRange.getEnd() == Approx(normalizeBend(13))); - region.parseOpcode({ "hibend", "-1" }); - REQUIRE(region.bendRange.getStart() == -1.0_a); - REQUIRE(region.bendRange.getEnd() == Approx(normalizeBend(-1))); - region.parseOpcode({ "hibend", "10000" }); - REQUIRE(region.bendRange == Range(-1.0f, 1.0f)); - } - - SECTION("locc, hicc") - { - REQUIRE(region.ccConditions.getWithDefault(0) == Range(0_norm, 127_norm)); - REQUIRE(region.ccConditions[127] == Range(0_norm, 127_norm)); - region.parseOpcode({ "locc6", "4" }); - REQUIRE(region.ccConditions[6] == Range(4_norm, 127_norm)); - region.parseOpcode({ "locc12", "-128" }); - REQUIRE(region.ccConditions[12] == Range(0_norm, 127_norm)); - region.parseOpcode({ "hicc65", "39" }); - REQUIRE(region.ccConditions[65] == Range(0_norm, 39_norm)); - region.parseOpcode({ "hicc127", "135" }); - REQUIRE(region.ccConditions[127] == Range(0_norm, 127_norm)); - } - - SECTION("lohdcc, hihdcc") - { - region.parseOpcode({ "lohdcc7", "0.12" }); - REQUIRE(region.ccConditions[7].getStart() == Approx(0.12f)); - REQUIRE(region.ccConditions[7].getEnd() == 1.0f); - region.parseOpcode({ "lohdcc13", "-1.0" }); - REQUIRE(region.ccConditions[13] == sfz::Range(0.0f, 1.0f)); - region.parseOpcode({ "hihdcc64", "0.45" }); - REQUIRE(region.ccConditions[64].getStart() == 0.0f); - REQUIRE(region.ccConditions[64].getEnd() == Approx(0.45f)); - region.parseOpcode({ "hihdcc126", "1.2" }); - REQUIRE(region.ccConditions[126] == sfz::Range(0.0f, 1.0f)); - } - - SECTION("lorealcc, hirealcc") - { - region.parseOpcode({ "lorealcc8", "0.12" }); - REQUIRE(region.ccConditions[8].getStart() == Approx(0.12f)); - REQUIRE(region.ccConditions[8].getEnd() == 1.0f); - region.parseOpcode({ "lorealcc14", "-1.0" }); - REQUIRE(region.ccConditions[14] == sfz::Range(0.0f, 1.0f)); - region.parseOpcode({ "hirealcc63", "0.45" }); - REQUIRE(region.ccConditions[63].getStart() == 0.0f); - REQUIRE(region.ccConditions[63].getEnd() == Approx(0.45f)); - region.parseOpcode({ "hirealcc125", "1.2" }); - REQUIRE(region.ccConditions[125] == sfz::Range(0.0f, 1.0f)); - } - - SECTION("sw_label") - { - REQUIRE(!region.keyswitchLabel); - region.parseOpcode({ "sw_label", "note" }); - REQUIRE(region.keyswitchLabel == "note"); - region.parseOpcode({ "sw_label", "ring" }); - REQUIRE(region.keyswitchLabel == "ring"); - } - - SECTION("sw_last") - { - REQUIRE(!region.lastKeyswitch); - region.parseOpcode({ "sw_last", "4" }); - REQUIRE(region.lastKeyswitch); - REQUIRE(*region.lastKeyswitch == 4); - region.parseOpcode({ "sw_last", "128" }); - REQUIRE(region.lastKeyswitch); - REQUIRE(*region.lastKeyswitch == 127); - region.parseOpcode({ "sw_last", "-1" }); - REQUIRE(region.lastKeyswitch); - REQUIRE(*region.lastKeyswitch == 0); - } - - SECTION("sw_lolast/hilast") - { - REQUIRE(!region.lastKeyswitchRange); - region.parseOpcode({ "sw_lolast", "4" }); - REQUIRE(region.lastKeyswitchRange); - REQUIRE(*region.lastKeyswitchRange == Range(4, 4)); - region.parseOpcode({ "sw_hilast", "128" }); - REQUIRE(*region.lastKeyswitchRange == Range(4, 127)); - region.parseOpcode({ "sw_hilast", "63" }); - REQUIRE(*region.lastKeyswitchRange == Range(4, 63)); - region.parseOpcode({ "sw_lolast", "64" }); - REQUIRE(*region.lastKeyswitchRange == Range(64, 64)); - region.parseOpcode({ "sw_lolast", "-1" }); - REQUIRE(*region.lastKeyswitchRange == Range(0, 64)); - } - - SECTION("sw_hilast disables sw_last") - { - REQUIRE(!region.lastKeyswitchRange); - REQUIRE(!region.lastKeyswitch); - region.parseOpcode({ "sw_last", "4" }); - REQUIRE(region.lastKeyswitch); - region.parseOpcode({ "sw_hilast", "63" }); - REQUIRE(region.lastKeyswitchRange); - REQUIRE(!region.lastKeyswitch); - region.parseOpcode({ "sw_last", "4" }); - REQUIRE(!region.lastKeyswitch); - } - - SECTION("sw_lolast disables sw_last") - { - REQUIRE(!region.lastKeyswitchRange); - REQUIRE(!region.lastKeyswitch); - region.parseOpcode({ "sw_last", "4" }); - REQUIRE(region.lastKeyswitch); - region.parseOpcode({ "sw_lolast", "63" }); - REQUIRE(region.lastKeyswitchRange); - REQUIRE(!region.lastKeyswitch); - region.parseOpcode({ "sw_last", "4" }); - REQUIRE(!region.lastKeyswitch); - } - - SECTION("sw_up") - { - REQUIRE(!region.upKeyswitch); - region.parseOpcode({ "sw_up", "4" }); - REQUIRE(region.upKeyswitch); - REQUIRE(*region.upKeyswitch == 4); - region.parseOpcode({ "sw_up", "128" }); - REQUIRE(region.upKeyswitch); - REQUIRE(*region.upKeyswitch == 127); - region.parseOpcode({ "sw_up", "-1" }); - REQUIRE(region.upKeyswitch); - REQUIRE(*region.upKeyswitch == 0); - } - - SECTION("sw_down") - { - REQUIRE(!region.downKeyswitch); - region.parseOpcode({ "sw_down", "4" }); - REQUIRE(region.downKeyswitch); - REQUIRE(*region.downKeyswitch == 4); - region.parseOpcode({ "sw_down", "128" }); - REQUIRE(region.downKeyswitch); - REQUIRE(*region.downKeyswitch == 127); - region.parseOpcode({ "sw_down", "-1" }); - REQUIRE(region.downKeyswitch); - REQUIRE(*region.downKeyswitch == 0); - } - - SECTION("sw_previous") - { - REQUIRE(!region.previousKeyswitch); - region.parseOpcode({ "sw_previous", "4" }); - REQUIRE(region.previousKeyswitch); - REQUIRE(*region.previousKeyswitch == 4); - region.parseOpcode({ "sw_previous", "128" }); - REQUIRE(region.previousKeyswitch); - REQUIRE(*region.previousKeyswitch == 127); - region.parseOpcode({ "sw_previous", "-1" }); - REQUIRE(region.previousKeyswitch); - REQUIRE(*region.previousKeyswitch == 0); - } - - SECTION("sw_vel") - { - REQUIRE(region.velocityOverride == SfzVelocityOverride::current); - region.parseOpcode({ "sw_vel", "current" }); - REQUIRE(region.velocityOverride == SfzVelocityOverride::current); - region.parseOpcode({ "sw_vel", "previous" }); - REQUIRE(region.velocityOverride == SfzVelocityOverride::previous); - } - - SECTION("lochanaft, hichanaft") - { - REQUIRE(region.aftertouchRange == Range(0, 127)); - region.parseOpcode({ "lochanaft", "4" }); - REQUIRE(region.aftertouchRange == Range(4, 127)); - region.parseOpcode({ "lochanaft", "128" }); - REQUIRE(region.aftertouchRange == Range(127, 127)); - region.parseOpcode({ "lochanaft", "0" }); - REQUIRE(region.aftertouchRange == Range(0, 127)); - region.parseOpcode({ "hichanaft", "39" }); - REQUIRE(region.aftertouchRange == Range(0, 39)); - region.parseOpcode({ "hichanaft", "135" }); - REQUIRE(region.aftertouchRange == Range(0, 127)); - region.parseOpcode({ "hichanaft", "-1" }); - REQUIRE(region.aftertouchRange == Range(0, 0)); - } - - SECTION("lobpm, hibpm") - { - REQUIRE(region.bpmRange == Range(0, 500)); - region.parseOpcode({ "lobpm", "47.5" }); - REQUIRE(region.bpmRange == Range(47.5, 500)); - region.parseOpcode({ "lobpm", "594" }); - REQUIRE(region.bpmRange == Range(500, 500)); - region.parseOpcode({ "lobpm", "0" }); - REQUIRE(region.bpmRange == Range(0, 500)); - region.parseOpcode({ "hibpm", "78" }); - REQUIRE(region.bpmRange == Range(0, 78)); - region.parseOpcode({ "hibpm", "895.4" }); - REQUIRE(region.bpmRange == Range(0, 500)); - region.parseOpcode({ "hibpm", "-1" }); - REQUIRE(region.bpmRange == Range(0, 0)); - } - - SECTION("lorand, hirand") - { - REQUIRE(region.randRange == Range(0, 1)); - region.parseOpcode({ "lorand", "0.5" }); - REQUIRE(region.randRange == Range(0.5, 1)); - region.parseOpcode({ "lorand", "4" }); - REQUIRE(region.randRange == Range(1, 1)); - region.parseOpcode({ "lorand", "0" }); - REQUIRE(region.randRange == Range(0, 1)); - region.parseOpcode({ "hirand", "39" }); - REQUIRE(region.randRange == Range(0, 1)); - region.parseOpcode({ "hirand", "0.7" }); - REQUIRE(region.randRange == Range(0, 0.7f)); - region.parseOpcode({ "hirand", "-1" }); - REQUIRE(region.randRange == Range(0, 0)); - } - - SECTION("seq_length") - { - REQUIRE(region.sequenceLength == 1); - region.parseOpcode({ "seq_length", "89" }); - REQUIRE(region.sequenceLength == 89); - region.parseOpcode({ "seq_length", "189" }); - REQUIRE(region.sequenceLength == 100); - region.parseOpcode({ "seq_length", "-1" }); - REQUIRE(region.sequenceLength == 1); - } - - SECTION("seq_position") - { - REQUIRE(region.sequencePosition == 1); - region.parseOpcode({ "seq_position", "89" }); - REQUIRE(region.sequencePosition == 89); - region.parseOpcode({ "seq_position", "189" }); - REQUIRE(region.sequencePosition == 100); - region.parseOpcode({ "seq_position", "-1" }); - REQUIRE(region.sequencePosition == 1); - } - - SECTION("trigger") - { - REQUIRE(region.trigger == SfzTrigger::attack); - region.parseOpcode({ "trigger", "attack" }); - REQUIRE(region.trigger == SfzTrigger::attack); - region.parseOpcode({ "trigger", "release" }); - REQUIRE(region.trigger == SfzTrigger::release); - region.parseOpcode({ "trigger", "release_key" }); - REQUIRE(region.trigger == SfzTrigger::release_key); - region.parseOpcode({ "trigger", "first" }); - REQUIRE(region.trigger == SfzTrigger::first); - region.parseOpcode({ "trigger", "legato" }); - REQUIRE(region.trigger == SfzTrigger::legato); - } - - SECTION("on_locc, on_hicc") - { - for (int ccIdx = 1; ccIdx < 128; ++ccIdx) { - REQUIRE(!region.ccTriggers.contains(ccIdx)); - } - region.parseOpcode({ "on_locc45", "15" }); - REQUIRE(region.ccTriggers.contains(45)); - REQUIRE(region.ccTriggers[45] == Range(15_norm, 127_norm)); - region.parseOpcode({ "on_hicc4", "47" }); - REQUIRE(region.ccTriggers.contains(45)); - REQUIRE(region.ccTriggers[4] == Range(0_norm, 47_norm)); - } - - SECTION("on_lohdcc, on_hihdcc") - { - for (int ccIdx = 1; ccIdx < 128; ++ccIdx) { - REQUIRE(!region.ccTriggers.contains(ccIdx)); - } - region.parseOpcode({ "on_lohdcc46", "0.15" }); - REQUIRE(region.ccTriggers.contains(46)); - REQUIRE(region.ccTriggers[46].getStart() == Approx(0.15f)); - REQUIRE(region.ccTriggers[46].getEnd() == 1.0f); - region.parseOpcode({ "on_hihdcc5", "0.47" }); - REQUIRE(region.ccTriggers.contains(5)); - REQUIRE(region.ccTriggers[5].getStart() == 0.0f); - REQUIRE(region.ccTriggers[5].getEnd() == Approx(0.47f)); - } - - SECTION("volume") - { - REQUIRE(region.volume == 0.0f); - region.parseOpcode({ "volume", "4.2" }); - REQUIRE(region.volume == 4.2f); - region.parseOpcode({ "volume", "-4.2" }); - REQUIRE(region.volume == -4.2f); - region.parseOpcode({ "volume", "-123" }); - REQUIRE(region.volume == -123.0f); - region.parseOpcode({ "volume", "-185" }); - REQUIRE(region.volume == -144.0f); - region.parseOpcode({ "volume", "79" }); - REQUIRE(region.volume == 48.0f); - } - - SECTION("pan") - { - REQUIRE(region.pan == 0.0f); - region.parseOpcode({ "pan", "4.2" }); - REQUIRE(region.pan == 0.042_a); - region.parseOpcode({ "pan", "-4.2" }); - REQUIRE(region.pan == -0.042_a); - region.parseOpcode({ "pan", "-123" }); - REQUIRE(region.pan == -1.0_a); - region.parseOpcode({ "pan", "132" }); - REQUIRE(region.pan == 1.0_a); - } - - SECTION("pan_oncc") - { - const ModKey target = ModKey::createNXYZ(ModId::Pan, region.getId()); - const RegionCCView view(region, target); - REQUIRE(view.empty()); - region.parseOpcode({ "pan_oncc45", "4.2" }); - REQUIRE(view.valueAt(45) == 4.2_a); - region.parseOpcode({ "pan_curvecc17", "18" }); - REQUIRE(view.at(17).curve == 18); - region.parseOpcode({ "pan_curvecc17", "15482" }); - REQUIRE(view.at(17).curve == 255); - region.parseOpcode({ "pan_curvecc17", "-2" }); - REQUIRE(view.at(17).curve == 0); - region.parseOpcode({ "pan_smoothcc14", "85" }); - REQUIRE(view.at(14).smooth == 85); - region.parseOpcode({ "pan_smoothcc14", "15482" }); - REQUIRE(view.at(14).smooth == 100); - region.parseOpcode({ "pan_smoothcc14", "-2" }); - REQUIRE(view.at(14).smooth == 0); - region.parseOpcode({ "pan_stepcc120", "24" }); - REQUIRE(view.at(120).step == 24.0_a); - region.parseOpcode({ "pan_stepcc120", "15482" }); - REQUIRE(view.at(120).step == 200.0_a); - region.parseOpcode({ "pan_stepcc120", "-2" }); - REQUIRE(view.at(120).step == 0.0f); - } - - SECTION("width") - { - REQUIRE(region.width == 1.0_a); - region.parseOpcode({ "width", "4.2" }); - REQUIRE(region.width == 0.042_a); - region.parseOpcode({ "width", "-4.2" }); - REQUIRE(region.width == -0.042_a); - region.parseOpcode({ "width", "-123" }); - REQUIRE(region.width == -1.0_a); - region.parseOpcode({ "width", "132" }); - REQUIRE(region.width == 1.0_a); - } - - SECTION("width_oncc") - { - const ModKey target = ModKey::createNXYZ(ModId::Width, region.getId()); - const RegionCCView view(region, target); - REQUIRE(view.empty()); - region.parseOpcode({ "width_oncc45", "4.2" }); - REQUIRE(view.valueAt(45) == 4.2_a); - region.parseOpcode({ "width_curvecc17", "18" }); - REQUIRE(view.at(17).curve == 18); - region.parseOpcode({ "width_curvecc17", "15482" }); - REQUIRE(view.at(17).curve == 255); - region.parseOpcode({ "width_curvecc17", "-2" }); - REQUIRE(view.at(17).curve == 0); - region.parseOpcode({ "width_smoothcc14", "85" }); - REQUIRE(view.at(14).smooth == 85); - region.parseOpcode({ "width_smoothcc14", "15482" }); - REQUIRE(view.at(14).smooth == 100); - region.parseOpcode({ "width_smoothcc14", "-2" }); - REQUIRE(view.at(14).smooth == 0); - region.parseOpcode({ "width_stepcc120", "24" }); - REQUIRE(view.at(120).step == 24.0_a); - region.parseOpcode({ "width_stepcc120", "15482" }); - REQUIRE(view.at(120).step == 200.0_a); - region.parseOpcode({ "width_stepcc120", "-20" }); - REQUIRE(view.at(120).step == 0.0f); - } - - SECTION("position") - { - REQUIRE(region.position == 0.0f); - region.parseOpcode({ "position", "4.2" }); - REQUIRE(region.position == 0.042_a); - region.parseOpcode({ "position", "-4.2" }); - REQUIRE(region.position == -0.042_a); - region.parseOpcode({ "position", "-123" }); - REQUIRE(region.position == -1.0_a); - region.parseOpcode({ "position", "132" }); - REQUIRE(region.position == 1.0_a); - } - - SECTION("position_oncc") - { - const ModKey target = ModKey::createNXYZ(ModId::Position, region.getId()); - const RegionCCView view(region, target); - REQUIRE(view.empty()); - region.parseOpcode({ "position_oncc45", "4.2" }); - REQUIRE(view.valueAt(45) == 4.2_a); - region.parseOpcode({ "position_curvecc17", "18" }); - REQUIRE(view.at(17).curve == 18); - region.parseOpcode({ "position_curvecc17", "15482" }); - REQUIRE(view.at(17).curve == 255); - region.parseOpcode({ "position_curvecc17", "-2" }); - REQUIRE(view.at(17).curve == 0); - region.parseOpcode({ "position_smoothcc14", "85" }); - REQUIRE(view.at(14).smooth == 85); - region.parseOpcode({ "position_smoothcc14", "15482" }); - REQUIRE(view.at(14).smooth == 100); - region.parseOpcode({ "position_smoothcc14", "-2" }); - REQUIRE(view.at(14).smooth == 0); - region.parseOpcode({ "position_stepcc120", "24" }); - REQUIRE(view.at(120).step == 24.0_a); - region.parseOpcode({ "position_stepcc120", "15482" }); - REQUIRE(view.at(120).step == 200.0_a); - region.parseOpcode({ "position_stepcc120", "-2" }); - REQUIRE(view.at(120).step == 0.0f); - } - - SECTION("amp_keycenter") - { - REQUIRE(region.ampKeycenter == 60); - region.parseOpcode({ "amp_keycenter", "40" }); - REQUIRE(region.ampKeycenter == 40); - region.parseOpcode({ "amp_keycenter", "-1" }); - REQUIRE(region.ampKeycenter == 0); - region.parseOpcode({ "amp_keycenter", "132" }); - REQUIRE(region.ampKeycenter == 127); - } - - SECTION("amp_keytrack") - { - REQUIRE(region.ampKeytrack == 0.0f); - region.parseOpcode({ "amp_keytrack", "4.2" }); - REQUIRE(region.ampKeytrack == 4.2f); - region.parseOpcode({ "amp_keytrack", "-4.2" }); - REQUIRE(region.ampKeytrack == -4.2f); - region.parseOpcode({ "amp_keytrack", "-123" }); - REQUIRE(region.ampKeytrack == -96.0f); - region.parseOpcode({ "amp_keytrack", "132" }); - REQUIRE(region.ampKeytrack == 12.0f); - } - - SECTION("amp_veltrack") - { - REQUIRE(region.ampVeltrack == 1.0f); - region.parseOpcode({ "amp_veltrack", "4.2" }); - REQUIRE(region.ampVeltrack == Approx(0.042f)); - region.parseOpcode({ "amp_veltrack", "-4.2" }); - REQUIRE(region.ampVeltrack == Approx(-0.042f)); - region.parseOpcode({ "amp_veltrack", "-123" }); - REQUIRE(region.ampVeltrack == -1.0f); - region.parseOpcode({ "amp_veltrack", "132" }); - REQUIRE(region.ampVeltrack == 1.0f); - } - - SECTION("amp_random") - { - REQUIRE(region.ampRandom == 0.0f); - region.parseOpcode({ "amp_random", "4.2" }); - REQUIRE(region.ampRandom == 4.2f); - region.parseOpcode({ "amp_random", "-4.2" }); - REQUIRE(region.ampRandom == 0.0f); - region.parseOpcode({ "amp_random", "132" }); - REQUIRE(region.ampRandom == 24.0f); - } - - SECTION("amp_velcurve") - { - region.parseOpcode({ "amp_velcurve_6", "0.4" }); - REQUIRE(region.velocityPoints.back() == std::pair(6, 0.4f)); - region.parseOpcode({ "amp_velcurve_127", "-1.0" }); - REQUIRE(region.velocityPoints.back() == std::pair(127, 0.0f)); - region.parseOpcode({ "amp_velcurve_008", "0.3" }); - REQUIRE(region.velocityPoints.back() == std::pair(8, 0.3f)); - region.parseOpcode({ "amp_velcurve_064", "0.9" }); - REQUIRE(region.velocityPoints.back() == std::pair(64, 0.9f)); - } - - SECTION("xfin_lokey, xfin_hikey") - { - REQUIRE(region.crossfadeKeyInRange == Range(0, 0)); - region.parseOpcode({ "xfin_lokey", "4" }); - REQUIRE(region.crossfadeKeyInRange == Range(4, 4)); - region.parseOpcode({ "xfin_lokey", "128" }); - REQUIRE(region.crossfadeKeyInRange == Range(127, 127)); - region.parseOpcode({ "xfin_lokey", "59" }); - REQUIRE(region.crossfadeKeyInRange == Range(59, 127)); - region.parseOpcode({ "xfin_hikey", "59" }); - REQUIRE(region.crossfadeKeyInRange == Range(59, 59)); - region.parseOpcode({ "xfin_hikey", "128" }); - REQUIRE(region.crossfadeKeyInRange == Range(59, 127)); - region.parseOpcode({ "xfin_hikey", "0" }); - REQUIRE(region.crossfadeKeyInRange == Range(0, 0)); - region.parseOpcode({ "xfin_hikey", "-1" }); - REQUIRE(region.crossfadeKeyInRange == Range(0, 0)); - } - - SECTION("xfin_lovel, xfin_hivel") - { - REQUIRE(region.crossfadeVelInRange == Range(0_norm, 0_norm)); - region.parseOpcode({ "xfin_lovel", "4" }); - REQUIRE(region.crossfadeVelInRange == Range(4_norm, 4_norm)); - region.parseOpcode({ "xfin_lovel", "128" }); - REQUIRE(region.crossfadeVelInRange == Range(127_norm, 127_norm)); - region.parseOpcode({ "xfin_lovel", "59" }); - REQUIRE(region.crossfadeVelInRange == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfin_hivel", "59" }); - REQUIRE(region.crossfadeVelInRange == Range(59_norm, 59_norm)); - region.parseOpcode({ "xfin_hivel", "128" }); - REQUIRE(region.crossfadeVelInRange == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfin_hivel", "0" }); - REQUIRE(region.crossfadeVelInRange == Range(0_norm, 0_norm)); - region.parseOpcode({ "xfin_hivel", "-1" }); - REQUIRE(region.crossfadeVelInRange == Range(0_norm, 0_norm)); - } - - SECTION("xfout_lokey, xfout_hikey") - { - REQUIRE(region.crossfadeKeyOutRange == Range(127, 127)); - region.parseOpcode({ "xfout_lokey", "4" }); - REQUIRE(region.crossfadeKeyOutRange == Range(4, 127)); - region.parseOpcode({ "xfout_lokey", "128" }); - REQUIRE(region.crossfadeKeyOutRange == Range(127, 127)); - region.parseOpcode({ "xfout_lokey", "59" }); - REQUIRE(region.crossfadeKeyOutRange == Range(59, 127)); - region.parseOpcode({ "xfout_hikey", "59" }); - REQUIRE(region.crossfadeKeyOutRange == Range(59, 59)); - region.parseOpcode({ "xfout_hikey", "128" }); - REQUIRE(region.crossfadeKeyOutRange == Range(59, 127)); - region.parseOpcode({ "xfout_hikey", "0" }); - REQUIRE(region.crossfadeKeyOutRange == Range(0, 0)); - region.parseOpcode({ "xfout_hikey", "-1" }); - REQUIRE(region.crossfadeKeyOutRange == Range(0, 0)); - } - - SECTION("xfout_lovel, xfout_hivel") - { - REQUIRE(region.crossfadeVelOutRange == Range(127_norm, 127_norm)); - region.parseOpcode({ "xfout_lovel", "4" }); - REQUIRE(region.crossfadeVelOutRange == Range(4_norm, 127_norm)); - region.parseOpcode({ "xfout_lovel", "128" }); - REQUIRE(region.crossfadeVelOutRange == Range(127_norm, 127_norm)); - region.parseOpcode({ "xfout_lovel", "59" }); - REQUIRE(region.crossfadeVelOutRange == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfout_hivel", "59" }); - REQUIRE(region.crossfadeVelOutRange == Range(59_norm, 59_norm)); - region.parseOpcode({ "xfout_hivel", "128" }); - REQUIRE(region.crossfadeVelOutRange == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfout_hivel", "0" }); - REQUIRE(region.crossfadeVelOutRange == Range(0_norm, 0_norm)); - region.parseOpcode({ "xfout_hivel", "-1" }); - REQUIRE(region.crossfadeVelOutRange == Range(0_norm, 0_norm)); - } - - SECTION("xfin_locc, xfin_hicc") - { - REQUIRE(!region.crossfadeCCInRange.contains(4)); - region.parseOpcode({ "xfin_locc4", "4" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(4_norm, 4_norm)); - region.parseOpcode({ "xfin_locc4", "128" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(127_norm, 127_norm)); - region.parseOpcode({ "xfin_locc4", "59" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfin_hicc4", "59" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(59_norm, 59_norm)); - region.parseOpcode({ "xfin_hicc4", "128" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfin_hicc4", "0" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(0_norm, 0_norm)); - region.parseOpcode({ "xfin_hicc4", "-1" }); - REQUIRE(region.crossfadeCCInRange[4] == Range(0_norm, 0_norm)); - } - - SECTION("xfout_locc, xfout_hicc") - { - REQUIRE(!region.crossfadeCCOutRange.contains(4)); - region.parseOpcode({ "xfout_locc4", "4" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(4_norm, 127_norm)); - region.parseOpcode({ "xfout_locc4", "128" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(127_norm, 127_norm)); - region.parseOpcode({ "xfout_locc4", "59" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfout_hicc4", "59" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(59_norm, 59_norm)); - region.parseOpcode({ "xfout_hicc4", "128" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(59_norm, 127_norm)); - region.parseOpcode({ "xfout_hicc4", "0" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(0_norm, 0_norm)); - region.parseOpcode({ "xfout_hicc4", "-1" }); - REQUIRE(region.crossfadeCCOutRange[4] == Range(0_norm, 0_norm)); - } - - SECTION("xf_keycurve") - { - REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_keycurve", "gain" }); - REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::gain); - region.parseOpcode({ "xf_keycurve", "power" }); - REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_keycurve", "something" }); - REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_keycurve", "gain" }); - region.parseOpcode({ "xf_keycurve", "something" }); - REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::gain); - } - - SECTION("xf_velcurve") - { - REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_velcurve", "gain" }); - REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::gain); - region.parseOpcode({ "xf_velcurve", "power" }); - REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_velcurve", "something" }); - REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_velcurve", "gain" }); - region.parseOpcode({ "xf_velcurve", "something" }); - REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::gain); - } - - SECTION("xf_cccurve") - { - REQUIRE(region.crossfadeCCCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_cccurve", "gain" }); - REQUIRE(region.crossfadeCCCurve == SfzCrossfadeCurve::gain); - region.parseOpcode({ "xf_cccurve", "power" }); - REQUIRE(region.crossfadeCCCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_cccurve", "something" }); - REQUIRE(region.crossfadeCCCurve == SfzCrossfadeCurve::power); - region.parseOpcode({ "xf_cccurve", "gain" }); - region.parseOpcode({ "xf_cccurve", "something" }); - REQUIRE(region.crossfadeCCCurve == SfzCrossfadeCurve::gain); - } - - SECTION("*_volume") - { - const std::pair assoc_pairs[] = { - {"global_volume", ®ion.globalVolume}, - {"master_volume", ®ion.masterVolume}, - {"group_volume", ®ion.groupVolume}, - }; - for (auto a : assoc_pairs) { - REQUIRE(region.volume == 0.0f); - region.parseOpcode({ a.first, "4.2" }); - REQUIRE(*a.second == 4.2f); - region.parseOpcode({ a.first, "-4.2" }); - REQUIRE(*a.second == -4.2f); - region.parseOpcode({ a.first, "-123" }); - REQUIRE(*a.second == -123.0f); - region.parseOpcode({ a.first, "-185" }); - REQUIRE(*a.second == -144.0f); - region.parseOpcode({ a.first, "79" }); - REQUIRE(*a.second == 48.0f); - } - } - - SECTION("*_amplitude") - { - const std::pair assoc_pairs[] = { - {"global_amplitude", ®ion.globalAmplitude}, - {"master_amplitude", ®ion.masterAmplitude}, - {"group_amplitude", ®ion.groupAmplitude}, - }; - for (auto a : assoc_pairs) { - REQUIRE(*a.second == 1.0_a); - region.parseOpcode({ a.first, "40" }); - REQUIRE(*a.second == 0.4_a); - region.parseOpcode({ a.first, "-40" }); - REQUIRE(*a.second == 0_a); - region.parseOpcode({ a.first, "140" }); - REQUIRE(*a.second == 1.4_a); - } - } - - SECTION("pitch_keycenter") - { - REQUIRE(region.pitchKeycenter == 60); - region.parseOpcode({ "pitch_keycenter", "40" }); - REQUIRE(region.pitchKeycenter == 40); - region.parseOpcode({ "pitch_keycenter", "-1" }); - REQUIRE(region.pitchKeycenter == 0); - region.parseOpcode({ "pitch_keycenter", "132" }); - REQUIRE(region.pitchKeycenter == 127); - } - - SECTION("pitch_keytrack") - { - REQUIRE(region.pitchKeytrack == 100); - region.parseOpcode({ "pitch_keytrack", "40" }); - REQUIRE(region.pitchKeytrack == 40); - region.parseOpcode({ "pitch_keytrack", "-1" }); - REQUIRE(region.pitchKeytrack == -1); - region.parseOpcode({ "pitch_keytrack", "1320" }); - REQUIRE(region.pitchKeytrack == 1200); - region.parseOpcode({ "pitch_keytrack", "-1320" }); - REQUIRE(region.pitchKeytrack == -1200); - } - - SECTION("pitch_random") - { - REQUIRE(region.pitchRandom == 0); - region.parseOpcode({ "pitch_random", "40" }); - REQUIRE(region.pitchRandom == 40); - region.parseOpcode({ "pitch_random", "-1" }); - REQUIRE(region.pitchRandom == 0); - region.parseOpcode({ "pitch_random", "12320" }); - REQUIRE(region.pitchRandom == 12000); - } - - SECTION("pitch_veltrack") - { - REQUIRE(region.pitchVeltrack == 0); - region.parseOpcode({ "pitch_veltrack", "40" }); - REQUIRE(region.pitchVeltrack == 40); - region.parseOpcode({ "pitch_veltrack", "-1" }); - REQUIRE(region.pitchVeltrack == -1); - region.parseOpcode({ "pitch_veltrack", "13020" }); - REQUIRE(region.pitchVeltrack == 12000); - region.parseOpcode({ "pitch_veltrack", "-13020" }); - REQUIRE(region.pitchVeltrack == -12000); - } - - SECTION("transpose") - { - REQUIRE(region.transpose == 0); - region.parseOpcode({ "transpose", "40" }); - REQUIRE(region.transpose == 40); - region.parseOpcode({ "transpose", "-1" }); - REQUIRE(region.transpose == -1); - region.parseOpcode({ "transpose", "154" }); - REQUIRE(region.transpose == 127); - region.parseOpcode({ "transpose", "-154" }); - REQUIRE(region.transpose == -127); - } - - SECTION("tune") - { - REQUIRE(region.tune == 0); - region.parseOpcode({ "tune", "40" }); - REQUIRE(region.tune == 40); - region.parseOpcode({ "tune", "-1" }); - REQUIRE(region.tune == -1); - region.parseOpcode({ "tune", "15432" }); - REQUIRE(region.tune == 12000); - region.parseOpcode({ "tune", "-15432" }); - REQUIRE(region.tune == -12000); - } - - SECTION("bend_up, bend_down, bend_step, bend_smooth") - { - REQUIRE(region.bendUp == 200); - REQUIRE(region.bendDown == -200); - REQUIRE(region.bendStep == 1); - region.parseOpcode({ "bend_up", "400" }); - REQUIRE(region.bendUp == 400); - region.parseOpcode({ "bend_up", "-200" }); - REQUIRE(region.bendUp == -200); - region.parseOpcode({ "bend_up", "12700" }); - REQUIRE(region.bendUp == 12000); - region.parseOpcode({ "bend_up", "-12700" }); - REQUIRE(region.bendUp == -12000); - region.parseOpcode({ "bend_down", "400" }); - REQUIRE(region.bendDown == 400); - region.parseOpcode({ "bend_down", "-200" }); - REQUIRE(region.bendDown == -200); - region.parseOpcode({ "bend_down", "12700" }); - REQUIRE(region.bendDown == 12000); - region.parseOpcode({ "bend_down", "-12700" }); - REQUIRE(region.bendDown == -12000); - region.parseOpcode({ "bend_step", "400" }); - REQUIRE(region.bendStep == 400); - region.parseOpcode({ "bend_step", "-200" }); - REQUIRE(region.bendStep == 1); - region.parseOpcode({ "bend_step", "9700" }); - REQUIRE(region.bendStep == 1200); - region.parseOpcode({ "bend_smooth", "10" }); - REQUIRE(region.bendSmooth == 10); - region.parseOpcode({ "bend_smooth", "120" }); - REQUIRE(region.bendSmooth == 100); - region.parseOpcode({ "bend_smooth", "-2" }); - REQUIRE(region.bendSmooth == 0); - } - - SECTION("ampeg") - { - // Defaults - REQUIRE(region.amplitudeEG.attack == 0.0f); - REQUIRE(region.amplitudeEG.decay == 0.0f); - REQUIRE(region.amplitudeEG.delay == 0.0f); - REQUIRE(region.amplitudeEG.hold == 0.0f); - REQUIRE(region.amplitudeEG.release == 0.001f); - REQUIRE(region.amplitudeEG.start == 0.0f); - REQUIRE(region.amplitudeEG.sustain == 100.0f); - REQUIRE(region.amplitudeEG.depth == 0); - REQUIRE(region.amplitudeEG.vel2attack == 0.0f); - REQUIRE(region.amplitudeEG.vel2decay == 0.0f); - REQUIRE(region.amplitudeEG.vel2delay == 0.0f); - REQUIRE(region.amplitudeEG.vel2hold == 0.0f); - REQUIRE(region.amplitudeEG.vel2release == 0.0f); - REQUIRE(region.amplitudeEG.vel2sustain == 0.0f); - REQUIRE(region.amplitudeEG.vel2depth == 0); - // - region.parseOpcode({ "ampeg_attack", "1" }); - region.parseOpcode({ "ampeg_decay", "2" }); - region.parseOpcode({ "ampeg_delay", "3" }); - region.parseOpcode({ "ampeg_hold", "4" }); - region.parseOpcode({ "ampeg_release", "5" }); - region.parseOpcode({ "ampeg_start", "6" }); - region.parseOpcode({ "ampeg_sustain", "7" }); - region.parseOpcode({ "ampeg_depth", "8" }); - region.parseOpcode({ "ampeg_vel2attack", "9" }); - region.parseOpcode({ "ampeg_vel2decay", "10" }); - region.parseOpcode({ "ampeg_vel2delay", "11" }); - region.parseOpcode({ "ampeg_vel2hold", "12" }); - region.parseOpcode({ "ampeg_vel2release", "13" }); - region.parseOpcode({ "ampeg_vel2sustain", "14" }); - region.parseOpcode({ "ampeg_vel2depth", "15" }); - REQUIRE(region.amplitudeEG.attack == 1.0f); - REQUIRE(region.amplitudeEG.decay == 2.0f); - REQUIRE(region.amplitudeEG.delay == 3.0f); - REQUIRE(region.amplitudeEG.hold == 4.0f); - REQUIRE(region.amplitudeEG.release == 5.0f); - REQUIRE(region.amplitudeEG.start == 6.0f); - REQUIRE(region.amplitudeEG.sustain == 7.0f); - REQUIRE(region.amplitudeEG.depth == 0); // ignored for ampeg - REQUIRE(region.amplitudeEG.vel2attack == 9.0f); - REQUIRE(region.amplitudeEG.vel2decay == 10.0f); - REQUIRE(region.amplitudeEG.vel2delay == 11.0f); - REQUIRE(region.amplitudeEG.vel2hold == 12.0f); - REQUIRE(region.amplitudeEG.vel2release == 13.0f); - REQUIRE(region.amplitudeEG.vel2sustain == 14.0f); - REQUIRE(region.amplitudeEG.vel2depth == 0); // ignored for ampeg - // - region.parseOpcode({ "ampeg_attack", "1000" }); - region.parseOpcode({ "ampeg_decay", "1000" }); - region.parseOpcode({ "ampeg_delay", "1000" }); - region.parseOpcode({ "ampeg_hold", "1000" }); - region.parseOpcode({ "ampeg_release", "1000" }); - region.parseOpcode({ "ampeg_start", "1000" }); - region.parseOpcode({ "ampeg_sustain", "1000" }); - region.parseOpcode({ "ampeg_depth", "1000" }); - region.parseOpcode({ "ampeg_vel2attack", "1000" }); - region.parseOpcode({ "ampeg_vel2decay", "1000" }); - region.parseOpcode({ "ampeg_vel2delay", "1000" }); - region.parseOpcode({ "ampeg_vel2hold", "1000" }); - region.parseOpcode({ "ampeg_vel2release", "1000" }); - region.parseOpcode({ "ampeg_vel2sustain", "1000" }); - region.parseOpcode({ "ampeg_vel2depth", "1000" }); - REQUIRE(region.amplitudeEG.attack == 100.0f); - REQUIRE(region.amplitudeEG.decay == 100.0f); - REQUIRE(region.amplitudeEG.delay == 100.0f); - REQUIRE(region.amplitudeEG.hold == 100.0f); - REQUIRE(region.amplitudeEG.release == 100.0f); - REQUIRE(region.amplitudeEG.start == 100.0f); - REQUIRE(region.amplitudeEG.sustain == 100.0f); - REQUIRE(region.amplitudeEG.depth == 0); // ignored for ampeg - REQUIRE(region.amplitudeEG.vel2attack == 100.0f); - REQUIRE(region.amplitudeEG.vel2decay == 100.0f); - REQUIRE(region.amplitudeEG.vel2delay == 100.0f); - REQUIRE(region.amplitudeEG.vel2hold == 100.0f); - REQUIRE(region.amplitudeEG.vel2release == 100.0f); - REQUIRE(region.amplitudeEG.vel2sustain == 100.0f); - REQUIRE(region.amplitudeEG.vel2depth == 0); // ignored for ampeg - // - region.parseOpcode({ "ampeg_attack", "-101" }); - region.parseOpcode({ "ampeg_decay", "-101" }); - region.parseOpcode({ "ampeg_delay", "-101" }); - region.parseOpcode({ "ampeg_hold", "-101" }); - region.parseOpcode({ "ampeg_release", "-101" }); - region.parseOpcode({ "ampeg_start", "-101" }); - region.parseOpcode({ "ampeg_sustain", "-101" }); - region.parseOpcode({ "ampeg_depth", "-101" }); - region.parseOpcode({ "ampeg_vel2attack", "-101" }); - region.parseOpcode({ "ampeg_vel2decay", "-101" }); - region.parseOpcode({ "ampeg_vel2delay", "-101" }); - region.parseOpcode({ "ampeg_vel2hold", "-101" }); - region.parseOpcode({ "ampeg_vel2release", "-101" }); - region.parseOpcode({ "ampeg_vel2sustain", "-101" }); - region.parseOpcode({ "ampeg_vel2depth", "-101" }); - REQUIRE(region.amplitudeEG.attack == 0.0f); - REQUIRE(region.amplitudeEG.decay == 0.0f); - REQUIRE(region.amplitudeEG.delay == 0.0f); - REQUIRE(region.amplitudeEG.hold == 0.0f); - REQUIRE(region.amplitudeEG.release == 0.0f); - REQUIRE(region.amplitudeEG.start == 0.0f); - REQUIRE(region.amplitudeEG.sustain == 0.0f); - REQUIRE(region.amplitudeEG.depth == 0); // ignored for ampeg - REQUIRE(region.amplitudeEG.vel2attack == -100.0f); - REQUIRE(region.amplitudeEG.vel2decay == -100.0f); - REQUIRE(region.amplitudeEG.vel2delay == -100.0f); - REQUIRE(region.amplitudeEG.vel2hold == -100.0f); - REQUIRE(region.amplitudeEG.vel2release == -100.0f); - REQUIRE(region.amplitudeEG.vel2sustain == -100.0f); - } - - SECTION("ampeg_XX_onccNN") - { - // Defaults - REQUIRE(region.amplitudeEG.ccAttack.empty()); - REQUIRE(region.amplitudeEG.ccDecay.empty()); - REQUIRE(region.amplitudeEG.ccDelay.empty()); - REQUIRE(region.amplitudeEG.ccHold.empty()); - REQUIRE(region.amplitudeEG.ccRelease.empty()); - REQUIRE(region.amplitudeEG.ccStart.empty()); - REQUIRE(region.amplitudeEG.ccSustain.empty()); - // - region.parseOpcode({ "ampeg_attack_oncc1", "1" }); - region.parseOpcode({ "ampeg_decay_oncc2", "2" }); - region.parseOpcode({ "ampeg_delay_oncc3", "3" }); - region.parseOpcode({ "ampeg_hold_oncc4", "4" }); - region.parseOpcode({ "ampeg_release_oncc5", "5" }); - region.parseOpcode({ "ampeg_start_oncc6", "6" }); - region.parseOpcode({ "ampeg_sustain_oncc7", "7" }); - REQUIRE(region.amplitudeEG.ccAttack.contains(1)); - REQUIRE(region.amplitudeEG.ccDecay.contains(2)); - REQUIRE(region.amplitudeEG.ccDelay.contains(3)); - REQUIRE(region.amplitudeEG.ccHold.contains(4)); - REQUIRE(region.amplitudeEG.ccRelease.contains(5)); - REQUIRE(region.amplitudeEG.ccStart.contains(6)); - REQUIRE(region.amplitudeEG.ccSustain.contains(7)); - REQUIRE(region.amplitudeEG.ccAttack[1] == 1.0f); - REQUIRE(region.amplitudeEG.ccDecay[2] == 2.0f); - REQUIRE(region.amplitudeEG.ccDelay[3] == 3.0f); - REQUIRE(region.amplitudeEG.ccHold[4] == 4.0f); - REQUIRE(region.amplitudeEG.ccRelease[5] == 5.0f); - REQUIRE(region.amplitudeEG.ccStart[6] == 6.0f); - REQUIRE(region.amplitudeEG.ccSustain[7] == 7.0f); - // - region.parseOpcode({ "ampeg_attack_oncc1", "101" }); - region.parseOpcode({ "ampeg_decay_oncc2", "101" }); - region.parseOpcode({ "ampeg_delay_oncc3", "101" }); - region.parseOpcode({ "ampeg_hold_oncc4", "101" }); - region.parseOpcode({ "ampeg_release_oncc5", "101" }); - region.parseOpcode({ "ampeg_start_oncc6", "101" }); - region.parseOpcode({ "ampeg_sustain_oncc7", "101" }); - REQUIRE(region.amplitudeEG.ccAttack[1] == 100.0f); - REQUIRE(region.amplitudeEG.ccDecay[2] == 100.0f); - REQUIRE(region.amplitudeEG.ccDelay[3] == 100.0f); - REQUIRE(region.amplitudeEG.ccHold[4] == 100.0f); - REQUIRE(region.amplitudeEG.ccRelease[5] == 100.0f); - REQUIRE(region.amplitudeEG.ccStart[6] == 100.0f); - REQUIRE(region.amplitudeEG.ccSustain[7] == 100.0f); - // - region.parseOpcode({ "ampeg_attack_oncc1", "-101" }); - region.parseOpcode({ "ampeg_decay_oncc2", "-101" }); - region.parseOpcode({ "ampeg_delay_oncc3", "-101" }); - region.parseOpcode({ "ampeg_hold_oncc4", "-101" }); - region.parseOpcode({ "ampeg_release_oncc5", "-101" }); - region.parseOpcode({ "ampeg_start_oncc6", "-101" }); - region.parseOpcode({ "ampeg_sustain_oncc7", "-101" }); - REQUIRE(region.amplitudeEG.ccAttack[1] == -100.0f); - REQUIRE(region.amplitudeEG.ccDecay[2] == -100.0f); - REQUIRE(region.amplitudeEG.ccDelay[3] == -100.0f); - REQUIRE(region.amplitudeEG.ccHold[4] == -100.0f); - REQUIRE(region.amplitudeEG.ccRelease[5] == -100.0f); - REQUIRE(region.amplitudeEG.ccStart[6] == -100.0f); - REQUIRE(region.amplitudeEG.ccSustain[7] == -100.0f); - // - region.parseOpcode({ "ampeg_attack_oncc1", "1" }); - region.parseOpcode({ "ampeg_decay_oncc2", "2" }); - region.parseOpcode({ "ampeg_delay_oncc3", "3" }); - region.parseOpcode({ "ampeg_hold_oncc4", "4" }); - region.parseOpcode({ "ampeg_release_oncc5", "5" }); - region.parseOpcode({ "ampeg_start_oncc6", "6" }); - region.parseOpcode({ "ampeg_sustain_oncc7", "7" }); - region.parseOpcode({ "ampeg_attack_oncc2", "2" }); - region.parseOpcode({ "ampeg_decay_oncc3", "3" }); - region.parseOpcode({ "ampeg_delay_oncc4", "4" }); - region.parseOpcode({ "ampeg_hold_oncc5", "5" }); - region.parseOpcode({ "ampeg_release_oncc6", "6" }); - region.parseOpcode({ "ampeg_start_oncc7", "7" }); - region.parseOpcode({ "ampeg_sustain_oncc8", "8" }); - REQUIRE(region.amplitudeEG.ccAttack.contains(1)); - REQUIRE(region.amplitudeEG.ccDecay.contains(2)); - REQUIRE(region.amplitudeEG.ccDelay.contains(3)); - REQUIRE(region.amplitudeEG.ccHold.contains(4)); - REQUIRE(region.amplitudeEG.ccRelease.contains(5)); - REQUIRE(region.amplitudeEG.ccStart.contains(6)); - REQUIRE(region.amplitudeEG.ccSustain.contains(7)); - REQUIRE(region.amplitudeEG.ccAttack.contains(2)); - REQUIRE(region.amplitudeEG.ccDecay.contains(3)); - REQUIRE(region.amplitudeEG.ccDelay.contains(4)); - REQUIRE(region.amplitudeEG.ccHold.contains(5)); - REQUIRE(region.amplitudeEG.ccRelease.contains(6)); - REQUIRE(region.amplitudeEG.ccStart.contains(7)); - REQUIRE(region.amplitudeEG.ccSustain.contains(8)); - REQUIRE(region.amplitudeEG.ccAttack[1] == 1.0f); - REQUIRE(region.amplitudeEG.ccDecay[2] == 2.0f); - REQUIRE(region.amplitudeEG.ccDelay[3] == 3.0f); - REQUIRE(region.amplitudeEG.ccHold[4] == 4.0f); - REQUIRE(region.amplitudeEG.ccRelease[5] == 5.0f); - REQUIRE(region.amplitudeEG.ccStart[6] == 6.0f); - REQUIRE(region.amplitudeEG.ccSustain[7] == 7.0f); - REQUIRE(region.amplitudeEG.ccAttack[2] == 2.0f); - REQUIRE(region.amplitudeEG.ccDecay[3] == 3.0f); - REQUIRE(region.amplitudeEG.ccDelay[4] == 4.0f); - REQUIRE(region.amplitudeEG.ccHold[5] == 5.0f); - REQUIRE(region.amplitudeEG.ccRelease[6] == 6.0f); - REQUIRE(region.amplitudeEG.ccStart[7] == 7.0f); - REQUIRE(region.amplitudeEG.ccSustain[8] == 8.0f); - } - - SECTION("sustain_sw and sostenuto_sw") - { - REQUIRE(region.checkSustain); - REQUIRE(region.checkSostenuto); - region.parseOpcode({ "sustain_sw", "off" }); - REQUIRE(!region.checkSustain); - region.parseOpcode({ "sustain_sw", "on" }); - REQUIRE(region.checkSustain); - region.parseOpcode({ "sustain_sw", "off" }); - region.parseOpcode({ "sustain_sw", "obladi" }); - REQUIRE(region.checkSustain); - region.parseOpcode({ "sostenuto_sw", "off" }); - REQUIRE(!region.checkSostenuto); - region.parseOpcode({ "sostenuto_sw", "on" }); - REQUIRE(region.checkSostenuto); - region.parseOpcode({ "sostenuto_sw", "off" }); - region.parseOpcode({ "sostenuto_sw", "obladi" }); - REQUIRE(region.checkSostenuto); - } - - SECTION("sustain_cc") - { - REQUIRE(region.sustainCC == 64); - region.parseOpcode({ "sustain_cc", "63" }); - REQUIRE(region.sustainCC == 63); - region.parseOpcode({ "sustain_cc", "-1" }); - REQUIRE(region.sustainCC == 0); - } - - SECTION("sustain_lo") - { - REQUIRE(region.sustainThreshold == Approx(0.5_norm).margin(1e-3)); - region.parseOpcode({ "sustain_lo", "-1" }); - REQUIRE(region.sustainThreshold == 0_norm); - region.parseOpcode({ "sustain_lo", "1" }); - REQUIRE(region.sustainThreshold == 1_norm); - region.parseOpcode({ "sustain_lo", "63" }); - REQUIRE(region.sustainThreshold == 63_norm); - region.parseOpcode({ "sustain_lo", "128" }); - REQUIRE(region.sustainThreshold == 127_norm); - } - - SECTION("Filter stacking and cutoffs") - { - REQUIRE(region.filters.empty()); - - region.parseOpcode({ "cutoff", "500" }); - REQUIRE(region.filters.size() == 1); - REQUIRE(region.filters[0].cutoff == 500.0f); - // Check filter defaults - REQUIRE(region.filters[0].keycenter == 60); - REQUIRE(region.filters[0].type == FilterType::kFilterLpf2p); - REQUIRE(region.filters[0].keytrack == 0); - REQUIRE(region.filters[0].gain == 0); - REQUIRE(region.filters[0].veltrack == 0); - REQUIRE(region.filters[0].resonance == 0.0f); - - region.parseOpcode({ "cutoff2", "5000" }); - REQUIRE(region.filters.size() == 2); - REQUIRE(region.filters[1].cutoff == 5000.0f); - // Check filter defaults - REQUIRE(region.filters[1].keycenter == 60); - REQUIRE(region.filters[1].type == FilterType::kFilterLpf2p); - REQUIRE(region.filters[1].keytrack == 0); - REQUIRE(region.filters[1].gain == 0); - REQUIRE(region.filters[1].veltrack == 0); - REQUIRE(region.filters[1].resonance == 0.0f); - - region.parseOpcode({ "cutoff4", "50" }); - REQUIRE(region.filters.size() == 4); - REQUIRE(region.filters[2].cutoff == 0.0f); - REQUIRE(region.filters[3].cutoff == 50.0f); - // Check filter defaults - REQUIRE(region.filters[2].keycenter == 60); - REQUIRE(region.filters[2].type == FilterType::kFilterLpf2p); - REQUIRE(region.filters[2].keytrack == 0); - REQUIRE(region.filters[2].gain == 0); - REQUIRE(region.filters[2].veltrack == 0); - REQUIRE(region.filters[2].resonance == 0.0f); - REQUIRE(region.filters[3].keycenter == 60); - REQUIRE(region.filters[3].type == FilterType::kFilterLpf2p); - REQUIRE(region.filters[3].keytrack == 0); - REQUIRE(region.filters[3].gain == 0); - REQUIRE(region.filters[3].veltrack == 0); - REQUIRE(region.filters[3].resonance == 0.0f); - } - - SECTION("Filter parameter dispatch") - { - region.parseOpcode({ "cutoff3", "50" }); - REQUIRE(region.filters.size() == 3); - REQUIRE(region.filters[2].cutoff == 50.0f); - region.parseOpcode({ "resonance2", "3" }); - REQUIRE(region.filters[1].resonance == 3.0f); - region.parseOpcode({ "fil2_gain", "-5" }); - REQUIRE(region.filters[1].gain == -5.0f); - region.parseOpcode({ "fil_gain", "5" }); - REQUIRE(region.filters[0].gain == 5.0f); - region.parseOpcode({ "fil1_gain", "-5" }); - REQUIRE(region.filters[0].gain == -5.0f); - region.parseOpcode({ "fil2_veltrack", "-100" }); - REQUIRE(region.filters[1].veltrack == -100); - region.parseOpcode({ "fil3_keytrack", "100" }); - REQUIRE(region.filters[2].keytrack == 100); - - } - - SECTION("Filter values") - { - REQUIRE(region.filters.empty()); - - region.parseOpcode({ "cutoff", "500" }); - REQUIRE(region.filters.size() == 1); - REQUIRE(region.filters[0].cutoff == 500.0f); - region.parseOpcode({ "cutoff", "-100" }); - REQUIRE(region.filters[0].cutoff == 0.0f); - region.parseOpcode({ "cutoff", "2000000" }); - REQUIRE(region.filters[0].cutoff == 20000.0f); - - REQUIRE(region.filters[0].resonance == 0.0f); - region.parseOpcode({ "resonance", "5" }); - REQUIRE(region.filters[0].resonance == 5.0f); - region.parseOpcode({ "resonance", "-5" }); - REQUIRE(region.filters[0].resonance == 0.0f); - region.parseOpcode({ "resonance", "500" }); - REQUIRE(region.filters[0].resonance == 96.0f); - - REQUIRE(region.filters[0].veltrack == 0); - region.parseOpcode({ "fil_veltrack", "50" }); - REQUIRE(region.filters[0].veltrack == 50); - region.parseOpcode({ "fil_veltrack", "-5" }); - REQUIRE(region.filters[0].veltrack == -5); - region.parseOpcode({ "fil_veltrack", "13000" }); - REQUIRE(region.filters[0].veltrack == 12000); - region.parseOpcode({ "fil_veltrack", "-13000" }); - REQUIRE(region.filters[0].veltrack == -12000); - - REQUIRE(region.filters[0].keycenter == 60); - region.parseOpcode({ "fil_keycenter", "50" }); - REQUIRE(region.filters[0].keycenter == 50); - region.parseOpcode({ "fil_keycenter", "-2" }); - REQUIRE(region.filters[0].keycenter == 0); - region.parseOpcode({ "fil_keycenter", "1000" }); - REQUIRE(region.filters[0].keycenter == 127); - region.parseOpcode({ "fil_keycenter", "c4" }); - REQUIRE(region.filters[0].keycenter == 60); - - region.parseOpcode({ "fil_gain", "250" }); - REQUIRE(region.filters[0].gain == 96.0f); - region.parseOpcode({ "fil_gain", "-200" }); - REQUIRE(region.filters[0].gain == -96.0f); - } - - SECTION("Filter types") - { - REQUIRE(region.filters.empty()); - - region.parseOpcode({ "fil_type", "lpf_1p" }); - REQUIRE(region.filters.size() == 1); - REQUIRE(region.filters[0].type == FilterType::kFilterLpf1p); - region.parseOpcode({ "fil_type", "lpf_2p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterLpf2p); - region.parseOpcode({ "fil_type", "hpf_1p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterHpf1p); - region.parseOpcode({ "fil_type", "hpf_2p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterHpf2p); - region.parseOpcode({ "fil_type", "bpf_2p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterBpf2p); - region.parseOpcode({ "fil_type", "brf_2p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterBrf2p); - region.parseOpcode({ "fil_type", "bpf_1p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterBpf1p); - region.parseOpcode({ "fil_type", "brf_1p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterBrf1p); - region.parseOpcode({ "fil_type", "apf_1p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterApf1p); - region.parseOpcode({ "fil_type", "lpf_2p_sv" }); - REQUIRE(region.filters[0].type == FilterType::kFilterLpf2pSv); - region.parseOpcode({ "fil_type", "hpf_2p_sv" }); - REQUIRE(region.filters[0].type == FilterType::kFilterHpf2pSv); - region.parseOpcode({ "fil_type", "bpf_2p_sv" }); - REQUIRE(region.filters[0].type == FilterType::kFilterBpf2pSv); - region.parseOpcode({ "fil_type", "brf_2p_sv" }); - REQUIRE(region.filters[0].type == FilterType::kFilterBrf2pSv); - region.parseOpcode({ "fil_type", "lpf_4p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterLpf4p); - region.parseOpcode({ "fil_type", "hpf_4p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterHpf4p); - region.parseOpcode({ "fil_type", "lpf_6p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterLpf6p); - region.parseOpcode({ "fil_type", "hpf_6p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterHpf6p); - region.parseOpcode({ "fil_type", "pink" }); - REQUIRE(region.filters[0].type == FilterType::kFilterPink); - region.parseOpcode({ "fil_type", "lsh" }); - REQUIRE(region.filters[0].type == FilterType::kFilterLsh); - region.parseOpcode({ "fil_type", "hsh" }); - REQUIRE(region.filters[0].type == FilterType::kFilterHsh); - region.parseOpcode({ "fil_type", "peq" }); - REQUIRE(region.filters[0].type == FilterType::kFilterPeq); - region.parseOpcode({ "fil_type", "lpf_1p" }); - region.parseOpcode({ "fil_type", "pkf_2p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterPeq); - region.parseOpcode({ "fil_type", "lpf_1p" }); - region.parseOpcode({ "fil_type", "bpk_2p" }); - REQUIRE(region.filters[0].type == FilterType::kFilterPeq); - region.parseOpcode({ "fil_type", "unknown" }); - REQUIRE(region.filters[0].type == FilterType::kFilterNone); - } - - SECTION("EQ stacking and gains") - { - REQUIRE(region.equalizers.empty()); - - region.parseOpcode({ "eq1_gain", "6" }); - REQUIRE(region.equalizers.size() == 1); - REQUIRE(region.equalizers[0].gain == 6.0f); - // Check defaults - REQUIRE(region.equalizers[0].type == EqType::kEqPeak); - REQUIRE(region.equalizers[0].bandwidth == 1.0f); - REQUIRE(region.equalizers[0].frequency == 0.0f); - REQUIRE(region.equalizers[0].vel2frequency == 0); - REQUIRE(region.equalizers[0].vel2gain == 0); - - region.parseOpcode({ "eq2_gain", "-400" }); - REQUIRE(region.equalizers.size() == 2); - REQUIRE(region.equalizers[1].gain == -96.0f); - // Check defaults - REQUIRE(region.equalizers[1].type == EqType::kEqPeak); - REQUIRE(region.equalizers[1].bandwidth == 1.0f); - REQUIRE(region.equalizers[1].frequency == 0.0f); - REQUIRE(region.equalizers[1].vel2frequency == 0); - REQUIRE(region.equalizers[1].vel2gain == 0); - - region.parseOpcode({ "eq4_gain", "500" }); - REQUIRE(region.equalizers.size() == 4); - REQUIRE(region.equalizers[2].gain == 0.0f); - REQUIRE(region.equalizers[3].type == EqType::kEqPeak); - REQUIRE(region.equalizers[3].gain == 96.0f); - // Check defaults - REQUIRE(region.equalizers[2].bandwidth == 1.0f); - REQUIRE(region.equalizers[2].frequency == 0.0f); - REQUIRE(region.equalizers[2].vel2frequency == 0); - REQUIRE(region.equalizers[2].vel2gain == 0); - REQUIRE(region.equalizers[3].bandwidth == 1.0f); - REQUIRE(region.equalizers[3].frequency == 0.0f); - REQUIRE(region.equalizers[3].vel2frequency == 0); - REQUIRE(region.equalizers[3].vel2gain == 0); - } - - SECTION("EQ types") - { - region.parseOpcode({ "eq1_type", "hshelf" }); - REQUIRE(region.equalizers[0].type == EqType::kEqHighShelf); - region.parseOpcode({ "eq1_type", "somethingsomething" }); - REQUIRE(region.equalizers[0].type == EqType::kEqNone); - region.parseOpcode({ "eq1_type", "lshelf" }); - REQUIRE(region.equalizers[0].type == EqType::kEqLowShelf); - region.parseOpcode({ "eq1_type", "peak" }); - REQUIRE(region.equalizers[0].type == EqType::kEqPeak); - } - - SECTION("EQ parameter dispatch") - { - region.parseOpcode({ "eq3_bw", "2" }); - REQUIRE(region.equalizers.size() == 3); - REQUIRE(region.equalizers[2].bandwidth == 2.0f); - region.parseOpcode({ "eq1_gain", "-25" }); - REQUIRE(region.equalizers[0].gain == -25.0f); - region.parseOpcode({ "eq2_freq", "300" }); - REQUIRE(region.equalizers[1].frequency == 300.0f); - region.parseOpcode({ "eq3_type", "lshelf" }); - REQUIRE(region.equalizers[2].type == EqType::kEqLowShelf); - region.parseOpcode({ "eq3_vel2gain", "10" }); - REQUIRE(region.equalizers[2].vel2gain == 10.0f); - region.parseOpcode({ "eq1_vel2freq", "100" }); - REQUIRE(region.equalizers[0].vel2frequency == 100.0f); - region.parseOpcode({ "eq1_type", "hshelf" }); - REQUIRE(region.equalizers[0].type == EqType::kEqHighShelf); - } - - SECTION("EQ parameter values") - { - region.parseOpcode({ "eq1_bw", "2" }); - REQUIRE(region.equalizers.size() == 1); - REQUIRE(region.equalizers[0].bandwidth == 2.0f); - region.parseOpcode({ "eq1_bw", "5" }); - REQUIRE(region.equalizers[0].bandwidth == 4.0f); - region.parseOpcode({ "eq1_bw", "0" }); - REQUIRE(region.equalizers[0].bandwidth == 0.001f); - region.parseOpcode({ "eq1_freq", "300" }); - REQUIRE(region.equalizers[0].frequency == 300.0f); - region.parseOpcode({ "eq1_freq", "-300" }); - REQUIRE(region.equalizers[0].frequency == 0.0f); - region.parseOpcode({ "eq1_freq", "35000" }); - REQUIRE(region.equalizers[0].frequency == 30000.0f); - region.parseOpcode({ "eq1_vel2gain", "4" }); - REQUIRE(region.equalizers[0].vel2gain == 4.0f); - region.parseOpcode({ "eq1_vel2gain", "250" }); - REQUIRE(region.equalizers[0].vel2gain == 96.0f); - region.parseOpcode({ "eq1_vel2gain", "-123" }); - REQUIRE(region.equalizers[0].vel2gain == -96.0f); - region.parseOpcode({ "eq1_vel2freq", "40" }); - REQUIRE(region.equalizers[0].vel2frequency == 40.0f); - region.parseOpcode({ "eq1_vel2freq", "35000" }); - REQUIRE(region.equalizers[0].vel2frequency == 30000.0f); - region.parseOpcode({ "eq1_vel2freq", "-35000" }); - REQUIRE(region.equalizers[0].vel2frequency == -30000.0f); - } - - SECTION("Effects send") - { - REQUIRE(region.gainToEffect.size() == 1); - REQUIRE(region.gainToEffect[0] == 1.0f); - region.parseOpcode({ "effect1", "50.4" }); - REQUIRE(region.gainToEffect.size() == 2); - REQUIRE(region.gainToEffect[1] == 0.504f); - region.parseOpcode({ "effect3", "100" }); - REQUIRE(region.gainToEffect.size() == 4); - REQUIRE(region.gainToEffect[2] == 0.0f); - REQUIRE(region.gainToEffect[3] == 1.0f); - region.parseOpcode({ "effect3", "150.1" }); - REQUIRE(region.gainToEffect[3] == 1.0f); - region.parseOpcode({ "effect3", "-50.65" }); - REQUIRE(region.gainToEffect[3] == 0.0f); - } - - SECTION("Wavetable phase") - { - REQUIRE(region.oscillatorPhase == 0.0f); - region.parseOpcode({ "oscillator_phase", "0.25" }); - REQUIRE(region.oscillatorPhase == 0.25f); - region.parseOpcode({ "oscillator_phase", "0.3" }); - REQUIRE(region.oscillatorPhase == 0.3_a); - region.parseOpcode({ "oscillator_phase", "-1" }); - REQUIRE(region.oscillatorPhase == -1.0f); - region.parseOpcode({ "oscillator_phase", "1.1" }); - REQUIRE(region.oscillatorPhase == 0.0f); - } - - SECTION("Note polyphony") - { - REQUIRE(!region.notePolyphony); - region.parseOpcode({ "note_polyphony", "45" }); - REQUIRE(region.notePolyphony); - REQUIRE(*region.notePolyphony == 45); - region.parseOpcode({ "note_polyphony", "-1" }); - REQUIRE(region.notePolyphony); - REQUIRE(*region.notePolyphony == 0); - } - - SECTION("Note selfmask") - { - REQUIRE(region.selfMask == SfzSelfMask::mask); - region.parseOpcode({ "note_selfmask", "off" }); - REQUIRE(region.selfMask == SfzSelfMask::dontMask); - region.parseOpcode({ "note_selfmask", "on" }); - REQUIRE(region.selfMask == SfzSelfMask::mask); - region.parseOpcode({ "note_selfmask", "off" }); - region.parseOpcode({ "note_selfmask", "garbage" }); - REQUIRE(region.selfMask == SfzSelfMask::dontMask); - } - - SECTION("Release dead") - { - REQUIRE(region.rtDead == false); - region.parseOpcode({ "rt_dead", "on" }); - REQUIRE(region.rtDead == true); - region.parseOpcode({ "rt_dead", "off" }); - REQUIRE(region.rtDead == false); - region.parseOpcode({ "rt_dead", "on" }); - region.parseOpcode({ "rt_dead", "garbage" }); - REQUIRE(region.rtDead == true); - } - - SECTION("amplitude") - { - REQUIRE(region.amplitude == 1.0_a); - region.parseOpcode({ "amplitude", "40" }); - REQUIRE(region.amplitude == 0.4_a); - region.parseOpcode({ "amplitude", "-40" }); - REQUIRE(region.amplitude == 0_a); - region.parseOpcode({ "amplitude", "140" }); - REQUIRE(region.amplitude == 1.4_a); - } - - SECTION("amplitude_cc") - { - const ModKey target = ModKey::createNXYZ(ModId::Amplitude, region.getId()); - const RegionCCView view(region, target); - REQUIRE(view.empty()); - region.parseOpcode({ "amplitude_cc1", "40" }); - REQUIRE(view.valueAt(1) == 40.0_a); - region.parseOpcode({ "amplitude_oncc2", "30" }); - REQUIRE(view.valueAt(2) == 30.0_a); - region.parseOpcode({ "amplitude_curvecc17", "18" }); - REQUIRE(view.at(17).curve == 18); - region.parseOpcode({ "amplitude_curvecc17", "15482" }); - REQUIRE(view.at(17).curve == 255); - region.parseOpcode({ "amplitude_curvecc17", "-2" }); - REQUIRE(view.at(17).curve == 0); - region.parseOpcode({ "amplitude_smoothcc14", "85" }); - REQUIRE(view.at(14).smooth == 85); - region.parseOpcode({ "amplitude_smoothcc14", "15482" }); - REQUIRE(view.at(14).smooth == 100); - region.parseOpcode({ "amplitude_smoothcc14", "-2" }); - REQUIRE(view.at(14).smooth == 0); - region.parseOpcode({ "amplitude_stepcc120", "24" }); - REQUIRE(view.at(120).step == 24.0_a); - region.parseOpcode({ "amplitude_stepcc120", "15482" }); - REQUIRE(view.at(120).step == 15482.0_a); - region.parseOpcode({ "amplitude_stepcc120", "-2" }); - REQUIRE(view.at(120).step == 0.0f); - } - - SECTION("volume_oncc/gain_cc") - { - const ModKey target = ModKey::createNXYZ(ModId::Volume, region.getId()); - const RegionCCView view(region, target); - REQUIRE(view.empty()); - region.parseOpcode({ "gain_cc1", "40" }); - REQUIRE(view.valueAt(1) == 40_a); - region.parseOpcode({ "volume_oncc2", "-76" }); - REQUIRE(view.valueAt(2) == -76.0_a); - region.parseOpcode({ "gain_oncc4", "-1" }); - REQUIRE(view.valueAt(4) == -1.0_a); - region.parseOpcode({ "volume_curvecc17", "18" }); - REQUIRE(view.at(17).curve == 18); - region.parseOpcode({ "volume_curvecc17", "15482" }); - REQUIRE(view.at(17).curve == 255); - region.parseOpcode({ "volume_curvecc17", "-2" }); - REQUIRE(view.at(17).curve == 0); - region.parseOpcode({ "volume_smoothcc14", "85" }); - REQUIRE(view.at(14).smooth == 85); - region.parseOpcode({ "volume_smoothcc14", "15482" }); - REQUIRE(view.at(14).smooth == 100); - region.parseOpcode({ "volume_smoothcc14", "-2" }); - REQUIRE(view.at(14).smooth == 0); - region.parseOpcode({ "volume_stepcc120", "24" }); - REQUIRE(view.at(120).step == 24.0f); - region.parseOpcode({ "volume_stepcc120", "15482" }); - REQUIRE(view.at(120).step == 144.0f); - region.parseOpcode({ "volume_stepcc120", "-2" }); - REQUIRE(view.at(120).step == 0.0f); - } - - SECTION("tune_cc/pitch_cc") - { - const ModKey target = ModKey::createNXYZ(ModId::Pitch, region.getId()); - const RegionCCView view(region, target); - REQUIRE(view.empty()); - region.parseOpcode({ "pitch_cc1", "40" }); - REQUIRE(view.valueAt(1) == 40.0); - region.parseOpcode({ "tune_oncc2", "-76" }); - REQUIRE(view.valueAt(2) == -76.0); - region.parseOpcode({ "pitch_oncc4", "-1" }); - REQUIRE(view.valueAt(4) == -1.0); - region.parseOpcode({ "tune_curvecc17", "18" }); - REQUIRE(view.at(17).curve == 18); - region.parseOpcode({ "pitch_curvecc17", "15482" }); - REQUIRE(view.at(17).curve == 255); - region.parseOpcode({ "tune_curvecc17", "-2" }); - REQUIRE(view.at(17).curve == 0); - region.parseOpcode({ "pitch_smoothcc14", "85" }); - REQUIRE(view.at(14).smooth == 85); - region.parseOpcode({ "tune_smoothcc14", "15482" }); - REQUIRE(view.at(14).smooth == 100); - region.parseOpcode({ "pitch_smoothcc14", "-2" }); - REQUIRE(view.at(14).smooth == 0); - region.parseOpcode({ "tune_stepcc120", "24" }); - REQUIRE(view.at(120).step == 24.0f); - region.parseOpcode({ "pitch_stepcc120", "15482" }); - REQUIRE(view.at(120).step == 12000.0f); - region.parseOpcode({ "tune_stepcc120", "-2" }); - REQUIRE(view.at(120).step == 0.0f); - } -} - -// Specific region bugs -TEST_CASE("[Region] Non-conforming floating point values in integer opcodes") -{ - MidiState midiState; - Region region { 0, midiState }; - region.parseOpcode({ "offset", "2014.5" }); - REQUIRE(region.offset == 2014); - region.parseOpcode({ "pitch_keytrack", "-2.1" }); - REQUIRE(region.pitchKeytrack == -2); -} - - -TEST_CASE("[Region] Release and release key") -{ - MidiState midiState; - Region region { 0, midiState }; - region.parseOpcode({ "lokey", "63" }); - region.parseOpcode({ "hikey", "65" }); - region.parseOpcode({ "sample", "*sine" }); - SECTION("Release key without sustain") - { - region.parseOpcode({ "trigger", "release_key" }); - midiState.ccEvent(0, 64, 0.0f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); - } - SECTION("Release key with sustain") - { - region.parseOpcode({ "trigger", "release_key" }); - midiState.ccEvent(0, 64, 1.0f); - REQUIRE( !region.registerCC(64, 1.0f) ); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); - } - - SECTION("Release without sustain") - { - region.parseOpcode({ "trigger", "release" }); - midiState.ccEvent(0, 64, 0.0f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); - } - - SECTION("Release with sustain") - { - region.parseOpcode({ "trigger", "release" }); - midiState.ccEvent(0, 64, 1.0f); - midiState.noteOnEvent(0, 63, 0.5f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) ); - REQUIRE( region.delayedReleases.size() == 1 ); - std::vector> expected = { - { 63, 0.5f } - }; - REQUIRE( region.delayedReleases == expected ); - } - - SECTION("Release with sustain and 2 notes") - { - region.parseOpcode({ "trigger", "release" }); - midiState.ccEvent(0, 64, 1.0f); - midiState.noteOnEvent(0, 63, 0.5f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - midiState.noteOnEvent(0, 64, 0.6f); - REQUIRE( !region.registerNoteOn(64, 0.6f, 0.0f) ); - REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) ); - REQUIRE( !region.registerNoteOff(64, 0.2f, 0.0f) ); - REQUIRE( region.delayedReleases.size() == 2 ); - std::vector> expected = { - { 63, 0.5f }, - { 64, 0.6f } - }; - REQUIRE( region.delayedReleases == expected ); - } - - SECTION("Release with sustain and 2 notes but 1 outside") - { - region.parseOpcode({ "trigger", "release" }); - midiState.ccEvent(0, 64, 1.0f); - midiState.noteOnEvent(0, 63, 0.5f); - REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); - midiState.noteOnEvent(0, 66, 0.6f); - REQUIRE( !region.registerNoteOn(66, 0.6f, 0.0f) ); - REQUIRE( !region.registerNoteOff(63, 0.0f, 0.0f) ); - REQUIRE( !region.registerNoteOff(66, 0.2f, 0.0f) ); - REQUIRE( region.delayedReleases.size() == 1 ); - std::vector> expected = { - { 63, 0.5f } - }; - REQUIRE( region.delayedReleases == expected ); - } -} - -TEST_CASE("[Region] Offsets with CCs") -{ - MidiState midiState; - Region region { 0, midiState }; - - region.parseOpcode({ "offset_cc4", "255" }); - region.parseOpcode({ "offset", "10" }); - REQUIRE( region.getOffset() == 10 ); - midiState.ccEvent(0, 4, 127_norm); - REQUIRE( region.getOffset() == 265 ); - midiState.ccEvent(0, 4, 100_norm); - REQUIRE( region.getOffset() == 210 ); - midiState.ccEvent(0, 4, 10_norm); - REQUIRE( region.getOffset() == 30 ); - midiState.ccEvent(0, 4, 0); - REQUIRE( region.getOffset() == 10 ); -} - -TEST_CASE("[Region] Pitch variation with veltrack") -{ - MidiState midiState; - Region region { 0, midiState }; - - REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); - REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == 1.0); - REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == 1.0); - region.parseOpcode({ "pitch_veltrack", "1200" }); - REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); - REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == Approx(centsFactor(600.0)).margin(0.01f)); - REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == Approx(centsFactor(1200.0)).margin(0.01f)); -} diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index 3b2d3e32..60937f3d 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -6,17 +6,20 @@ #include "sfizz/Defaults.h" #include "sfizz/Region.h" +#include "sfizz/MidiState.h" #include "sfizz/SfzHelpers.h" #include "catch2/catch.hpp" #include #include using namespace Catch::literals; using namespace sfz::literals; +using namespace sfz; constexpr int numRandomTests { 64 }; + TEST_CASE("[Region] Crossfade in on key") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "3" }); @@ -27,8 +30,8 @@ TEST_CASE("[Region] Crossfade in on key") TEST_CASE("[Region] Crossfade in on key - 2") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); @@ -42,8 +45,8 @@ TEST_CASE("[Region] Crossfade in on key - 2") TEST_CASE("[Region] Crossfade in on key - gain") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); @@ -57,8 +60,8 @@ TEST_CASE("[Region] Crossfade in on key - gain") TEST_CASE("[Region] Crossfade out on key") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); @@ -73,8 +76,8 @@ TEST_CASE("[Region] Crossfade out on key") TEST_CASE("[Region] Crossfade out on key - gain") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); @@ -90,8 +93,8 @@ TEST_CASE("[Region] Crossfade out on key - gain") TEST_CASE("[Region] Crossfade in on velocity") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); @@ -107,8 +110,8 @@ TEST_CASE("[Region] Crossfade in on velocity") TEST_CASE("[Region] Crossfade in on vel - gain") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); @@ -125,8 +128,8 @@ TEST_CASE("[Region] Crossfade in on vel - gain") TEST_CASE("[Region] Crossfade out on vel") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); @@ -142,8 +145,8 @@ TEST_CASE("[Region] Crossfade out on vel") TEST_CASE("[Region] Crossfade out on vel - gain") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); @@ -160,8 +163,8 @@ TEST_CASE("[Region] Crossfade out on vel - gain") TEST_CASE("[Region] Crossfade in on CC") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); @@ -184,8 +187,8 @@ TEST_CASE("[Region] Crossfade in on CC") TEST_CASE("[Region] Crossfade in on CC - gain") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); @@ -208,8 +211,8 @@ TEST_CASE("[Region] Crossfade in on CC - gain") } TEST_CASE("[Region] Crossfade out on CC") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); @@ -232,8 +235,8 @@ TEST_CASE("[Region] Crossfade out on CC") TEST_CASE("[Region] Crossfade out on CC - gain") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); @@ -257,8 +260,8 @@ TEST_CASE("[Region] Crossfade out on CC - gain") TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "0" }); REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); @@ -268,8 +271,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "100" }); REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); @@ -278,8 +281,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "-100" }); REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001)); @@ -288,29 +291,29 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") TEST_CASE("[Region] rt_decay") { - sfz::MidiState midiState; + MidiState midiState; midiState.setSampleRate(1000); - sfz::Region region { 0, midiState }; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume).margin(0.1) ); } TEST_CASE("[Region] Base delay") { - sfz::MidiState midiState; - sfz::Region region { 0, midiState }; + MidiState midiState; + Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "delay", "10" }); REQUIRE( region.getDelay() == 10.0f ); @@ -322,3 +325,36 @@ TEST_CASE("[Region] Base delay") REQUIRE( (delay >= 10.0 && delay <= 20.0) ); } } + +TEST_CASE("[Region] Offsets with CCs") +{ + MidiState midiState; + Region region { 0, midiState }; + + region.parseOpcode({ "offset_cc4", "255" }); + region.parseOpcode({ "offset", "10" }); + REQUIRE( region.getOffset() == 10 ); + midiState.ccEvent(0, 4, 127_norm); + REQUIRE( region.getOffset() == 265 ); + midiState.ccEvent(0, 4, 100_norm); + REQUIRE( region.getOffset() == 210 ); + midiState.ccEvent(0, 4, 10_norm); + REQUIRE( region.getOffset() == 30 ); + midiState.ccEvent(0, 4, 0); + REQUIRE( region.getOffset() == 10 ); +} + +TEST_CASE("[Region] Pitch variation with veltrack") +{ + MidiState midiState; + Region region { 0, midiState }; + + REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); + REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == 1.0); + REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == 1.0); + region.parseOpcode({ "pitch_veltrack", "1200" }); + REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); + REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == Approx(centsFactor(600.0)).margin(0.01f)); + REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == Approx(centsFactor(1200.0)).margin(0.01f)); +} + diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp new file mode 100644 index 00000000..f0274fd1 --- /dev/null +++ b/tests/RegionValuesT.cpp @@ -0,0 +1,3033 @@ +// 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 "TestHelpers.h" +#include "sfizz/Synth.h" +#include "sfizz/Messaging.h" +#include "catch2/catch.hpp" +#include +#include +#include +using namespace Catch::literals; +using namespace sfz; + +void simpleMessageReceiver(void* data, int delay, const char* path, const char* sig, const sfizz_arg_t* args) +{ + (void)delay; + auto& messageList = *reinterpret_cast*>(data); + + std::string newMessage = absl::StrCat(path, ",", sig, " : { "); + for (unsigned i = 0, n = strlen(sig); i < n; ++i) { + switch(sig[i]){ + case 'i': + absl::StrAppend(&newMessage, args[i].i); + break; + case 'f': + absl::StrAppend(&newMessage, args[i].f); + break; + case 'd': + absl::StrAppend(&newMessage, args[i].d); + break; + case 'h': + absl::StrAppend(&newMessage, args[i].h); + break; + case 's': + absl::StrAppend(&newMessage, args[i].s); + break; + } + + if (i == (n - 1)) + absl::StrAppend(&newMessage, " }"); + else + absl::StrAppend(&newMessage, ", "); + } + + messageList.push_back(std::move(newMessage)); +} + +TEST_CASE("[Values] Delay") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=*sine + sample=*sine delay=1 + sample=*sine delay=-1 + sample=*sine delay=1 delay=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/delay", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/delay", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/delay", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region3/delay", "", nullptr); + std::vector expected { + "/region0/delay,f : { 0 }", + "/region1/delay,f : { 1 }", + "/region2/delay,f : { 0 }", + // TODO: activate for the new region parser ; ignore the second value + // "/region3/delay,f : { 1 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Random") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=*sine + sample=*sine delay_random=1 + sample=*sine delay_random=-1 + sample=*sine delay_random=1 delay_random=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/delay_random", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/delay_random", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/delay_random", "", nullptr); + // synth.dispatchMessage(client, 0, "/region3/delay_random", "", nullptr); + std::vector expected { + "/region0/delay_random,f : { 0 }", + "/region1/delay_random,f : { 1 }", + "/region2/delay_random,f : { 0 }", + // "/region3/delay_random,f : { 1 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Sample and direction") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=*sine + sample=kick.wav + sample=kick.wav direction=reverse + )"); + synth.dispatchMessage(client, 0, "/region0/sample", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sample", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/direction", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/direction", "", nullptr); + std::vector expected { + "/region0/sample,s : { *sine }", + "/region1/sample,s : { kick.wav }", + "/region1/direction,s : { forward }", + "/region2/direction,s : { reverse }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Offset") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav offset=12 + sample=kick.wav offset=-1 + sample=kick.wav offset=12 offset=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/offset", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/offset", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/offset", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region3/offset", "", nullptr); + std::vector expected { + "/region0/offset,h : { 0 }", + "/region1/offset,h : { 12 }", + "/region2/offset,h : { 0 }", + // TODO: activate for the new region parser ; ignore the second value + // "/region3/offset,f : { 12 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Random") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav offset_random=1 + sample=kick.wav offset_random=-1 + sample=kick.wav offset_random=1 offset_random=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/offset_random", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/offset_random", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/offset_random", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region3/offset_random", "", nullptr); + std::vector expected { + "/region0/offset_random,h : { 0 }", + "/region1/offset_random,h : { 1 }", + "/region2/offset_random,h : { 0 }", + // "/region3/offset_random,f : { 1 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav offset_cc12=12 + sample=kick.wav offset_cc12=-12 + sample=kick.wav offset_cc14=14 offset_cc12=12 offset_cc12=-12 + )"); + synth.dispatchMessage(client, 0, "/region0/offset_cc12", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/offset_cc12", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/offset_cc12", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/offset_cc14", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region3/offset_cc12", "", nullptr); + std::vector expected { + "/region0/offset_cc12,h : { 0 }", + "/region1/offset_cc12,h : { 12 }", + "/region2/offset_cc12,h : { 0 }", + "/region3/offset_cc14,h : { 14 }", + // "/region3/offset_cc12,h : { 12 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] End") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav end=194 + sample=kick.wav end=-1 + sample=kick.wav end=0 + sample=kick.wav end=194 end=-1 + sample=kick.wav end=0 end=194 + )"); + synth.dispatchMessage(client, 0, "/region0/end", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/enabled", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/enabled", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/enabled", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/enabled", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/enabled", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/end", "", nullptr); + std::vector expected { + "/region0/end,h : { 194 }", + "/region0/enabled,T : { }", + "/region1/enabled,F : { }", + "/region2/enabled,F : { }", + "/region3/enabled,F : { }", + "/region4/enabled,T : { }", + "/region4/end,h : { 194 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Count") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav count=2 + sample=kick.wav count=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/count", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/count", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/count", "", nullptr); + std::vector expected { + "/region0/count,N : { }", + "/region1/count,h : { 2 }", + "/region2/count,h : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Loop mode") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav loop_mode=one_shot + sample=kick.wav loopmode=one_shot + sample=kick.wav loop_mode=loop_sustain + sample=kick.wav loop_mode=loop_continuous + sample=kick.wav loop_mode=loop_continuous loop_mode=no_loop + )"); + synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/loop_mode", "", nullptr); + std::vector expected { + "/region0/loop_mode,s : { no_loop }", + "/region1/loop_mode,s : { one_shot }", + "/region2/loop_mode,s : { one_shot }", + "/region3/loop_mode,s : { loop_sustain }", + "/region4/loop_mode,s : { loop_continuous }", + "/region5/loop_mode,s : { no_loop }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Loops") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav loop_mode=one_shot + sample=kick.wav loopmode=one_shot + sample=kick.wav loop_mode=loop_sustain + sample=kick.wav loop_mode=loop_continuous + sample=kick.wav loop_mode=loop_continuous loop_mode=no_loop + )"); + synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/loop_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/loop_mode", "", nullptr); + std::vector expected { + "/region0/loop_mode,s : { no_loop }", + "/region1/loop_mode,s : { one_shot }", + "/region2/loop_mode,s : { one_shot }", + "/region3/loop_mode,s : { loop_sustain }", + "/region4/loop_mode,s : { loop_continuous }", + "/region5/loop_mode,s : { no_loop }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Loop range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav loop_start=10 loop_end=100 + sample=kick.wav loopstart=10 loopend=100 + sample=kick.wav loop_start=-1 loopend=-100 + )"); + synth.dispatchMessage(client, 0, "/region0/loop_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/loop_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/loop_range", "", nullptr); + std::vector expected { + "/region0/loop_range,hh : { 10, 100 }", + "/region1/loop_range,hh : { 10, 100 }", + "/region2/loop_range,hh : { 0, 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Loop crossfade") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav loop_crossfade=0.5 + sample=kick.wav loop_crossfade=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/loop_crossfade", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/loop_crossfade", "", nullptr); + std::vector expected { + "/region0/loop_crossfade,f : { 0.5 }", + "/region1/loop_crossfade,f : { 0.001 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Group") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav group=5 + sample=kick.wav group=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/group", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/group", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/group", "", nullptr); + std::vector expected { + "/region0/group,h : { 0 }", + "/region1/group,h : { 5 }", + "/region2/group,h : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Off by") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav off_by=5 + sample=kick.wav off_by=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/off_by", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/off_by", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/off_by", "", nullptr); + std::vector expected { + "/region0/off_by,N : { }", + "/region1/off_by,h : { 5 }", + "/region2/off_by,N : { }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Off mode") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav off_mode=fast + sample=kick.wav off_mode=normal + sample=kick.wav off_mode=time + sample=kick.wav off_mode=time off_mode=normal + sample=kick.wav off_mode=nothing + )"); + synth.dispatchMessage(client, 0, "/region0/off_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/off_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/off_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/off_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/off_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/off_mode", "", nullptr); + std::vector expected { + "/region0/off_mode,s : { fast }", + "/region1/off_mode,s : { fast }", + "/region2/off_mode,s : { normal }", + "/region3/off_mode,s : { time }", + "/region4/off_mode,s : { normal }", + "/region5/off_mode,s : { fast }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Off time") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav off_time=0.1 + sample=kick.wav off_time=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/off_time", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/off_time", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/off_time", "", nullptr); + std::vector expected { + "/region0/off_time,f : { 0.006 }", + "/region1/off_time,f : { 0.1 }", + "/region2/off_time,f : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Key range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lokey=34 hikey=60 + sample=kick.wav lokey=c4 hikey=b5 + sample=kick.wav lokey=-3 hikey=60 + sample=kick.wav hikey=-1 + sample=kick.wav pitch_keycenter=32 + sample=kick.wav pitch_keycenter=-1 + sample=kick.wav key=26 + )"); + synth.dispatchMessage(client, 0, "/region0/key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/pitch_keycenter", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region6/pitch_keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region7/key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region7/pitch_keycenter", "", nullptr); + std::vector expected { + "/region0/key_range,ii : { 0, 127 }", + "/region1/key_range,ii : { 34, 60 }", + "/region2/key_range,ii : { 60, 83 }", + "/region3/key_range,ii : { 0, 60 }", + "/region4/key_range,ii : { 0, 0 }", + "/region0/pitch_keycenter,i : { 60 }", + "/region5/pitch_keycenter,i : { 32 }", + // "/region6/pitch_keycenter,i : { 60 }", + "/region7/key_range,ii : { 26, 26 }", + "/region7/pitch_keycenter,i : { 26 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Velocity range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lovel=34 hivel=60 + sample=kick.wav lovel=-3 hivel=60 + sample=kick.wav hivel=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/vel_range", "", nullptr); + std::vector expected { + "/region0/vel_range,ff : { 0, 1 }", + "/region1/vel_range,ff : { 0.267717, 0.472441 }", + "/region2/vel_range,ff : { 0, 0.472441 }", + "/region3/vel_range,ff : { 0, 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Bend range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lobend=891 hibend=2000 + sample=kick.wav lobend=-891 hibend=891 + sample=kick.wav hibend=-10000 + )"); + synth.dispatchMessage(client, 0, "/region0/bend_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/bend_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/bend_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/bend_range", "", nullptr); + std::vector expected { + "/region0/bend_range,ff : { -1, 1 }", + "/region1/bend_range,ff : { 0.108778, 0.24417 }", + "/region2/bend_range,ff : { -0.108778, 0.108778 }", + "/region3/bend_range,ff : { -1, -1 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] CC condition range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav locc1=0 hicc1=54 + sample=kick.wav locc1=0 hicc1=54 locc2=2 hicc2=10 + sample=kick.wav locc1=10 hicc1=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/cc_range2", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); + std::vector expected { + "/region0/cc_range1,ff : { 0, 1 }", + "/region1/cc_range1,ff : { 0, 0.425197 }", + "/region2/cc_range1,ff : { 0, 0.425197 }", + "/region2/cc_range2,ff : { 0.015748, 0.0787402 }", + "/region3/cc_range1,ff : { 0, 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("hdcc") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lohdcc1=0 hihdcc1=0.1 + sample=kick.wav lohdcc1=0 hihdcc1=0.1 lohdcc2=0.1 hihdcc2=0.2 + sample=kick.wav lohdcc1=0.1 hihdcc1=-0.1 + )"); + synth.dispatchMessage(client, 0, "/region0/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/cc_range2", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); + std::vector expected { + "/region0/cc_range1,ff : { 0, 1 }", + "/region1/cc_range1,ff : { 0, 0.1 }", + "/region2/cc_range1,ff : { 0, 0.1 }", + "/region2/cc_range2,ff : { 0.1, 0.2 }", + "/region3/cc_range1,ff : { 0, 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("realcc") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lorealcc1=0 hirealcc1=0.1 + sample=kick.wav lorealcc1=0 hirealcc1=0.1 lorealcc2=0.1 hirealcc2=0.2 + sample=kick.wav lorealcc1=0.1 hirealcc1=-0.1 + )"); + synth.dispatchMessage(client, 0, "/region0/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/cc_range2", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); + std::vector expected { + "/region0/cc_range1,ff : { 0, 1 }", + "/region1/cc_range1,ff : { 0, 0.1 }", + "/region2/cc_range1,ff : { 0, 0.1 }", + "/region2/cc_range2,ff : { 0.1, 0.2 }", + "/region3/cc_range1,ff : { 0, 0 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Last keyswitch") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sw_last=12 + sample=kick.wav sw_last=c4 + sample=kick.wav sw_lolast=14 sw_hilast=16 + sample=kick.wav sw_lolast=c4 sw_hilast=b5 + sample=kick.wav sw_last=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/sw_last", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sw_last", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sw_last", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sw_last", "", nullptr); + // TODO: activate for the new region parser ; can handle note names + // synth.dispatchMessage(client, 0, "/region4/sw_last", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region5/sw_last", "", nullptr); + std::vector expected { + "/region0/sw_last,N : { }", + "/region1/sw_last,i : { 12 }", + "/region2/sw_last,i : { 60 }", + "/region3/sw_last,ii : { 14, 16 }", + // "/region4/sw_last,ii : { 60, 83 }", + // "/region5/sw_last,ii : { 0, 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("sw_lolast disables sw_last over the whole region") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav sw_last=12 sw_lolast=14 sw_last=16 + )"); + synth.dispatchMessage(client, 0, "/region0/sw_last", "", nullptr); + std::vector expected { + "/region0/sw_last,ii : { 14, 14 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Keyswitch label") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sw_label=hello + )"); + synth.dispatchMessage(client, 0, "/region0/sw_label", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sw_label", "", nullptr); + std::vector expected { + "/region0/sw_label,N : { }", + "/region1/sw_label,s : { hello }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Upswitch") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sw_up=16 + sample=kick.wav sw_up=-1 + sample=kick.wav sw_up=128 + sample=kick.wav sw_up=c4 + sample=kick.wav sw_up=64 sw_up=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/sw_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sw_up", "", nullptr); + // TODO: activate for the new region parser; ignore oob + // synth.dispatchMessage(client, 0, "/region2/sw_up", "", nullptr); + // synth.dispatchMessage(client, 0, "/region3/sw_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/sw_up", "", nullptr); + // TODO: activate for the new region parser; ignore the second value + // synth.dispatchMessage(client, 0, "/region5/sw_up", "", nullptr); + std::vector expected { + "/region0/sw_up,N : { }", + "/region1/sw_up,i : { 16 }", + // "/region2/sw_up,N : { }", + // "/region3/sw_up,N : { }", + "/region4/sw_up,i : { 60 }", + // "/region5/sw_up,i : { 64 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Downswitch") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sw_down=16 + sample=kick.wav sw_down=-1 + sample=kick.wav sw_down=128 + sample=kick.wav sw_down=c4 + sample=kick.wav sw_down=64 sw_down=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/sw_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sw_down", "", nullptr); + // TODO: activate for the new region parser; ignore oob + // synth.dispatchMessage(client, 0, "/region2/sw_down", "", nullptr); + // synth.dispatchMessage(client, 0, "/region3/sw_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/sw_down", "", nullptr); + // TODO: activate for the new region parser; ignore the second value + // synth.dispatchMessage(client, 0, "/region5/sw_down", "", nullptr); + std::vector expected { + "/region0/sw_down,N : { }", + "/region1/sw_down,i : { 16 }", + // "/region2/sw_down,N : { }", + // "/region3/sw_down,N : { }", + "/region4/sw_down,i : { 60 }", + // "/region5/sw_down,i : { 64 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Previous keyswitch") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sw_previous=16 + sample=kick.wav sw_previous=-1 + sample=kick.wav sw_previous=128 + sample=kick.wav sw_previous=c4 + sample=kick.wav sw_previous=64 sw_previous=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/sw_previous", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sw_previous", "", nullptr); + // TODO: activate for the new region parser; ignore oob + // synth.dispatchMessage(client, 0, "/region2/sw_previous", "", nullptr); + // synth.dispatchMessage(client, 0, "/region3/sw_previous", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/sw_previous", "", nullptr); + // TODO: activate for the new region parser; ignore the second value + // synth.dispatchMessage(client, 0, "/region5/sw_previous", "", nullptr); + std::vector expected { + "/region0/sw_previous,N : { }", + "/region1/sw_previous,i : { 16 }", + // "/region2/sw_previous,N : { }", + // "/region3/sw_previous,N : { }", + "/region4/sw_previous,i : { 60 }", + // "/region5/sw_previous,i : { 64 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Velocity override") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sw_vel=current + sample=kick.wav sw_vel=previous + sample=kick.wav sw_vel=previous sw_vel=current + )"); + synth.dispatchMessage(client, 0, "/region0/sw_vel", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sw_vel", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sw_vel", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sw_vel", "", nullptr); + std::vector expected { + "/region0/sw_vel,s : { current }", + "/region1/sw_vel,s : { current }", + "/region2/sw_vel,s : { previous }", + "/region3/sw_vel,s : { current }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Aftertouch range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lochanaft=34 hichanaft=60 + sample=kick.wav lochanaft=-3 hichanaft=60 + sample=kick.wav lochanaft=20 hichanaft=-1 + sample=kick.wav lochanaft=20 hichanaft=10 + )"); + synth.dispatchMessage(client, 0, "/region0/chanaft_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/chanaft_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/chanaft_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/chanaft_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/chanaft_range", "", nullptr); + std::vector expected { + "/region0/chanaft_range,ii : { 0, 127 }", + "/region1/chanaft_range,ii : { 34, 60 }", + "/region2/chanaft_range,ii : { 0, 60 }", + "/region3/chanaft_range,ii : { 0, 0 }", + "/region4/chanaft_range,ii : { 10, 10 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] BPM range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lobpm=34.1 hibpm=60.2 + sample=kick.wav lobpm=-3 hibpm=60 + sample=kick.wav lobpm=20 hibpm=-1 + sample=kick.wav lobpm=20 hibpm=10 + )"); + synth.dispatchMessage(client, 0, "/region0/bpm_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/bpm_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/bpm_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/bpm_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/bpm_range", "", nullptr); + std::vector expected { + "/region0/bpm_range,ff : { 0, 500 }", + "/region1/bpm_range,ff : { 34.1, 60.2 }", + "/region2/bpm_range,ff : { 0, 60 }", + "/region3/bpm_range,ff : { 0, 0 }", + "/region4/bpm_range,ff : { 10, 10 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Rand range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav lorand=0.2 hirand=0.4 + sample=kick.wav lorand=-0.1 hirand=0.4 + sample=kick.wav lorand=0.2 hirand=-0.1 + sample=kick.wav lorand=0.2 hirand=0.1 + )"); + synth.dispatchMessage(client, 0, "/region0/rand_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/rand_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/rand_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/rand_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/rand_range", "", nullptr); + std::vector expected { + "/region0/rand_range,ff : { 0, 1 }", + "/region1/rand_range,ff : { 0.2, 0.4 }", + "/region2/rand_range,ff : { 0, 0.4 }", + "/region3/rand_range,ff : { 0, 0 }", + "/region4/rand_range,ff : { 0.1, 0.1 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Sequence length") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav seq_length=12 + sample=kick.wav seq_length=-1 + sample=kick.wav seq_length=12 seq_length=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/seq_length", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/seq_length", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/seq_length", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region3/seq_length", "", nullptr); + std::vector expected { + "/region0/seq_length,h : { 1 }", + "/region1/seq_length,h : { 12 }", + "/region2/seq_length,h : { 1 }", + // TODO: activate for the new region parser ; ignore the second value + // "/region3/seq_length,f : { 12 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Sequence position") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav seq_position=12 + sample=kick.wav seq_position=-1 + sample=kick.wav seq_position=12 seq_position=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/seq_position", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/seq_position", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/seq_position", "", nullptr); + // TODO: activate for the new region parser ; ignore the second value + // synth.dispatchMessage(client, 0, "/region3/seq_position", "", nullptr); + std::vector expected { + "/region0/seq_position,h : { 1 }", + "/region1/seq_position,h : { 12 }", + "/region2/seq_position,h : { 1 }", + // TODO: activate for the new region parser ; ignore the second value + // "/region3/seq_position,f : { 12 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Trigger type") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav trigger=release + sample=kick.wav trigger=release_key + sample=kick.wav trigger=legato + sample=kick.wav trigger=first + sample=kick.wav trigger=nothing + sample=kick.wav trigger=release trigger=attack + )"); + synth.dispatchMessage(client, 0, "/region0/trigger", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/trigger", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/trigger", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/trigger", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/trigger", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/trigger", "", nullptr); + synth.dispatchMessage(client, 0, "/region6/trigger", "", nullptr); + std::vector expected { + "/region0/trigger,s : { attack }", + "/region1/trigger,s : { release }", + "/region2/trigger,s : { release_key }", + "/region3/trigger,s : { legato }", + "/region4/trigger,s : { first }", + "/region5/trigger,s : { attack }", + "/region6/trigger,s : { attack }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Start on cc range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav on_locc1=15 + sample=kick.wav on_hicc1=84 + sample=kick.wav on_locc1=15 on_hicc1=84 + sample=kick.wav on_lohdcc2=0.1 + sample=kick.wav on_hihdcc2=0.4 + sample=kick.wav on_lohdcc2=0.1 on_hihdcc2=0.4 + )"); + synth.dispatchMessage(client, 0, "/region0/start_cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/start_cc_range2", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/start_cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/start_cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/start_cc_range1", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/start_cc_range2", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/start_cc_range2", "", nullptr); + synth.dispatchMessage(client, 0, "/region6/start_cc_range2", "", nullptr); + std::vector expected { + "/region0/start_cc_range1,N : { }", + "/region0/start_cc_range2,N : { }", + "/region1/start_cc_range1,ff : { 0.11811, 1 }", + "/region2/start_cc_range1,ff : { 0, 0.661417 }", + "/region3/start_cc_range1,ff : { 0.11811, 0.661417 }", + "/region4/start_cc_range2,ff : { 0.1, 1 }", + "/region5/start_cc_range2,ff : { 0, 0.4 }", + "/region6/start_cc_range2,ff : { 0.1, 0.4 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Volume") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav volume=4.2 + sample=kick.wav gain=-200 + )"); + synth.dispatchMessage(client, 0, "/region0/volume", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/volume", "", nullptr); + // TODO: activate for the new region parser ; allow oob + // synth.dispatchMessage(client, 0, "/region2/volume", "", nullptr); + std::vector expected { + "/region0/volume,f : { 0 }", + "/region1/volume,f : { 4.2 }", + // "/region2/volume,f : { -200 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Depth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav volume_oncc42=4.2 + sample=kick.wav gain_oncc2=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/volume_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/volume_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/volume_cc2", "", nullptr); + std::vector expected { + "/region0/volume_cc42,N : { }", + "/region1/volume_cc42,f : { 4.2 }", + "/region2/volume_cc2,f : { -10 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav volume_stepcc42=4.2 + sample=kick.wav volume_smoothcc42=4 + sample=kick.wav volume_curvecc42=2 + sample=kick.wav volume_stepcc42=-1 + sample=kick.wav volume_smoothcc42=-4 + sample=kick.wav volume_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/volume_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/volume_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/volume_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/volume_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/volume_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/volume_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/volume_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/volume_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/volume_curvecc42", "", nullptr); + std::vector expected { + "/region0/volume_stepcc42,N : { }", + "/region0/volume_smoothcc42,N : { }", + "/region0/volume_curvecc42,N : { }", + "/region1/volume_stepcc42,f : { 4.2 }", + "/region2/volume_smoothcc42,i : { 4 }", + "/region3/volume_curvecc42,i : { 2 }", + // "/region4/volume_stepcc42,N : { }", + // "/region5/volume_smoothcc42,N : { }", + // "/region6/volume_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params (with gain_)") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav gain_stepcc42=4.2 + sample=kick.wav gain_smoothcc42=4 + sample=kick.wav gain_curvecc42=2 + sample=kick.wav gain_stepcc42=-1 + sample=kick.wav gain_smoothcc42=-4 + sample=kick.wav gain_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/volume_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/volume_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/volume_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/volume_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/volume_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/volume_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/volume_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/volume_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/volume_curvecc42", "", nullptr); + std::vector expected { + "/region0/volume_stepcc42,N : { }", + "/region0/volume_smoothcc42,N : { }", + "/region0/volume_curvecc42,N : { }", + "/region1/volume_stepcc42,f : { 4.2 }", + "/region2/volume_smoothcc42,i : { 4 }", + "/region3/volume_curvecc42,i : { 2 }", + // "/region4/volume_stepcc42,N : { }", + // "/region5/volume_smoothcc42,N : { }", + // "/region6/volume_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Pan") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pan=4.2 + sample=kick.wav pan=-200 + )"); + synth.dispatchMessage(client, 0, "/region0/pan", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pan", "", nullptr); + // TODO: activate for the new region parser ; accept oob + // synth.dispatchMessage(client, 0, "/region2/pan", "", nullptr); + std::vector expected { + "/region0/pan,f : { 0 }", + "/region1/pan,f : { 4.2 }", + // TODO: activate for the new region parser ; accept oob + // "/region2/pan,f : { -200 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Depth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pan_oncc42=4.2 + sample=kick.wav pan_oncc2=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/pan_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pan_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pan_cc2", "", nullptr); + std::vector expected { + "/region0/pan_cc42,N : { }", + "/region1/pan_cc42,f : { 4.2 }", + "/region2/pan_cc2,f : { -10 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pan_stepcc42=4.2 + sample=kick.wav pan_smoothcc42=4 + sample=kick.wav pan_curvecc42=2 + sample=kick.wav pan_stepcc42=-1 + sample=kick.wav pan_smoothcc42=-4 + sample=kick.wav pan_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/pan_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pan_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pan_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pan_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pan_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/pan_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/pan_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/pan_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/pan_curvecc42", "", nullptr); + std::vector expected { + "/region0/pan_stepcc42,N : { }", + "/region0/pan_smoothcc42,N : { }", + "/region0/pan_curvecc42,N : { }", + "/region1/pan_stepcc42,f : { 4.2 }", + "/region2/pan_smoothcc42,i : { 4 }", + "/region3/pan_curvecc42,i : { 2 }", + // "/region4/pan_stepcc42,N : { }", + // "/region5/pan_smoothcc42,N : { }", + // "/region6/pan_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Width") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav width=4.2 + sample=kick.wav width=-200 + )"); + synth.dispatchMessage(client, 0, "/region0/width", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/width", "", nullptr); + // TODO: activate for the new region parser ; accept oob + // synth.dispatchMessage(client, 0, "/region2/width", "", nullptr); + std::vector expected { + "/region0/width,f : { 100 }", + "/region1/width,f : { 4.2 }", + // TODO: activate for the new region parser ; accept oob + // "/region2/width,f : { -200 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Depth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav width_oncc42=4.2 + sample=kick.wav width_oncc2=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/width_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/width_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/width_cc2", "", nullptr); + std::vector expected { + "/region0/width_cc42,N : { }", + "/region1/width_cc42,f : { 4.2 }", + "/region2/width_cc2,f : { -10 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav width_stepcc42=4.2 + sample=kick.wav width_smoothcc42=4 + sample=kick.wav width_curvecc42=2 + sample=kick.wav width_stepcc42=-1 + sample=kick.wav width_smoothcc42=-4 + sample=kick.wav width_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/width_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/width_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/width_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/width_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/width_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/width_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/width_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/width_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/width_curvecc42", "", nullptr); + std::vector expected { + "/region0/width_stepcc42,N : { }", + "/region0/width_smoothcc42,N : { }", + "/region0/width_curvecc42,N : { }", + "/region1/width_stepcc42,f : { 4.2 }", + "/region2/width_smoothcc42,i : { 4 }", + "/region3/width_curvecc42,i : { 2 }", + // "/region4/width_stepcc42,N : { }", + // "/region5/width_smoothcc42,N : { }", + // "/region6/width_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Position") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav position=4.2 + sample=kick.wav position=-200 + )"); + synth.dispatchMessage(client, 0, "/region0/position", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/position", "", nullptr); + // TODO: activate for the new region parser; accept oob + // synth.dispatchMessage(client, 0, "/region2/position", "", nullptr); + std::vector expected { + "/region0/position,f : { 0 }", + "/region1/position,f : { 4.2 }", + // TODO: activate for the new region parser; accept oob + // "/region2/position,f : { -200 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Depth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav position_oncc42=4.2 + sample=kick.wav position_oncc2=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/position_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/position_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/position_cc2", "", nullptr); + std::vector expected { + "/region0/position_cc42,N : { }", + "/region1/position_cc42,f : { 4.2 }", + "/region2/position_cc2,f : { -10 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav position_stepcc42=4.2 + sample=kick.wav position_smoothcc42=4 + sample=kick.wav position_curvecc42=2 + sample=kick.wav position_stepcc42=-1 + sample=kick.wav position_smoothcc42=-4 + sample=kick.wav position_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/position_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/position_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/position_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/position_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/position_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/position_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/position_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/position_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/position_curvecc42", "", nullptr); + std::vector expected { + "/region0/position_stepcc42,N : { }", + "/region0/position_smoothcc42,N : { }", + "/region0/position_curvecc42,N : { }", + "/region1/position_stepcc42,f : { 4.2 }", + "/region2/position_smoothcc42,i : { 4 }", + "/region3/position_curvecc42,i : { 2 }", + // "/region4/position_stepcc42,N : { }", + // "/region5/position_smoothcc42,N : { }", + // "/region6/position_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Amplitude") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amplitude=4.2 + sample=kick.wav amplitude=-200 + )"); + synth.dispatchMessage(client, 0, "/region0/amplitude", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amplitude", "", nullptr); + // TODO: activate for the new region parser; ignore oob + // synth.dispatchMessage(client, 0, "/region2/amplitude", "", nullptr); + std::vector expected { + "/region0/amplitude,f : { 100 }", + "/region1/amplitude,f : { 4.2 }", + // "/region2/amplitude,f : { 100 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Depth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amplitude_oncc42=4.2 + sample=kick.wav amplitude_oncc2=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/amplitude_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amplitude_cc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/amplitude_cc2", "", nullptr); + std::vector expected { + "/region0/amplitude_cc42,N : { }", + "/region1/amplitude_cc42,f : { 4.2 }", + // "/region2/amplitude_cc2,N : { }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amplitude_stepcc42=4.2 + sample=kick.wav amplitude_smoothcc42=4 + sample=kick.wav amplitude_curvecc42=2 + sample=kick.wav amplitude_stepcc42=-1 + sample=kick.wav amplitude_smoothcc42=-4 + sample=kick.wav amplitude_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/amplitude_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/amplitude_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/amplitude_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amplitude_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/amplitude_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/amplitude_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/amplitude_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/amplitude_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/amplitude_curvecc42", "", nullptr); + std::vector expected { + "/region0/amplitude_stepcc42,N : { }", + "/region0/amplitude_smoothcc42,N : { }", + "/region0/amplitude_curvecc42,N : { }", + "/region1/amplitude_stepcc42,f : { 4.2 }", + "/region2/amplitude_smoothcc42,i : { 4 }", + "/region3/amplitude_curvecc42,i : { 2 }", + // "/region4/amplitude_stepcc42,N : { }", + // "/region5/amplitude_smoothcc42,N : { }", + // "/region6/amplitude_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Amp Keycenter") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amp_keycenter=40 + sample=kick.wav amp_keycenter=-1 + sample=kick.wav amp_keycenter=c3 + )"); + synth.dispatchMessage(client, 0, "/region0/amp_keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_keycenter", "", nullptr); + // TODO: activate for the new region parser ; ignore oob and parse note + // synth.dispatchMessage(client, 0, "/region2/amp_keycenter", "", nullptr); + // synth.dispatchMessage(client, 0, "/region3/amp_keycenter", "", nullptr); + std::vector expected { + "/region0/amp_keycenter,i : { 60 }", + "/region1/amp_keycenter,i : { 40 }", + // "/region2/amp_keycenter,i : { 60 }", + // "/region3/amp_keycenter,i : { 48 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Amp Keytrack") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amp_keytrack=10.1 + sample=kick.wav amp_keytrack=40 + )"); + synth.dispatchMessage(client, 0, "/region0/amp_keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_keytrack", "", nullptr); + // TODO: activate for the new region parser ; accept oob + // synth.dispatchMessage(client, 0, "/region2/amp_keytrack", "", nullptr); + std::vector expected { + "/region0/amp_keytrack,f : { 0 }", + "/region1/amp_keytrack,f : { 10.1 }", + // "/region2/amp_keytrack,f : { 40 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Amp Veltrack") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amp_veltrack=10.1 + sample=kick.wav amp_veltrack=-132 + )"); + synth.dispatchMessage(client, 0, "/region0/amp_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_veltrack", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr); + std::vector expected { + "/region0/amp_veltrack,f : { 100 }", + "/region1/amp_veltrack,f : { 10.1 }", + // "/region2/amp_veltrack,f : { 100 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Amp Random") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amp_random=10.1 + sample=kick.wav amp_random=-4 + )"); + synth.dispatchMessage(client, 0, "/region0/amp_random", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_random", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/amp_random", "", nullptr); + std::vector expected { + "/region0/amp_random,f : { 0 }", + "/region1/amp_random,f : { 10.1 }", + // "/region2/amp_random,f : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Crossfade key range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Xfin") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xfin_lokey=10 xfin_hikey=40 + sample=kick.wav xfin_lokey=c4 xfin_hikey=b5 + sample=kick.wav xfin_lokey=-10 xfin_hikey=40 + sample=kick.wav xfin_lokey=10 xfin_hikey=140 + )"); + synth.dispatchMessage(client, 0, "/region0/xfin_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xfin_key_range", "", nullptr); + // TODO: activate for the new region parser ; parse note value + // synth.dispatchMessage(client, 0, "/region2/xfin_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xfin_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/xfin_key_range", "", nullptr); + std::vector expected { + "/region0/xfin_key_range,ii : { 0, 0 }", + "/region1/xfin_key_range,ii : { 10, 40 }", + // "/region2/xfin_key_range,ii : { 60, 83 }", + "/region3/xfin_key_range,ii : { 0, 40 }", + "/region4/xfin_key_range,ii : { 10, 127 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Xfout") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xfout_lokey=10 xfout_hikey=40 + sample=kick.wav xfout_lokey=c4 xfout_hikey=b5 + sample=kick.wav xfout_lokey=-10 xfout_hikey=40 + sample=kick.wav xfout_lokey=10 xfout_hikey=140 + )"); + synth.dispatchMessage(client, 0, "/region0/xfout_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xfout_key_range", "", nullptr); + // TODO: activate for the new region parser ; parse note value + // synth.dispatchMessage(client, 0, "/region2/xfout_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xfout_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/xfout_key_range", "", nullptr); + std::vector expected { + "/region0/xfout_key_range,ii : { 127, 127 }", + "/region1/xfout_key_range,ii : { 10, 40 }", + // "/region2/xfout_key_range,ii : { 60, 83 }", + "/region3/xfout_key_range,ii : { 0, 40 }", + "/region4/xfout_key_range,ii : { 10, 127 }", + }; + REQUIRE(messageList == expected); + } +} + + +TEST_CASE("[Values] Crossfade velocity range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Xfin") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xfin_lovel=10 xfin_hivel=40 + sample=kick.wav xfin_lovel=-10 xfin_hivel=40 + sample=kick.wav xfin_lovel=10 xfin_hivel=140 + )"); + synth.dispatchMessage(client, 0, "/region0/xfin_vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xfin_vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xfin_vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xfin_vel_range", "", nullptr); + std::vector expected { + "/region0/xfin_vel_range,ff : { 0, 0 }", + "/region1/xfin_vel_range,ff : { 0.0787402, 0.314961 }", + "/region2/xfin_vel_range,ff : { 0, 0.314961 }", + "/region3/xfin_vel_range,ff : { 0.0787402, 1 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Xfout") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xfout_lovel=10 xfout_hivel=40 + sample=kick.wav xfout_lovel=-10 xfout_hivel=40 + sample=kick.wav xfout_lovel=10 xfout_hivel=140 + )"); + synth.dispatchMessage(client, 0, "/region0/xfout_vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xfout_vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xfout_vel_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xfout_vel_range", "", nullptr); + std::vector expected { + "/region0/xfout_vel_range,ff : { 1, 1 }", + "/region1/xfout_vel_range,ff : { 0.0787402, 0.314961 }", + "/region2/xfout_vel_range,ff : { 0, 0.314961 }", + "/region3/xfout_vel_range,ff : { 0.0787402, 1 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Crossfade curves") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Key") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xf_keycurve=gain + sample=kick.wav xf_keycurve=something + sample=kick.wav xf_keycurve=gain xf_keycurve=power + )"); + synth.dispatchMessage(client, 0, "/region0/xf_keycurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xf_keycurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xf_keycurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xf_keycurve", "", nullptr); + std::vector expected { + "/region0/xf_keycurve,s : { power }", + "/region1/xf_keycurve,s : { gain }", + "/region2/xf_keycurve,s : { power }", + "/region3/xf_keycurve,s : { power }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Velocity") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xf_velcurve=gain + sample=kick.wav xf_velcurve=something + sample=kick.wav xf_velcurve=gain xf_velcurve=power + )"); + synth.dispatchMessage(client, 0, "/region0/xf_velcurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xf_velcurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xf_velcurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xf_velcurve", "", nullptr); + std::vector expected { + "/region0/xf_velcurve,s : { power }", + "/region1/xf_velcurve,s : { gain }", + "/region2/xf_velcurve,s : { power }", + "/region3/xf_velcurve,s : { power }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xf_cccurve=gain + sample=kick.wav xf_cccurve=something + sample=kick.wav xf_cccurve=gain xf_cccurve=power + )"); + synth.dispatchMessage(client, 0, "/region0/xf_cccurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xf_cccurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xf_cccurve", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xf_cccurve", "", nullptr); + std::vector expected { + "/region0/xf_cccurve,s : { power }", + "/region1/xf_cccurve,s : { gain }", + "/region2/xf_cccurve,s : { power }", + "/region3/xf_cccurve,s : { power }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Crossfade CC range") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Xfin") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xfin_locc4=10 xfin_hicc4=40 + sample=kick.wav xfin_locc4=-10 xfin_hicc4=40 + sample=kick.wav xfin_locc4=10 xfin_hicc4=140 + )"); + synth.dispatchMessage(client, 0, "/region0/xfin_cc_range4", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xfin_cc_range4", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xfin_cc_range4", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xfin_cc_range4", "", nullptr); + std::vector expected { + "/region0/xfin_cc_range4,N : { }", + "/region1/xfin_cc_range4,ff : { 0.0787402, 0.314961 }", + "/region2/xfin_cc_range4,ff : { 0, 0.314961 }", + "/region3/xfin_cc_range4,ff : { 0.0787402, 1 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Xfout") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav xfout_locc4=10 xfout_hicc4=40 + sample=kick.wav xfout_locc4=-10 xfout_hicc4=40 + sample=kick.wav xfout_locc4=10 xfout_hicc4=140 + )"); + synth.dispatchMessage(client, 0, "/region0/xfout_cc_range4", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/xfout_cc_range4", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xfout_cc_range4", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/xfout_cc_range4", "", nullptr); + std::vector expected { + "/region0/xfout_cc_range4,N : { }", + "/region1/xfout_cc_range4,ff : { 0.0787402, 0.314961 }", + "/region2/xfout_cc_range4,ff : { 0, 0.314961 }", + "/region3/xfout_cc_range4,ff : { 0.0787402, 1 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Global volumes and amplitudes") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Volumes") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + global_volume=4.4 + master_volume=5.5 + group_volume=6.6 + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/global_volume", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/master_volume", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/group_volume", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/global_volume", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/master_volume", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/group_volume", "", nullptr); + std::vector expected { + "/region0/global_volume,f : { 0 }", + "/region0/master_volume,f : { 0 }", + "/region0/group_volume,f : { 0 }", + "/region1/global_volume,f : { 4.4 }", + "/region1/master_volume,f : { 5.5 }", + "/region1/group_volume,f : { 6.6 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Amplitudes") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + global_amplitude=4.4 + master_amplitude=5.5 + group_amplitude=6.6 + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/global_amplitude", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/master_amplitude", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/group_amplitude", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/global_amplitude", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/master_amplitude", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/group_amplitude", "", nullptr); + std::vector expected { + "/region0/global_amplitude,f : { 100 }", + "/region0/master_amplitude,f : { 100 }", + "/region0/group_amplitude,f : { 100 }", + "/region1/global_amplitude,f : { 4.4 }", + "/region1/master_amplitude,f : { 5.5 }", + "/region1/group_amplitude,f : { 6.6 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Pitch Keytrack") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch_keytrack=1000 + sample=kick.wav pitch_keytrack=-100 + )"); + synth.dispatchMessage(client, 0, "/region0/pitch_keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_keytrack", "", nullptr); + std::vector expected { + "/region0/pitch_keytrack,i : { 100 }", + "/region1/pitch_keytrack,i : { 1000 }", + "/region2/pitch_keytrack,i : { -100 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Pitch Veltrack") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch_veltrack=10 + sample=kick.wav pitch_veltrack=-132 + )"); + synth.dispatchMessage(client, 0, "/region0/pitch_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr); + std::vector expected { + "/region0/pitch_veltrack,i : { 0 }", + "/region1/pitch_veltrack,i : { 10 }", + "/region2/pitch_veltrack,i : { -132 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Pitch Random") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch_random=10 + sample=kick.wav pitch_random=-4 + )"); + synth.dispatchMessage(client, 0, "/region0/pitch_random", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_random", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/pitch_random", "", nullptr); + std::vector expected { + "/region0/pitch_random,f : { 0 }", + "/region1/pitch_random,f : { 10 }", + // "/region2/pitch_random,f : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Transpose") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav transpose=10 + sample=kick.wav transpose=-4 + sample=kick.wav transpose=-400 + sample=kick.wav transpose=400 + )"); + synth.dispatchMessage(client, 0, "/region0/transpose", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/transpose", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/transpose", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region3/transpose", "", nullptr); + // synth.dispatchMessage(client, 0, "/region4/transpose", "", nullptr); + std::vector expected { + "/region0/transpose,i : { 0 }", + "/region1/transpose,i : { 10 }", + "/region2/transpose,i : { -4 }", + // "/region3/transpose,i : { 0 }", + // "/region4/transpose,i : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Pitch/Tune") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch=4.2 + sample=kick.wav tune=-200 + )"); + synth.dispatchMessage(client, 0, "/region0/tune", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/tune", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/tune", "", nullptr); + std::vector expected { + "/region0/tune,f : { 0 }", + "/region1/tune,f : { 4.2 }", + "/region2/tune,f : { -200 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Depth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav tune_oncc42=4.2 + sample=kick.wav pitch_oncc2=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/tune_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/tune_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/tune_cc2", "", nullptr); + std::vector expected { + "/region0/tune_cc42,N : { }", + "/region1/tune_cc42,f : { 4.2 }", + "/region2/tune_cc2,f : { -10 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav tune_stepcc42=4.2 + sample=kick.wav tune_smoothcc42=4 + sample=kick.wav tune_curvecc42=2 + sample=kick.wav tune_stepcc42=-1 + sample=kick.wav tune_smoothcc42=-4 + sample=kick.wav tune_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/tune_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/tune_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/tune_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/tune_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/tune_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/tune_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/tune_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/tune_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/tune_curvecc42", "", nullptr); + std::vector expected { + "/region0/tune_stepcc42,N : { }", + "/region0/tune_smoothcc42,N : { }", + "/region0/tune_curvecc42,N : { }", + "/region1/tune_stepcc42,f : { 4.2 }", + "/region2/tune_smoothcc42,i : { 4 }", + "/region3/tune_curvecc42,i : { 2 }", + // "/region4/tune_stepcc42,N : { }", + // "/region5/tune_smoothcc42,N : { }", + // "/region6/tune_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC Params (with pitch_)") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch_stepcc42=4.2 + sample=kick.wav pitch_smoothcc42=4 + sample=kick.wav pitch_curvecc42=2 + sample=kick.wav pitch_stepcc42=-1 + sample=kick.wav pitch_smoothcc42=-4 + sample=kick.wav pitch_curvecc42=300 + )"); + synth.dispatchMessage(client, 0, "/region0/tune_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/tune_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/tune_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/tune_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/tune_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/tune_curvecc42", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/tune_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/tune_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/tune_curvecc42", "", nullptr); + std::vector expected { + "/region0/tune_stepcc42,N : { }", + "/region0/tune_smoothcc42,N : { }", + "/region0/tune_curvecc42,N : { }", + "/region1/tune_stepcc42,f : { 4.2 }", + "/region2/tune_smoothcc42,i : { 4 }", + "/region3/tune_curvecc42,i : { 2 }", + // "/region4/tune_stepcc42,N : { }", + // "/region5/tune_smoothcc42,N : { }", + // "/region6/tune_curvecc42,N : { }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Bend behavior") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav bend_up=100 bend_down=-400 bend_step=10 bend_smooth=10 + sample=kick.wav bend_up=-100 bend_down=400 bend_step=-10 bend_smooth=-10 + )"); + synth.dispatchMessage(client, 0, "/region0/bend_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/bend_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/bend_step", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/bend_smooth", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/bend_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/bend_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/bend_step", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/bend_smooth", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/bend_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/bend_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/bend_step", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/bend_smooth", "", nullptr); + std::vector expected { + "/region0/bend_up,i : { 200 }", + "/region0/bend_down,i : { -200 }", + "/region0/bend_step,i : { 1 }", + "/region0/bend_smooth,i : { 0 }", + "/region1/bend_up,i : { 100 }", + "/region1/bend_down,i : { -400 }", + "/region1/bend_step,i : { 10 }", + "/region1/bend_smooth,i : { 10 }", + "/region2/bend_up,i : { -100 }", + "/region2/bend_down,i : { 400 }", + "/region2/bend_step,i : { 1 }", + "/region2/bend_smooth,i : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] ampeg") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav + ampeg_attack=1 ampeg_delay=2 ampeg_decay=3 + ampeg_hold=4 ampeg_release=5 ampeg_start=6 + ampeg_sustain=7 ampeg_depth=8 + sample=kick.wav + ampeg_attack=-1 ampeg_delay=-2 ampeg_decay=-3 + ampeg_hold=-4 ampeg_release=-5 ampeg_start=-6 + ampeg_sustain=-7 ampeg_depth=-8 + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_attack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_decay", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_hold", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_release", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_start", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_sustain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_depth", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_attack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_decay", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_hold", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_release", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_start", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_sustain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_depth", "", nullptr); + // TODO after new parser : ignore oob + // synth.dispatchMessage(client, 0, "/region2/ampeg_attack", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_delay", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_decay", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_hold", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_release", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_start", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_sustain", "", nullptr); + // synth.dispatchMessage(client, 0, "/region2/ampeg_depth", "", nullptr); + std::vector expected { + "/region0/ampeg_attack,f : { 0 }", + "/region0/ampeg_delay,f : { 0 }", + "/region0/ampeg_decay,f : { 0 }", + "/region0/ampeg_hold,f : { 0 }", + "/region0/ampeg_release,f : { 0.001 }", + "/region0/ampeg_start,f : { 0 }", + "/region0/ampeg_sustain,f : { 100 }", + "/region0/ampeg_depth,i : { 0 }", + "/region1/ampeg_attack,f : { 1 }", + "/region1/ampeg_delay,f : { 2 }", + "/region1/ampeg_decay,f : { 3 }", + "/region1/ampeg_hold,f : { 4 }", + "/region1/ampeg_release,f : { 5 }", + "/region1/ampeg_start,f : { 6 }", + "/region1/ampeg_sustain,f : { 7 }", + "/region1/ampeg_depth,i : { 0 }", + // "/region2/ampeg_attack,f : { 0 }", + // "/region2/ampeg_delay,f : { 0 }", + // "/region2/ampeg_decay,f : { 0 }", + // "/region2/ampeg_hold,f : { 0 }", + // "/region2/ampeg_release,f : { 0.001 }", + // "/region2/ampeg_start,f : { 0 }", + // "/region2/ampeg_sustain,f : { 100 }", + // "/region2/ampeg_depth,i : { 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Velocity") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav + ampeg_vel2attack=1 ampeg_vel2delay=2 ampeg_vel2decay=3 + ampeg_vel2hold=4 ampeg_vel2release=5 + ampeg_vel2sustain=7 ampeg_vel2depth=8 + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2attack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2delay", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2decay", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2hold", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2release", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2sustain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_vel2depth", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2attack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2delay", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2decay", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2hold", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2release", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2sustain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_vel2depth", "", nullptr); + std::vector expected { + "/region0/ampeg_vel2attack,f : { 0 }", + "/region0/ampeg_vel2delay,f : { 0 }", + "/region0/ampeg_vel2decay,f : { 0 }", + "/region0/ampeg_vel2hold,f : { 0 }", + "/region0/ampeg_vel2release,f : { 0 }", + "/region0/ampeg_vel2sustain,f : { 0 }", + "/region0/ampeg_vel2depth,i : { 0 }", + "/region1/ampeg_vel2attack,f : { 1 }", + "/region1/ampeg_vel2delay,f : { 2 }", + "/region1/ampeg_vel2decay,f : { 3 }", + "/region1/ampeg_vel2hold,f : { 4 }", + "/region1/ampeg_vel2release,f : { 5 }", + "/region1/ampeg_vel2sustain,f : { 7 }", + "/region1/ampeg_vel2depth,i : { 0 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Note polyphony") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav note_polyphony=10 + sample=kick.wav note_polyphony=-4 + sample=kick.wav note_polyphony=10 note_polyphony=-4 + )"); + synth.dispatchMessage(client, 0, "/region0/note_polyphony", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/note_polyphony", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/note_polyphony", "", nullptr); + // synth.dispatchMessage(client, 0, "/region3/note_polyphony", "", nullptr); + std::vector expected { + "/region0/note_polyphony,N : { }", + "/region1/note_polyphony,i : { 10 }", + // "/region2/note_polyphony,N : { }", + // "/region3/note_polyphony,i : { 10 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Self-mask") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav note_selfmask=off + sample=kick.wav note_selfmask=off note_selfmask=on + sample=kick.wav note_selfmask=off note_selfmask=garbage + )"); + synth.dispatchMessage(client, 0, "/region0/note_selfmask", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/note_selfmask", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/note_selfmask", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/note_selfmask", "", nullptr); + std::vector expected { + "/region0/note_selfmask,T : { }", + "/region1/note_selfmask,F : { }", + "/region2/note_selfmask,T : { }", + "/region3/note_selfmask,F : { }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] RT dead") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav rt_dead=on + sample=kick.wav rt_dead=on rt_dead=off + sample=kick.wav rt_dead=on rt_dead=garbage + )"); + synth.dispatchMessage(client, 0, "/region0/rt_dead", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/rt_dead", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/rt_dead", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/rt_dead", "", nullptr); + std::vector expected { + "/region0/rt_dead,F : { }", + "/region1/rt_dead,T : { }", + "/region2/rt_dead,F : { }", + "/region3/rt_dead,T : { }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Sustain switch") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sustain_sw=off + sample=kick.wav sustain_sw=off sustain_sw=on + sample=kick.wav sustain_sw=off sustain_sw=garbage + )"); + synth.dispatchMessage(client, 0, "/region0/sustain_sw", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sustain_sw", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sustain_sw", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sustain_sw", "", nullptr); + std::vector expected { + "/region0/sustain_sw,T : { }", + "/region1/sustain_sw,F : { }", + "/region2/sustain_sw,T : { }", + "/region3/sustain_sw,T : { }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Sostenuto switch") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sostenuto_sw=off + sample=kick.wav sostenuto_sw=off sostenuto_sw=on + sample=kick.wav sostenuto_sw=off sostenuto_sw=garbage + )"); + synth.dispatchMessage(client, 0, "/region0/sostenuto_sw", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sostenuto_sw", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sostenuto_sw", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sostenuto_sw", "", nullptr); + std::vector expected { + "/region0/sostenuto_sw,T : { }", + "/region1/sostenuto_sw,F : { }", + "/region2/sostenuto_sw,T : { }", + "/region3/sostenuto_sw,T : { }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Sustain CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sustain_cc=10 + sample=kick.wav sustain_cc=20 sustain_cc=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/sustain_cc", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sustain_cc", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/sustain_cc", "", nullptr); + std::vector expected { + "/region0/sustain_cc,i : { 64 }", + "/region1/sustain_cc,i : { 10 }", + // "/region2/sustain_cc,i : { 20 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Sustain low") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav sustain_lo=10 + sample=kick.wav sustain_lo=10 sustain_lo=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/sustain_lo", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/sustain_lo", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region2/sustain_lo", "", nullptr); + std::vector expected { + "/region0/sustain_lo,f : { 0.0039 }", + "/region1/sustain_lo,f : { 0.0787402 }", + // "/region2/sustain_lo,f : { 0.0787402 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Oscillator phase") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav oscillator_phase=0.1 + sample=kick.wav oscillator_phase=1.1 + sample=kick.wav oscillator_phase=-1.2 + )"); + synth.dispatchMessage(client, 0, "/region0/oscillator_phase", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/oscillator_phase", "", nullptr); + // TODO: activate for the new region parser ; properly wrap + // synth.dispatchMessage(client, 0, "/region2/oscillator_phase", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/oscillator_phase", "", nullptr); + std::vector expected { + "/region0/oscillator_phase,f : { 0 }", + "/region1/oscillator_phase,f : { 0.1 }", + // "/region2/oscillator_phase,f : { 0.1 }", + "/region3/oscillator_phase,f : { -1 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Effect sends") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav effect1=10 + sample=kick.wav effect2=50.4 + sample=kick.wav effect1=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/effect1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/effect1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/effect1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/effect2", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/effect1", "", nullptr); + std::vector expected { + // No reply to the first question + "/region1/effect1,f : { 10 }", + "/region2/effect1,f : { 0 }", + "/region2/effect2,f : { 50.4 }", + // "/region4/effect1,f : { 100 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Support floating point for int values") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav offset=1042.5 + sample=kick.wav pitch_keytrack=-2.1 + )"); + synth.dispatchMessage(client, 0, "/region0/offset", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); + // TODO: activate for the new region parser ; ignore oob + // synth.dispatchMessage(client, 0, "/region4/effect1", "", nullptr); + std::vector expected { + "/region0/offset,h : { 1042 }", + "/region1/pitch_keytrack,i : { -2 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] ampeg CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/ampeg_attack_cc1,f : { 0 }", + "/region0/ampeg_delay_cc2,f : { 0 }", + "/region0/ampeg_decay_cc3,f : { 0 }", + "/region0/ampeg_hold_cc4,f : { 0 }", + "/region0/ampeg_release_cc5,f : { 0 }", + "/region0/ampeg_start_cc6,f : { 0 }", + "/region0/ampeg_sustain_cc7,f : { 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Positive values") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + ampeg_attack_oncc1=1 ampeg_delay_oncc2=2 ampeg_decay_oncc3=3 + ampeg_hold_oncc4=4 ampeg_release_oncc5=5 ampeg_start_oncc6=6 + ampeg_sustain_oncc7=7 + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/ampeg_attack_cc1,f : { 1 }", + "/region0/ampeg_delay_cc2,f : { 2 }", + "/region0/ampeg_decay_cc3,f : { 3 }", + "/region0/ampeg_hold_cc4,f : { 4 }", + "/region0/ampeg_release_cc5,f : { 5 }", + "/region0/ampeg_start_cc6,f : { 6 }", + "/region0/ampeg_sustain_cc7,f : { 7 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + ampeg_attack_cc1=-1 ampeg_delay_cc2=-2 ampeg_decay_cc3=-3 + ampeg_hold_cc4=-4 ampeg_release_cc5=-5 ampeg_start_cc6=-6 + ampeg_sustain_cc7=-7 + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/ampeg_attack_cc1,f : { -1 }", + "/region0/ampeg_delay_cc2,f : { -2 }", + "/region0/ampeg_decay_cc3,f : { -3 }", + "/region0/ampeg_hold_cc4,f : { -4 }", + "/region0/ampeg_release_cc5,f : { -5 }", + "/region0/ampeg_start_cc6,f : { -6 }", + "/region0/ampeg_sustain_cc7,f : { -7 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Filter stacking and cutoffs") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav cutoff=50 + sample=kick.wav cutoff2=500 + )"); + + SECTION("Test first region") + { + synth.dispatchMessage(client, 0, "/region0/filter0/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr); + std::vector expected { + // No filters + }; + REQUIRE(messageList == expected); + } + + SECTION("Test second region") + { + synth.dispatchMessage(client, 0, "/region1/filter0/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter1/type", "", nullptr); + std::vector expected { + "/region1/filter0/cutoff,f : { 50 }", + "/region1/filter0/gain,f : { 0 }", + "/region1/filter0/resonance,f : { 0 }", + "/region1/filter0/keycenter,i : { 60 }", + "/region1/filter0/keytrack,i : { 0 }", + "/region1/filter0/veltrack,i : { 0 }", + "/region1/filter0/type,s : { lpf_2p }", + // No second filter + }; + REQUIRE(messageList == expected); + } + + SECTION("Test third region") + { + synth.dispatchMessage(client, 0, "/region2/filter0/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/keycenter", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter1/type", "", nullptr); + std::vector expected { + // The first filter is default-filled + "/region2/filter0/cutoff,f : { 0 }", + "/region2/filter0/gain,f : { 0 }", + "/region2/filter0/resonance,f : { 0 }", + "/region2/filter0/keycenter,i : { 60 }", + "/region2/filter0/keytrack,i : { 0 }", + "/region2/filter0/veltrack,i : { 0 }", + "/region2/filter0/type,s : { lpf_2p }", + "/region2/filter1/cutoff,f : { 500 }", + "/region2/filter1/gain,f : { 0 }", + "/region2/filter1/resonance,f : { 0 }", + "/region2/filter1/keycenter,i : { 60 }", + "/region2/filter1/keytrack,i : { 0 }", + "/region2/filter1/veltrack,i : { 0 }", + "/region2/filter1/type,s : { lpf_2p }", + // No second filter + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] Filter types") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav fil_type=lpf_1p + sample=kick.wav fil_type=hpf_1p + sample=kick.wav fil_type=lpf_2p + sample=kick.wav fil_type=hpf_2p + sample=kick.wav fil_type=bpf_2p + sample=kick.wav fil_type=brf_2p + sample=kick.wav fil_type=bpf_1p + sample=kick.wav fil_type=brf_1p + sample=kick.wav fil_type=apf_1p + sample=kick.wav fil_type=lpf_2p_sv + sample=kick.wav fil_type=hpf_2p_sv + sample=kick.wav fil_type=bpf_2p_sv + sample=kick.wav fil_type=brf_2p_sv + sample=kick.wav fil_type=lpf_4p + sample=kick.wav fil_type=hpf_4p + sample=kick.wav fil_type=lpf_6p + sample=kick.wav fil_type=hpf_6p + sample=kick.wav fil_type=pink + sample=kick.wav fil_type=lsh + sample=kick.wav fil_type=hsh + sample=kick.wav fil_type=peq + sample=kick.wav fil2_type=peq + sample=kick.wav fil2_type=something + )"); + + synth.dispatchMessage(client, 0, "/region0/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region6/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region7/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region8/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region9/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region10/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region11/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region12/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region13/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region14/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region15/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region16/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region17/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region18/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region19/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region20/filter0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region21/filter1/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region22/filter1/type", "", nullptr); + std::vector expected { + "/region0/filter0/type,s : { lpf_1p }", + "/region1/filter0/type,s : { hpf_1p }", + "/region2/filter0/type,s : { lpf_2p }", + "/region3/filter0/type,s : { hpf_2p }", + "/region4/filter0/type,s : { bpf_2p }", + "/region5/filter0/type,s : { brf_2p }", + "/region6/filter0/type,s : { bpf_1p }", + "/region7/filter0/type,s : { brf_1p }", + "/region8/filter0/type,s : { apf_1p }", + "/region9/filter0/type,s : { lpf_2p_sv }", + "/region10/filter0/type,s : { hpf_2p_sv }", + "/region11/filter0/type,s : { bpf_2p_sv }", + "/region12/filter0/type,s : { brf_2p_sv }", + "/region13/filter0/type,s : { lpf_4p }", + "/region14/filter0/type,s : { hpf_4p }", + "/region15/filter0/type,s : { lpf_6p }", + "/region16/filter0/type,s : { hpf_6p }", + "/region17/filter0/type,s : { pink }", + "/region18/filter0/type,s : { lsh }", + "/region19/filter0/type,s : { hsh }", + "/region20/filter0/type,s : { peq }", + "/region21/filter1/type,s : { peq }", + "/region22/filter1/type,s : { none }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Filter dispatching") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + cutoff3=50 resonance2=3 fil2_gain=-5 fil3_keytrack=100 + fil_gain=5 fil1_gain=-5 fil2_veltrack=-100 + )"); + + synth.dispatchMessage(client, 0, "/region0/filter2/cutoff", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/resonance", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter2/keytrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter1/veltrack", "", nullptr); + std::vector expected { + "/region0/filter2/cutoff,f : { 50 }", + "/region0/filter1/resonance,f : { 3 }", + "/region0/filter1/gain,f : { -5 }", + "/region0/filter2/keytrack,i : { 100 }", + "/region0/filter0/gain,f : { -5 }", + "/region0/filter1/veltrack,i : { -100 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Filter value bounds") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Cutoff") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav cutoff=20000000 // Bound this to 20k + sample=kick.wav cutoff=50 cutoff=-100 + )"); + synth.dispatchMessage(client, 0, "/region0/filter0/cutoff", "", nullptr); + // TODO: activate after new parser; ignore OOB + // synth.dispatchMessage(client, 0, "/region0/filter0/cutoff", "", nullptr); + std::vector expected { + "/region0/filter0/cutoff,f : { 20000 }", + // "/region0/filter0/cutoff,f : { 50 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Cutoff") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav resonance=5 resonance=-5 + )"); + // TODO: activate after new parser; ignore OOB + // synth.dispatchMessage(client, 0, "/region0/filter0/resonance", "", nullptr); + std::vector expected { + // "/region0/filter0/resonance,f : { 5 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Keycenter") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav keycenter=40 keycenter=-5 + sample=kick.wav keycenter=40 keycenter=1000 + sample=kick.wav keycenter=c3 + )"); + // TODO: activate after new parser; ignore OOB + // synth.dispatchMessage(client, 0, "/region0/filter0/keycenter", "", nullptr); + // synth.dispatchMessage(client, 0, "/region1/filter0/keycenter", "", nullptr); + // TODO: activate after new parser; parse note + // synth.dispatchMessage(client, 0, "/region2/filter0/keycenter", "", nullptr); + std::vector expected { + // "/region0/filter0/keycenter,i : { 40 }", + // "/region1/filter0/keycenter,i : { 40 }", + // "/region2/filter0/keycenter,i : { 48 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] EQ stacking and gains") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav eq1_gain=3 + sample=kick.wav eq4_gain=6 + )"); + + SECTION("Test first region") + { + synth.dispatchMessage(client, 0, "/region0/eq0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/vel2freq", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/vel2freq", "", nullptr); + std::vector expected { + // No eqs + }; + REQUIRE(messageList == expected); + } + + SECTION("Test second region") + { + synth.dispatchMessage(client, 0, "/region1/eq0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq0/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq0/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq0/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq0/vel2freq", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq1/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq1/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq1/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq1/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq1/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq1/vel2freq", "", nullptr); + std::vector expected { + "/region1/eq0/gain,f : { 3 }", + "/region1/eq0/type,s : { peak }", + "/region1/eq0/bandwidth,f : { 1 }", + "/region1/eq0/frequency,f : { 50 }", + "/region1/eq0/vel2gain,f : { 0 }", + "/region1/eq0/vel2freq,f : { 0 }", + // No second eq + }; + REQUIRE(messageList == expected); + } + + SECTION("Test third region") + { + synth.dispatchMessage(client, 0, "/region2/eq0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq0/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq0/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq0/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq0/vel2freq", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq3/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq3/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq3/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq3/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq3/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq3/vel2freq", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq1/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq2/frequency", "", nullptr); + std::vector expected { + // The first eq is default-filled + "/region2/eq0/gain,f : { 0 }", + "/region2/eq0/type,s : { peak }", + "/region2/eq0/bandwidth,f : { 1 }", + "/region2/eq0/frequency,f : { 50 }", + "/region2/eq0/vel2gain,f : { 0 }", + "/region2/eq0/vel2freq,f : { 0 }", + "/region2/eq3/gain,f : { 6 }", + "/region2/eq3/type,s : { peak }", + "/region2/eq3/bandwidth,f : { 1 }", + "/region2/eq3/frequency,f : { 0 }", + "/region2/eq3/vel2gain,f : { 0 }", + "/region2/eq3/vel2freq,f : { 0 }", + "/region2/eq1/frequency,f : { 500 }", + "/region2/eq2/frequency,f : { 5000 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] EQ types") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav eq1_type=hshelf + sample=kick.wav eq1_type=lshelf + sample=kick.wav eq1_type=hshelf eq1_type=peak + sample=kick.wav eq1_type=something + )"); + + synth.dispatchMessage(client, 0, "/region0/eq0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/eq0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/eq0/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/eq0/type", "", nullptr); + std::vector expected { + "/region0/eq0/type,s : { hshelf }", + "/region1/eq0/type,s : { lshelf }", + "/region2/eq0/type,s : { peak }", + "/region3/eq0/type,s : { none }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] EQ dispatching") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + eq3_bw=2 eq1_gain=-25 eq2_freq=300 eq3_type=lshelf + eq3_vel2gain=10 eq1_vel2freq=100 + )"); + + synth.dispatchMessage(client, 0, "/region0/eq2/bandwidth", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq1/frequency", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq2/type", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq2/vel2gain", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eq0/vel2freq", "", nullptr); + std::vector expected { + "/region0/eq2/bandwidth,f : { 2 }", + "/region0/eq0/gain,f : { -25 }", + "/region0/eq1/frequency,f : { 300 }", + "/region0/eq2/type,s : { lshelf }", + "/region0/eq2/vel2gain,f : { 10 }", + "/region0/eq0/vel2freq,f : { 100 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] EQ value bounds") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Frequency") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav eq1_freq=20000000 // Bound this to 30k + sample=kick.wav eq1_freq=50 eq1_freq=-100 + )"); + synth.dispatchMessage(client, 0, "/region0/eq0/frequency", "", nullptr); + // TODO: activate after new parser; ignore OOB + // synth.dispatchMessage(client, 0, "/region0/eq0/frequency", "", nullptr); + std::vector expected { + "/region0/eq0/frequency,f : { 30000 }", + // "/region0/eq0/frequency,f : { 50 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Bandwidth") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav eq1_bw=5 eq1_bw=-5 + )"); + // TODO: activate after new parser; ignore OOB + // synth.dispatchMessage(client, 0, "/region0/eq0/bandwidth", "", nullptr); + std::vector expected { + // "/region0/eq0/bandwidth,f : { 5 }", + }; + REQUIRE(messageList == expected); + } +}