diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 23c06cda..a3c1a8b6 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -126,6 +126,51 @@ struct OpcodeSpec } operator T() const { return normalizeInput(defaultInputValue); } + + + /** + * @brief Demormalizes an input as needed for the spec + * + * @tparam U + * @param input + * @return U + */ + template + typename std::enable_if::value, U>::type denormalizeInput(U input) const + { + constexpr int needsOperation { + kNormalizePercent | + kNormalizeMidi | + kNormalizeBend | + kDb2Mag + }; + + if (!(flags & needsOperation)) + return input; + else if (flags & kNormalizePercent) + return static_cast(input * U(100)); + else if (flags & kNormalizeMidi) + return static_cast(input * U(127)); + else if (flags & kNormalizeBend) + return static_cast(input * U(8191)); + else if (flags & kDb2Mag) + return static_cast(mag2db(input)); + + return input; + } + + /** + * @brief Normalizes an input as needed for the spec + * + * @tparam U + * @param input + * @return U + */ + template + typename std::enable_if::value, U>::type denormalizeInput(U input) const + { + return input; + } }; namespace Default diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index eb98a453..7725090a 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -740,8 +740,8 @@ public: */ void setBroadcastCallback(sfizz_receive_t* broadcast, void* data); -private: struct Impl; +private: std::unique_ptr impl_; LEAK_DETECTOR(Synth); diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index dbb0c06b..8b69c679 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -4,1829 +4,842 @@ // 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 "CCMap.h" +#include "Config.h" +#include "Defaults.h" +#include "EQDescription.h" +#include "FileId.h" +#include "FilterDescription.h" +#include "FlexEGDescription.h" +#include "LFOCommon.h" +#include "LFODescription.h" +#include "Range.h" +#include "SfzFilter.h" +#include "SfzHelpers.h" #include "SynthPrivate.h" #include "FilePool.h" #include "Curve.h" #include "MidiState.h" #include "SynthConfig.h" +#include "TriggerEvent.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" +#include "modulations/ModId.h" +#include "modulations/ModKey.h" +#include "modulations/ModKeyHash.h" #include "utility/StringViewHelpers.h" #include +#include "utility/Size.h" +#include #include +#include +#include // TODO: `ccModDepth` and `ccModParameters` are O(N), need better implementation namespace sfz { static constexpr unsigned maxIndices = 8; -static bool extractMessage(const char* pattern, const char* path, unsigned* indices); -static uint64_t hashMessagePath(const char* path, const char* sig); +static uint64_t hashMessagePath(const char* path, const char* sig) +{ + uint64_t h = Fnv1aBasis; + while (unsigned char c = *path++) { + if (!absl::ascii_isdigit(c)) + h = hashByte(c, h); + else { + h = hashByte('&', h); + while (absl::ascii_isdigit(*path)) + ++path; + } + } + h = hashByte(',', h); + while (unsigned char c = *sig++) + h = hashByte(c, h); + return h; +} + +class MessagingHelper +{ +public: + MessagingHelper(Client& client, int delay, const char* path, const char* sig, sfz::Synth::Impl& impl) + : client(client), impl(impl), delay(delay), path(path), sig(sig) + { + indices.reserve(maxIndices); + } + + enum class ModParam { Depth, Curve, Smooth, Step }; + + bool match(const char* pattern, const char* sig) + { + indices.clear(); + const char* path_ = this->path; + + while (const char *endp = strchr(pattern, '&')) { + if (indices.size() == maxIndices) + return false; + + size_t length = endp - pattern; + if (strncmp(pattern, path_, length) != 0) + return false; + pattern += length; + path_ += length; + + length = 0; + while (absl::ascii_isdigit(path_[length])) + ++length; + + indices.push_back(0); + if (!absl::SimpleAtoi(absl::string_view(path_, length), &indices.back())) + return false; + + pattern += 1; + path_ += length; + } + + return !strcmp(path_, pattern) && !strcmp(this->sig, sig); + } + + // These are the reply/reply2 overloads for the (almost) concrete types, that should + // translate to actual calls to the client.receive(...) method. + + void reply(const std::string& value) { client.receive<'s'>(delay, path, value.data()); } + void reply(const char* value) { client.receive<'s'>(delay, path, value); } + void reply(float value) { client.receive<'f'>(delay, path, value); } + void reply(absl::nullopt_t) { client.receive<'N'>(delay, path, {}); } + + void reply(bool value) + { + if (value) + client.receive<'T'>(delay, path, {}); + else + client.receive<'F'>(delay, path, {}); + } + + template::value>> + void reply(T value) + { + if (sizeof(value) <= 4) + client.receive<'i'>(delay, path, static_cast(value)); + else + client.receive<'h'>(delay, path, static_cast(value)); + } + template + void reply(const BitArray& array) + { + sfizz_blob_t blob { array.data(), static_cast(array.byte_size()) }; + client.receive<'b'>(delay, path, &blob); + } + + // Call reply but denormalizes the input if needed by the opcode spec + template + void reply(T value, OpcodeSpec spec) { reply(spec.denormalizeInput(value)); } + + // sfizz specific types + void reply(sfz::LFOWave wave) { reply(static_cast(wave)); } + void reply(sfz::SelfMask mode) { reply(mode == SelfMask::mask); } + void reply(sfz::LoopMode mode) + { + switch (mode) { + case LoopMode::no_loop: reply("no_loop"); break; + case LoopMode::loop_continuous: reply("loop_continuous"); break; + case LoopMode::loop_sustain: reply("loop_sustain"); break; + case LoopMode::one_shot: reply("one_shot"); break; + } + } + + void reply(sfz::CrossfadeCurve curve) + { + switch (curve) { + case CrossfadeCurve::gain: reply("gain"); break; + case CrossfadeCurve::power: reply("power"); break; + } + } + + void reply(sfz::Trigger mode) + { + switch (mode) { + case Trigger::attack: reply("attack"); break; + case Trigger::first: reply("first"); break; + case Trigger::legato: reply("legato"); break; + case Trigger::release: reply("release"); break; + case Trigger::release_key: reply("release_key"); break; + } + } + + void reply(sfz::VelocityOverride mode) + { + switch (mode) { + case VelocityOverride::current: reply("current"); break; + case VelocityOverride::previous: reply("previous"); break; + } + } + + void reply(sfz::OffMode mode) + { + switch (mode) { + case OffMode::fast: reply("fast"); break; + case OffMode::time: reply("time"); break; + case OffMode::normal: reply("normal"); break; + } + } + + void reply(sfz::FilterType type) + { + switch (type) { + case FilterType::kFilterLpf1p: reply("lpf_1p"); break; + case FilterType::kFilterHpf1p: reply("hpf_1p"); break; + case FilterType::kFilterLpf2p: reply("lpf_2p"); break; + case FilterType::kFilterHpf2p: reply("hpf_2p"); break; + case FilterType::kFilterBpf2p: reply("bpf_2p"); break; + case FilterType::kFilterBrf2p: reply("brf_2p"); break; + case FilterType::kFilterBpf1p: reply("bpf_1p"); break; + case FilterType::kFilterBrf1p: reply("brf_1p"); break; + case FilterType::kFilterApf1p: reply("apf_1p"); break; + case FilterType::kFilterLpf2pSv: reply("lpf_2p_sv"); break; + case FilterType::kFilterHpf2pSv: reply("hpf_2p_sv"); break; + case FilterType::kFilterBpf2pSv: reply("bpf_2p_sv"); break; + case FilterType::kFilterBrf2pSv: reply("brf_2p_sv"); break; + case FilterType::kFilterLpf4p: reply("lpf_4p"); break; + case FilterType::kFilterHpf4p: reply("hpf_4p"); break; + case FilterType::kFilterLpf6p: reply("lpf_6p"); break; + case FilterType::kFilterHpf6p: reply("hpf_6p"); break; + case FilterType::kFilterPink: reply("pink"); break; + case FilterType::kFilterLsh: reply("lsh"); break; + case FilterType::kFilterHsh: reply("hsh"); break; + case FilterType::kFilterPeq: reply("peq"); break; + case FilterType::kFilterBpf4p: reply("bpf_4p"); break; + case FilterType::kFilterBpf6p: reply("bpf_6p"); break; + case FilterType::kFilterNone: reply("none"); break; + } + } + + void reply(sfz::EqType type) + { + switch(type) { + case EqType::kEqNone: reply("none"); break; + case EqType::kEqPeak: reply("peak"); break; + case EqType::kEqLshelf: reply("lshelf"); break; + case EqType::kEqHshelf: reply("hshelf"); break; + } + } + + void reply(sfz::TriggerEventType type) + { + switch(type) { + case TriggerEventType::NoteOff: reply("note_off"); break; + case TriggerEventType::NoteOn: reply("note_on"); break; + case TriggerEventType::CC: reply("cc"); break; + } + } + + // reply2 for pairs (Usually only for float/int ranges) + template::value>> + void reply2(T value1, T value2) + { + sfizz_arg_t args[2]; + if (sizeof(value1) <= 4) { + args[0].i = value1; + args[1].i = value2; + client.receive(delay, path, "ii", args); + } else { + args[0].h = value1; + args[1].h = value2; + client.receive(delay, path, "hh", args); + } + } + + void reply2(float value1, float value2) + { + sfizz_arg_t args[2]; + args[0].f = value1; + args[1].f = value2; + client.receive(delay, path, "ff", args); + } + + // Now we have some templated reply overloads that decay into "concrete" reply calls + // but add some logic (e.g. an optional can either reply the value, or send 'N' for + // null) + template + void reply(absl::optional value, Args... args) + { + if (!value) { + client.receive<'N'>(delay, path, {}); + return; + } + + reply(*value, args...); + } + + + template + void reply(T* value, Args... args) + { + if (!value) { + client.receive<'N'>(delay, path, {}); + return; + } + + reply(*value, args...); + } + + template + void reply(std::shared_ptr value) { reply(value.get()); } + + template + void reply(sfz::UncheckedRange range) { reply2(range.getStart(), range.getEnd()); } + + template + void reply(absl::optional value, T def) { reply(value.value_or(def)); } + + template + void reply(const sfz::ModifierCurvePair& modCurve, ModParam which, Args...args) + { + if (auto region = getRegion()) { + switch (which) { + case ModParam::Curve: reply(modCurve.curve); break; + default: reply(modCurve.modifier, args...); break; + } + } + } + + template + void reply(const sfz::ModKey::Parameters& params, ModParam which, Args...args) + { + if (auto region = getRegion()) { + switch (which) { + case ModParam::Depth: break; + case ModParam::Curve: reply(params.curve); break; + case ModParam::Smooth: reply(params.smooth); break; + case ModParam::Step: reply(params.step, args...); break; + } + } + } + + template + void reply(CCMap map, Args...args) { reply(map, true, args...); } + + template + void reply(CCMap map, bool useDefault, Args...args) + { + if (useDefault) + reply(map.getWithDefault(indices.back()), args...); + else if (map.contains(indices.back())) + reply(map.get(indices.back()), args...); + else + client.receive<'N'>(delay, path, {}); + } + + // Below are all methods that will fetch various data structure elements (regions, + // egs, ...), check that they exist, and call an actual "concrete" reply + // implementation. They use pointer-to-member variables, which allow the compiler to + // dispatch to the correct logic. For example, if the pointer-to-member is + // `sfz::Region::*`, we aim at sending the value of a member of the sfz::Region + // struct. The first thing is to read the index of the region and fetch it from the + // sfizz data structure using `getRegion(...)`, and then apply the pointer-to-member + // to this particular region to send the value. + + // Adding new dispatching overloads seemed simple enough to this point, although I + // haven't found a nice way to the same with pointer-to-member function. + // TODO: maybe a semi-templated overload that check that if the member is a function, + // TODO: and call it with `args...` could be an approach to try. + + template + void reply(T sfz::Region::* member, Args... args) + { + if (auto region = getRegion()) + reply((*region).*member, args...); + } + + template + void reply(const sfz::EGDescription& eg, T sfz::EGDescription::* member, Args...args) + { + reply(eg.*member, args...); + } + + template + void reply(T sfz::FilterDescription::* member, Args... args) + { + if (auto region = getRegion()) + if (auto filter = getFilter(*region)) + reply((*filter).*member, args...); + } + + template + void reply(T sfz::EQDescription::* member, Args... args) + { + if (auto region = getRegion()) + if (auto eq = getEQ(*region)) + reply((*eq).*member, args...); + } + + template + void reply(T sfz::LFODescription::* member, Args... args) + { + if (auto region = getRegion()) + if (auto lfo = getLFO(*region)) + reply((*lfo).*member, args...); + } + + template + void reply(T sfz::LFODescription::Sub::* member, Args... args) + { + if (auto region = getRegion()) + if (auto lfo = getLFO(*region)) + if (!lfo->sub.empty()) + reply((lfo->sub[0]).*member, args...); + } + + template + void reply(T sfz::FlexEGPoint::* member, Args... args) + { + if (auto region = getRegion()) + if (auto eg = getEG(*region)) + if (auto point = getEGPoint(*eg)) + reply((*point).*member, args...); + } + + template + void reply(T sfz::TriggerEvent::* member, Args... args) + { + if (auto voice = getVoice()) + reply((*voice).getTriggerEvent().*member, args...); + } + + template + void reply(T sfz::Voice::* member, Args... args) + { + if (auto voice = getVoice()) { + reply((*voice.*member)(args...)); // Voice only has callables + } + } + + template + void reply(ModId id, ModParam param, Args...args) + { + if (auto region = getRegion()) { + int cc = static_cast (indices.back()); + switch (id){ + case ModId::FilCutoff: + case ModId::FilGain: + switch (param) { + case ModParam::Depth: reply(region->ccModDepth(cc, id, indices[1]), args...); break; + default: reply(region->ccModParameters(cc, id, indices[1]), param, args...); break; + } break; + default: + switch (param) { + case ModParam::Depth: reply(region->ccModDepth(cc, id), args...); break; + default: reply(region->ccModParameters(cc, id), param, args...); break; + } + } + } + } + + // Validate and fetch elements from the sfizz data structures. By default, we kind of + // assume that regions/voices will be the first index, CCs will be the last, and + // EQ/Filter/.. will be in-between. + sfz::Region* getRegion(absl::optional index = {}) + { + const auto idx = index.value_or(indices[0]); + if (idx >= impl.layers_.size()) + return {}; + + Layer& layer = *impl.layers_[idx]; + return &layer.getRegion(); + } + + sfz::FilterDescription* getFilter(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.filters.size() <= idx) + return {}; + + return ®ion.filters[idx]; + } + + sfz::EQDescription* getEQ(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.equalizers.size() <= idx) + return {}; + + return ®ion.equalizers[idx]; + } + + sfz::LFODescription* getLFO(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.lfos.size() <= idx) + return {}; + + return ®ion.lfos[idx]; + } + + sfz::LFODescription::Sub* getLFOSub(sfz::LFODescription& lfo, absl::optional index = {}) + { + const auto idx = index.value_or(indices[2]); + if (lfo.sub.size() <= idx) + return {}; + + return &lfo.sub[idx]; + } + + sfz::FlexEGDescription* getEG(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.flexEGs.size() <= idx) + return {}; + + return ®ion.flexEGs[idx]; + } + + sfz::FlexEGPoint* getEGPoint(sfz::FlexEGDescription& desc, absl::optional index = {}) + { + const auto idx = index.value_or(indices[2]) + 1; + if (desc.points.size() <= idx) + return {}; + + return &desc.points[idx]; + } + + sfz::Voice* getVoice(absl::optional index = {}) + { + const auto idx = index.value_or(indices[0]); + if (static_cast(idx) >= impl.numVoices_) + return {}; + + auto& voice = impl.voiceManager_[idx]; + if (voice.isFree()) + return {}; + + return &voice; + } + + // Helpers to get and check the values of the indices + template + absl::optional index(int i) + { + if (i <= ssize(indices)) + return static_cast(indices[i]); + + return absl::nullopt; + } + + absl::optional sindex(int i) { return index(i); } + + absl::optional checkCC(int i = 0) + { + auto cc = index(i); + return cc <= config::numCCs ? cc : absl::nullopt; + } + + absl::optional checkNote(int i = 0) + { + auto note = sindex(i); + return note <= 127 ? note : absl::nullopt; + } + +private: + Client& client; + std::vector indices; + sfz::Synth::Impl& impl; + int delay; + 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]; + MessagingHelper m {client, delay, path, sig, impl}; + using ModParam = MessagingHelper::ModParam; 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.layers_.size()) \ - break; \ - Layer& layer = *impl.layers_[idx]; \ - const Region& region = layer.getRegion(); - - #define GET_FILTER_OR_BREAK(idx) \ - if (idx >= region.filters.size()) \ - break; \ - const auto& filter = region.filters[idx]; - - #define GET_EQ_OR_BREAK(idx) \ - if (idx >= region.equalizers.size()) \ - break; \ - const auto& eq = region.equalizers[idx]; - - #define GET_LFO_OR_BREAK(idx) \ - if (idx >= region.lfos.size()) \ - break; \ - const auto& lfo = region.lfos[idx]; - - #define GET_EG_OR_BREAK(idx) \ - if (idx >= region.flexEGs.size()) \ - break; \ - auto& eg = region.flexEGs[idx]; - - #define GET_EG_POINT_OR_BREAK(idx) \ - if (idx >= eg.points.size()) \ - break; \ - auto& point = eg.points[idx]; - - MATCH("/hello", "") { - client.receive(delay, "/hello", "", nullptr); - } break; - + #define MATCH(p, s) case hash(p "," s): if (m.match(p, s)) + MATCH("/hello", "") { m.reply(""); } break; //---------------------------------------------------------------------- - - MATCH("/num_regions", "") { - client.receive<'i'>(delay, path, int(impl.layers_.size())); - } break; - - MATCH("/num_groups", "") { - client.receive<'i'>(delay, path, impl.numGroups_); - } break; - - MATCH("/num_masters", "") { - client.receive<'i'>(delay, path, impl.numMasters_); - } break; - - MATCH("/num_curves", "") { - client.receive<'i'>(delay, path, int(impl.resources_.getCurves().getNumCurves())); - } break; - - MATCH("/num_samples", "") { - client.receive<'i'>(delay, path, int(impl.resources_.getFilePool().getNumPreloadedSamples())); - } break; - - MATCH("/octave_offset", "") { - client.receive<'i'>(delay, path, impl.octaveOffset_); - } break; - - MATCH("/note_offset", "") { - client.receive<'i'>(delay, path, impl.noteOffset_); - } break; - - MATCH("/num_outputs", "") { - client.receive<'i'>(delay, path, impl.numOutputs_); - } break; - + MATCH("/num_regions", "") { m.reply(impl.layers_.size()); } break; + MATCH("/num_groups", "") { m.reply(impl.numGroups_); } break; + MATCH("/num_masters", "") { m.reply(impl.numMasters_); } break; + MATCH("/num_curves", "") { m.reply(impl.resources_.getCurves().getNumCurves()); } break; + MATCH("/num_samples", "") { m.reply(impl.resources_.getFilePool().getNumPreloadedSamples()); } break; + MATCH("/octave_offset", "") { m.reply(impl.octaveOffset_); } break; + MATCH("/note_offset", "") { m.reply(impl.noteOffset_); } break; + MATCH("/num_outputs", "") { m.reply(impl.numOutputs_); } break; + MATCH("/num_active_voices", "") { m.reply(uint32_t(impl.voiceManager_.getNumActiveVoices())); } break; //---------------------------------------------------------------------- - - MATCH("/key/slots", "") { - const BitArray<128>& keys = impl.keySlots_; - sfizz_blob_t blob { keys.data(), static_cast(keys.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/key&/label", "") { - if (indices[0] >= 128) - break; - const std::string* label = impl.getKeyLabel(indices[0]); - client.receive<'s'>(delay, path, label ? label->c_str() : ""); - } break; - + MATCH("/key/slots", "") { m.reply(impl.keySlots_); } break; + MATCH("/key&/label", "") { if (auto k = m.sindex(0)) m.reply(impl.getKeyLabel(*k)); } break; //---------------------------------------------------------------------- - - MATCH("/root_path", "") { - client.receive<'s'>(delay, path, impl.rootPath_.c_str()); - } break; - - MATCH("/image", "") { - client.receive<'s'>(delay, path, impl.image_.c_str()); - } break; - - MATCH("/image_controls", "") { - client.receive<'s'>(delay, path, impl.image_controls_.c_str()); - } break; - + MATCH("/root_path", "") { m.reply(impl.rootPath_); } break; + MATCH("/image", "") { m.reply(impl.image_.c_str()); } break; + MATCH("/image_controls", "") { m.reply(impl.image_controls_.c_str()); } break; //---------------------------------------------------------------------- - - MATCH("/sw/last/slots", "") { - const BitArray<128>& switches = impl.swLastSlots_; - sfizz_blob_t blob { switches.data(), static_cast(switches.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/sw/last/current", "") { - if (impl.currentSwitch_) - client.receive<'i'>(delay, path, *impl.currentSwitch_); - else - client.receive<'N'>(delay, path, {}); - } break; - - MATCH("/sw/last/&/label", "") { - if (indices[0] >= 128) - break; - const std::string* label = impl.getKeyswitchLabel(indices[0]); - client.receive<'s'>(delay, path, label ? label->c_str() : ""); - } break; - + MATCH("/sw/last/slots", "") { m.reply(impl.swLastSlots_); } break; + MATCH("/sw/last/current", "") { m.reply(impl.currentSwitch_); } break; + MATCH("/sw/last/&/label", "") { if (auto k = m.sindex(0)) m.reply(impl.getKeyswitchLabel(*k)); } break; //---------------------------------------------------------------------- - - MATCH("/cc/slots", "") { - const BitArray& ccs = impl.currentUsedCCs_; - sfizz_blob_t blob { ccs.data(), static_cast(ccs.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/cc&/default", "") { - if (indices[0] >= config::numCCs) - break; - client.receive<'f'>(delay, path, impl.defaultCCValues_[indices[0]]); - } break; - - MATCH("/cc&/value", "") { - if (indices[0] >= config::numCCs) - break; - // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getCCValue(indices[0])); - } break; - - MATCH("/cc&/value", "f") { - if (indices[0] >= config::numCCs) - break; - impl.resources_.getMidiState().ccEvent(delay, indices[0], args[0].f); - } break; - - MATCH("/cc&/label", "") { - if (indices[0] >= config::numCCs) - break; - const std::string* label = impl.getCCLabel(indices[0]); - client.receive<'s'>(delay, path, label ? label->c_str() : ""); - } break; - - MATCH("/cc/changed", "") { - const BitArray& changedCCs = impl.changedCCsThisCycle_; - sfizz_blob_t blob { changedCCs.data(), static_cast(changedCCs.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/cc/changed~", "") { - const BitArray& changedCCs = impl.changedCCsLastCycle_; - sfizz_blob_t blob { changedCCs.data(), static_cast(changedCCs.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/sustain_or_sostenuto/slots", "") { - const BitArray<128>& sustainOrSostenuto = impl.sustainOrSostenuto_; - sfizz_blob_t blob { sustainOrSostenuto.data(), - static_cast(sustainOrSostenuto.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/aftertouch", "") { - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getChannelAftertouch()); - } break; - - MATCH("/poly_aftertouch/&", "") { - if (indices[0] > 127) - break; - // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPolyAftertouch(indices[0])); - } break; - - MATCH("/pitch_bend", "") { - // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPitchBend()); - } break; - + MATCH("/cc/slots", "") { m.reply(impl.currentUsedCCs_); } break; + MATCH("/cc&/default", "") { if (auto cc = m.checkCC()) m.reply(impl.defaultCCValues_[*cc]); } break; + MATCH("/cc&/value", "") { if (auto cc = m.checkCC()) m.reply(impl.resources_.getMidiState().getCCValue(*cc)); } break; + MATCH("/cc&/value", "f") { if (auto cc = m.checkCC()) impl.resources_.getMidiState().ccEvent(delay, *cc, args[0].f); } break; + MATCH("/cc&/label", "") { if (auto cc = m.checkCC()) m.reply(impl.getCCLabel(*cc)); } break; + MATCH("/cc/changed", "") { m.reply(impl.changedCCsThisCycle_); } break; + MATCH("/cc/changed~", "") { m.reply(impl.changedCCsLastCycle_); } break; + MATCH("/sustain_or_sostenuto/slots", "") { m.reply(impl.sustainOrSostenuto_); } break; + MATCH("/aftertouch", "") { m.reply(impl.resources_.getMidiState().getChannelAftertouch()); } break; + MATCH("/poly_aftertouch/&", "") { if (auto note = m.checkNote()) m.reply(impl.resources_.getMidiState().getPolyAftertouch(*note)); } break; + MATCH("/pitch_bend", "") { m.reply(impl.resources_.getMidiState().getPitchBend()); } break; //---------------------------------------------------------------------- - - MATCH("/mem/buffers", "") { - uint64_t total = BufferCounter::counter().getTotalBytes(); - client.receive<'h'>(delay, path, total); - } break; - + MATCH("/mem/buffers", "") { m.reply(BufferCounter::counter().getTotalBytes()); } 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&/delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.delayCC.getWithDefault(indices[1])); - } 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&/end_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.endCC.getWithDefault(indices[1])); - } 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&/trigger_on_note", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.triggerOnNote) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/trigger_on_cc", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.triggerOnCC) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/use_timer_range", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.useTimerRange) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/count", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.sampleCount) - client.receive<'h'>(delay, path, *region.sampleCount); - else - client.receive<'N'>(delay, path, {}); - } 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_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.loopStartCC.getWithDefault(indices[1])); - } break; - - MATCH("/region&/loop_end_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.loopEndCC.getWithDefault(indices[1])); - } 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 LoopMode::no_loop: - client.receive<'s'>(delay, path, "no_loop"); - break; - case LoopMode::loop_continuous: - client.receive<'s'>(delay, path, "loop_continuous"); - break; - case LoopMode::loop_sustain: - client.receive<'s'>(delay, path, "loop_sustain"); - break; - case LoopMode::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&/loop_count", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.loopCount) - client.receive<'h'>(delay, path, *region.loopCount); - else - client.receive<'N'>(delay, path, {}); - } break; - - MATCH("/region&/output", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.output); - } 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 OffMode::time: - client.receive<'s'>(delay, path, "time"); - break; - case OffMode::normal: - client.receive<'s'>(delay, path, "normal"); - break; - case OffMode::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&/program_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].i = region.programRange.getStart(); - args[1].i = region.programRange.getEnd(); - client.receive(delay, path, "ii", 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&/delay", "") { m.reply(&Region::delay); } break; + MATCH("/region&/delay_random", "") { m.reply(&Region::delayRandom); } break; + MATCH("/region&/sample", "") { if (auto region = m.getRegion()) { m.reply(region->sampleId->filename()); } } break; + MATCH("/region&/direction", "") { if (auto region = m.getRegion()) { m.reply(region->sampleId->isReverse() ? "reverse" : "forward"); } } break; + MATCH("/region&/delay_cc&", "") { m.reply(&Region::delayCC); } break; + MATCH("/region&/offset", "") { m.reply(&Region::offset); } break; + MATCH("/region&/offset_random", "") { m.reply(&Region::offsetRandom); } break; + MATCH("/region&/offset_cc&", "") { m.reply(&Region::offsetCC); } break; + MATCH("/region&/end", "") { m.reply(&Region::sampleEnd); } break; + MATCH("/region&/end_cc&", "") { m.reply(&Region::endCC); } break; + MATCH("/region&/enabled", "") { if (auto region = m.getRegion()) { m.reply(!region->disabled()); } } break; + MATCH("/region&/trigger_on_note", "") { m.reply(&Region::triggerOnNote); } break; + MATCH("/region&/trigger_on_cc", "") { m.reply(&Region::triggerOnCC); } break; + MATCH("/region&/use_timer_range", "") { m.reply(&Region::useTimerRange); } break; + MATCH("/region&/count", "") { m.reply(&Region::sampleCount); } break; + MATCH("/region&/loop_range", "") { m.reply(&Region::loopRange); } break; + MATCH("/region&/loop_start_cc&", "") { m.reply(&Region::loopStartCC); } break; + MATCH("/region&/loop_end_cc&", "") { m.reply(&Region::loopEndCC); } break; + MATCH("/region&/loop_mode", "") { m.reply(&Region::loopMode, LoopMode::no_loop); } break; + MATCH("/region&/loop_crossfade", "") { m.reply(&Region::loopCrossfade); } break; + MATCH("/region&/loop_count", "") { m.reply(&Region::loopCount); } break; + MATCH("/region&/output", "") { m.reply(&Region::output); } break; + MATCH("/region&/group", "") { m.reply(&Region::group); } break; + MATCH("/region&/off_by", "") { m.reply(&Region::offBy); } break; + MATCH("/region&/off_mode", "") { m.reply(&Region::offMode); } break; + MATCH("/region&/key_range", "") { m.reply(&Region::keyRange); } break; + MATCH("/region&/off_time", "") { m.reply(&Region::offTime); } break; + MATCH("/region&/pitch_keycenter", "") { m.reply(&Region::pitchKeycenter); } break; + MATCH("/region&/vel_range", "") { m.reply(&Region::velocityRange); } break; + MATCH("/region&/bend_range", "") { m.reply(&Region::bendRange); } break; + MATCH("/region&/program_range", "") { m.reply(&Region::programRange); } break; + MATCH("/region&/cc_range&", "") { m.reply(&Region::ccConditions); } 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, {}); + if (auto region = m.getRegion()) { + if (region->lastKeyswitch) m.reply(region->lastKeyswitch); + else m.reply(region->lastKeyswitchRange); } } 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 VelocityOverride::current: - client.receive<'s'>(delay, path, "current"); - break; - case VelocityOverride::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].f = region.aftertouchRange.getStart(); - args[1].f = region.aftertouchRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/polyaft_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.polyAftertouchRange.getStart(); - args[1].f = region.polyAftertouchRange.getEnd(); - client.receive(delay, path, "ff", 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 Trigger::attack: - client.receive<'s'>(delay, path, "attack"); - break; - case Trigger::first: - client.receive<'s'>(delay, path, "first"); - break; - case Trigger::release: - client.receive<'s'>(delay, path, "release"); - break; - case Trigger::release_key: - client.receive<'s'>(delay, path, "release_key"); - break; - case Trigger::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 * 100.0f); - } 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 * 100.0f); - } 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 * 100.0f); - } 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 * 100.0f); - } 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&/timer_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.timerRange.getStart(); - args[1].f = region.timerRange.getEnd(); - client.receive(delay, path, "ff", args); - } 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 * 100.0f); - } 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 * 100.0f); - } 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 * 100.0f); - } 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 * 100.0f); - } 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_veltrack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.ampVeltrackCC.contains(indices[1])) { - const auto& cc = region.ampVeltrackCC.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/amp_veltrack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.ampVeltrackCC.contains(indices[1])) { - const auto& cc = region.ampVeltrackCC.getWithDefault(indices[1]); - client.receive<'i'>(delay, path, cc.curve ); - } else { - client.receive<'N'>(delay, path, {}); - } - } 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 CrossfadeCurve::gain: - client.receive<'s'>(delay, path, "gain"); - break; - case CrossfadeCurve::power: - client.receive<'s'>(delay, path, "power"); - break; - } - } break; - - MATCH("/region&/xf_velcurve", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.crossfadeVelCurve) { - case CrossfadeCurve::gain: - client.receive<'s'>(delay, path, "gain"); - break; - case CrossfadeCurve::power: - client.receive<'s'>(delay, path, "power"); - break; - } - } break; - - MATCH("/region&/xf_cccurve", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.crossfadeCCCurve) { - case CrossfadeCurve::gain: - client.receive<'s'>(delay, path, "gain"); - break; - case CrossfadeCurve::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_veltrack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.pitchVeltrackCC.contains(indices[1])) { - const auto& cc = region.pitchVeltrackCC.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pitch_veltrack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.pitchVeltrackCC.contains(indices[1])) { - const auto& cc = region.pitchVeltrackCC.getWithDefault(indices[1]); - client.receive<'i'>(delay, path, cc.curve ); - } else { - client.receive<'N'>(delay, path, {}); - } - } 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&/pitch", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.pitch); - } break; - - MATCH("/region&/pitch_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&/pitch_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&/pitch_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&/pitch_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<'f'>(delay, path, region.bendUp); - } break; - - MATCH("/region&/bend_down", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.bendDown); - } break; - - MATCH("/region&/bend_step", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(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 * 100.0f); - } break; - - MATCH("/region&/ampeg_sustain", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.sustain * 100.0f); - } break; - - MATCH("/region&/ampeg_depth", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(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 * 100.0f); - } break; - - MATCH("/region&/ampeg_vel&depth", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth); - } break; - - MATCH("/region&/ampeg_dynamic", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.amplitudeEG.dynamic) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/fileg_dynamic", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.filterEG && region.filterEG->dynamic) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/pitcheg_dynamic", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.pitchEG && region.pitchEG->dynamic) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/pitcheg_attack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/pitcheg_decay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/pitcheg_delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/pitcheg_hold_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/pitcheg_release_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/pitcheg_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/pitcheg_sustain_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/pitcheg_attack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/pitcheg_decay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/pitcheg_delay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/pitcheg_hold_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/pitcheg_release_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/pitcheg_start_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/pitcheg_sustain_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } 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 SelfMask::mask: - client.receive(delay, path, "T", nullptr); - break; - case SelfMask::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&/sostenuto_cc", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.sostenutoCC); - } break; - - MATCH("/region&/sustain_lo", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.sustainThreshold); - } break; - - MATCH("/region&/sostenuto_lo", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.sostenutoThreshold); - } break; - - MATCH("/region&/oscillator_phase", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.oscillatorPhase); - } break; - - MATCH("/region&/oscillator_quality", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.oscillatorQuality) { - client.receive<'i'>(delay, path, *region.oscillatorQuality); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/oscillator_mode", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.oscillatorMode); - } break; - - MATCH("/region&/oscillator_multi", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.oscillatorMulti); - } break; - - MATCH("/region&/oscillator_detune", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.oscillatorDetune); - } break; - - MATCH("/region&/oscillator_mod_depth", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.oscillatorModDepth * 100.0f); - } break; - + MATCH("/region&/sw_label", "") { m.reply(&Region::keyswitchLabel); } break; + MATCH("/region&/sw_up", "") { m.reply(&Region::upKeyswitch); } break; + MATCH("/region&/sw_down", "") { m.reply(&Region::downKeyswitch); } break; + MATCH("/region&/sw_previous", "") { m.reply(&Region::previousKeyswitch); } break; + MATCH("/region&/sw_vel", "") { m.reply(&Region::velocityOverride); } break; + MATCH("/region&/chanaft_range", "") { m.reply(&Region::aftertouchRange); } break; + MATCH("/region&/polyaft_range", "") { m.reply(&Region::polyAftertouchRange); } break; + MATCH("/region&/bpm_range", "") { m.reply(&Region::bpmRange); } break; + MATCH("/region&/rand_range", "") { m.reply(&Region::randRange); } break; + MATCH("/region&/seq_length", "") { m.reply(&Region::sequenceLength); } break; + MATCH("/region&/seq_position", "") { m.reply(&Region::sequencePosition); } break; + MATCH("/region&/trigger", "") { m.reply(&Region::trigger); } break; + MATCH("/region&/start_cc_range&", "") { m.reply(&Region::ccTriggers, false); } break; + MATCH("/region&/volume", "") { m.reply(&Region::volume); } break; + MATCH("/region&/volume_cc&", "") { m.reply(ModId::Volume, ModParam::Depth); } break; + MATCH("/region&/volume_stepcc&", "") { m.reply(ModId::Volume, ModParam::Step); } break; + MATCH("/region&/volume_smoothcc&", "") { m.reply(ModId::Volume, ModParam::Smooth); } break; + MATCH("/region&/volume_curvecc&", "") { m.reply(ModId::Volume, ModParam::Curve); } break; + MATCH("/region&/pan", "") { m.reply(&Region::pan, Default::pan); } break; + MATCH("/region&/pan_cc&", "") { m.reply(ModId::Pan, ModParam::Depth, Default::pan); } break; + MATCH("/region&/pan_stepcc&", "") { m.reply(ModId::Pan, ModParam::Step, Default::pan); } break; + MATCH("/region&/pan_smoothcc&", "") { m.reply(ModId::Pan, ModParam::Smooth, Default::pan); } break; + MATCH("/region&/pan_curvecc&", "") { m.reply(ModId::Pan, ModParam::Curve, Default::pan); } break; + MATCH("/region&/width", "") { m.reply(&Region::width, Default::width); } break; + MATCH("/region&/width_cc&", "") { m.reply(ModId::Width, ModParam::Depth, Default::width); } break; + MATCH("/region&/width_stepcc&", "") { m.reply(ModId::Width, ModParam::Step, Default::width); } break; + MATCH("/region&/width_smoothcc&", "") { m.reply(ModId::Width, ModParam::Smooth, Default::width); } break; + MATCH("/region&/width_curvecc&", "") { m.reply(ModId::Width, ModParam::Curve, Default::width); } break; + MATCH("/region&/timer_range", "") { m.reply(&Region::timerRange); } break; + MATCH("/region&/position", "") { m.reply(&Region::position, Default::position); } break; + MATCH("/region&/position_cc&", "") { m.reply(ModId::Position, ModParam::Depth, Default::position); } break; + MATCH("/region&/position_stepcc&", "") { m.reply(ModId::Position, ModParam::Step, Default::position); } break; + MATCH("/region&/position_smoothcc&", "") { m.reply(ModId::Position, ModParam::Smooth, Default::position); } break; + MATCH("/region&/position_curvecc&", "") { m.reply(ModId::Position, ModParam::Curve, Default::position); } break; + MATCH("/region&/amplitude", "") { m.reply(&Region::amplitude, Default::amplitude); } break; + MATCH("/region&/amplitude_cc&", "") { m.reply(ModId::Amplitude, ModParam::Depth, Default::amplitude); } break; + MATCH("/region&/amplitude_stepcc&", "") { m.reply(ModId::Amplitude, ModParam::Step, Default::amplitude); } break; + MATCH("/region&/amplitude_smoothcc&", "") { m.reply(ModId::Amplitude, ModParam::Smooth, Default::amplitude); } break; + MATCH("/region&/amplitude_curvecc&", "") { m.reply(ModId::Amplitude, ModParam::Curve, Default::amplitude); } break; + MATCH("/region&/amp_keycenter", "") { m.reply(&Region::ampKeycenter); } break; + MATCH("/region&/amp_keytrack", "") { m.reply(&Region::ampKeytrack); } break; + MATCH("/region&/amp_veltrack", "") { m.reply(&Region::ampVeltrack, Default::ampVeltrack); } break; + MATCH("/region&/amp_veltrack_cc&", "") { m.reply(&Region::ampVeltrackCC, false, ModParam::Depth, Default::ampVeltrackMod); } break; + MATCH("/region&/amp_veltrack_curvecc&", "") { m.reply(&Region::ampVeltrackCC, false, ModParam::Curve, Default::ampVeltrackMod); } break; + MATCH("/region&/amp_random", "") { m.reply(&Region::ampRandom); } break; + MATCH("/region&/xfin_key_range", "") { m.reply(&Region::crossfadeKeyInRange); } break; + MATCH("/region&/xfout_key_range", "") { m.reply(&Region::crossfadeKeyOutRange); } break; + MATCH("/region&/xfin_vel_range", "") { m.reply(&Region::crossfadeVelInRange); } break; + MATCH("/region&/xfout_vel_range", "") { m.reply(&Region::crossfadeVelOutRange); } break; + MATCH("/region&/xfin_cc_range&", "") { m.reply(&Region::crossfadeCCInRange, false); } break; + MATCH("/region&/xfout_cc_range&", "") { m.reply(&Region::crossfadeCCOutRange, false); } break; + MATCH("/region&/xf_keycurve", "") { m.reply(&Region::crossfadeKeyCurve); } break; + MATCH("/region&/xf_velcurve", "") { m.reply(&Region::crossfadeVelCurve); } break; + MATCH("/region&/xf_cccurve", "") { m.reply(&Region::crossfadeCCCurve); } break; + MATCH("/region&/global_volume", "") { m.reply(&Region::globalVolume); } break; + MATCH("/region&/master_volume", "") { m.reply(&Region::masterVolume); } break; + MATCH("/region&/group_volume", "") { m.reply(&Region::groupVolume); } break; + MATCH("/region&/global_amplitude", "") { m.reply(&Region::globalAmplitude, Default::amplitude); } break; + MATCH("/region&/master_amplitude", "") { m.reply(&Region::masterAmplitude, Default::amplitude); } break; + MATCH("/region&/group_amplitude", "") { m.reply(&Region::groupAmplitude, Default::amplitude); } break; + MATCH("/region&/pitch_keytrack", "") { m.reply(&Region::pitchKeytrack); } break; + MATCH("/region&/pitch_veltrack", "") { m.reply(&Region::pitchVeltrack); } break; + MATCH("/region&/pitch_veltrack_cc&", "") { m.reply(&Region::pitchVeltrackCC, false, ModParam::Depth); } break; + MATCH("/region&/pitch_veltrack_curvecc&", "") { m.reply(&Region::pitchVeltrackCC, false, ModParam::Curve); } break; + MATCH("/region&/pitch_random", "") { m.reply(&Region::pitchRandom); } break; + MATCH("/region&/transpose", "") { m.reply(&Region::transpose); } break; + MATCH("/region&/pitch", "") { m.reply(&Region::pitch); } break; + MATCH("/region&/pitch_cc&", "") { m.reply(ModId::Pitch, ModParam::Depth, Default::pitch); } break; + MATCH("/region&/pitch_stepcc&", "") { m.reply(ModId::Pitch, ModParam::Step, Default::pitch); } break; + MATCH("/region&/pitch_smoothcc&", "") { m.reply(ModId::Pitch, ModParam::Smooth, Default::pitch); } break; + MATCH("/region&/pitch_curvecc&", "") { m.reply(ModId::Pitch, ModParam::Curve, Default::pitch); } break; + MATCH("/region&/bend_up", "") { m.reply(&Region::bendUp); } break; + MATCH("/region&/bend_down", "") { m.reply(&Region::bendDown); } break; + MATCH("/region&/bend_step", "") { m.reply(&Region::bendStep); } break; + MATCH("/region&/bend_smooth", "") { m.reply(&Region::bendSmooth); } break; + MATCH("/region&/ampeg_attack", "") { m.reply(&Region::amplitudeEG, &EGDescription::attack); } break; + MATCH("/region&/ampeg_delay", "") { m.reply(&Region::amplitudeEG, &EGDescription::delay); } break; + MATCH("/region&/ampeg_decay", "") { m.reply(&Region::amplitudeEG, &EGDescription::decay); } break; + MATCH("/region&/ampeg_hold", "") { m.reply(&Region::amplitudeEG, &EGDescription::hold); } break; + MATCH("/region&/ampeg_release", "") { m.reply(&Region::amplitudeEG, &EGDescription::release); } break; + MATCH("/region&/ampeg_start", "") { m.reply(&Region::amplitudeEG, &EGDescription::start, Default::egPercentMod); } break; + MATCH("/region&/ampeg_sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/ampeg_depth", "") { m.reply(&Region::amplitudeEG, &EGDescription::depth); } break; + MATCH("/region&/ampeg_attack_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Depth); } break; + MATCH("/region&/ampeg_attack_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Curve); } break; + MATCH("/region&/ampeg_decay_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDecay, ModParam::Depth); } break; + MATCH("/region&/ampeg_decay_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDecay, ModParam::Curve); } break; + MATCH("/region&/ampeg_delay_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDelay, ModParam::Depth); } break; + MATCH("/region&/ampeg_delay_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDelay, ModParam::Curve); } break; + MATCH("/region&/ampeg_hold_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccHold, ModParam::Depth); } break; + MATCH("/region&/ampeg_hold_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccHold, ModParam::Curve); } break; + MATCH("/region&/ampeg_release_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccRelease, ModParam::Depth); } break; + MATCH("/region&/ampeg_release_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccRelease, ModParam::Curve); } break; + MATCH("/region&/ampeg_sustain_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/ampeg_sustain_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/ampeg_vel&attack", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2attack); } break; + MATCH("/region&/ampeg_vel&delay", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2delay); } break; + MATCH("/region&/ampeg_vel&decay", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2decay); } break; + MATCH("/region&/ampeg_vel&hold", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2hold); } break; + MATCH("/region&/ampeg_vel&release", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2release); } break; + MATCH("/region&/ampeg_vel&sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2sustain, Default::egPercentMod); } break; + MATCH("/region&/ampeg_vel&depth", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2depth); } break; + MATCH("/region&/ampeg_dynamic", "") { m.reply(&Region::amplitudeEG, &EGDescription::dynamic); } break; + MATCH("/region&/fileg_attack", "") { m.reply(&Region::filterEG, &EGDescription::attack); } break; + MATCH("/region&/fileg_delay", "") { m.reply(&Region::filterEG, &EGDescription::delay); } break; + MATCH("/region&/fileg_decay", "") { m.reply(&Region::filterEG, &EGDescription::decay); } break; + MATCH("/region&/fileg_hold", "") { m.reply(&Region::filterEG, &EGDescription::hold); } break; + MATCH("/region&/fileg_release", "") { m.reply(&Region::filterEG, &EGDescription::release); } break; + MATCH("/region&/fileg_start", "") { m.reply(&Region::filterEG, &EGDescription::start, Default::egPercentMod); } break; + MATCH("/region&/fileg_sustain", "") { m.reply(&Region::filterEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/fileg_depth", "") { m.reply(&Region::filterEG, &EGDescription::depth); } break; + MATCH("/region&/fileg_attack_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccAttack, ModParam::Depth); } break; + MATCH("/region&/fileg_attack_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccAttack, ModParam::Curve); } break; + MATCH("/region&/fileg_decay_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDecay, ModParam::Depth); } break; + MATCH("/region&/fileg_decay_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDecay, ModParam::Curve); } break; + MATCH("/region&/fileg_delay_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDelay, ModParam::Depth); } break; + MATCH("/region&/fileg_delay_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDelay, ModParam::Curve); } break; + MATCH("/region&/fileg_hold_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccHold, ModParam::Depth); } break; + MATCH("/region&/fileg_hold_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccHold, ModParam::Curve); } break; + MATCH("/region&/fileg_release_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccRelease, ModParam::Depth); } break; + MATCH("/region&/fileg_release_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccRelease, ModParam::Curve); } break; + MATCH("/region&/fileg_sustain_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/fileg_sustain_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/fileg_start_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/fileg_start_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/fileg_dynamic", "") { m.reply(&Region::filterEG, &EGDescription::dynamic); } break; + MATCH("/region&/pitcheg_attack", "") { m.reply(&Region::pitchEG, &EGDescription::attack); } break; + MATCH("/region&/pitcheg_delay", "") { m.reply(&Region::pitchEG, &EGDescription::delay); } break; + MATCH("/region&/pitcheg_decay", "") { m.reply(&Region::pitchEG, &EGDescription::decay); } break; + MATCH("/region&/pitcheg_hold", "") { m.reply(&Region::pitchEG, &EGDescription::hold); } break; + MATCH("/region&/pitcheg_release", "") { m.reply(&Region::pitchEG, &EGDescription::release); } break; + MATCH("/region&/pitcheg_start", "") { m.reply(&Region::pitchEG, &EGDescription::start, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_sustain", "") { m.reply(&Region::pitchEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_depth", "") { m.reply(&Region::pitchEG, &EGDescription::depth); } break; + MATCH("/region&/pitcheg_attack_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccAttack, ModParam::Depth); } break; + MATCH("/region&/pitcheg_attack_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccAttack, ModParam::Curve); } break; + MATCH("/region&/pitcheg_decay_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDecay, ModParam::Depth); } break; + MATCH("/region&/pitcheg_decay_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDecay, ModParam::Curve); } break; + MATCH("/region&/pitcheg_delay_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDelay, ModParam::Depth); } break; + MATCH("/region&/pitcheg_delay_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDelay, ModParam::Curve); } break; + MATCH("/region&/pitcheg_hold_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccHold, ModParam::Depth); } break; + MATCH("/region&/pitcheg_hold_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccHold, ModParam::Curve); } break; + MATCH("/region&/pitcheg_release_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccRelease, ModParam::Depth); } break; + MATCH("/region&/pitcheg_release_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccRelease, ModParam::Curve); } break; + MATCH("/region&/pitcheg_sustain_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_sustain_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_start_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_start_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_dynamic", "") { m.reply(&Region::pitchEG, &EGDescription::dynamic); } break; + MATCH("/region&/note_polyphony", "") { m.reply(&Region::notePolyphony); } break; + MATCH("/region&/rt_dead", "") { m.reply(&Region::rtDead); } break; + MATCH("/region&/sustain_sw", "") { m.reply(&Region::checkSustain); } break; + MATCH("/region&/sostenuto_sw", "") { m.reply(&Region::checkSostenuto); } break; + MATCH("/region&/sustain_cc", "") { m.reply(&Region::sustainCC); } break; + MATCH("/region&/sostenuto_cc", "") { m.reply(&Region::sostenutoCC); } break; + MATCH("/region&/sustain_lo", "") { m.reply(&Region::sustainThreshold); } break; + MATCH("/region&/sostenuto_lo", "") { m.reply(&Region::sostenutoThreshold); } break; + MATCH("/region&/note_selfmask", "") { m.reply(&Region::selfMask); } break; + MATCH("/region&/oscillator_phase", "") { m.reply(&Region::oscillatorPhase); } break; + MATCH("/region&/oscillator_quality", "") { m.reply(&Region::oscillatorQuality); } break; + MATCH("/region&/oscillator_mode", "") { m.reply(&Region::oscillatorMode); } break; + MATCH("/region&/oscillator_multi", "") { m.reply(&Region::oscillatorMulti); } break; + MATCH("/region&/oscillator_detune", "") { m.reply(&Region::oscillatorDetune); } break; + MATCH("/region&/oscillator_mod_depth", "") { m.reply(&Region::oscillatorModDepth, Default::oscillatorModDepth); } break; // TODO: detune cc, mod depth cc 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); + if (auto region = m.getRegion()) + if (auto effectIdx = m.sindex(1)) + if (effectIdx > 0 && effectIdx < ssize(region->gainToEffect)) + m.reply(region->gainToEffect[*effectIdx], Default::effect); } break; - - MATCH("/region&/ampeg_attack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_decay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_hold_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_release_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/ampeg_sustain_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/ampeg_attack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/ampeg_decay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/ampeg_delay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/ampeg_hold_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/ampeg_release_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/ampeg_start_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/ampeg_sustain_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - 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&/cutoff_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto depth = region.ccModDepth(indices[2], ModId::FilCutoff, indices[1]); - if (depth) - client.receive<'f'>(delay, path, *depth); - } break; - - MATCH("/region&/filter&/cutoff_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto params = region.ccModParameters(indices[2], ModId::FilCutoff, indices[1]); - if (params) - client.receive<'i'>(delay, path, params->curve); - } break; - - MATCH("/region&/filter&/cutoff_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto params = region.ccModParameters(indices[2], ModId::FilCutoff, indices[1]); - if (params) - client.receive<'i'>(delay, path, params->step); - } break; - - MATCH("/region&/filter&/cutoff_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto params = region.ccModParameters(indices[2], ModId::FilCutoff, indices[1]); - if (params) - client.receive<'i'>(delay, path, params->smooth); - } 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&/veltrack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - if (filter.veltrackCC.contains(indices[2])) { - const auto& cc = filter.veltrackCC.getWithDefault(indices[2]); - client.receive<'f'>(delay, path, cc.modifier); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/filter&/veltrack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - if (filter.veltrackCC.contains(indices[2])) { - const auto& cc = filter.veltrackCC.getWithDefault(indices[2]); - client.receive<'i'>(delay, path, cc.curve ); - } else { - client.receive<'N'>(delay, path, {}); - } - } 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; - - MATCH("/region&/fileg_attack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_decay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_hold_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_release_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/fileg_sustain_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/fileg_attack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/fileg_decay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/fileg_delay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/fileg_hold_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/fileg_release_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/fileg_start_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - MATCH("/region&/fileg_sustain_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - - 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::kEqLshelf: client.receive<'s'>(delay, path, "lshelf"); break; - case EqType::kEqHshelf: client.receive<'s'>(delay, path, "hshelf"); break; - } - } break; - - MATCH("/region&/lfo&/wave", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_LFO_OR_BREAK(indices[1]) - if (lfo.sub.size() == 0) - break; - - client.receive<'i'>(delay, path, static_cast(lfo.sub[0].wave)); - } break; - - MATCH("/region&/eg&/point&/time", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EG_OR_BREAK(indices[1]) - GET_EG_POINT_OR_BREAK(indices[2] + 1) - - client.receive<'f'>(delay, path, point.time); - } break; - - MATCH("/region&/eg&/point&/time_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EG_OR_BREAK(indices[1]) - GET_EG_POINT_OR_BREAK(indices[2] + 1) - - client.receive<'f'>(delay, path, point.ccTime.getWithDefault(indices[3])); - } break; - - MATCH("/region&/eg&/point&/level", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EG_OR_BREAK(indices[1]) - GET_EG_POINT_OR_BREAK(indices[2] + 1) - - client.receive<'f'>(delay, path, point.level); - } break; - - MATCH("/region&/eg&/point&/level_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EG_OR_BREAK(indices[1]) - GET_EG_POINT_OR_BREAK(indices[2] + 1) - - client.receive<'f'>(delay, path, point.ccLevel.getWithDefault(indices[3])); - } break; - - #undef GET_REGION_OR_BREAK - #undef GET_FILTER_OR_BREAK - #undef GET_EQ_OR_BREAK - #undef GET_LFO_OR_BREAK - #undef GET_EG_OR_BREAK - #undef GET_EG_POINT_OR_BREAK - + MATCH("/region&/filter&/cutoff", "") { m.reply(&FilterDescription::cutoff); } break; + MATCH("/region&/filter&/cutoff_cc&", "") { m.reply(ModId::FilCutoff, ModParam::Depth); } break; + MATCH("/region&/filter&/cutoff_curvecc&", "") { m.reply(ModId::FilCutoff, ModParam::Curve); } break; + MATCH("/region&/filter&/cutoff_stepcc&", "") { m.reply(ModId::FilCutoff, ModParam::Step); } break; + MATCH("/region&/filter&/cutoff_smoothcc&", "") { m.reply(ModId::FilCutoff, ModParam::Smooth); } break; + MATCH("/region&/filter&/resonance", "") { m.reply(&FilterDescription::resonance); } break; + MATCH("/region&/filter&/gain", "") { m.reply(&FilterDescription::gain); } break; + MATCH("/region&/filter&/keycenter", "") { m.reply(&FilterDescription::keycenter); } break; + MATCH("/region&/filter&/keytrack", "") { m.reply(&FilterDescription::keytrack); } break; + MATCH("/region&/filter&/veltrack", "") { m.reply(&FilterDescription::veltrack); } break; + MATCH("/region&/filter&/veltrack_cc&", "") { m.reply(&FilterDescription::veltrackCC, ModParam::Depth); } break; + MATCH("/region&/filter&/veltrack_curvecc&", "") { m.reply(&FilterDescription::veltrackCC, ModParam::Curve); } break; + MATCH("/region&/filter&/type", "") { m.reply(&FilterDescription::type); } break; //---------------------------------------------------------------------- + MATCH("/region&/eq&/gain", "") { m.reply(&EQDescription::gain); } break; + MATCH("/region&/eq&/bandwidth", "") { m.reply(&EQDescription::bandwidth); } break; + MATCH("/region&/eq&/frequency", "") { m.reply(&EQDescription::frequency); } break; + MATCH("/region&/eq&/vel&freq", "") { m.reply(&EQDescription::vel2frequency); } break; + MATCH("/region&/eq&/vel&gain", "") { m.reply(&EQDescription::vel2gain); } break; + MATCH("/region&/eq&/type", "") { m.reply(&EQDescription::type); } break; + //---------------------------------------------------------------------- + MATCH("/region&/lfo&/wave", "") { m.reply(&LFODescription::Sub::wave); } break; + //---------------------------------------------------------------------- + MATCH("/region&/eg&/point&/time", "") { m.reply(&FlexEGPoint::time); } break; + MATCH("/region&/eg&/point&/time_cc&", "") { m.reply(&FlexEGPoint::ccTime); } break; + MATCH("/region&/eg&/point&/level", "") { m.reply(&FlexEGPoint::level); } break; + MATCH("/region&/eg&/point&/level_cc&", "") { m.reply(&FlexEGPoint::ccLevel); } break; + //---------------------------------------------------------------------- + MATCH("/voice&/trigger_value", "") { m.reply(&TriggerEvent::value); } break; + MATCH("/voice&/trigger_number", "") { m.reply(&TriggerEvent::number); } break; + MATCH("/voice&/trigger_type", "") { m.reply(&TriggerEvent::type); } break; + MATCH("/voice&/remaining_delay", "") { m.reply(&Voice::getRemainingDelay); } break; + MATCH("/voice&/source_position", "") { m.reply(&Voice::getSourcePosition); } break; + //---------------------------------------------------------------------- + // Setting values // Note: all these must be rt-safe within the parseOpcode method in region @@ -1858,161 +871,38 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co impl.resources_.getSynthConfig().sustainCancelsRelease = false; } break; - #define GET_REGION_OR_BREAK(idx) \ - if (idx >= impl.layers_.size()) \ - break; \ - Layer& layer = *impl.layers_[idx]; \ - Region& region = layer.getRegion(); - - #define GET_FILTER_OR_BREAK(idx) \ - if (idx >= region.filters.size()) \ - break; \ - auto& filter = region.filters[idx]; - - #define GET_LFO_OR_BREAK(idx) \ - if (idx >= region.lfos.size()) \ - break; \ - auto& lfo = region.lfos[idx]; - - #define GET_LFO_SUB_OR_BREAK(idx) \ - if (idx >= lfo.sub.size()) \ - break; \ - auto& sub = lfo.sub[idx]; - MATCH("/region&/pitch_keycenter", "i") { - GET_REGION_OR_BREAK(indices[0]) - region.pitchKeycenter = Opcode::transform(Default::key, args[0].i); + if (auto region = m.getRegion()) + region->pitchKeycenter = Opcode::transform(Default::key, args[0].i); } break; MATCH("/region&/loop_mode", "s") { - GET_REGION_OR_BREAK(indices[0]) - region.loopMode = Opcode::readOptional(Default::loopMode, args[0].s); + if (auto region = m.getRegion()) + region->loopMode = Opcode::readOptional(Default::loopMode, args[0].s); } break; MATCH("/region&/filter&/type", "s") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - filter.type = Opcode::read(Default::filter, args[0].s); + if (auto region = m.getRegion()) + if (auto filter = m.getFilter(*region)) + filter->type = Opcode::read(Default::filter, args[0].s); } break; MATCH("/region&/lfo&/wave", "i") { - indices[2] = 0; - goto set_lfoN_wave; + if (auto region = m.getRegion()) + if (auto lfo = m.getLFO(*region)) + if (!lfo->sub.empty()) + lfo->sub[0].wave = Opcode::transform(Default::lfoWave, args[0].i); } break; MATCH("/region&/lfo&/wave&", "i") { - set_lfoN_wave: - GET_REGION_OR_BREAK(indices[0]) - GET_LFO_OR_BREAK(indices[1]) - GET_LFO_SUB_OR_BREAK(indices[2]) - sub.wave = Opcode::transform(Default::lfoWave, args[0].i); - } break; - - #undef GET_REGION_OR_BREAK - #undef GET_FILTER_OR_BREAK - #undef GET_LFO_OR_BREAK - #undef GET_LFO_SUB_OR_BREAK - - //---------------------------------------------------------------------- - // Voices - - MATCH("/num_active_voices", "") { - client.receive<'i'>(delay, path, impl.voiceManager_.getNumActiveVoices()); - } break; - - #define GET_VOICE_OR_BREAK(idx) \ - if (static_cast(idx) >= impl.numVoices_) \ - break; \ - const auto& voice = impl.voiceManager_[idx]; \ - if (voice.isFree()) \ - break; - - MATCH("/voice&/trigger_value", "") { - GET_VOICE_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, voice.getTriggerEvent().value); - } break; - - MATCH("/voice&/trigger_number", "") { - GET_VOICE_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, voice.getTriggerEvent().number); - } break; - - MATCH("/voice&/trigger_type", "") { - GET_VOICE_OR_BREAK(indices[0]) - const auto& event = voice.getTriggerEvent(); - switch (event.type) { - case TriggerEventType::CC: - client.receive<'s'>(delay, path, "cc"); - break; - case TriggerEventType::NoteOn: - client.receive<'s'>(delay, path, "note_on"); - break; - case TriggerEventType::NoteOff: - client.receive<'s'>(delay, path, "note_on"); - break; - } - - } break; - - MATCH("/voice&/remaining_delay", "") { - GET_VOICE_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, voice.getRemainingDelay()); - } break; - - MATCH("/voice&/source_position", "") { - GET_VOICE_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, voice.getSourcePosition()); + if (auto region = m.getRegion()) + if (auto lfo = m.getLFO(*region)) + if (auto sub = m.getLFOSub(*lfo)) + sub->wave = Opcode::transform(Default::lfoWave, args[0].i); } break; #undef MATCH - // TODO... } } -static bool extractMessage(const char* pattern, const char* path, unsigned* indices) -{ - unsigned nthIndex = 0; - - while (const char *endp = strchr(pattern, '&')) { - if (nthIndex == maxIndices) - return false; - - size_t length = endp - pattern; - if (strncmp(pattern, path, length)) - return false; - pattern += length; - path += length; - - length = 0; - while (absl::ascii_isdigit(path[length])) - ++length; - - if (!absl::SimpleAtoi(absl::string_view(path, length), &indices[nthIndex++])) - return false; - - pattern += 1; - path += length; - } - - return !strcmp(path, pattern); -} - -static uint64_t hashMessagePath(const char* path, const char* sig) -{ - uint64_t h = Fnv1aBasis; - while (unsigned char c = *path++) { - if (!absl::ascii_isdigit(c)) - h = hashByte(c, h); - else { - h = hashByte('&', h); - while (absl::ascii_isdigit(*path)) - ++path; - } - } - h = hashByte(',', h); - while (unsigned char c = *sig++) - h = hashByte(c, h); - return h; -} - } // namespace sfz diff --git a/src/sfizz/utility/Size.h b/src/sfizz/utility/Size.h new file mode 100644 index 00000000..f7d5277c --- /dev/null +++ b/src/sfizz/utility/Size.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once + +#include + +template +constexpr auto ssize(const C& c) + -> std::common_type_t> +{ + using R = std::common_type_t>; + return static_cast(c.size()); +} + +template +constexpr std::ptrdiff_t ssize(const T (&)[N]) noexcept +{ + return N; +} diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index 5423d9af..837be6af 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -4,6 +4,7 @@ // 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 "Defaults.h" #include "sfizz/Region.h" #include "catch2/catch.hpp" using namespace Catch::literals; @@ -573,3 +574,11 @@ TEST_CASE("[Opcode] readBooleanFromOpcode") REQUIRE(readBoolean({"On"}) == true); REQUIRE(readBoolean({"oFf"}) == false); } + +TEST_CASE("[Opcode] denormalize") +{ + REQUIRE(Default::egTimeMod.denormalizeInput(0.0f) == 0.0f); + REQUIRE(Default::egTimeMod.denormalizeInput(1.0f) == 1.0f); + REQUIRE(Default::egPercentMod.denormalizeInput(0.0f) == 0.0f); + REQUIRE(Default::egPercentMod.denormalizeInput(0.01f) == 1.0f); +} diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 01c596e2..2e0a0988 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -256,7 +256,7 @@ TEST_CASE("[Values] Count") synth.dispatchMessage(client, 0, "/region2/count", "", nullptr); std::vector expected { "/region0/count,N : { }", - "/region1/count,h : { 2 }", + "/region1/count,i : { 2 }", "/region2/count,N : { }", }; REQUIRE(messageList == expected); @@ -421,7 +421,7 @@ TEST_CASE("[Values] Loop count") synth.dispatchMessage(client, 0, "/region2/loop_count", "", nullptr); std::vector expected { "/region0/loop_count,N : { }", - "/region1/loop_count,h : { 2 }", + "/region1/loop_count,i : { 2 }", "/region2/loop_count,N : { }", }; REQUIRE(messageList == expected); @@ -1158,9 +1158,9 @@ TEST_CASE("[Values] Sequence length") // 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 }", + "/region0/seq_length,i : { 1 }", + "/region1/seq_length,i : { 12 }", + "/region2/seq_length,i : { 1 }", // TODO: activate for the new region parser ; ignore the second value // "/region3/seq_length,f : { 12 }", }; @@ -1186,9 +1186,9 @@ TEST_CASE("[Values] Sequence position") // 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 }", + "/region0/seq_position,i : { 1 }", + "/region1/seq_position,i : { 12 }", + "/region2/seq_position,i : { 1 }", // TODO: activate for the new region parser ; ignore the second value // "/region3/seq_position,f : { 12 }", }; @@ -2133,9 +2133,9 @@ TEST_CASE("[Values] Pitch Keytrack") 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 }", + "/region0/pitch_keytrack,f : { 100 }", + "/region1/pitch_keytrack,f : { 1000 }", + "/region2/pitch_keytrack,f : { -100 }", }; REQUIRE(messageList == expected); } @@ -2158,9 +2158,9 @@ TEST_CASE("[Values] Pitch Veltrack") 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 }", + "/region0/pitch_veltrack,f : { 0 }", + "/region1/pitch_veltrack,f : { 10 }", + "/region2/pitch_veltrack,f : { -132 }", }; REQUIRE(messageList == expected); } @@ -2233,11 +2233,11 @@ TEST_CASE("[Values] Transpose") 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 : { -400 }", - "/region4/transpose,i : { 400 }", + "/region0/transpose,f : { 0 }", + "/region1/transpose,f : { 10 }", + "/region2/transpose,f : { -4 }", + "/region3/transpose,f : { -400 }", + "/region4/transpose,f : { 400 }", }; REQUIRE(messageList == expected); } @@ -2891,7 +2891,7 @@ TEST_CASE("[Values] Support floating point for int values") synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); std::vector expected { "/region0/offset,h : { 1042 }", - "/region1/pitch_keytrack,i : { -2 }", + "/region1/pitch_keytrack,f : { -2.1 }", }; REQUIRE(messageList == expected); } @@ -3001,7 +3001,15 @@ TEST_CASE("[Values] fileg CC") synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/fileg_attack_cc1,N : { }", + "/region0/fileg_delay_cc2,N : { }", + "/region0/fileg_decay_cc3,N : { }", + "/region0/fileg_hold_cc4,N : { }", + "/region0/fileg_release_cc5,N : { }", + "/region0/fileg_start_cc6,N : { }", + "/region0/fileg_sustain_cc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3079,7 +3087,15 @@ TEST_CASE("[Values] pitcheg CC") synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/pitcheg_attack_cc1,N : { }", + "/region0/pitcheg_delay_cc2,N : { }", + "/region0/pitcheg_decay_cc3,N : { }", + "/region0/pitcheg_hold_cc4,N : { }", + "/region0/pitcheg_release_cc5,N : { }", + "/region0/pitcheg_start_cc6,N : { }", + "/region0/pitcheg_sustain_cc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3158,13 +3174,13 @@ TEST_CASE("[Values] ampeg curve CC") synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/ampeg_attack_curvecc1,f : { 0 }", - "/region0/ampeg_delay_curvecc2,f : { 0 }", - "/region0/ampeg_decay_curvecc3,f : { 0 }", - "/region0/ampeg_hold_curvecc4,f : { 0 }", - "/region0/ampeg_release_curvecc5,f : { 0 }", - "/region0/ampeg_start_curvecc6,f : { 0 }", - "/region0/ampeg_sustain_curvecc7,f : { 0 }", + "/region0/ampeg_attack_curvecc1,i : { 0 }", + "/region0/ampeg_delay_curvecc2,i : { 0 }", + "/region0/ampeg_decay_curvecc3,i : { 0 }", + "/region0/ampeg_hold_curvecc4,i : { 0 }", + "/region0/ampeg_release_curvecc5,i : { 0 }", + "/region0/ampeg_start_curvecc6,i : { 0 }", + "/region0/ampeg_sustain_curvecc7,i : { 0 }", }; REQUIRE(messageList == expected); } @@ -3185,13 +3201,13 @@ TEST_CASE("[Values] ampeg curve CC") synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/ampeg_attack_curvecc1,f : { 1 }", - "/region0/ampeg_delay_curvecc2,f : { 2 }", - "/region0/ampeg_decay_curvecc3,f : { 3 }", - "/region0/ampeg_hold_curvecc4,f : { 4 }", - "/region0/ampeg_release_curvecc5,f : { 5 }", - "/region0/ampeg_start_curvecc6,f : { 6 }", - "/region0/ampeg_sustain_curvecc7,f : { 7 }", + "/region0/ampeg_attack_curvecc1,i : { 1 }", + "/region0/ampeg_delay_curvecc2,i : { 2 }", + "/region0/ampeg_decay_curvecc3,i : { 3 }", + "/region0/ampeg_hold_curvecc4,i : { 4 }", + "/region0/ampeg_release_curvecc5,i : { 5 }", + "/region0/ampeg_start_curvecc6,i : { 6 }", + "/region0/ampeg_sustain_curvecc7,i : { 7 }", }; REQUIRE(messageList == expected); } @@ -3217,7 +3233,15 @@ TEST_CASE("[Values] fileg curve CC") synth.dispatchMessage(client, 0, "/region0/fileg_release_curvecc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/fileg_attack_curvecc1,N : { }", + "/region0/fileg_delay_curvecc2,N : { }", + "/region0/fileg_decay_curvecc3,N : { }", + "/region0/fileg_hold_curvecc4,N : { }", + "/region0/fileg_release_curvecc5,N : { }", + "/region0/fileg_start_curvecc6,N : { }", + "/region0/fileg_sustain_curvecc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3237,13 +3261,13 @@ TEST_CASE("[Values] fileg curve CC") synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/fileg_attack_curvecc1,f : { 1 }", - "/region0/fileg_delay_curvecc2,f : { 2 }", - "/region0/fileg_decay_curvecc3,f : { 3 }", - "/region0/fileg_hold_curvecc4,f : { 4 }", - "/region0/fileg_release_curvecc5,f : { 5 }", - "/region0/fileg_start_curvecc6,f : { 6 }", - "/region0/fileg_sustain_curvecc7,f : { 7 }", + "/region0/fileg_attack_curvecc1,i : { 1 }", + "/region0/fileg_delay_curvecc2,i : { 2 }", + "/region0/fileg_decay_curvecc3,i : { 3 }", + "/region0/fileg_hold_curvecc4,i : { 4 }", + "/region0/fileg_release_curvecc5,i : { 5 }", + "/region0/fileg_start_curvecc6,i : { 6 }", + "/region0/fileg_sustain_curvecc7,i : { 7 }", }; REQUIRE(messageList == expected); } @@ -3269,7 +3293,15 @@ TEST_CASE("[Values] pitcheg curve CC") synth.dispatchMessage(client, 0, "/region0/pitcheg_release_curvecc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/pitcheg_attack_curvecc1,N : { }", + "/region0/pitcheg_delay_curvecc2,N : { }", + "/region0/pitcheg_decay_curvecc3,N : { }", + "/region0/pitcheg_hold_curvecc4,N : { }", + "/region0/pitcheg_release_curvecc5,N : { }", + "/region0/pitcheg_start_curvecc6,N : { }", + "/region0/pitcheg_sustain_curvecc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3289,17 +3321,16 @@ TEST_CASE("[Values] pitcheg curve CC") synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/pitcheg_attack_curvecc1,f : { 1 }", - "/region0/pitcheg_delay_curvecc2,f : { 2 }", - "/region0/pitcheg_decay_curvecc3,f : { 3 }", - "/region0/pitcheg_hold_curvecc4,f : { 4 }", - "/region0/pitcheg_release_curvecc5,f : { 5 }", - "/region0/pitcheg_start_curvecc6,f : { 6 }", - "/region0/pitcheg_sustain_curvecc7,f : { 7 }", + "/region0/pitcheg_attack_curvecc1,i : { 1 }", + "/region0/pitcheg_delay_curvecc2,i : { 2 }", + "/region0/pitcheg_decay_curvecc3,i : { 3 }", + "/region0/pitcheg_hold_curvecc4,i : { 4 }", + "/region0/pitcheg_release_curvecc5,i : { 5 }", + "/region0/pitcheg_start_curvecc6,i : { 6 }", + "/region0/pitcheg_sustain_curvecc7,i : { 7 }", }; REQUIRE(messageList == expected); } - } TEST_CASE("[Values] Filter stacking and cutoffs") @@ -3358,8 +3389,8 @@ TEST_CASE("[Values] Filter stacking and cutoffs") "/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/keytrack,f : { 0 }", + "/region1/filter0/veltrack,f : { 0 }", "/region1/filter0/type,s : { lpf_2p }", // No second filter }; @@ -3388,15 +3419,15 @@ TEST_CASE("[Values] Filter stacking and cutoffs") "/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/keytrack,f : { 0 }", + "/region2/filter0/veltrack,f : { 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/keytrack,f : { 0 }", + "/region2/filter1/veltrack,f : { 0 }", "/region2/filter1/type,s : { lpf_2p }", // No second filter }; @@ -3429,12 +3460,16 @@ TEST_CASE("[Values] Cutoff modifiers") synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_smoothcc3", "", nullptr); synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_curvecc3", "", nullptr); std::vector expected { + "/region0/filter0/cutoff_cc1,N : { }", + "/region0/filter0/cutoff_stepcc1,N : { }", + "/region0/filter0/cutoff_smoothcc1,N : { }", + "/region0/filter0/cutoff_curvecc1,N : { }", "/region0/filter0/cutoff_cc2,f : { 1000 }", - "/region0/filter0/cutoff_stepcc2,i : { 10 }", + "/region0/filter0/cutoff_stepcc2,f : { 10 }", "/region0/filter0/cutoff_smoothcc2,i : { 2 }", "/region0/filter0/cutoff_curvecc2,i : { 4 }", "/region1/filter1/cutoff_cc3,f : { 100 }", - "/region1/filter1/cutoff_stepcc3,i : { 1 }", + "/region1/filter1/cutoff_stepcc3,f : { 1 }", "/region1/filter1/cutoff_smoothcc3,i : { 20 }", "/region1/filter1/cutoff_curvecc3,i : { 3 }", }; @@ -3551,9 +3586,9 @@ TEST_CASE("[Values] Filter dispatching") "/region0/filter2/cutoff,f : { 50 }", "/region0/filter1/resonance,f : { 3 }", "/region0/filter1/gain,f : { -5 }", - "/region0/filter2/keytrack,i : { 100 }", + "/region0/filter2/keytrack,f : { 100 }", "/region0/filter0/gain,f : { -5 }", - "/region0/filter1/veltrack,i : { -100 }", + "/region0/filter1/veltrack,f : { -100 }", "/region0/filter3/veltrack_cc7,f : { -100 }", "/region0/filter4/veltrack_curvecc2,i : { 2 }", }; @@ -3870,8 +3905,8 @@ TEST_CASE("[Values] Dynamic EGs") synth.dispatchMessage(client, 0, "/region1/fileg_dynamic", "", nullptr); std::vector expected { "/region0/ampeg_dynamic,F : { }", - "/region0/pitcheg_dynamic,F : { }", - "/region0/fileg_dynamic,F : { }", + "/region0/pitcheg_dynamic,N : { }", + "/region0/fileg_dynamic,N : { }", "/region1/ampeg_dynamic,T : { }", "/region1/pitcheg_dynamic,T : { }", "/region1/fileg_dynamic,T : { }",