Killed voices pre-render their next block

This data is ramped out before the main rendering loop, to smooth the voice killings
This commit is contained in:
Paul Ferrand 2020-05-07 20:49:42 +02:00
parent 691c25362b
commit dbb75398cc

View file

@ -137,6 +137,7 @@ void sfz::Synth::clear()
effectBuses[0]->setGainToMain(1.0); effectBuses[0]->setGainToMain(1.0);
effectBuses[0]->setSamplesPerBlock(samplesPerBlock); effectBuses[0]->setSamplesPerBlock(samplesPerBlock);
effectBuses[0]->setSampleRate(sampleRate); effectBuses[0]->setSampleRate(sampleRate);
effectBuses[0]->clearInputs(samplesPerBlock);
resources.clear(); resources.clear();
numGroups = 0; numGroups = 0;
numMasters = 0; numMasters = 0;
@ -248,6 +249,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
bus.reset(new EffectBus); bus.reset(new EffectBus);
bus->setSampleRate(sampleRate); bus->setSampleRate(sampleRate);
bus->setSamplesPerBlock(samplesPerBlock); bus->setSamplesPerBlock(samplesPerBlock);
bus->clearInputs(samplesPerBlock);
} }
return *bus; return *bus;
}; };
@ -508,6 +510,19 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept
const auto envThreshold = sumEnvelope / static_cast<float>(voiceViewArray.size()) * 0.5f; const auto envThreshold = sumEnvelope / static_cast<float>(voiceViewArray.size()) * 0.5f;
const auto ageThreshold = voiceViewArray.front()->getAge() * 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(); Voice* returnedVoice = voiceViewArray.front();
unsigned idx = 0; unsigned idx = 0;
while (idx < voiceViewArray.size()) { while (idx < voiceViewArray.size()) {
@ -519,12 +534,12 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept
unsigned killIdx = 1; unsigned killIdx = 1;
while (killIdx < voiceViewArray.size() while (killIdx < voiceViewArray.size()
&& sisterVoices(returnedVoice, voiceViewArray[killIdx])) { && sisterVoices(returnedVoice, voiceViewArray[killIdx])) {
voiceViewArray[killIdx]->reset(); killVoice(voiceViewArray[killIdx]);
killIdx++; killIdx++;
} }
// std::cout << "Went too far, picking the oldest voice and killing " // std::cout << "Went too far, picking the oldest voice and killing "
// << killIdx << " voices" << '\n'; // << killIdx << " voices" << '\n';
returnedVoice->reset(); killVoice(returnedVoice);
break; break;
} }
@ -537,12 +552,12 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept
if (maxEnvelope < envThreshold) { if (maxEnvelope < envThreshold) {
returnedVoice = ref; returnedVoice = ref;
// std::cout << "Killing " << idx - refIdx << " voices" << '\n'; // std::cout << "Killing " << idx - refIdx << " voices" << '\n';
for (unsigned j = refIdx; j < idx; j++) for (unsigned j = refIdx; j < idx; j++) {
voiceViewArray[j]->reset(); killVoice(voiceViewArray[j]);
}
break; break;
} }
} }
assert(returnedVoice->isFree()); assert(returnedVoice->isFree());
return returnedVoice; return returnedVoice;
} }
@ -615,19 +630,12 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
size_t numFrames = buffer.getNumFrames(); size_t numFrames = buffer.getNumFrames();
auto tempSpan = resources.bufferPool.getStereoBuffer(numFrames); auto tempSpan = resources.bufferPool.getStereoBuffer(numFrames);
auto tempMixSpan = 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... "); DBG("[sfizz] Could not get a temporary buffer; exiting callback... ");
return; 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 }; int numActiveVoices { 0 };
{ // Main render block { // Main render block
ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration };
@ -635,6 +643,14 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
tempMixSpan->fill(0.0f); tempMixSpan->fill(0.0f);
resources.filePool.cleanupPromises(); resources.filePool.cleanupPromises();
// Ramp out whatever is in the buffer at this point; should only be killed voice data
linearRamp<float>(*rampSpan, 1.0f, -1.0f / static_cast<float>(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) { for (auto& voice : voices) {
if (voice->isFree()) if (voice->isFree())
continue; continue;
@ -694,6 +710,14 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
// Reset the dispatch counter // Reset the dispatch counter
dispatchDuration = Duration(0); 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(0)));
ASSERT(!hasNanInf(buffer.getConstSpan(1))); ASSERT(!hasNanInf(buffer.getConstSpan(1)));
ASSERT(isValidAudio(buffer.getConstSpan(0))); ASSERT(isValidAudio(buffer.getConstSpan(0)));