From 65ea4a27bfc5a4fb2166d9d67446df943f2fb408 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Wed, 24 Mar 2021 13:23:51 +0100 Subject: [PATCH] Working state --- src/sfizz/MidiState.cpp | 3 ++ src/sfizz/MidiState.h | 16 +++++++++ src/sfizz/Region.cpp | 78 ++++++++++++++++++++++++++++------------- src/sfizz/Region.h | 11 ++++-- src/sfizz/Synth.cpp | 18 +++++++--- src/sfizz/Voice.cpp | 6 ++-- tests/DirectRegionT.cpp | 6 ++++ tests/SynthT.cpp | 35 ++++++++++++++++++ 8 files changed, 139 insertions(+), 34 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 938fba1e..c8df355a 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -23,6 +23,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex noteOnTimes[noteNumber] = internalClock + static_cast(delay); lastNotePlayed = noteNumber; activeNotes++; + noteStates[noteNumber] = true; } } @@ -37,6 +38,7 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe noteOffTimes[noteNumber] = internalClock + static_cast(delay); if (activeNotes > 0) activeNotes--; + noteStates[noteNumber] = false; } } @@ -181,6 +183,7 @@ void sfz::MidiState::reset() noexcept activeNotes = 0; internalClock = 0; lastNotePlayed = 0; + noteStates.reset(); absl::c_fill(noteOnTimes, 0); absl::c_fill(noteOffTimes, 0); } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index f9766c81..bb42f09a 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -6,6 +6,7 @@ #pragma once #include +#include #include "CCMap.h" #include "Range.h" @@ -142,6 +143,15 @@ public: */ void flushEvents() noexcept; + /** + * @brief Check if a note is currently depressed + * + * @param noteNumber + * @return true + * @return false + */ + bool isNotePressed(int noteNumber) const noexcept { return noteStates[noteNumber]; } + /** * @brief Get the CC value for CC number * @@ -191,6 +201,12 @@ private: MidiNoteArray noteOffTimes { {} }; + /** + * @brief Store the note states + * + */ + std::bitset<128> noteStates; + /** * @brief Stores the velocity of the note ons for currently * depressed notes. diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index eedc64d2..733474f4 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1504,7 +1504,7 @@ bool sfz::Region::isSwitchedOn() const noexcept return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all(); } -void sfz::Region::delaySustainRelease(int noteNumber, float velocity) +void sfz::Region::delaySustainRelease(int noteNumber, float velocity) noexcept { if (delayedSustainReleases.size() == delayedSustainReleases.capacity()) return; @@ -1512,7 +1512,7 @@ void sfz::Region::delaySustainRelease(int noteNumber, float velocity) delayedSustainReleases.emplace_back(noteNumber, velocity); } -void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity) +void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity) noexcept { if (delayedSostenutoReleases.size() == delayedSostenutoReleases.capacity()) return; @@ -1520,13 +1520,37 @@ void sfz::Region::delaySostenutoRelease(int noteNumber, float velocity) delayedSostenutoReleases.emplace_back(noteNumber, velocity); } -void sfz::Region::removeFromSostenutoReleases(int noteNumber) +void sfz::Region::removeFromSostenutoReleases(int noteNumber) noexcept { swapAndPopFirst(delayedSostenutoReleases, [=](const std::pair& p) { return p.first == noteNumber; }); } +void sfz::Region::storeSostenutoNotes() noexcept +{ + ASSERT(delayedSostenutoReleases.empty()); + for (int note = keyRange.getStart(); note <= keyRange.getEnd(); ++note) { + if (midiState.isNotePressed(note)) + delaySostenutoRelease(note, midiState.getNoteVelocity(note)); + } +} + + +bool sfz::Region::isNoteSustained(int noteNumber) const noexcept +{ + return absl::c_find_if(delayedSustainReleases, [=](const std::pair& p) { + return p.first == noteNumber; + }) != delayedSustainReleases.end(); +} + +bool sfz::Region::isNoteSostenutoed(int noteNumber) const noexcept +{ + return absl::c_find_if(delayedSostenutoReleases, [=](const std::pair& p) { + return p.first == noteNumber; + }) != delayedSostenutoReleases.end(); +} + bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); @@ -1553,13 +1577,6 @@ bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue const bool attackTrigger = (trigger == Trigger::attack); const bool notFirstLegatoNote = (trigger == Trigger::legato && midiState.getActiveNotes() > 1); - if (trigger == Trigger::release && - keyOk && velOk - && checkSostenuto && midiState.getCCValue(sostenutoCC) < sostenutoThreshold) { - // This note on will possibly be "sostenutoed" - delaySostenutoRelease(noteNumber, velocity); - } - return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote); } @@ -1588,22 +1605,21 @@ bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValu return true; if (trigger == Trigger::release) { - if (checkSostenuto && midiState.getCCValue(sostenutoCC) < sostenutoThreshold) + const bool sostenutoed = isNoteSostenutoed(noteNumber); + + if (sostenutoed && !sostenutoPressed) { removeFromSostenutoReleases(noteNumber); + if (sustainPressed) + delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber)); + } - const bool shouldSustain = checkSustain && midiState.getCCValue(sustainCC) >= sustainThreshold; - const bool shouldSostenuto = - checkSostenuto && midiState.getCCValue(sostenutoCC) >= sostenutoThreshold - && absl::c_find_if(delayedSostenutoReleases, [=](const std::pair& p) { - return p.first == noteNumber; - }) != delayedSostenutoReleases.end(); - - if (!shouldSustain && !shouldSostenuto) - return true; - - // If we reach this part, we're storing the notes to delay their release on CC up - // This is handled by the Synth object - delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber)); + if (sustainPressed) { + if (!sostenutoPressed || !sostenutoed) + delaySustainRelease(noteNumber, midiState.getNoteVelocity(noteNumber)); + } else { + if (!sostenutoPressed || !sostenutoed) + return true; + } } return false; @@ -1613,6 +1629,20 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0f && ccValue <= 1.0f); + if (ccNumber == sustainCC) + sustainPressed = checkSustain && ccValue >= sustainThreshold; + + if (ccNumber == sostenutoCC) { + const bool newState = checkSostenuto && ccValue >= sostenutoThreshold; + if (!sostenutoPressed && newState) + storeSostenutoNotes(); + + if (!newState && sostenutoPressed) + delayedSostenutoReleases.clear(); + + sostenutoPressed = newState; + } + if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue)) ccSwitched.set(ccNumber, true); else diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 74df3291..aa089908 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -508,11 +508,16 @@ struct Region { RegionSet* parent { nullptr }; // Started notes + bool sustainPressed { false }; + bool sostenutoPressed { false }; std::vector> delayedSustainReleases; std::vector> delayedSostenutoReleases; - void delaySustainRelease(int noteNumber, float velocity); - void delaySostenutoRelease(int noteNumber, float velocity); - void removeFromSostenutoReleases(int noteNumber); + void delaySustainRelease(int noteNumber, float velocity) noexcept; + void delaySostenutoRelease(int noteNumber, float velocity) noexcept; + void storeSostenutoNotes() noexcept; + void removeFromSostenutoReleases(int noteNumber) noexcept; + bool isNoteSustained(int noteNumber) const noexcept; + bool isNoteSostenutoed(int noteNumber) const noexcept; const MidiState& midiState; bool keySwitched { true }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a63b4550..b0f6fc71 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -659,7 +659,8 @@ void Synth::Impl::finalizeSfzLoad() for (int cc = 0; cc < config::numCCs; cc++) { if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc) - || (cc == region->sustainCC && region->trigger == Trigger::release)) + || (cc == region->sustainCC && region->trigger == Trigger::release) + || (cc == region->sostenutoCC && region->trigger == Trigger::release)) ccActivationLists_[cc].push_back(region); } @@ -1163,6 +1164,7 @@ void Synth::Impl::startDelayedSustainReleases(Region* region, int delay, SisterV const TriggerEvent noteOffEvent { TriggerEventType::NoteOff, note.first, note.second }; startVoice(region, delay, noteOffEvent, ring); } + region->delayedSustainReleases.clear(); } @@ -1191,11 +1193,19 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept SisterVoiceRingBuilder ring; const TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value }; for (auto& region : ccActivationLists_[ccNumber]) { - if (ccNumber == region->sustainCC && value < region->sustainThreshold) + if (region->checkSustain && ccNumber == region->sustainCC && value < region->sustainThreshold) startDelayedSustainReleases(region, delay, ring); - if (ccNumber == region->sostenutoCC && value < region->sostenutoThreshold) - startDelayedSostenutoReleases(region, delay, ring); + if (region->checkSostenuto && ccNumber == region->sostenutoCC && value < region->sostenutoThreshold) { + if (region->sustainPressed) { + for (const auto& v: region->delayedSostenutoReleases) + region->delaySustainRelease(v.first, v.second); + + region->delayedSostenutoReleases.clear(); + } else { + startDelayedSostenutoReleases(region, delay, ring); + } + } if (region->registerCC(ccNumber, value)) startVoice(region, delay, triggerEvent, ring); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 47cfec83..92294130 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -599,12 +599,12 @@ void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept } const bool sustainPedalReleaseCondition = !impl.region_->checkSustain - || (impl.noteIsOff_ && (impl.sustainState_ != Impl::SustainState::Sustaining)); + || (impl.sustainState_ != Impl::SustainState::Sustaining); const bool sostenutoPedalReleaseCondition = !impl.region_->checkSostenuto - || (impl.noteIsOff_ && (impl.sostenutoState_ != Impl::SostenutoState::Sustaining)); + || (impl.sostenutoState_ != Impl::SostenutoState::Sustaining); - if (sostenutoPedalReleaseCondition && sustainPedalReleaseCondition) + if (impl.noteIsOff_ && sostenutoPedalReleaseCondition && sustainPedalReleaseCondition) release(delay); } diff --git a/tests/DirectRegionT.cpp b/tests/DirectRegionT.cpp index 5a40b804..8f5bf6a6 100644 --- a/tests/DirectRegionT.cpp +++ b/tests/DirectRegionT.cpp @@ -42,6 +42,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") { region.parseOpcode({ "trigger", "release_key" }); midiState.ccEvent(0, 64, 0.0f); + region.registerCC(64, 0.0f); REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -49,6 +50,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") { region.parseOpcode({ "trigger", "release_key" }); midiState.ccEvent(0, 64, 1.0f); + region.registerCC(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) ); @@ -58,6 +60,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") { region.parseOpcode({ "trigger", "release" }); midiState.ccEvent(0, 64, 0.0f); + region.registerCC(64, 0.0f); REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( region.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -66,6 +69,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") { region.parseOpcode({ "trigger", "release" }); midiState.ccEvent(0, 64, 1.0f); + region.registerCC(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( !region.registerNoteOff(63, 0.5f, 0.0f) ); @@ -80,6 +84,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") { region.parseOpcode({ "trigger", "release" }); midiState.ccEvent(0, 64, 1.0f); + region.registerCC(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 64, 0.6f); @@ -98,6 +103,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") { region.parseOpcode({ "trigger", "release" }); midiState.ccEvent(0, 64, 1.0f); + region.registerCC(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !region.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 66, 0.6f); diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index eea284f9..cb97a0f7 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -889,6 +889,41 @@ TEST_CASE("[Synth] Release (Different sostenuto CC)") } } +TEST_CASE("[Synth] Release (sustain + sostenuto)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( + key=62 sample=*silence + key=62 sample=*sine trigger=release + key=64 sample=*silence + key=64 sample=*sine trigger=release + )"); + SECTION("Sustain up first") + { + synth.noteOn(0, 62, 85); + synth.cc(1, 66, 127); + synth.cc(1, 64, 127); + synth.noteOff(2, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); + synth.cc(3, 64, 0); + REQUIRE( synth.getNumActiveVoices() == 1 ); + synth.cc(4, 66, 0); + REQUIRE( synth.getNumActiveVoices() == 2 ); + } + SECTION("Sostenuto up first") + { + synth.noteOn(0, 62, 85); + synth.cc(1, 66, 127); + synth.cc(1, 64, 127); + synth.noteOff(2, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); + synth.cc(3, 66, 0); + REQUIRE( synth.getNumActiveVoices() == 1 ); + synth.cc(4, 64, 0); + REQUIRE( synth.getNumActiveVoices() == 2 ); + } +} + TEST_CASE("[Synth] Sustain threshold default") { sfz::Synth synth;