diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 477c3ce3..eede94f2 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -131,6 +131,13 @@ namespace config { static constexpr int loopXfadeCurve = 2; // 0: linear // 1: use curves 5 & 6 // 2: use S-shaped curve + /** + * @brief Overflow voices in the engine, relative to the required voices. + * These are additional voices that more or less hold the "dying" voices + * due to engine polyphony being reached. + */ + static constexpr float overflowVoiceMultiplier { 1.5f }; + static_assert(overflowVoiceMultiplier >= 1.0f); } // namespace config } // namespace sfz diff --git a/src/sfizz/RegionSet.cpp b/src/sfizz/RegionSet.cpp index 1387438a..bfceb4e2 100644 --- a/src/sfizz/RegionSet.cpp +++ b/src/sfizz/RegionSet.cpp @@ -53,3 +53,8 @@ unsigned sfz::RegionSet::numPlayingVoices() const noexcept return !v->releasedOrFree(); }); } + +void sfz::RegionSet::removeAllVoices() noexcept +{ + voices.clear(); +} diff --git a/src/sfizz/RegionSet.h b/src/sfizz/RegionSet.h index 4424a030..4077fbe7 100644 --- a/src/sfizz/RegionSet.h +++ b/src/sfizz/RegionSet.h @@ -122,6 +122,11 @@ public: * @return const std::vector& */ const std::vector& getSubsets() const noexcept { return subsets; } + + /** + * @brief Remove all voices from the set + */ + void removeAllVoices() noexcept; private: RegionSet* parent { nullptr }; OpcodeScope level { kOpcodeScopeGeneric }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 498a9f62..039b967b 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -41,6 +41,7 @@ sfz::Synth::Synth(int numVoices) initializeSIMDDispatchers(); const std::lock_guard disableCallback { callbackGuard }; + engineSet = absl::make_unique(nullptr, OpcodeScope::kOpcodeScopeGeneric); parser.setListener(this); effectFactory.registerStandardEffectTypes(); effectBuses.reserve(5); // sufficient room for main and fx1-4 @@ -387,6 +388,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) default: DBG("Unsupported value for hint_stealing: " << member.value); } + break; default: // Unsupported control opcode DBG("Unsupported control opcode: " << member.opcode); @@ -742,23 +744,8 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept if (freeVoice != voices.end()) return freeVoice->get(); - // Engine polyphony reached - Voice* stolenVoice = stealer.steal(absl::MakeSpan(voiceViewArray)); - if (stolenVoice == nullptr) - return {}; - - // Never kill age 0 voices - if (stolenVoice->getAge() == 0) - return {}; - - - auto tempSpan = resources.bufferPool.getStereoBuffer(samplesPerBlock); - SisterVoiceRing::applyToRing(stolenVoice, [&] (Voice* v) { - renderVoiceToOutputs(*v, *tempSpan); - v->reset(); - }); - - return stolenVoice; + DBG("Engine polyphony reached"); + return {}; } int sfz::Synth::getNumActiveVoices(bool recompute) const noexcept @@ -1511,7 +1498,7 @@ void sfz::Synth::setVolume(float volume) noexcept int sfz::Synth::getNumVoices() const noexcept { - return numVoices; + return numRequiredVoices; } void sfz::Synth::setNumVoices(int numVoices) noexcept @@ -1520,7 +1507,7 @@ void sfz::Synth::setNumVoices(int numVoices) noexcept const std::lock_guard disableCallback { callbackGuard }; // fast path - if (numVoices == this->numVoices) + if (numVoices == this->numRequiredVoices) return; resetVoices(numVoices); @@ -1528,16 +1515,25 @@ void sfz::Synth::setNumVoices(int numVoices) noexcept void sfz::Synth::resetVoices(int numVoices) { + numActualVoices = + static_cast(config::overflowVoiceMultiplier * numVoices); + numRequiredVoices = numVoices; + + for (auto& set : sets) + set->removeAllVoices(); + engineSet->removeAllVoices(); + engineSet->setPolyphonyLimit(numRequiredVoices); + voices.clear(); - voices.reserve(numVoices); + voices.reserve(numActualVoices); voiceViewArray.clear(); - voiceViewArray.reserve(numVoices); + voiceViewArray.reserve(numActualVoices); tempPolyphonyArray.clear(); - tempPolyphonyArray.reserve(numVoices); + tempPolyphonyArray.reserve(numActualVoices); - for (int i = 0; i < numVoices; ++i) { + for (int i = 0; i < numActualVoices; ++i) { auto voice = absl::make_unique(i, resources); voice->setStateListener(this); voiceViewArray.push_back(voice.get()); @@ -1549,8 +1545,6 @@ void sfz::Synth::resetVoices(int numVoices) voice->setSamplesPerBlock(this->samplesPerBlock); } - this->numVoices = numVoices; - applySettingsPerVoice(); } diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index c0d32c2e..2663afcc 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -819,6 +819,9 @@ private: // These are more general "groups" than sfz and encapsulates the full hierarchy RegionSet* currentSet { nullptr }; std::vector sets; + // This region set holds the engine set of voices, which tries to respect the required + // engine polyphony + RegionSetPtr engineSet; // These are the `group=` groups where you can off voices std::vector polyphonyGroups; @@ -902,7 +905,8 @@ private: int samplesPerBlock { config::defaultSamplesPerBlock }; float sampleRate { config::defaultSampleRate }; float volume { Default::globalVolume }; - int numVoices { config::numVoices }; + int numRequiredVoices { config::numVoices }; + int numActualVoices { static_cast(config::numVoices * config::overflowVoiceMultiplier) }; int activeVoices { 0 }; Oversampling oversamplingFactor { config::defaultOversamplingFactor };