Move the polyphony logic in the voice list and refactor

This commit is contained in:
Paul Ferrand 2020-10-31 18:51:19 +01:00
parent 0f7fadc7a9
commit ed151880d9
7 changed files with 384 additions and 321 deletions

View file

@ -6,6 +6,7 @@
#pragma once
#include "Config.h"
#include "Region.h"
#include "Voice.h"
#include "SwapAndPop.h"

View file

@ -31,7 +31,6 @@
#include "utility/SpinMutex.h"
#include "utility/XmlHelpers.h"
#include "VoiceList.h"
#include "VoiceStealing.h"
#include <absl/types/optional.h>
#include <absl/types/span.h>
#include <algorithm>
@ -42,15 +41,10 @@
namespace sfz {
struct Synth::Impl : public Voice::StateListener, public Parser::Listener {
struct Synth::Impl: public Parser::Listener {
Impl();
~Impl();
/**
* @brief The voice callback which is called during a change of state.
*/
void onVoiceStateChanged(NumericId<Voice> idNumber, Voice::State state) final;
/**
* @brief The parser callback; this is called by the parent object each time
* a new region, group, master, global, curve or control set of opcodes
@ -71,15 +65,6 @@ struct Synth::Impl : public Voice::StateListener, public Parser::Listener {
*/
void onParseWarning(const SourceRange& range, const std::string& message) final;
/**
* @brief change the group maximum polyphony
*
* @param groupIdx the group index
* @param polyphone the max polyphony
*/
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept;
/**
* @brief Reset all CCs; to be used on CC 121
*
@ -193,46 +178,6 @@ struct Synth::Impl : public Voice::StateListener, public Parser::Listener {
Voice* findFreeVoice() noexcept;
/**
* @brief Check the region polyphony, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkRegionPolyphony(const Region* region, int delay) noexcept;
/**
* @brief Check the note polyphony, releasing voices if necessary
*
* @param region
* @param delay
* @param triggerEvent
*/
void checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept;
/**
* @brief Check the group polyphony, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkGroupPolyphony(const Region* region, int delay) noexcept;
/**
* @brief Check the region set polyphony at all levels, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkSetPolyphony(const Region* region, int delay) noexcept;
/**
* @brief Check the engine polyphony, fast releasing voices if necessary
*
* @param delay
*/
void checkEnginePolyphony(int delay) noexcept;
/**
* @brief Start a voice for a specific region.
* This will do the needed polyphony checks and voice stealing.
@ -308,14 +253,10 @@ struct Synth::Impl : public Voice::StateListener, public Parser::Listener {
// engine polyphony
RegionSetPtr engineSet_;
// These are the `group=` groups where you can off voices
std::vector<PolyphonyGroup> polyphonyGroups_;
// Views to speed up iteration over the regions and voices when events
// occur in the audio callback
VoiceViewVector tempPolyphonyArray_;
VoiceViewVector voiceViewArray_;
VoiceStealing stealer_;
std::array<RegionViewVector, 128> lastKeyswitchLists_;
std::array<RegionViewVector, 128> downKeyswitchLists_;
@ -413,19 +354,6 @@ Synth::Impl::~Impl()
resources_.filePool.emptyFileLoadingQueues();
}
void Synth::Impl::onVoiceStateChanged(NumericId<Voice> id, Voice::State state)
{
(void)id;
(void)state;
if (state == Voice::State::idle) {
auto voice = voiceList_.getVoiceById(id);
RegionSet::removeVoiceFromHierarchy(voice->getRegion(), voice);
engineSet_->removeVoice(voice);
polyphonyGroups_[voice->getRegion()->group].removeVoice(voice);
}
}
void Synth::Impl::onParseFullBlock(const std::string& header, const std::vector<Opcode>& members)
{
const auto newRegionSet = [&](OpcodeScope level) {
@ -546,8 +474,12 @@ void Synth::Impl::buildRegion(const std::vector<Opcode>& regionOpcodes)
currentSwitch_ = *lastRegion->defaultSwitch;
// There was a combination of group= and polyphony= on a region, so set the group polyphony
if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices)
setGroupPolyphony(lastRegion->group, lastRegion->polyphony);
if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices) {
voiceList_.setGroupPolyphony(lastRegion->group, lastRegion->polyphony);
} else {
// Just check that there are enough polyphony groups
voiceList_.ensureNumPolyphonyGroups(lastRegion->group);
}
if (currentSet_ != nullptr) {
lastRegion->parent = currentSet_;
@ -595,7 +527,6 @@ void Synth::Impl::clear()
resources_.midiState.reset();
resources_.filePool.clear();
resources_.filePool.setRamLoading(config::loadInRam);
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest);
ccLabels_.clear();
keyLabels_.clear();
keyswitchLabels_.clear();
@ -603,9 +534,6 @@ void Synth::Impl::clear()
masterOpcodes_.clear();
groupOpcodes_.clear();
unknownOpcodes_.clear();
polyphonyGroups_.clear();
polyphonyGroups_.emplace_back();
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
modificationTime_ = fs::file_time_type::min();
// set default controllers
@ -689,12 +617,12 @@ void Synth::Impl::handleGroupOpcodes(const std::vector<Opcode>& members, const s
parseOpcode(member);
if (groupIdx && maxPolyphony) {
setGroupPolyphony(*groupIdx, *maxPolyphony);
voiceList_.setGroupPolyphony(*groupIdx, *maxPolyphony);
} else if (maxPolyphony) {
ASSERT(currentSet_ != nullptr);
currentSet_->setPolyphonyLimit(*maxPolyphony);
} else if (groupIdx && *groupIdx > polyphonyGroups_.size()) {
setGroupPolyphony(*groupIdx, config::maxVoices);
} else if (groupIdx) {
voiceList_.ensureNumPolyphonyGroups(*groupIdx);
}
}
@ -749,22 +677,13 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& members)
case hash("hint_stealing"):
switch(hash(member.value)) {
case hash("first"):
for (auto& voice : voiceList_)
voice.disablePowerFollower();
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::First);
voiceList_.setStealingAlgorithm(StealingAlgorithm::First);
break;
case hash("oldest"):
for (auto& voice : voiceList_)
voice.disablePowerFollower();
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest);
voiceList_.setStealingAlgorithm(StealingAlgorithm::Oldest);
break;
case hash("envelope_and_age"):
for (auto& voice : voiceList_)
voice.enablePowerFollower();
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::EnvelopeAndAge);
voiceList_.setStealingAlgorithm(StealingAlgorithm::EnvelopeAndAge);
break;
default:
DBG("Unsupported value for hint_stealing: " << member.value);
@ -993,12 +912,6 @@ void Synth::Impl::finalizeSfzLoad()
}
}
// Some regions had group number but no "group-level" opcodes handled the polyphony
while (polyphonyGroups_.size() <= region->group) {
polyphonyGroups_.emplace_back();
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
}
for (auto note = 0; note < 128; note++) {
if (region->keyRange.containsWithEnd(note))
noteActivationLists_[note].push_back(region);
@ -1374,12 +1287,7 @@ void Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept
void Synth::Impl::startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept
{
checkNotePolyphony(region, delay, triggerEvent);
checkRegionPolyphony(region, delay);
checkGroupPolyphony(region, delay);
checkSetPolyphony(region, delay);
checkEnginePolyphony(delay);
voiceList_.checkPolyphony(region, delay, triggerEvent);
Voice* selectedVoice = findFreeVoice();
if (selectedVoice == nullptr)
return;
@ -1387,9 +1295,6 @@ void Synth::Impl::startVoice(Region* region, int delay, const TriggerEvent& trig
ASSERT(selectedVoice->isFree());
selectedVoice->startVoice(region, delay, triggerEvent);
ring.addVoiceToRing(selectedVoice);
engineSet_->registerVoice(selectedVoice);
RegionSet::registerVoiceInHierarchy(region, selectedVoice);
polyphonyGroups_[region->group].registerVoice(selectedVoice);
}
void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noexcept
@ -1414,100 +1319,6 @@ void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noe
}
}
void Synth::Impl::checkRegionPolyphony(const Region* region, int delay) noexcept
{
tempPolyphonyArray_.clear();
absl::c_copy_if(voiceViewArray_,
std::back_inserter(tempPolyphonyArray_),
[region](Voice* v) { return v->getRegion() == region && !v->releasedOrFree(); });
if (tempPolyphonyArray_.size() >= region->polyphony) {
const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_));
SisterVoiceRing::offAllSisters(voiceToSteal, delay);
}
}
void Synth::Impl::checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept
{
if (!region->notePolyphony)
return;
unsigned notePolyphonyCounter { 0 };
Voice* selfMaskCandidate { nullptr };
for (Voice* voice : voiceViewArray_) {
const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent();
const bool skipVoice = (triggerEvent.type == TriggerEventType::NoteOn && voice->releasedOrFree()) || voice->isFree();
if (!skipVoice
&& voice->getRegion()->group == region->group
&& voiceTriggerEvent.number == triggerEvent.number
&& voiceTriggerEvent.type == triggerEvent.type) {
notePolyphonyCounter += 1;
switch (region->selfMask) {
case SfzSelfMask::mask:
if (voiceTriggerEvent.value <= triggerEvent.value) {
if (!selfMaskCandidate || selfMaskCandidate->getTriggerEvent().value > voiceTriggerEvent.value) {
selfMaskCandidate = voice;
}
}
break;
case SfzSelfMask::dontMask:
if (!selfMaskCandidate || selfMaskCandidate->getAge() < voice->getAge())
selfMaskCandidate = voice;
break;
}
}
}
if (notePolyphonyCounter >= *region->notePolyphony && selfMaskCandidate) {
SisterVoiceRing::offAllSisters(selfMaskCandidate, delay);
}
}
void Synth::Impl::checkGroupPolyphony(const Region* region, int delay) noexcept
{
const auto& activeVoices = polyphonyGroups_[region->group].getActiveVoices();
tempPolyphonyArray_.clear();
absl::c_copy_if(activeVoices,
std::back_inserter(tempPolyphonyArray_), [](Voice* v) { return !v->releasedOrFree(); });
if (tempPolyphonyArray_.size() >= polyphonyGroups_[region->group].getPolyphonyLimit()) {
const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_));
SisterVoiceRing::offAllSisters(voiceToSteal, delay);
}
}
void Synth::Impl::checkSetPolyphony(const Region* region, int delay) noexcept
{
auto parent = region->parent;
while (parent != nullptr) {
const auto& activeVoices = parent->getActiveVoices();
tempPolyphonyArray_.clear();
absl::c_copy_if(activeVoices,
std::back_inserter(tempPolyphonyArray_), [](Voice* v) { return !v->releasedOrFree(); });
if (tempPolyphonyArray_.size() >= parent->getPolyphonyLimit()) {
const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_));
SisterVoiceRing::offAllSisters(voiceToSteal, delay);
}
parent = parent->getParent();
}
}
void Synth::Impl::checkEnginePolyphony(int delay) noexcept
{
auto& activeVoices = engineSet_->getActiveVoices();
if (activeVoices.size() >= static_cast<size_t>(numRequiredVoices_)) {
tempPolyphonyArray_.clear();
absl::c_copy_if(activeVoices,
std::back_inserter(tempPolyphonyArray_), [](Voice* v) { return !v->releasedOrFree(); });
const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_));
SisterVoiceRing::offAllSisters(voiceToSteal, delay, true);
}
}
void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noexcept
{
const auto randValue = randNoteDistribution_(Random::randomGenerator);
@ -1839,7 +1650,7 @@ const RegionSet* Synth::getRegionSetView(int idx) const noexcept
const PolyphonyGroup* Synth::getPolyphonyGroupView(int idx) const noexcept
{
Impl& impl = *impl_;
return (size_t)idx < impl.polyphonyGroups_.size() ? &impl.polyphonyGroups_[idx] : nullptr;
return impl.voiceList_.getPolyphonyGroupView(idx);
}
const Region* Synth::getRegionById(NumericId<Region> id) const noexcept
@ -1869,7 +1680,7 @@ const Voice* Synth::getVoiceView(int idx) const noexcept
unsigned Synth::getNumPolyphonyGroups() const noexcept
{
Impl& impl = *impl_;
return impl.polyphonyGroups_.size();
return impl.voiceList_.getNumPolyphonyGroups();
}
const std::vector<std::string>& Synth::getUnknownOpcodes() const noexcept
@ -1971,7 +1782,7 @@ void Synth::Impl::resetVoices(int numVoices)
Voice& lastVoice = voiceList_.back();
lastVoice.setSampleRate(this->sampleRate_);
lastVoice.setSamplesPerBlock(this->samplesPerBlock_);
lastVoice.setStateListener(this);
lastVoice.setStateListener(&voiceList_);
voiceViewArray_.push_back(&lastVoice);
}
@ -1988,15 +1799,6 @@ void Synth::Impl::applySettingsPerVoice()
voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG);
voice.setFilterEGEnabledPerVoice(settingsPerVoice_.haveFilterEG);
}
if (stealer_.getStealingAlgorithm() ==
VoiceStealing::StealingAlgorithm::EnvelopeAndAge) {
for (auto& voice : voiceList_)
voice.enablePowerFollower();
} else {
for (auto& voice : voiceList_)
voice.disablePowerFollower();
}
}
void Synth::Impl::setupModMatrix()
@ -2076,7 +1878,8 @@ void Synth::setOversamplingFactor(Oversampling factor) noexcept
if (factor == impl.oversamplingFactor_)
return;
impl.voiceList_.reset();
for (auto& voice : impl.voiceList_)
voice.reset();
impl.resources_.filePool.emptyFileLoadingQueues();
impl.resources_.filePool.setOversamplingFactor(factor);
@ -2191,19 +1994,12 @@ void Synth::allSoundOff() noexcept
Impl& impl = *impl_;
const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ };
impl.voiceList_.reset();
for (auto& voice : impl.voiceList_)
voice.reset();
for (auto& effectBus : impl.effectBuses_)
effectBus->clear();
}
void Synth::Impl::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept
{
while (polyphonyGroups_.size() <= groupIdx)
polyphonyGroups_.emplace_back();
polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony);
}
std::bitset<config::numCCs> Synth::getUsedCCs() const noexcept
{
Impl& impl = *impl_;

View file

@ -1582,7 +1582,7 @@ void Voice::Impl::switchState(State s)
if (s != state_) {
state_ = s;
if (stateListener_)
stateListener_->onVoiceStateChanged(id_, s);
stateListener_->onVoiceStateChanging(id_, s);
}
}

View file

@ -53,7 +53,7 @@ public:
class StateListener {
public:
virtual void onVoiceStateChanged(NumericId<Voice> /*id*/, State /*state*/) {}
virtual void onVoiceStateChanging(NumericId<Voice> /*id*/, State /*state*/) {}
};
/**

View file

@ -7,14 +7,37 @@
#pragma once
#include "Voice.h"
#include "Config.h"
#include "Region.h"
#include "SisterVoiceRing.h"
#include "PolyphonyGroup.h"
#include "RegionSet.h"
#include "VoiceStealing.h"
#include <vector>
#include <absl/algorithm/container.h>
namespace sfz {
struct VoiceList
struct VoiceList : public Voice::StateListener
{
/**
* @brief The voice callback which is called during a change of state.
*/
void onVoiceStateChanging(NumericId<Voice> id, Voice::State state) final
{
(void)id;
if (state == Voice::State::idle) {
auto voice = getVoiceById(id);
RegionSet::removeVoiceFromHierarchy(voice->getRegion(), voice);
swapAndPopFirst(activeVoices_, [voice](const Voice* v) { return v == voice; });
polyphonyGroups_[voice->getRegion()->group].removeVoice(voice);
} else if (state == Voice::State::playing) {
auto voice = getVoiceById(id);
activeVoices_.push_back(voice);
RegionSet::registerVoiceInHierarchy(voice->getRegion(), voice);
polyphonyGroups_[voice->getRegion()->group].registerVoice(voice);
}
}
/**
* @brief Find the voice which is associated with the given identifier.
*
@ -48,6 +71,11 @@ struct VoiceList
{
for (auto& voice : list_)
voice.reset();
polyphonyGroups_.clear();
polyphonyGroups_.emplace_back();
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
setStealingAlgorithm(StealingAlgorithm::Oldest);
}
bool playingAttackVoice(const Region* releaseRegion) noexcept
@ -68,21 +96,187 @@ struct VoiceList
return true;
}
typename std::vector<Voice>::iterator begin() { return list_.begin(); }
typename std::vector<Voice>::const_iterator cbegin() const { return list_.cbegin(); }
typename std::vector<Voice>::iterator end() { return list_.end(); }
typename std::vector<Voice>::const_iterator cend() const { return list_.cend(); }
typename std::vector<Voice>::reference operator[] (size_t n) { return list_[n]; }
typename std::vector<Voice>::const_reference operator[] (size_t n) const { return list_[n]; }
typename std::vector<Voice>::reference back() { return list_.back(); }
typename std::vector<Voice>::const_reference back() const { return list_.back(); }
void ensureNumPolyphonyGroups(unsigned groupIdx) noexcept
{
while (polyphonyGroups_.size() <= groupIdx)
polyphonyGroups_.emplace_back();
}
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept
{
ensureNumPolyphonyGroups(groupIdx);
polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony);
}
size_t getNumPolyphonyGroups() const noexcept { return polyphonyGroups_.size(); }
const PolyphonyGroup* getPolyphonyGroupView(int idx) const noexcept
{
return (size_t)idx < polyphonyGroups_.size() ? &polyphonyGroups_[idx] : nullptr;
}
void clear()
{
reset();
list_.clear();
activeVoices_.clear();
}
void setStealingAlgorithm(StealingAlgorithm algorithm)
{
switch(algorithm){
case StealingAlgorithm::First: // fallthrough
for (auto& voice : list_)
voice.disablePowerFollower();
stealer_ = absl::make_unique<FirstStealer>();
break;
case StealingAlgorithm::Oldest:
for (auto& voice : list_)
voice.disablePowerFollower();
stealer_ = absl::make_unique<OldestStealer>();
break;
case StealingAlgorithm::EnvelopeAndAge:
for (auto& voice : list_)
voice.enablePowerFollower();
stealer_ = absl::make_unique<EnvelopeAndAgeStealer>();
break;
}
}
void checkPolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept
{
checkNotePolyphony(region, delay, triggerEvent);
checkRegionPolyphony(region, delay);
checkGroupPolyphony(region, delay);
checkSetPolyphony(region, delay);
checkEnginePolyphony(delay);
}
private:
std::vector<Voice> list_;
std::vector<Voice*> activeVoices_;
std::vector<Voice*> temp_;
// These are the `group=` groups where you can off voices
std::vector<PolyphonyGroup> polyphonyGroups_;
std::unique_ptr<VoiceStealer> stealer_ { absl::make_unique<OldestStealer>() };
/**
* @brief Check the region polyphony, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkRegionPolyphony(const Region* region, int delay) noexcept
{
Voice* candidate = stealer_->checkRegionPolyphony(region, absl::MakeSpan(activeVoices_));
SisterVoiceRing::offAllSisters(candidate, delay);
}
/**
* @brief Check the note polyphony, releasing voices if necessary
*
* @param region
* @param delay
* @param triggerEvent
*/
void checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept
{
if (!region->notePolyphony)
return;
unsigned notePolyphonyCounter { 0 };
Voice* selfMaskCandidate { nullptr };
for (Voice* voice : activeVoices_) {
const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent();
const bool skipVoice = (triggerEvent.type == TriggerEventType::NoteOn && voice->releasedOrFree()) || voice->isFree();
if (!skipVoice
&& voice->getRegion()->group == region->group
&& voiceTriggerEvent.number == triggerEvent.number
&& voiceTriggerEvent.type == triggerEvent.type) {
notePolyphonyCounter += 1;
switch (region->selfMask) {
case SfzSelfMask::mask:
if (voiceTriggerEvent.value <= triggerEvent.value) {
if (!selfMaskCandidate || selfMaskCandidate->getTriggerEvent().value > voiceTriggerEvent.value) {
selfMaskCandidate = voice;
}
}
break;
case SfzSelfMask::dontMask:
if (!selfMaskCandidate || selfMaskCandidate->getAge() < voice->getAge())
selfMaskCandidate = voice;
break;
}
}
}
if (notePolyphonyCounter >= *region->notePolyphony) {
SisterVoiceRing::offAllSisters(selfMaskCandidate, delay);
}
}
/**
* @brief Check the group polyphony, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkGroupPolyphony(const Region* region, int delay) noexcept
{
auto& group = polyphonyGroups_[region->group];
Voice* candidate = stealer_->checkPolyphony(
absl::MakeSpan(group.getActiveVoices()), group.getPolyphonyLimit());
SisterVoiceRing::offAllSisters(candidate, delay);
}
/**
* @brief Check the region set polyphony at all levels, releasing voices if necessary
*
* @param region
* @param delay
*/
void checkSetPolyphony(const Region* region, int delay) noexcept
{
auto parent = region->parent;
while (parent != nullptr) {
Voice* candidate = stealer_->checkPolyphony(
absl::MakeSpan(parent->getActiveVoices()), parent->getPolyphonyLimit());
SisterVoiceRing::offAllSisters(candidate, delay);
parent = parent->getParent();
}
}
/**
* @brief Check the engine polyphony, fast releasing voices if necessary
*
* @param delay
*/
void checkEnginePolyphony(int delay) noexcept
{
// TODO (paul): should have the "required" vs "actual" number of voices here
Voice* candidate = stealer_->checkPolyphony(
absl::MakeSpan(activeVoices_), list_.size());
SisterVoiceRing::offAllSisters(candidate, delay);
}
public:
// Vector shortcuts
typename decltype(list_)::iterator begin() { return list_.begin(); }
typename decltype(list_)::const_iterator cbegin() const { return list_.cbegin(); }
typename decltype(list_)::iterator end() { return list_.end(); }
typename decltype(list_)::const_iterator cend() const { return list_.cend(); }
typename decltype(list_)::reference operator[] (size_t n) { return list_[n]; }
typename decltype(list_)::const_reference operator[] (size_t n) const { return list_[n]; }
typename decltype(list_)::reference back() { return list_.back(); }
typename decltype(list_)::const_reference back() const { return list_.back(); }
size_t size() const { return list_.size(); }
void clear() { list_.clear(); }
void reserve(size_t n) { list_.reserve(n); }
template< class... Args >
void emplace_back(Args&&... args) { list_.emplace_back(std::forward<Args>(args)...); }
private:
std::vector<Voice> list_;
};
} // namespace sfz

View file

@ -1,57 +1,102 @@
#include "VoiceStealing.h"
#include "SisterVoiceRing.h"
sfz::VoiceStealing::VoiceStealing()
namespace sfz {
/**
* @brief Generic polyphony checker
* A voice is counted as incrementing the voice count and "stealable" if voiceCond(voice) is true.
* For each stealable voice, the voice becomes the stealing candidate if candidateCont(voice, candidate) is true.
*
* @tparam F
* @tparam G
* @param candidates
* @param voiceCond a functor with signature bool(Voice* voice)
* @param candidateCond a functor with signature bool(Voice* voice, Voice* candidate)
* @return Voice*
*/
template<class F, class G>
Voice* genericPolyphonyCheck(absl::Span<Voice*> candidates, unsigned polyphony, F&& voiceCond, G&& candidateCond)
{
voiceScores.reserve(config::maxVoices);
}
sfz::Voice* sfz::VoiceStealing::steal(absl::Span<sfz::Voice*> voices) noexcept
{
if (voices.empty())
return {};
switch(stealingAlgorithm) {
case StealingAlgorithm::First:
return stealFirst(voices);
case StealingAlgorithm::EnvelopeAndAge:
return stealEnvelopeAndAge(voices);
case StealingAlgorithm::Oldest:
default:
return stealOldest(voices);
Voice* candidate = nullptr;
unsigned numPlaying = 0;
for (const auto& voice : candidates) {
if (voiceCond(voice)) {
if (candidateCond(voice, candidate))
candidate = voice;
numPlaying += 1;
}
}
if (numPlaying >= polyphony)
return candidate;
return {};
}
void sfz::VoiceStealing::setStealingAlgorithm(StealingAlgorithm algorithm) noexcept
/**
* @brief Helper to ignore the voice if released depending on a boolean
*
* @param voice
* @param ignoreReleased
*/
constexpr bool ignoreVoice(const Voice* voice)
{
stealingAlgorithm = algorithm;
return (voice == nullptr || voice->releasedOrFree());
}
sfz::Voice* sfz::VoiceStealing::stealFirst(absl::Span<Voice*> voices) noexcept
Voice* FirstStealer::checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates)
{
return voices.front();
ASSERT(region);
return genericPolyphonyCheck(candidates, region->polyphony,
[=](const Voice* v) { return (!ignoreVoice(v) && v->getRegion() == region); },
[=](const Voice* v, const Voice* c) { return c == nullptr; });
}
sfz::Voice* sfz::VoiceStealing::stealOldest(absl::Span<Voice*> voices) noexcept
Voice* FirstStealer::checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony)
{
absl::c_sort(voices, voiceOrdering);
return voices.front();
return genericPolyphonyCheck(candidates, maxPolyphony,
[=](const Voice* v) { return (!ignoreVoice(v)); },
[=](const Voice* v, const Voice* c) { return c == nullptr; });
}
sfz::Voice* sfz::VoiceStealing::stealEnvelopeAndAge(absl::Span<Voice*> voices) noexcept
Voice* OldestStealer::checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates)
{
ASSERT(region);
return genericPolyphonyCheck(candidates, region->polyphony,
[=](const Voice* v) { return (!ignoreVoice(v) && v->getRegion() == region); },
[=](const Voice* v, const Voice* c) { return (c == nullptr || v->getAge() > c->getAge()); });
}
Voice* OldestStealer::checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony)
{
return genericPolyphonyCheck(candidates, maxPolyphony,
[=](const Voice* v) { return (!ignoreVoice(v)); },
[=](const Voice* v, const Voice* c) { return (c == nullptr || v->getAge() > c->getAge()); });
}
/**
* @brief Stealer on envelope and age.
* The stealer checks that the power to try and kill voices with relative low contribution
* to the output compared to the rest.
* The stealer also checks the age so that voices have the time to build up attack
* This is not perfect because pad-type voices will take a long time to output
* their sound, but it's reasonable for sounds with a quick attack and longer
* release.
*
* @param voices
* @return sfz::Voice*
*/
sfz::Voice* stealEnvelopeAndAge(absl::Span<Voice*> voices) noexcept
{
absl::c_sort(voices, voiceOrdering);
const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {
return sum + v->getAveragePower();
});
// We are checking the power to try and kill voices with relative low contribution
// to the output compared to the rest.
const auto powerThreshold = sumPower
/ static_cast<float>(voices.size()) * config::stealingPowerCoeff;
// We are checking the age so that voices have the time to build up attack
// This is not perfect because pad-type voices will take a long time to output
// their sound, but it's reasonable for sounds with a quick attack and longer
// release.
const auto ageThreshold =
static_cast<int>(voices.front()->getAge() * config::stealingAgeCoeff);
@ -82,3 +127,37 @@ sfz::Voice* sfz::VoiceStealing::stealEnvelopeAndAge(absl::Span<Voice*> voices) n
return returnedVoice;
}
Voice* EnvelopeAndAgeStealer::checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates)
{
ASSERT(region);
temp_.clear();
absl::c_copy_if(candidates, std::back_inserter(temp_), [=](Voice* v) {
return (!ignoreVoice(v) && v->getRegion() == region);
});
if (temp_.size() >= region->polyphony)
return stealEnvelopeAndAge(absl::MakeSpan(temp_));
return {};
}
Voice* EnvelopeAndAgeStealer::checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony)
{
temp_.clear();
absl::c_copy_if(candidates, std::back_inserter(temp_), [=](Voice* v) {
return !ignoreVoice(v);
});
if (temp_.size() >= maxPolyphony)
return stealEnvelopeAndAge(absl::MakeSpan(temp_));
return {};
}
EnvelopeAndAgeStealer::EnvelopeAndAgeStealer()
{
temp_.reserve(config::maxVoices);
}
}

View file

@ -7,6 +7,7 @@
#pragma once
#include "Config.h"
#include "Region.h"
#include "Voice.h"
#include "SisterVoiceRing.h"
#include <vector>
@ -14,64 +15,56 @@
namespace sfz
{
class VoiceStealing
enum class StealingAlgorithm {
First,
Oldest,
EnvelopeAndAge
};
class VoiceStealer
{
public:
enum class StealingAlgorithm {
First,
Oldest,
EnvelopeAndAge
};
VoiceStealing();
/**
* @brief Get the current stealing algorithm
* @brief Check that the region polyphony is respected.
*
* @return StealingAlgorithm
* @param region
* @param candidates
* @return Voice* a non-null voice if the region polyphony is not respected
*/
StealingAlgorithm getStealingAlgorithm() const noexcept { return stealingAlgorithm; }
virtual Voice* checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates) = 0;
/**
* @brief Set a default stealing algorithm
* @brief Check that then polyphony is respected.
*
* @param algorithm
* @param region
* @param candidates
* @return Voice* a non-null voice if the region polyphony is not respected
*/
void setStealingAlgorithm(StealingAlgorithm algorithm) noexcept;
/**
* @brief Propose a voice to steal from a set of voices
*
* @param voices
* @return Voice*
*/
Voice* steal(absl::Span<Voice*> voices) noexcept;
private:
StealingAlgorithm stealingAlgorithm { StealingAlgorithm::Oldest };
Voice* stealFirst(absl::Span<Voice*> voices) noexcept;
Voice* stealOldest(absl::Span<Voice*> voices) noexcept;
Voice* stealEnvelopeAndAge(absl::Span<Voice*> voices) noexcept;
struct VoiceScore
{
Voice* voice;
double score;
};
struct VoiceScoreComparator
{
bool operator()(const VoiceScore& voiceScore, const double& score)
{
return (voiceScore.score < score);
}
bool operator()(const double& score, const VoiceScore& voiceScore)
{
return (score < voiceScore.score);
}
bool operator()(const VoiceScore& lhs, const VoiceScore& rhs)
{
return (lhs.score < rhs.score);
}
};
std::vector<VoiceScore> voiceScores;
virtual Voice* checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony) = 0;
};
class FirstStealer : public VoiceStealer
{
public:
Voice* checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates) final;
Voice* checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony) final;
};
class OldestStealer : public VoiceStealer
{
public:
Voice* checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates) final;
Voice* checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony) final;
};
class EnvelopeAndAgeStealer : public VoiceStealer
{
public:
EnvelopeAndAgeStealer();
Voice* checkRegionPolyphony(const Region* region, absl::Span<Voice*> candidates) final;
Voice* checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony) final;
private:
std::vector<Voice*> temp_;
};
}