From c39a50ee92ab85b410cdd7b6360e330d19c1909d Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 18 Jul 2020 11:37:29 +0200 Subject: [PATCH] Voice stealing tweaks --- src/sfizz/Synth.cpp | 23 +++++++++++++++-------- src/sfizz/Voice.cpp | 3 ++- src/sfizz/Voice.h | 27 +++++++++++++++------------ src/sfizz/VoiceStealing.cpp | 12 ++++++++---- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e5f091a8..dc2e0596 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -680,6 +680,9 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept void sfz::Synth::renderVoiceToOutputs(Voice& voice, AudioSpan& tempSpan) noexcept { const Region* region = voice.getRegion(); + if (region == nullptr) + return; + voice.renderBlock(tempSpan); for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { if (auto& bus = effectBuses[i]) { @@ -936,8 +939,12 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc } render: + // For some reason we did not find a voice to use. + // This is a degraded case but we'll just drop the note on. + if (selectedVoice == nullptr) + continue; + // Kill voice if necessary, pre-rendering it into the output buffers - ASSERT(selectedVoice); if (!selectedVoice->isFree()) { auto tempSpan = resources.bufferPool.getStereoBuffer(samplesPerBlock); SisterVoiceRing::applyToRing(selectedVoice, [&] (Voice* v) { @@ -1295,22 +1302,22 @@ void sfz::Synth::resetVoices(int numVoices) voices.clear(); voices.reserve(numVoices); - for (int i = 0; i < numVoices; ++i) { - auto voice = absl::make_unique(i, resources); - voice->setStateListener(this); - voices.emplace_back(std::move(voice)); - } - voiceViewArray.clear(); voiceViewArray.reserve(numVoices); regionPolyphonyArray.clear(); regionPolyphonyArray.reserve(numVoices); + for (int i = 0; i < numVoices; ++i) { + auto voice = absl::make_unique(i, resources); + voice->setStateListener(this); + voiceViewArray.push_back(voice.get()); + voices.emplace_back(std::move(voice)); + } + for (auto& voice : voices) { voice->setSampleRate(this->sampleRate); voice->setSamplesPerBlock(this->samplesPerBlock); - voiceViewArray.push_back(voice.get()); } this->numVoices = numVoices; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 771900d1..910fed95 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -270,7 +270,8 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept ASSERT(static_cast(buffer.getNumFrames()) <= samplesPerBlock); buffer.fill(0.0f); - ASSERT(region != nullptr); + if (region == nullptr) + return; const auto delay = min(static_cast(initialDelay), buffer.getNumFrames()); auto delayed_buffer = buffer.subspan(delay); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 74fa080c..b00b2a18 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -494,28 +494,31 @@ private: inline bool sisterVoices(const Voice* lhs, const Voice* rhs) { - return lhs->getAge() == rhs->getAge() - && lhs->getTriggerNumber() == rhs->getTriggerNumber() - && lhs->getTriggerValue() == rhs->getTriggerValue() - && lhs->getTriggerType() == rhs->getTriggerType(); + if (lhs->getAge() != rhs->getAge()) + return false; + + if (lhs->getTriggerNumber() != rhs->getTriggerNumber()) + return false; + + if (lhs->getTriggerValue() != rhs->getTriggerValue()) + return false; + + if (lhs->getTriggerType() != rhs->getTriggerType()) + return false; + + return true; } inline bool voiceOrdering(const Voice* lhs, const Voice* rhs) { if (lhs->getAge() > rhs->getAge()) return true; - if (lhs->getAge() < rhs->getAge()) - return false; - if (lhs->getTriggerNumber() > rhs->getTriggerNumber()) - return true; if (lhs->getTriggerNumber() < rhs->getTriggerNumber()) - return false; - - if (lhs->getTriggerValue() > rhs->getTriggerValue()) return true; + if (lhs->getTriggerValue() < rhs->getTriggerValue()) - return false; + return true; if (lhs->getTriggerType() > rhs->getTriggerType()) return true; diff --git a/src/sfizz/VoiceStealing.cpp b/src/sfizz/VoiceStealing.cpp index b4eec3aa..9cb88bb8 100644 --- a/src/sfizz/VoiceStealing.cpp +++ b/src/sfizz/VoiceStealing.cpp @@ -8,7 +8,7 @@ sfz::VoiceStealing::VoiceStealing() sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept { // Start of the voice stealing algorithm - absl::c_sort(voices, voiceOrdering); + absl::c_stable_sort(voices, voiceOrdering); const auto sumEnvelope = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { return sum + v->getAverageEnvelope(); @@ -21,9 +21,8 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept // This is not perfect because pad-type voices will take a long time to output // their sound, but it's reasonable for sounds with a quick attack and longer // release. - const auto ageThreshold = voices.front()->getAge() * config::stealingAgeCoeff; - // This needs to be positive - ASSERT(ageThreshold >= 0); + const auto ageThreshold = + static_cast(voices.front()->getAge() * config::stealingAgeCoeff) + 1; Voice* returnedVoice = voices.front(); unsigned idx = 0; @@ -49,5 +48,10 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept do { idx++; } while (idx < voices.size() && sisterVoices(ref, voices[idx])); } + + // Guard for future changes: voices with age 0 just started; don't kill those. + if (returnedVoice->getAge() == 0) + return {}; + return returnedVoice; }