Add more voices than necessary

These will naturally be used for "dying" voices beyond the engine polyphony
This commit is contained in:
Paul Ferrand 2020-10-06 18:43:09 +02:00
parent 4e13493ab4
commit 8071cd0131
5 changed files with 41 additions and 26 deletions

View file

@ -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

View file

@ -53,3 +53,8 @@ unsigned sfz::RegionSet::numPlayingVoices() const noexcept
return !v->releasedOrFree();
});
}
void sfz::RegionSet::removeAllVoices() noexcept
{
voices.clear();
}

View file

@ -122,6 +122,11 @@ public:
* @return const std::vector<RegionSet*>&
*/
const std::vector<RegionSet*>& getSubsets() const noexcept { return subsets; }
/**
* @brief Remove all voices from the set
*/
void removeAllVoices() noexcept;
private:
RegionSet* parent { nullptr };
OpcodeScope level { kOpcodeScopeGeneric };

View file

@ -41,6 +41,7 @@ sfz::Synth::Synth(int numVoices)
initializeSIMDDispatchers();
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
engineSet = absl::make_unique<RegionSet>(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<Opcode>& 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<SpinMutex> 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<int>(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<Voice>(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();
}

View file

@ -819,6 +819,9 @@ private:
// These are more general "groups" than sfz and encapsulates the full hierarchy
RegionSet* currentSet { nullptr };
std::vector<RegionSetPtr> 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<PolyphonyGroup> 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<int>(config::numVoices * config::overflowVoiceMultiplier) };
int activeVoices { 0 };
Oversampling oversamplingFactor { config::defaultOversamplingFactor };