From c4e8675c049cb4e3d247e97b725ccc9c26f6f8b5 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Wed, 1 Jul 2020 15:41:43 +0200 Subject: [PATCH 1/2] Add support for "sustain_cc=" and "trigger=release_key" --- src/sfizz/Config.h | 1 - src/sfizz/Defaults.h | 1 + src/sfizz/Region.cpp | 20 ++++++++++++--- src/sfizz/Region.h | 2 ++ src/sfizz/Voice.cpp | 4 +-- tests/RegionT.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 918264caf..c6e2e8431 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -44,7 +44,6 @@ namespace config { constexpr uint8_t gainSmoothing { 5 }; constexpr unsigned powerTableSizeExponent { 11 }; constexpr int maxFilePromises { maxVoices }; - constexpr int sustainCC { 64 }; constexpr int allSoundOffCC { 120 }; constexpr int resetCC { 121 }; constexpr int allNotesOffCC { 123 }; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index f5edae7f9..1bcc7771a 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -215,6 +215,7 @@ namespace Default constexpr float vel2release { 0.0f }; constexpr float start { 0.0 }; constexpr float sustain { 100.0 }; + constexpr uint16_t sustainCC { 64 }; constexpr float vel2sustain { 0.0 }; constexpr int depth { 0 }; constexpr Range egTimeRange { 0.0, 100.0 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 287fc7adb..12f2ae339 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -273,6 +273,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; + case hash("sustain_cc"): + setValueFromOpcode(opcode, sustainCC, Default::ccNumberRange); + break; case hash("sustain_sw"): checkSustain = readBooleanFromOpcode(opcode).value_or(Default::checkSustain); break; @@ -984,7 +987,13 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu const bool velOk = velocityRange.containsWithEnd(velocity); const bool randOk = randRange.contains(randValue); - const bool releaseTrigger = (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key); + bool releaseTrigger = (trigger == SfzTrigger::release_key); + if (trigger == SfzTrigger::release) { + if (midiState.getCCValue(sustainCC) < config::halfCCThreshold) + releaseTrigger = true; + else + noteIsOff = true; + } return keyOk && velOk && randOk && releaseTrigger; } @@ -999,13 +1008,18 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept if (!isSwitchedOn()) return false; + if (sustainCC == ccNumber && ccValue < config::halfCCThreshold && noteIsOff) { + noteIsOff = false; + return true; + } + if (!triggerOnCC) return false; if (ccTriggers.contains(ccNumber) && ccTriggers[ccNumber].containsWithEnd(ccValue)) return true; - else - return false; + + return false; } void sfz::Region::registerPitchWheel(float pitch) noexcept diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index de80f53e5..3e5c4dade 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -301,6 +301,7 @@ struct Region { SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel bool checkSustain { Default::checkSustain }; // sustain_sw bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw + uint16_t sustainCC { Default::sustainCC }; // sustain_cc // Region logic: internal conditions Range aftertouchRange { Default::aftertouchRange }; // hichanaft and lochanaft @@ -373,6 +374,7 @@ struct Region { bool pitchSwitched { true }; bool bpmSwitched { true }; bool aftertouchSwitched { true }; + bool noteIsOff { false }; std::bitset ccSwitched; bool triggerOnCC { false }; absl::string_view defaultPath { "" }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 66a46d994..1b10fb3bd 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -198,7 +198,7 @@ void sfz::Voice::registerNoteOff(int delay, int noteNumber, float velocity) noex if (region->loopMode == SfzLoopMode::one_shot) return; - if (!region->checkSustain || resources.midiState.getCCValue(config::sustainCC) < config::halfCCThreshold) + if (!region->checkSustain || resources.midiState.getCCValue(region->sustainCC) < config::halfCCThreshold) release(delay); } } @@ -212,7 +212,7 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept if (state != State::playing) return; - if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold) + if (region->checkSustain && noteIsOff && ccNumber == region->sustainCC && ccValue < config::halfCCThreshold) release(delay); } diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index e4a9c09d2..5d508ca70 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1171,6 +1171,15 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.checkSostenuto); } + SECTION("sustain_cc") + { + REQUIRE(region.sustainCC == 64); + region.parseOpcode({ "sustain_cc", "63" }); + REQUIRE(region.sustainCC == 63); + region.parseOpcode({ "sustain_cc", "-1" }); + REQUIRE(region.sustainCC == 0); + } + SECTION("Filter stacking and cutoffs") { REQUIRE(region.filters.empty()); @@ -1685,3 +1694,52 @@ TEST_CASE("[Region] Non-conforming floating point values in integer opcodes") region.parseOpcode({ "pitch_keytrack", "-2.1" }); REQUIRE(region.pitchKeytrack == -2); } + + +TEST_CASE("[Region] Release and release key") +{ + MidiState midiState; + Region region { 0, midiState }; + region.parseOpcode({ "key", "63" }); + region.parseOpcode({ "sample", "*sine" }); + SECTION("Release key without sustain") + { + region.parseOpcode({ "trigger", "release_key" }); + midiState.ccEvent(0, 64, 0.0f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + } + SECTION("Release key with sustain") + { + region.parseOpcode({ "trigger", "release_key" }); + midiState.ccEvent(0, 64, 1.0f); + REQUIRE( !region.registerCC(64, 1.0f) ); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + midiState.ccEvent(0, 64, 0.0f); + REQUIRE( !region.registerCC(64, 0.0f) ); + } + SECTION("Release without sustain") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 0.0f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); + } + SECTION("Release with sustain") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 1.0f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) ); + } + SECTION("Release with sustain") + { + region.parseOpcode({ "trigger", "release" }); + midiState.ccEvent(0, 64, 1.0f); + REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); + REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) ); + midiState.ccEvent(0, 64, 0.0f); + REQUIRE( region.registerCC(64, 0.0f) ); + } +} From 6d56aaaa6c18bfc5e6feac0a2a875e1581deb627 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Wed, 1 Jul 2020 16:21:30 +0200 Subject: [PATCH 2/2] Correctly dispatch the CC to the region if it's a sustain ...with some tests --- src/sfizz/Region.h | 2 +- src/sfizz/Synth.cpp | 13 +++++++++-- tests/SynthT.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 3e5c4dade..09626703a 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -366,6 +366,7 @@ struct Region { // Modifiers ModifierArray> modifiers; + bool triggerOnCC { false }; // whether the region triggers on CC events or note events private: const MidiState& midiState; bool keySwitched { true }; @@ -376,7 +377,6 @@ struct Region { bool aftertouchSwitched { true }; bool noteIsOff { false }; std::bitset ccSwitched; - bool triggerOnCC { false }; absl::string_view defaultPath { "" }; int sequenceCounter { 0 }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 766c1786d..65f61473d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -446,7 +446,9 @@ void sfz::Synth::finalizeSfzLoad() } for (int cc = 0; cc < config::numCCs; cc++) { - if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc)) + if (region->ccTriggers.contains(cc) + || region->ccConditions.contains(cc) + || (cc == region->sustainCC && region->trigger == SfzTrigger::release)) ccActivationLists[cc].push_back(region); } @@ -915,7 +917,14 @@ void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept if (voice == nullptr) continue; - voice->startVoice(region, delay, ccNumber, normValue, Voice::TriggerType::CC); + if (!region->triggerOnCC) { + // This is a sustain trigger + const auto replacedVelocity = resources.midiState.getNoteVelocity(region->pitchKeycenter); + voice->startVoice(region, delay, region->pitchKeycenter, replacedVelocity, Voice::TriggerType::NoteOff); + } else { + voice->startVoice(region, delay, ccNumber, normValue, Voice::TriggerType::CC); + } + ring.addVoiceToRing(voice); } } diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index ecc002483..c0d761c93 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -609,3 +609,57 @@ TEST_CASE("[Synth] Sisters and off-by") REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 1 ); } + +TEST_CASE("[Synth] Release key") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + key=62 sample=*sine trigger=release_key + )"); + synth.noteOn(0, 62, 85); + synth.cc(0, 64, 127); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); +} + +TEST_CASE("[Synth] Release") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + key=62 sample=*sine trigger=release + )"); + synth.noteOn(0, 62, 85); + synth.cc(0, 64, 127); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 0 ); + synth.cc(0, 64, 0); + REQUIRE( synth.getNumActiveVoices() == 1 ); +} + +TEST_CASE("[Synth] Release key (Different sustain CC)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + sustain_cc=54 + key=62 sample=*sine trigger=release_key + )"); + synth.noteOn(0, 62, 85); + synth.cc(0, 54, 127); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); +} + +TEST_CASE("[Synth] Release (Different sustain CC)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path(), R"( + sustain_cc=54 + key=62 sample=*sine trigger=release + )"); + synth.noteOn(0, 62, 85); + synth.cc(0, 54, 127); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 0 ); + synth.cc(0, 54, 0); + REQUIRE( synth.getNumActiveVoices() == 1 ); +}