From a27182268c3b9d9a43ec9ba8fbf15e8455d200f4 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 5 May 2020 07:39:43 +0200 Subject: [PATCH 1/2] Generic CC processing: volume, amplitude, pan, position, width, pitch --- src/sfizz/Defaults.h | 7 +- src/sfizz/Opcode.h | 10 +++ src/sfizz/Region.cpp | 196 +++++++++++++------------------------------ src/sfizz/Region.h | 10 +++ src/sfizz/Voice.cpp | 10 +-- 5 files changed, 84 insertions(+), 149 deletions(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 6226ee9d..6796380f 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -111,21 +111,17 @@ namespace Default constexpr float volume { 0.0f }; constexpr Range volumeRange { -144.0, 6.0 }; constexpr Range volumeCCRange { -144.0, 48.0 }; - constexpr Range volumeStepRange { 0, 48.0 }; constexpr float amplitude { 100.0 }; constexpr Range amplitudeRange { 0.0, 100.0 }; constexpr float pan { 0.0 }; constexpr Range panRange { -100.0, 100.0 }; constexpr Range panCCRange { -200.0, 200.0 }; - constexpr Range panStepRange { 0.0, 200.0 }; constexpr float position { 0.0 }; constexpr Range positionRange { -100.0, 100.0 }; constexpr Range positionCCRange { -200.0, 200.0 }; - constexpr Range positionStepRange { 0.0, 200.0 }; constexpr float width { 100.0 }; constexpr Range widthRange { -100.0, 100.0 }; constexpr Range widthCCRange { -200.0, 200.0 }; - constexpr Range widthStepRange { 0.0, 200.0 }; constexpr uint8_t ampKeycenter { 60 }; constexpr float ampKeytrack { 0.0 }; constexpr Range ampKeytrackRange { -96, 12 }; @@ -200,8 +196,7 @@ namespace Default constexpr Range transposeRange { -127, 127 }; constexpr int tune { 0 }; constexpr Range tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA - constexpr Range tuneCCRange { -9600, 9600 }; - constexpr Range tuneStepRange { 0, 9600 }; + constexpr Range tuneCCRange { -9600, 9600 }; constexpr Range bendBoundRange { -9600, 9600 }; constexpr Range bendStepRange { 1, 1200 }; constexpr int bendUp { 200 }; // No range here because the bounds can be inverted diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index b6f64e09..29f05c7b 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -81,6 +81,16 @@ struct Opcode { */ std::string getDerivedName(OpcodeCategory newCategory, unsigned number = ~0u) const; + /** + * @brief Get whether the opcode categorizes as `ccN` of any kind. + * @return true if `ccN`, otherwise false + */ + bool isAnyCcN() const + { + return category == kOpcodeOnCcN || category == kOpcodeCurveCcN || + category == kOpcodeStepCcN || category == kOpcodeSmoothCcN; + } + private: static OpcodeCategory identifyCategory(absl::string_view name); LEAK_DETECTOR(Opcode); diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 965b9745..8d505186 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -15,6 +15,7 @@ #include "absl/strings/str_cat.h" #include "absl/algorithm/container.h" #include +#include template bool extendIfNecessary(std::vector& vec, unsigned size, unsigned defaultCapacity) @@ -36,6 +37,13 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const Opcode opcode = rawOpcode.cleanUp(kOpcodeScopeRegion); switch (opcode.lettersOnlyHash) { + // Helper for ccN processing + #define case_any_ccN(x) \ + case hash(x "_oncc&"): \ + case hash(x "_curvecc&"): \ + case hash(x "_stepcc&"): \ + case hash(x "_smoothcc&") + // Sound source: sample playback case hash("sample"): { @@ -306,141 +314,36 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("volume"): // also gain setValueFromOpcode(opcode, volume, Default::volumeRange); break; - case hash("volume_curvecc&"): // also gain_curvecc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::curveCCRange)) - volumeCC[opcode.parameters.back()].curve = *value; - break; - case hash("volume_stepcc&"): // also gain_stepcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::volumeStepRange)) - volumeCC[opcode.parameters.back()].step = *value; - break; - case hash("volume_smoothcc&"): // also gain_smoothcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) - volumeCC[opcode.parameters.back()].smooth = *value; - break; - case hash("volume_oncc&"): // also gain_oncc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) - volumeCC[opcode.parameters.back()].value = *value; + case_any_ccN("volume"): // also gain + processGenericCc(opcode, Default::volumeCCRange, &volumeCC); break; case hash("amplitude"): if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) amplitude = normalizePercents(*value); break; - case hash("amplitude_curvecc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::curveCCRange)) - amplitudeCC[opcode.parameters.back()].curve = *value; - break; - case hash("amplitude_stepcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) - amplitudeCC[opcode.parameters.back()].step = normalizePercents(*value); - break; - case hash("amplitude_smoothcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) - amplitudeCC[opcode.parameters.back()].smooth = *value; - break; - case hash("amplitude_oncc&"): // also amplitude_cc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) - amplitudeCC[opcode.parameters.back()].value = normalizePercents(*value); + case_any_ccN("amplitude"): + processGenericCc(opcode, Default::amplitudeRange, &litudeCC); break; case hash("pan"): if (auto value = readOpcode(opcode.value, Default::panRange)) pan = normalizePercents(*value); break; - case hash("pan_curvecc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::curveCCRange)) - panCC[opcode.parameters.back()].curve = *value; - break; - case hash("pan_stepcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::panStepRange)) - panCC[opcode.parameters.back()].step = normalizePercents(*value); - break; - case hash("pan_smoothcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) - panCC[opcode.parameters.back()].smooth = *value; - break; - case hash("pan_oncc&"): // also pan_cc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::panCCRange)) - panCC[opcode.parameters.back()].value = normalizePercents(*value); + case_any_ccN("pan"): + processGenericCc(opcode, Default::panCCRange, &panCC); break; case hash("position"): if (auto value = readOpcode(opcode.value, Default::positionRange)) position = normalizePercents(*value); break; - case hash("position_curvecc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::curveCCRange)) - positionCC[opcode.parameters.back()].curve = *value; - break; - case hash("position_stepcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::positionStepRange)) - positionCC[opcode.parameters.back()].step = normalizePercents(*value); - break; - case hash("position_smoothcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) - positionCC[opcode.parameters.back()].smooth = *value; - break; - case hash("position_oncc&"): // also position_cc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::positionCCRange)) - positionCC[opcode.parameters.back()].value = normalizePercents(*value); + case_any_ccN("position"): + processGenericCc(opcode, Default::positionCCRange, &positionCC); break; case hash("width"): if (auto value = readOpcode(opcode.value, Default::widthRange)) width = normalizePercents(*value); break; - case hash("width_curvecc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::curveCCRange)) - widthCC[opcode.parameters.back()].curve = *value; - break; - case hash("width_stepcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::widthStepRange)) - widthCC[opcode.parameters.back()].step = normalizePercents(*value); - break; - case hash("width_smoothcc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) - widthCC[opcode.parameters.back()].smooth = *value; - break; - case hash("width_oncc&"): - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::widthCCRange)) - widthCC[opcode.parameters.back()].value = normalizePercents(*value); + case_any_ccN("width"): + processGenericCc(opcode, Default::widthCCRange, &widthCC); break; case hash("amp_keycenter"): setValueFromOpcode(opcode, ampKeycenter, Default::keyRange); @@ -802,29 +705,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("pitch"): // also tune setValueFromOpcode(opcode, tune, Default::tuneRange); break; - case hash("pitch_curvecc&"): // also tune_curvecc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::curveCCRange)) - tuneCC[opcode.parameters.back()].curve = *value; - break; - case hash("pitch_stepcc&"): // also tune_stepcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::tuneStepRange)) - tuneCC[opcode.parameters.back()].step = *value; - break; - case hash("pitch_smoothcc&"): // also tune_smoothcc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) - tuneCC[opcode.parameters.back()].smooth = *value; - break; - case hash("pitch_oncc&"): // also pitch_cc&, tune_cc&, tune_oncc& - if (opcode.parameters.back() >= config::numCCs) - return false; - if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) - tuneCC[opcode.parameters.back()].value = *value; + case_any_ccN("pitch"): // also tune + processGenericCc(opcode, Default::tuneCCRange, &tuneCC); break; case hash("bend_up"): // also bendup setValueFromOpcode(opcode, bendUp, Default::bendBoundRange); @@ -932,11 +814,49 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; default: return false; + + #undef case_any_ccN } return true; } +bool sfz::Region::processGenericCc(const Opcode& opcode, Range range, CCMap *ccMap) +{ + if (!opcode.isAnyCcN()) + return false; + + const auto ccNumber = opcode.parameters.back(); + if (ccNumber >= config::numCCs) + return false; + + if (ccMap) { + Modifier& modifier = (*ccMap)[ccNumber]; + switch (opcode.category) { + case kOpcodeOnCcN: + setValueFromOpcode(opcode, modifier.value, range); + break; + case kOpcodeCurveCcN: + setValueFromOpcode(opcode, modifier.curve, Default::curveCCRange); + break; + case kOpcodeStepCcN: + { + const Range stepCCRange { 0.0f, std::max(std::abs(range.getStart()), std::abs(range.getEnd())) }; + setValueFromOpcode(opcode, modifier.step, stepCCRange); + } + break; + case kOpcodeSmoothCcN: + setValueFromOpcode(opcode, modifier.smooth, Default::smoothCCRange); + break; + default: + assert(false); + break; + } + } + + return true; +} + bool sfz::Region::isSwitchedOn() const noexcept { return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all(); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 17180bca..b532f712 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -213,6 +213,16 @@ struct Region { * @return false */ bool parseOpcode(const Opcode& opcode); + /** + * @brief Process a generic CC opcode, and fill the modulation parameters. + * + * @param opcode + * @param range + * @param ccMap + * @return true if the opcode was properly read and stored. + * @return false + */ + bool processGenericCc(const Opcode& opcode, Range range, CCMap *ccMap); void offsetAllKeys(int offset) noexcept; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 7eea837a..acf037db 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -262,7 +262,7 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept // Amplitude envelope applyGain(baseGain, modulationSpan); for (const auto& mod : region->amplitudeCC) { - linearModifier(resources, *tempSpan, mod); + linearModifier(resources, *tempSpan, mod, normalizePercents); applyGain(*tempSpan, modulationSpan); } @@ -339,7 +339,7 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); for (const auto& mod : region->panCC) { - linearModifier(resources, *tempSpan, mod); + linearModifier(resources, *tempSpan, mod, normalizePercents); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -360,7 +360,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); for (const auto& mod : region->panCC) { - linearModifier(resources, *tempSpan, mod); + linearModifier(resources, *tempSpan, mod, normalizePercents); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -368,14 +368,14 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // Apply the width/position process fill(*modulationSpan, region->width); for (const auto& mod : region->widthCC) { - linearModifier(resources, *tempSpan, mod); + linearModifier(resources, *tempSpan, mod, normalizePercents); add(*tempSpan, *modulationSpan); } width(*modulationSpan, leftBuffer, rightBuffer); fill(*modulationSpan, region->position); for (const auto& mod : region->positionCC) { - linearModifier(resources, *tempSpan, mod); + linearModifier(resources, *tempSpan, mod, normalizePercents); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); From fe6db9ac8d17a99eb0113967a36f0451e3572585 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 5 May 2020 17:20:53 +0200 Subject: [PATCH 2/2] Update tests for verbatim values, and stepcc auto range --- tests/FilesT.cpp | 2 +- tests/RegionT.cpp | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 2f22dea5..c9eb7dcf 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -351,7 +351,7 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 ); REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty()); REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10)); - REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 0.34f); + REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 34.0f); } TEST_CASE("[Files] Specific bug: relative path with backslashes") diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index bce542c1..c2efe488 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -501,7 +501,7 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.panCC.empty()); region.parseOpcode({ "pan_oncc45", "4.2" }); REQUIRE(region.panCC.contains(45)); - REQUIRE(region.panCC[45].value == 0.042_a); + REQUIRE(region.panCC[45].value == 4.2_a); region.parseOpcode({ "pan_curvecc17", "18" }); REQUIRE(region.panCC[17].curve == 18); region.parseOpcode({ "pan_curvecc17", "15482" }); @@ -515,9 +515,9 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "pan_smoothcc14", "-2" }); REQUIRE(region.panCC[14].smooth == 0); region.parseOpcode({ "pan_stepcc120", "24" }); - REQUIRE(region.panCC[120].step == 0.24_a); + REQUIRE(region.panCC[120].step == 24.0_a); region.parseOpcode({ "pan_stepcc120", "15482" }); - REQUIRE(region.panCC[120].step == 2.0_a); + REQUIRE(region.panCC[120].step == 200.0_a); region.parseOpcode({ "pan_stepcc120", "-2" }); REQUIRE(region.panCC[120].step == 0.0f); } @@ -540,7 +540,7 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.widthCC.empty()); region.parseOpcode({ "width_oncc45", "4.2" }); REQUIRE(region.widthCC.contains(45)); - REQUIRE(region.widthCC[45].value == 0.042_a); + REQUIRE(region.widthCC[45].value == 4.2_a); region.parseOpcode({ "width_curvecc17", "18" }); REQUIRE(region.widthCC[17].curve == 18); region.parseOpcode({ "width_curvecc17", "15482" }); @@ -554,9 +554,9 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "width_smoothcc14", "-2" }); REQUIRE(region.widthCC[14].smooth == 0); region.parseOpcode({ "width_stepcc120", "24" }); - REQUIRE(region.widthCC[120].step == 0.24_a); + REQUIRE(region.widthCC[120].step == 24.0_a); region.parseOpcode({ "width_stepcc120", "15482" }); - REQUIRE(region.widthCC[120].step == 2.0_a); + REQUIRE(region.widthCC[120].step == 200.0_a); region.parseOpcode({ "width_stepcc120", "-20" }); REQUIRE(region.widthCC[120].step == 0.0f); } @@ -579,7 +579,7 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.positionCC.empty()); region.parseOpcode({ "position_oncc45", "4.2" }); REQUIRE(region.positionCC.contains(45)); - REQUIRE(region.positionCC[45].value == 0.042_a); + REQUIRE(region.positionCC[45].value == 4.2_a); region.parseOpcode({ "position_curvecc17", "18" }); REQUIRE(region.positionCC[17].curve == 18); region.parseOpcode({ "position_curvecc17", "15482" }); @@ -593,9 +593,9 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "position_smoothcc14", "-2" }); REQUIRE(region.positionCC[14].smooth == 0); region.parseOpcode({ "position_stepcc120", "24" }); - REQUIRE(region.positionCC[120].step == 0.24_a); + REQUIRE(region.positionCC[120].step == 24.0_a); region.parseOpcode({ "position_stepcc120", "15482" }); - REQUIRE(region.positionCC[120].step == 2.0_a); + REQUIRE(region.positionCC[120].step == 200.0_a); region.parseOpcode({ "position_stepcc120", "-2" }); REQUIRE(region.positionCC[120].step == 0.0f); } @@ -1533,10 +1533,10 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.amplitudeCC.empty()); region.parseOpcode({ "amplitude_cc1", "40" }); REQUIRE(region.amplitudeCC.contains(1)); - REQUIRE(region.amplitudeCC[1].value == 0.40_a); + REQUIRE(region.amplitudeCC[1].value == 40.0_a); region.parseOpcode({ "amplitude_oncc2", "30" }); REQUIRE(region.amplitudeCC.contains(2)); - REQUIRE(region.amplitudeCC[2].value == 0.30_a); + REQUIRE(region.amplitudeCC[2].value == 30.0_a); region.parseOpcode({ "amplitude_curvecc17", "18" }); REQUIRE(region.amplitudeCC[17].curve == 18); region.parseOpcode({ "amplitude_curvecc17", "15482" }); @@ -1550,9 +1550,9 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "amplitude_smoothcc14", "-2" }); REQUIRE(region.amplitudeCC[14].smooth == 0); region.parseOpcode({ "amplitude_stepcc120", "24" }); - REQUIRE(region.amplitudeCC[120].step == 0.24_a); + REQUIRE(region.amplitudeCC[120].step == 24.0_a); region.parseOpcode({ "amplitude_stepcc120", "15482" }); - REQUIRE(region.amplitudeCC[120].step == 1.0_a); + REQUIRE(region.amplitudeCC[120].step == 100.0_a); region.parseOpcode({ "amplitude_stepcc120", "-2" }); REQUIRE(region.amplitudeCC[120].step == 0.0f); } @@ -1584,7 +1584,7 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "volume_stepcc120", "24" }); REQUIRE(region.volumeCC[120].step == 24.0f); region.parseOpcode({ "volume_stepcc120", "15482" }); - REQUIRE(region.volumeCC[120].step == 48.0f); + REQUIRE(region.volumeCC[120].step == 144.0f); region.parseOpcode({ "volume_stepcc120", "-2" }); REQUIRE(region.volumeCC[120].step == 0.0f); }