diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 94aba5bb..ce3edd70 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -202,6 +202,8 @@ struct Voice::Impl State state_ { State::idle }; bool noteIsOff_ { false }; + enum class SostenutoState { Up, Sustaining, PreviouslyDown }; + SostenutoState sostenutoState_ { SostenutoState::Up }; TriggerEvent triggerEvent_; absl::optional triggerDelay_; @@ -459,6 +461,13 @@ bool Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe impl.resources_.modMatrix.initVoice(impl.id_, region->getId(), impl.initialDelay_); impl.saveModulationTargets(region); + if (region->checkSostenuto) { + const bool sostenutoPressed = + impl.resources_.midiState.getCCValue(region->sostenutoCC) >= region->sostenutoThreshold; + impl.sostenutoState_ = + sostenutoPressed ? Impl::SostenutoState::PreviouslyDown : Impl::SostenutoState::Up; + } + return true; } @@ -546,6 +555,10 @@ void Voice::registerNoteOff(int delay, int noteNumber, float velocity) noexcept if (!impl.region_->checkSustain || impl.resources_.midiState.getCCValue(impl.region_->sustainCC) < impl.region_->sustainThreshold) release(delay); + + if (!impl.region_->checkSostenuto + || impl.sostenutoState_ != Impl::SostenutoState::Sustaining) + release(delay); } } @@ -564,6 +577,19 @@ void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept && ccNumber == impl.region_->sustainCC && ccValue < impl.region_->sustainThreshold) release(delay); + + if (impl.region_->checkSostenuto + && ccNumber == impl.region_->sostenutoCC) { + if (ccValue < impl.region_->sostenutoThreshold) { + impl.sostenutoState_ = Impl::SostenutoState::Up; + } else { + if (impl.sostenutoState_ == Impl::SostenutoState::Up) + impl.sostenutoState_ = Impl::SostenutoState::Sustaining; + } + + if (impl.sostenutoState_ != Impl::SostenutoState::Sustaining && impl.noteIsOff_) + release(delay); + } } void Voice::registerPitchWheel(int delay, float pitch) noexcept @@ -1512,6 +1538,7 @@ void Voice::reset() noexcept impl.count_ = 1; impl.floatPositionOffset_ = 0.0f; impl.noteIsOff_ = false; + impl.sostenutoState_ = Impl::SostenutoState::Up; impl.resetLoopInformation(); diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 3bf2cc64..8175b740 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -818,6 +818,35 @@ TEST_CASE("[Synth] Release (Different sustain CC)") REQUIRE( synth.getNumActiveVoices() == 2 ); } +TEST_CASE("[Synth] Release key (Different sostenuto CC)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( + sostenuto_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 sostenuto CC)") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( + sostenuto_cc=54 + key=62 sample=*silence + key=62 sample=*sine trigger=release + )"); + synth.noteOn(0, 62, 85); + synth.cc(0, 54, 127); + synth.noteOff(0, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); + synth.cc(0, 54, 0); + REQUIRE( synth.getNumActiveVoices() == 2 ); +} + TEST_CASE("[Synth] Sustain threshold default") { sfz::Synth synth; @@ -852,6 +881,55 @@ TEST_CASE("[Synth] Sustain threshold") REQUIRE( synth.getNumActiveVoices() == 5 ); } +TEST_CASE("[Synth] Sostenuto") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sostenuto.sfz", R"( + sample=*sine + )"); + + SECTION("1 note") + { + synth.noteOn(0, 62, 85); + synth.cc(1, 66, 1); + synth.noteOff(2, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); + synth.cc(3, 66, 0); + REQUIRE( synth.getNumActiveVoices() == 0 ); + } + + SECTION("2 notes") + { + synth.noteOn(0, 62, 85); + synth.noteOn(0, 64, 85); + synth.cc(1, 66, 1); + synth.noteOff(2, 62, 85); + synth.noteOff(2, 64, 85); + REQUIRE( synth.getNumActiveVoices() == 2 ); + synth.cc(3, 66, 0); + REQUIRE( synth.getNumActiveVoices() == 0 ); + } + + SECTION("1 note but after the pedal is depressed") + { + synth.cc(0, 66, 1); + synth.noteOn(1, 62, 85); + synth.noteOff(2, 62, 85); + REQUIRE( synth.getNumActiveVoices() == 0 ); + } + + SECTION("2 notes, 1 after the pedal is depressed") + { + synth.noteOn(0, 62, 85); + synth.cc(1, 66, 1); + synth.noteOn(2, 64, 85); + synth.noteOff(3, 62, 85); + synth.noteOff(3, 64, 85); + REQUIRE( synth.getNumActiveVoices() == 1 ); + REQUIRE( getActiveVoices(synth)[0]->getTriggerEvent().number == 62 ); + } +} + TEST_CASE("[Synth] Release (Multiple notes, release_key ignores the pedal)") { sfz::Synth synth;