diff --git a/src/sfizz/PolyphonyGroup.h b/src/sfizz/PolyphonyGroup.h index ed5d6159..74314da7 100644 --- a/src/sfizz/PolyphonyGroup.h +++ b/src/sfizz/PolyphonyGroup.h @@ -1,4 +1,11 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + #pragma once + #include "Region.h" #include "Voice.h" #include "SwapAndPop.h" diff --git a/src/sfizz/RegionSet.h b/src/sfizz/RegionSet.h index 3cb46196..5203442b 100644 --- a/src/sfizz/RegionSet.h +++ b/src/sfizz/RegionSet.h @@ -1,4 +1,11 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + #pragma once + #include "Region.h" #include "Voice.h" #include "SwapAndPop.h" @@ -53,6 +60,7 @@ public: RegionSet* getParent() const { return parent; } void setParent(RegionSet* parent) { this->parent = parent; } const std::vector& getActiveVoices() const { return voices; } + std::vector& getActiveVoices() { return voices; } const std::vector& getRegions() const { return regions; } const std::vector& getSubsets() const { return subsets; } private: diff --git a/src/sfizz/SisterVoiceRing.h b/src/sfizz/SisterVoiceRing.h index 2f8fac16..7bd4c0ef 100644 --- a/src/sfizz/SisterVoiceRing.h +++ b/src/sfizz/SisterVoiceRing.h @@ -3,6 +3,7 @@ // This code is part of the sfizz library and is licensed under a BSD 2-clause // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz +#pragma once #include "Voice.h" #include "absl/meta/type_traits.h" diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index f808200c..500e4edc 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -610,52 +610,11 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept auto freeVoice = absl::c_find_if(voices, [](const std::unique_ptr& voice) { return voice->isFree(); }); + if (freeVoice != voices.end()) return freeVoice->get(); - // Start of the voice stealing algorithm - absl::c_sort(voiceViewArray, voiceOrdering); - - const auto sumEnvelope = absl::c_accumulate(voiceViewArray, 0.0f, [](float sum, const Voice* v) { - return sum + v->getAverageEnvelope(); - }); - const auto envThreshold = sumEnvelope - / static_cast(voiceViewArray.size()) * config::stealingEnvelopeCoeff; - const auto ageThreshold = voiceViewArray.front()->getAge() * config::stealingAgeCoeff; - - Voice* returnedVoice = voiceViewArray.front(); - unsigned idx = 0; - while (idx < voiceViewArray.size()) { - const auto ref = voiceViewArray[idx]; - - if (ref->getAge() < ageThreshold) { - // Went too far, we'll kill the oldest note. - break; - } - - float maxEnvelope { 0.0f }; - SisterVoiceRing::applyToRing(ref, [&](Voice* v) { - maxEnvelope = max(maxEnvelope, v->getAverageEnvelope()); - }); - - if (maxEnvelope < envThreshold) { - returnedVoice = ref; - break; - } - - // Jump over the sister voices in the set - do { idx++; } - while (idx < voiceViewArray.size() && sisterVoices(ref, voiceViewArray[idx])); - } - - auto tempSpan = resources.bufferPool.getStereoBuffer(samplesPerBlock); - SisterVoiceRing::applyToRing(returnedVoice, [&] (Voice* v) { - renderVoiceToOutputs(*v, *tempSpan); - v->reset(); - }); - ASSERT(returnedVoice->isFree()); - - return returnedVoice; + return {}; } int sfz::Synth::getNumActiveVoices() const noexcept @@ -716,7 +675,6 @@ void sfz::Synth::renderVoiceToOutputs(Voice& voice, AudioSpan& tempSpan) bus->addToInputs(tempSpan, addGain, tempSpan.getNumFrames()); } } - } void sfz::Synth::renderBlock(AudioSpan buffer) noexcept @@ -889,9 +847,9 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc for (auto& region : noteActivationLists[noteNumber]) { if (region->registerNoteOn(noteNumber, velocity, randValue)) { unsigned notePolyphonyCounter { 0 }; - unsigned regionPolyphonyCounter { 0 }; Voice* selfMaskCandidate { nullptr }; Voice* selectedVoice { nullptr }; + regionPolyphonyArray.clear(); for (auto& voice : voices) { if (voice->isFree()) { @@ -900,8 +858,9 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc continue; } - if (voice->getRegion() == region) - regionPolyphonyCounter += 1; + if (voice->getRegion() == region) { + regionPolyphonyArray.push_back(voice.get()); + } if (region->notePolyphony) { if (voice->getTriggerNumber() == noteNumber && voice->getTriggerType() == Voice::TriggerType::NoteOn) { @@ -925,36 +884,58 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc noteOffDispatch(delay, voice->getTriggerNumber(), voice->getTriggerValue()); } - // FIXME: Do something for the polyphony limit - if (regionPolyphonyCounter >= region->polyphony) - continue; - - // FIXME: Do something for the polyphony limit - if (polyphonyGroups[region->group].getActiveVoices().size() - == polyphonyGroups[region->group].getPolyphonyLimit()) - continue; - - // FIXME: Do something for the polyphony limit auto parent = region->parent; - bool polyphonyReached { false }; + + // Polyphony reached on region + if (regionPolyphonyArray.size() >= region->polyphony) { + selectedVoice = stealer.steal(absl::MakeSpan(regionPolyphonyArray)); + goto render; + } + + // Polyphony reached on polyphony group + if (polyphonyGroups[region->group].getActiveVoices().size() + == polyphonyGroups[region->group].getPolyphonyLimit()) { + const auto activeVoices = absl::MakeSpan(polyphonyGroups[region->group].getActiveVoices()); + selectedVoice = stealer.steal(activeVoices); + goto render; + } + + // Polyphony reached some parent group/master/etc while (parent != nullptr) { if (parent->getActiveVoices().size() >= parent->getPolyphonyLimit()) { - polyphonyReached = true; - break; + const auto activeVoices = absl::MakeSpan(parent->getActiveVoices()); + selectedVoice = stealer.steal(activeVoices); + goto render; } - parent = parent->getParent(); } - if (polyphonyReached) - continue; + // Polyphony reached on note_polyphony if (region->notePolyphony && notePolyphonyCounter >= *region->notePolyphony) { - if (selfMaskCandidate != nullptr) - selfMaskCandidate->release(delay); - else // We're the lowest velocity guy here - continue; + if (selfMaskCandidate == nullptr) + continue; // We're the lowest velocity guy here + selectedVoice = selfMaskCandidate; + goto render; } + // Engine polyphony reached, we're stealing something + if (selectedVoice == nullptr) { + selectedVoice = stealer.steal(absl::MakeSpan(voiceViewArray)); + } + + render: + // 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) { + renderVoiceToOutputs(*v, *tempSpan); + v->reset(); + }); + } + + // Voice should be free now + ASSERT(selectedVoice->isFree()); selectedVoice->startVoice(region, delay, noteNumber, velocity, Voice::TriggerType::NoteOn); ring.addVoiceToRing(selectedVoice); RegionSet::registerVoiceInHierarchy(region, selectedVoice); @@ -1304,6 +1285,9 @@ void sfz::Synth::resetVoices(int numVoices) voiceViewArray.clear(); voiceViewArray.reserve(numVoices); + regionPolyphonyArray.clear(); + regionPolyphonyArray.reserve(numVoices); + for (auto& voice : voices) { voice->setSampleRate(this->sampleRate); voice->setSamplesPerBlock(this->samplesPerBlock); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 3c08e954..bcd79542 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -16,6 +16,7 @@ #include "MidiState.h" #include "AudioSpan.h" #include "parser/Parser.h" +#include "VoiceStealing.h" #include "absl/types/span.h" #include #include @@ -714,6 +715,9 @@ private: std::vector polyphonyGroups; // Views to speed up iteration over the regions and voices when events // occur in the audio callback + VoiceViewVector regionPolyphonyArray; + VoiceStealing stealer; + VoiceViewVector voiceViewArray; std::array noteActivationLists; std::array ccActivationLists; diff --git a/src/sfizz/VoiceStealing.h b/src/sfizz/VoiceStealing.h index 8cf4362b..6914d1b0 100644 --- a/src/sfizz/VoiceStealing.h +++ b/src/sfizz/VoiceStealing.h @@ -3,6 +3,9 @@ // This code is part of the sfizz library and is licensed under a BSD 2-clause // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once + #include "Config.h" #include "Voice.h" #include "SisterVoiceRing.h"