From dbb75398cc389809e8b027b603b7de2feb984dd5 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Thu, 7 May 2020 20:49:42 +0200 Subject: [PATCH] Killed voices pre-render their next block This data is ramped out before the main rendering loop, to smooth the voice killings --- src/sfizz/Synth.cpp | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index acfc371d..53383778 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -137,6 +137,7 @@ void sfz::Synth::clear() effectBuses[0]->setGainToMain(1.0); effectBuses[0]->setSamplesPerBlock(samplesPerBlock); effectBuses[0]->setSampleRate(sampleRate); + effectBuses[0]->clearInputs(samplesPerBlock); resources.clear(); numGroups = 0; numMasters = 0; @@ -248,6 +249,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& rawMembers) bus.reset(new EffectBus); bus->setSampleRate(sampleRate); bus->setSamplesPerBlock(samplesPerBlock); + bus->clearInputs(samplesPerBlock); } return *bus; }; @@ -508,6 +510,19 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept const auto envThreshold = sumEnvelope / static_cast(voiceViewArray.size()) * 0.5f; const auto ageThreshold = voiceViewArray.front()->getAge() * 0.5f; + auto tempSpan = resources.bufferPool.getStereoBuffer(samplesPerBlock); + const auto killVoice = [&] (Voice* v) { + const auto region = v->getRegion(); // voice can die after rendering, so save this + v->renderBlock(*tempSpan); + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (auto& bus = effectBuses[i]) { + float addGain = region->getGainToEffectBus(i); + bus->addToInputs(*tempSpan, addGain, samplesPerBlock); + } + } + v->reset(); + }; + Voice* returnedVoice = voiceViewArray.front(); unsigned idx = 0; while (idx < voiceViewArray.size()) { @@ -519,12 +534,12 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept unsigned killIdx = 1; while (killIdx < voiceViewArray.size() && sisterVoices(returnedVoice, voiceViewArray[killIdx])) { - voiceViewArray[killIdx]->reset(); + killVoice(voiceViewArray[killIdx]); killIdx++; } // std::cout << "Went too far, picking the oldest voice and killing " // << killIdx << " voices" << '\n'; - returnedVoice->reset(); + killVoice(returnedVoice); break; } @@ -537,12 +552,12 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept if (maxEnvelope < envThreshold) { returnedVoice = ref; // std::cout << "Killing " << idx - refIdx << " voices" << '\n'; - for (unsigned j = refIdx; j < idx; j++) - voiceViewArray[j]->reset(); + for (unsigned j = refIdx; j < idx; j++) { + killVoice(voiceViewArray[j]); + } break; } } - assert(returnedVoice->isFree()); return returnedVoice; } @@ -615,19 +630,12 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept size_t numFrames = buffer.getNumFrames(); auto tempSpan = resources.bufferPool.getStereoBuffer(numFrames); auto tempMixSpan = resources.bufferPool.getStereoBuffer(numFrames); - if (!tempSpan || !tempMixSpan) { + auto rampSpan = resources.bufferPool.getBuffer(numFrames); + if (!tempSpan || !tempMixSpan || !rampSpan) { DBG("[sfizz] Could not get a temporary buffer; exiting callback... "); return; } - { // Prepare the effect inputs. They are mixes of per-region outputs. - ScopedTiming logger { callbackBreakdown.effects }; - for (auto& bus : effectBuses) { - if (bus) - bus->clearInputs(numFrames); - } - } - int numActiveVoices { 0 }; { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; @@ -635,6 +643,14 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept tempMixSpan->fill(0.0f); resources.filePool.cleanupPromises(); + // Ramp out whatever is in the buffer at this point; should only be killed voice data + linearRamp(*rampSpan, 1.0f, -1.0f / static_cast(numFrames)); + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (auto& bus = effectBuses[i]) { + bus->applyGain(rampSpan->data(), numFrames); + } + } + for (auto& voice : voices) { if (voice->isFree()) continue; @@ -694,6 +710,14 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept // Reset the dispatch counter dispatchDuration = Duration(0); + { // Clear for the next run + ScopedTiming logger { callbackBreakdown.effects }; + for (auto& bus : effectBuses) { + if (bus) + bus->clearInputs(numFrames); + } + } + ASSERT(!hasNanInf(buffer.getConstSpan(0))); ASSERT(!hasNanInf(buffer.getConstSpan(1))); ASSERT(isValidAudio(buffer.getConstSpan(0)));