From 9d27c5aca7a9875e64cb244ae7c2c53045c50aa5 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 14 Jun 2020 18:10:13 +0200 Subject: [PATCH] The synth now resets the voicesThe voice does not reset itself anymore, but rather enters a zombie state.This intermediate state will be used by the synth to update the polyphony counters if needed. --- src/sfizz/MidiState.cpp | 6 ++++++ src/sfizz/MidiState.h | 12 ++++++++++++ src/sfizz/Synth.cpp | 10 ++++++++++ src/sfizz/Voice.cpp | 19 ++++++++----------- src/sfizz/Voice.h | 8 +++++++- tests/SynthT.cpp | 3 +++ 6 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 6f463020..3cb0a9ff 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -40,6 +40,12 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe } +void sfz::MidiState::allNotesOff(int delay) noexcept +{ + for (int note = 0; note < 128; note++) + noteOffEvent(delay, note, 0.0f); +} + void sfz::MidiState::setSampleRate(float sampleRate) noexcept { this->sampleRate = sampleRate; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 021ff415..f4875510 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -25,6 +25,7 @@ public: /** * @brief Update the state after a note on event * + * @param delay * @param noteNumber * @param velocity */ @@ -33,11 +34,22 @@ public: /** * @brief Update the state after a note off event * + * @param delay * @param noteNumber * @param velocity */ void noteOffEvent(int delay, int noteNumber, float velocity) noexcept; + /** + * @brief Set all notes off + * + * @param delay + */ + void allNotesOff(int delay) noexcept; + + /** + * @brief Get the number of active notes + */ int getActiveNotes() const noexcept { return activeNotes; } /** diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 3c35b6ee..5992367b 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -708,6 +708,9 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept callbackBreakdown.amplitude += voice->getLastAmplitudeDuration(); callbackBreakdown.filters += voice->getLastFilterDuration(); callbackBreakdown.panning += voice->getLastPanningDuration(); + + if (voice->toBeCleanedUp()) + voice->reset(); } } @@ -892,6 +895,13 @@ void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept return; } + if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) { + for (auto& voice : voices) + voice->reset(); + resources.midiState.allNotesOff(delay); + return; + } + for (auto& voice : voices) voice->registerCC(delay, ccNumber, normValue); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 768be01b..e393f282 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -77,7 +77,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, } else { currentPromise = resources.filePool.getFilePromise(region->sampleId); if (currentPromise == nullptr) { - reset(); + switchState(State::cleanMeUp); return; } speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); @@ -139,7 +139,7 @@ void sfz::Voice::release(int delay, bool fastRelease) noexcept return; if (egEnvelope.getRemainingDelay() > delay) { - reset(); + switchState(State::cleanMeUp); } else { egEnvelope.startRelease(delay, fastRelease); } @@ -173,21 +173,16 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept if (region == nullptr) return; - if (state == State::idle) + if (state != State::playing) return; - if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) { - reset(); - return; - } - if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold) release(delay); } void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept { - if (state == State::idle) + if (state != State::playing) return; UNUSED(delay); UNUSED(pitch); @@ -229,6 +224,8 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept ASSERT(static_cast(buffer.getNumFrames()) <= samplesPerBlock); buffer.fill(0.0f); + ASSERT(region != nullptr); + const auto delay = min(static_cast(initialDelay), buffer.getNumFrames()); auto delayed_buffer = buffer.subspan(delay); initialDelay -= static_cast(delay); @@ -252,7 +249,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept } if (!egEnvelope.isSmoothing()) - reset(); + switchState(State::cleanMeUp); updateChannelPowers(buffer); @@ -683,7 +680,7 @@ float sfz::Voice::getAverageEnvelope() const noexcept bool sfz::Voice::releasedOrFree() const noexcept { - return state == State::idle || egEnvelope.isReleased(); + return state != State::playing || egEnvelope.isReleased(); } uint32_t sfz::Voice::getSourcePosition() const noexcept diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 0dd5557a..cb9494dd 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -53,7 +53,8 @@ public: enum class State { idle, - playing + playing, + cleanMeUp, }; class StateListener { @@ -61,6 +62,11 @@ public: virtual void onVoiceStateChanged(NumericId /*id*/, State /*state*/) {} }; + /** + * @brief Return true if the voice is to be cleaned up (zombie state) + */ + bool toBeCleanedUp() const { return state == State::cleanMeUp; } + /** * @brief Sets the listener which is called when the voice state changes. */ diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 08112bef..a2d7fd6c 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -152,15 +152,18 @@ TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kil { sfz::Synth synth; synth.setSamplesPerBlock(1024); + sfz::AudioBuffer buffer { 2, 1024 }; synth.setNumVoices(1); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz"); synth.noteOn(0, 60, 63); REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.noteOff(100, 60, 63); + synth.renderBlock(buffer); REQUIRE( synth.getVoiceView(0)->isFree() ); synth.noteOn(200, 60, 63); REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.noteOff(1000, 60, 63); + synth.renderBlock(buffer); REQUIRE( !synth.getVoiceView(0)->isFree() ); }