Move the polyphony logic in the voice list and refactor
This commit is contained in:
parent
0f7fadc7a9
commit
ed151880d9
7 changed files with 384 additions and 321 deletions
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Config.h"
|
||||
#include "Region.h"
|
||||
#include "Voice.h"
|
||||
#include "SwapAndPop.h"
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public:
|
|||
|
||||
class StateListener {
|
||||
public:
|
||||
virtual void onVoiceStateChanged(NumericId<Voice> /*id*/, State /*state*/) {}
|
||||
virtual void onVoiceStateChanging(NumericId<Voice> /*id*/, State /*state*/) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue