diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index fbe8bcc5..657bae1b 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -6,11 +6,16 @@ #include "Opcode.h" #include "StringViewHelpers.h" +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include +#include sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) : opcode(trim(inputOpcode)) , value(trim(inputValue)) + , category(identifyCategory(inputOpcode)) { size_t nextCharIndex { 0 }; int parameterPosition { 0 }; @@ -34,3 +39,77 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) if (nextCharIndex != opcode.npos) lettersOnlyHash = hashNoAmpersand(opcode.substr(nextCharIndex), lettersOnlyHash); } + +static absl::string_view extractBackInteger(absl::string_view opcodeName) +{ + size_t n = opcodeName.size(); + size_t i = n; + while (i > 0 && absl::ascii_isdigit(opcodeName[i - 1])) --i; + return opcodeName.substr(i); +} + +std::string sfz::Opcode::getDerivedName(sfz::OpcodeCategory newCategory, unsigned number) const +{ + std::string derivedName(opcode); + + switch (category) { + case kOpcodeNormal: + break; + case kOpcodeOnCcN: + case kOpcodeCurveCcN: + case kOpcodeStepCcN: + case kOpcodeSmoothCcN: + { + // when the input is cc, first delete the suffix `_*cc` + size_t pos = opcode.rfind('_'); + assert(pos != opcode.npos); + derivedName.resize(pos); + } + break; + } + + // helper to extract the cc number optionally if the next part needs it + auto ccNumberSuffix = [this, number]() -> std::string { + return (number != ~0u) ? std::to_string(number) : + std::string(extractBackInteger(opcode)); + }; + + switch (newCategory) { + case kOpcodeNormal: + break; + case kOpcodeOnCcN: + absl::StrAppend(&derivedName, "_oncc", ccNumberSuffix()); + break; + case kOpcodeCurveCcN: + absl::StrAppend(&derivedName, "_curvecc", ccNumberSuffix()); + break; + case kOpcodeStepCcN: + absl::StrAppend(&derivedName, "_stepcc", ccNumberSuffix()); + break; + case kOpcodeSmoothCcN: + absl::StrAppend(&derivedName, "_smoothcc", ccNumberSuffix()); + break; + } + + return derivedName; +} + +sfz::OpcodeCategory sfz::Opcode::identifyCategory(absl::string_view name) +{ + sfz::OpcodeCategory category = kOpcodeNormal; + + if (!name.empty() && absl::ascii_isdigit(name.back())) { + absl::string_view part = name; + part.remove_suffix(extractBackInteger(name).size()); + if (absl::EndsWith(part, "_oncc") || absl::EndsWith(part, "_cc")) + category = kOpcodeOnCcN; + else if (absl::EndsWith(part, "_curvecc")) + category = kOpcodeCurveCcN; + else if (absl::EndsWith(part, "_stepcc")) + category = kOpcodeStepCcN; + else if (absl::EndsWith(part, "_smoothcc")) + category = kOpcodeSmoothCcN; + } + + return category; +} diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index 492cfb1e..189aca0b 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -10,7 +10,7 @@ #include "Range.h" #include "SfzHelpers.h" #include "StringViewHelpers.h" -#include +#include "absl/types/optional.h" #include "absl/meta/type_traits.h" #include #include @@ -20,6 +20,22 @@ #include "absl/strings/numbers.h" namespace sfz { +/** + * @brief A category which an opcode may belong to. + */ +enum OpcodeCategory { + //! An ordinary opcode + kOpcodeNormal, + //! A region opcode which matches *_onccN or *_ccN + kOpcodeOnCcN, + //! A region opcode which matches *_curveccN + kOpcodeCurveCcN, + //! A region opcode which matches *_stepccN + kOpcodeStepCcN, + //! A region opcode which matches *_smoothccN + kOpcodeSmoothCcN, +}; + /** * @brief Opcode description class. The class parses the parameters * of the opcode on construction. @@ -33,6 +49,19 @@ struct Opcode { uint64_t lettersOnlyHash { Fnv1aBasis }; // This is to handle the integer parameters of some opcodes std::vector parameters; + OpcodeCategory category; + + /* + * @brief Get the derived opcode name to convert it to another category. + * + * @param newCategory category to convert to + * @param number optional CC number, needed if destination is CC and source is not + * @return derived opcode name + */ + std::string getDerivedName(OpcodeCategory newCategory, unsigned number = ~0u) const; + +private: + static OpcodeCategory identifyCategory(absl::string_view name); LEAK_DETECTOR(Opcode); }; diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index ffbb07ab..c45357ff 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -138,3 +138,23 @@ TEST_CASE("[Opcode] Note values") REQUIRE(noteValue); REQUIRE(*noteValue == 61); } + +TEST_CASE("[Opcode] Categories") +{ + REQUIRE(sfz::Opcode("sample", "").category == sfz::kOpcodeNormal); + REQUIRE(sfz::Opcode("amplitude_oncc11", "").category == sfz::kOpcodeOnCcN); + REQUIRE(sfz::Opcode("cutoff_cc22", "").category == sfz::kOpcodeOnCcN); + REQUIRE(sfz::Opcode("lfo01_pitch_curvecc33", "").category == sfz::kOpcodeCurveCcN); + REQUIRE(sfz::Opcode("pan_stepcc44", "").category == sfz::kOpcodeStepCcN); + REQUIRE(sfz::Opcode("noise_level_smoothcc55", "").category == sfz::kOpcodeSmoothCcN); +} + +TEST_CASE("[Opcode] Derived names") +{ + REQUIRE(sfz::Opcode("sample", "").getDerivedName(sfz::kOpcodeNormal) == "sample"); + REQUIRE(sfz::Opcode("cutoff_cc22", "").getDerivedName(sfz::kOpcodeNormal) == "cutoff"); + REQUIRE(sfz::Opcode("lfo01_pitch_curvecc33", "").getDerivedName(sfz::kOpcodeOnCcN) == "lfo01_pitch_oncc33"); + REQUIRE(sfz::Opcode("pan_stepcc44", "").getDerivedName(sfz::kOpcodeCurveCcN) == "pan_curvecc44"); + REQUIRE(sfz::Opcode("noise_level_smoothcc55", "").getDerivedName(sfz::kOpcodeStepCcN) == "noise_level_stepcc55"); + REQUIRE(sfz::Opcode("sample", "").getDerivedName(sfz::kOpcodeSmoothCcN, 66) == "sample_smoothcc66"); +}