diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 32ebf631..2affd99b 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -6,21 +6,37 @@ #include "Opcode.h" #include "StringViewHelpers.h" - +#include "absl/strings/charconv.h" +#include sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) : opcode(inputOpcode) , value(inputValue) { - const auto lastCharIndex = inputOpcode.find_last_not_of("1234567890"); - if (lastCharIndex != inputOpcode.npos) { - int returnedValue; - absl::string_view parameterView = inputOpcode; - parameterView.remove_prefix(lastCharIndex + 1); - if (absl::SimpleAtoi(parameterView, &returnedValue)) { - parameter = returnedValue; - opcode.remove_suffix(opcode.size() - lastCharIndex - 1); - } - } trimInPlace(value); trimInPlace(opcode); + size_t firstCharIndex { 0 }; + auto firstNumIndex = opcode.find_first_of("1234567890"); + while (firstNumIndex != opcode.npos) { + lettersOnlyHash = hash(opcode.substr(firstCharIndex, firstNumIndex - firstCharIndex), lettersOnlyHash); + firstCharIndex = opcode.find_first_not_of("1234567890", firstNumIndex); + + uint32_t returnedValue; + if (firstCharIndex == absl::string_view::npos) { + if (absl::SimpleAtoi(opcode.substr(firstNumIndex), &returnedValue)) { + ASSERT(returnedValue < std::numeric_limits::max()); + backParameter = static_cast(returnedValue); + break; + } + } else { + if (absl::SimpleAtoi(opcode.substr(firstNumIndex, firstCharIndex - firstNumIndex), &returnedValue)) { + ASSERT(returnedValue < std::numeric_limits::max()); + parameters.push_back(static_cast(returnedValue)); + } + } + firstNumIndex = opcode.find_first_of("1234567890", firstCharIndex); + } + + if (firstCharIndex != opcode.npos) + lettersOnlyHash = hash(opcode.substr(firstCharIndex), lettersOnlyHash); + } diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index 30119001..186d309a 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -12,6 +12,7 @@ #include "StringViewHelpers.h" #include #include +#include #include // charconv support is still sketchy with clang/gcc so we use abseil's numbers @@ -29,8 +30,10 @@ struct Opcode { Opcode(absl::string_view inputOpcode, absl::string_view inputValue); absl::string_view opcode {}; absl::string_view value {}; - // This is to handle the integer parameter of some opcodes - absl::optional parameter; + uint64_t lettersOnlyHash { Fnv1aBasis }; + // This is to handle the integer parameters of some opcodes + std::vector parameters; + absl::optional backParameter {}; LEAK_DETECTOR(Opcode); }; @@ -186,8 +189,8 @@ template inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional& target, const Range& validRange) { auto value = readOpcode(opcode.value, validRange); - if (value && opcode.parameter && Default::ccNumberRange.containsWithEnd(*opcode.parameter)) - target = std::make_pair(*opcode.parameter, *value); + if (value && opcode.backParameter && Default::ccNumberRange.containsWithEnd(*opcode.backParameter)) + target = std::make_pair(*opcode.backParameter, *value); else target = {}; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 2428f079..e2875efb 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -18,12 +18,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) { // Check that the parameter is well formed - if (opcode.parameter && !sfz::Default::ccNumberRange.containsWithEnd(*opcode.parameter)) { - DBG("Wrong parameter value (" << std::to_string(*opcode.parameter) << ") for opcode " << opcode.opcode); + if (opcode.backParameter && !sfz::Default::ccNumberRange.containsWithEnd(*opcode.backParameter)) { + DBG("Wrong parameter value (" << std::to_string(*opcode.backParameter) << ") for opcode " << opcode.opcode); return false; } - switch (hash(opcode.opcode)) { + switch (opcode.lettersOnlyHash) { // Sound source: sample playback case hash("sample"): { @@ -135,13 +135,13 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); break; case hash("locc"): - if (opcode.parameter) { - setRangeStartFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccValueRange); + if (opcode.backParameter) { + setRangeStartFromOpcode(opcode, ccConditions[*opcode.backParameter], Default::ccValueRange); } break; case hash("hicc"): - if (opcode.parameter) - setRangeEndFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccValueRange); + if (opcode.backParameter) + setRangeEndFromOpcode(opcode, ccConditions[*opcode.backParameter], Default::ccValueRange); break; case hash("sw_lokey"): setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); @@ -233,13 +233,13 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("on_locc"): case hash("start_locc"): - if (opcode.parameter) - setRangeStartFromOpcode(opcode, ccTriggers[*opcode.parameter], Default::ccTriggerValueRange); + if (opcode.backParameter) + setRangeStartFromOpcode(opcode, ccTriggers[*opcode.backParameter], Default::ccTriggerValueRange); break; case hash("on_hicc"): case hash("start_hicc"): - if (opcode.parameter) - setRangeEndFromOpcode(opcode, ccTriggers[*opcode.parameter], Default::ccTriggerValueRange); + if (opcode.backParameter) + setRangeEndFromOpcode(opcode, ccTriggers[*opcode.backParameter], Default::ccTriggerValueRange); break; // Performance parameters: amplifier @@ -293,7 +293,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) { auto value = readOpcode(opcode.value, Default::ampVelcurveRange); if (value) - velocityPoints.emplace_back(*opcode.parameter, *value); + velocityPoints.emplace_back(*opcode.backParameter, *value); } break; case hash("xfin_lokey"): @@ -345,23 +345,23 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) } break; case hash("xfin_locc"): - if (opcode.parameter) { - setRangeStartFromOpcode(opcode, crossfadeCCInRange[*opcode.parameter], Default::ccValueRange); + if (opcode.backParameter) { + setRangeStartFromOpcode(opcode, crossfadeCCInRange[*opcode.backParameter], Default::ccValueRange); } break; case hash("xfin_hicc"): - if (opcode.parameter) { - setRangeEndFromOpcode(opcode, crossfadeCCInRange[*opcode.parameter], Default::ccValueRange); + if (opcode.backParameter) { + setRangeEndFromOpcode(opcode, crossfadeCCInRange[*opcode.backParameter], Default::ccValueRange); } break; case hash("xfout_locc"): - if (opcode.parameter) { - setRangeStartFromOpcode(opcode, crossfadeCCOutRange[*opcode.parameter], Default::ccValueRange); + if (opcode.backParameter) { + setRangeStartFromOpcode(opcode, crossfadeCCOutRange[*opcode.backParameter], Default::ccValueRange); } break; case hash("xfout_hicc"): - if (opcode.parameter) { - setRangeEndFromOpcode(opcode, crossfadeCCOutRange[*opcode.parameter], Default::ccValueRange); + if (opcode.backParameter) { + setRangeEndFromOpcode(opcode, crossfadeCCOutRange[*opcode.backParameter], Default::ccValueRange); } break; case hash("xf_cccurve"): @@ -433,23 +433,29 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("ampeg_sustain"): setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange); break; - case hash("ampeg_vel2attack"): - setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); + case hash("ampeg_velattack"): + if (!opcode.parameters.empty() && opcode.parameters.front() == 2) + setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); break; - case hash("ampeg_vel2decay"): - setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); + case hash("ampeg_veldecay"): + if (!opcode.parameters.empty() && opcode.parameters.front() == 2) + setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); break; - case hash("ampeg_vel2delay"): - setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); + case hash("ampeg_veldelay"): + if (!opcode.parameters.empty() && opcode.parameters.front() == 2) + setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); break; - case hash("ampeg_vel2hold"): - setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); + case hash("ampeg_velhold"): + if (!opcode.parameters.empty() && opcode.parameters.front() == 2) + setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); break; - case hash("ampeg_vel2release"): - setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); + case hash("ampeg_velrelease"): + if (!opcode.parameters.empty() && opcode.parameters.front() == 2) + setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); break; - case hash("ampeg_vel2sustain"): - setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); + case hash("ampeg_velsustain"): + if (!opcode.parameters.empty() && opcode.parameters.front() == 2) + setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); break; case hash("ampeg_attackcc"): case hash("ampeg_attack_oncc"): diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index d9f88d15..0638794f 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -137,7 +137,7 @@ void sfz::Synth::clear() void sfz::Synth::handleGlobalOpcodes(const std::vector& members) { for (auto& member : members) { - switch (hash(member.opcode)) { + switch (member.lettersOnlyHash) { case hash("sw_default"): setValueFromOpcode(member, defaultSwitch, Default::keyRange); break; @@ -152,20 +152,20 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector& members) void sfz::Synth::handleControlOpcodes(const std::vector& members) { for (auto& member : members) { - switch (hash(member.opcode)) { + switch (member.lettersOnlyHash) { case hash("Set_cc"): [[fallthrough]]; case hash("set_cc"): - if (member.parameter && Default::ccNumberRange.containsWithEnd(*member.parameter)) { + if (member.backParameter && Default::ccNumberRange.containsWithEnd(*member.backParameter)) { const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); - midiState.ccEvent(*member.parameter, ccValue); + midiState.ccEvent(*member.backParameter, ccValue); } break; case hash("Label_cc"): [[fallthrough]]; case hash("label_cc"): - if (member.parameter && Default::ccNumberRange.containsWithEnd(*member.parameter)) - ccNames.emplace_back(*member.parameter, std::string(member.value)); + if (member.backParameter && Default::ccNumberRange.containsWithEnd(*member.backParameter)) + ccNames.emplace_back(*member.backParameter, std::string(member.value)); break; case hash("Default_path"): [[fallthrough]]; diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index 9a9c2a75..bc73d68d 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -14,34 +14,89 @@ TEST_CASE("[Opcode] Construction") { sfz::Opcode opcode { "sample", "dummy" }; REQUIRE(opcode.opcode == "sample"); + REQUIRE(opcode.lettersOnlyHash == hash("sample")); + REQUIRE(opcode.parameters.empty()); REQUIRE(opcode.value == "dummy"); - REQUIRE(!opcode.parameter); + REQUIRE(!opcode.backParameter); } SECTION("Normal construction with underscore") { sfz::Opcode opcode { "sample_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample_underscore"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); + REQUIRE(opcode.parameters.empty()); REQUIRE(opcode.value == "dummy"); - REQUIRE(!opcode.parameter); + REQUIRE(!opcode.backParameter); } SECTION("Parameterized opcode") { sfz::Opcode opcode { "sample123", "dummy" }; - REQUIRE(opcode.opcode == "sample"); + REQUIRE(opcode.opcode == "sample123"); + REQUIRE(opcode.lettersOnlyHash == hash("sample")); REQUIRE(opcode.value == "dummy"); - REQUIRE(opcode.parameter); - REQUIRE(*opcode.parameter == 123); + REQUIRE(opcode.parameters.empty()); + REQUIRE(opcode.backParameter); + REQUIRE(*opcode.backParameter == 123); } SECTION("Parameterized opcode with underscore") { sfz::Opcode opcode { "sample_underscore123", "dummy" }; - REQUIRE(opcode.opcode == "sample_underscore"); + REQUIRE(opcode.opcode == "sample_underscore123"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.value == "dummy"); - REQUIRE(opcode.parameter); - REQUIRE(*opcode.parameter == 123); + REQUIRE(opcode.parameters.empty()); + REQUIRE(opcode.backParameter); + REQUIRE(*opcode.backParameter == 123); + } + + SECTION("Parameterized opcode within the opcode") + { + sfz::Opcode opcode { "sample1_underscore", "dummy" }; + REQUIRE(opcode.opcode == "sample1_underscore"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); + REQUIRE(opcode.value == "dummy"); + REQUIRE(opcode.parameters.size() == 1); + REQUIRE(opcode.parameters[0] == 1); + REQUIRE(!opcode.backParameter); + } + + SECTION("Parameterized opcode within the opcode") + { + sfz::Opcode opcode { "sample123_underscore", "dummy" }; + REQUIRE(opcode.opcode == "sample123_underscore"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); + REQUIRE(opcode.value == "dummy"); + REQUIRE(opcode.parameters.size() == 1); + REQUIRE(opcode.parameters[0] == 123); + REQUIRE(!opcode.backParameter); + } + + SECTION("Parameterized opcode within the opcode twice") + { + sfz::Opcode opcode { "sample123_double44_underscore", "dummy" }; + REQUIRE(opcode.opcode == "sample123_double44_underscore"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_double_underscore")); + REQUIRE(opcode.value == "dummy"); + REQUIRE(opcode.parameters.size() == 2); + REQUIRE(opcode.parameters[0] == 123); + REQUIRE(opcode.parameters[1] == 44); + REQUIRE(!opcode.backParameter); + } + + SECTION("Parameterized opcode within the opcode twice, with a back parameter") + { + sfz::Opcode opcode { "sample123_double44_underscore23", "dummy" }; + REQUIRE(opcode.opcode == "sample123_double44_underscore23"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_double_underscore")); + REQUIRE(opcode.value == "dummy"); + REQUIRE(opcode.parameters.size() == 2); + REQUIRE(opcode.parameters[0] == 123); + REQUIRE(opcode.parameters[1] == 44); + REQUIRE(opcode.backParameter); + REQUIRE(*opcode.backParameter == 23); } }