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
|
#pragma once
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "Voice.h"
|
#include "Voice.h"
|
||||||
#include "SwapAndPop.h"
|
#include "SwapAndPop.h"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@
|
||||||
#include "utility/SpinMutex.h"
|
#include "utility/SpinMutex.h"
|
||||||
#include "utility/XmlHelpers.h"
|
#include "utility/XmlHelpers.h"
|
||||||
#include "VoiceList.h"
|
#include "VoiceList.h"
|
||||||
#include "VoiceStealing.h"
|
|
||||||
#include <absl/types/optional.h>
|
#include <absl/types/optional.h>
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -42,15 +41,10 @@
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
struct Synth::Impl : public Voice::StateListener, public Parser::Listener {
|
struct Synth::Impl: public Parser::Listener {
|
||||||
Impl();
|
Impl();
|
||||||
~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
|
* @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
|
* 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;
|
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
|
* @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;
|
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.
|
* @brief Start a voice for a specific region.
|
||||||
* This will do the needed polyphony checks and voice stealing.
|
* 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
|
// engine polyphony
|
||||||
RegionSetPtr engineSet_;
|
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
|
// Views to speed up iteration over the regions and voices when events
|
||||||
// occur in the audio callback
|
// occur in the audio callback
|
||||||
VoiceViewVector tempPolyphonyArray_;
|
VoiceViewVector tempPolyphonyArray_;
|
||||||
VoiceViewVector voiceViewArray_;
|
VoiceViewVector voiceViewArray_;
|
||||||
VoiceStealing stealer_;
|
|
||||||
|
|
||||||
std::array<RegionViewVector, 128> lastKeyswitchLists_;
|
std::array<RegionViewVector, 128> lastKeyswitchLists_;
|
||||||
std::array<RegionViewVector, 128> downKeyswitchLists_;
|
std::array<RegionViewVector, 128> downKeyswitchLists_;
|
||||||
|
|
@ -413,19 +354,6 @@ Synth::Impl::~Impl()
|
||||||
resources_.filePool.emptyFileLoadingQueues();
|
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)
|
void Synth::Impl::onParseFullBlock(const std::string& header, const std::vector<Opcode>& members)
|
||||||
{
|
{
|
||||||
const auto newRegionSet = [&](OpcodeScope level) {
|
const auto newRegionSet = [&](OpcodeScope level) {
|
||||||
|
|
@ -546,8 +474,12 @@ void Synth::Impl::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||||
currentSwitch_ = *lastRegion->defaultSwitch;
|
currentSwitch_ = *lastRegion->defaultSwitch;
|
||||||
|
|
||||||
// There was a combination of group= and polyphony= on a region, so set the group polyphony
|
// 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)
|
if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices) {
|
||||||
setGroupPolyphony(lastRegion->group, lastRegion->polyphony);
|
voiceList_.setGroupPolyphony(lastRegion->group, lastRegion->polyphony);
|
||||||
|
} else {
|
||||||
|
// Just check that there are enough polyphony groups
|
||||||
|
voiceList_.ensureNumPolyphonyGroups(lastRegion->group);
|
||||||
|
}
|
||||||
|
|
||||||
if (currentSet_ != nullptr) {
|
if (currentSet_ != nullptr) {
|
||||||
lastRegion->parent = currentSet_;
|
lastRegion->parent = currentSet_;
|
||||||
|
|
@ -595,7 +527,6 @@ void Synth::Impl::clear()
|
||||||
resources_.midiState.reset();
|
resources_.midiState.reset();
|
||||||
resources_.filePool.clear();
|
resources_.filePool.clear();
|
||||||
resources_.filePool.setRamLoading(config::loadInRam);
|
resources_.filePool.setRamLoading(config::loadInRam);
|
||||||
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest);
|
|
||||||
ccLabels_.clear();
|
ccLabels_.clear();
|
||||||
keyLabels_.clear();
|
keyLabels_.clear();
|
||||||
keyswitchLabels_.clear();
|
keyswitchLabels_.clear();
|
||||||
|
|
@ -603,9 +534,6 @@ void Synth::Impl::clear()
|
||||||
masterOpcodes_.clear();
|
masterOpcodes_.clear();
|
||||||
groupOpcodes_.clear();
|
groupOpcodes_.clear();
|
||||||
unknownOpcodes_.clear();
|
unknownOpcodes_.clear();
|
||||||
polyphonyGroups_.clear();
|
|
||||||
polyphonyGroups_.emplace_back();
|
|
||||||
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
|
|
||||||
modificationTime_ = fs::file_time_type::min();
|
modificationTime_ = fs::file_time_type::min();
|
||||||
|
|
||||||
// set default controllers
|
// set default controllers
|
||||||
|
|
@ -689,12 +617,12 @@ void Synth::Impl::handleGroupOpcodes(const std::vector<Opcode>& members, const s
|
||||||
parseOpcode(member);
|
parseOpcode(member);
|
||||||
|
|
||||||
if (groupIdx && maxPolyphony) {
|
if (groupIdx && maxPolyphony) {
|
||||||
setGroupPolyphony(*groupIdx, *maxPolyphony);
|
voiceList_.setGroupPolyphony(*groupIdx, *maxPolyphony);
|
||||||
} else if (maxPolyphony) {
|
} else if (maxPolyphony) {
|
||||||
ASSERT(currentSet_ != nullptr);
|
ASSERT(currentSet_ != nullptr);
|
||||||
currentSet_->setPolyphonyLimit(*maxPolyphony);
|
currentSet_->setPolyphonyLimit(*maxPolyphony);
|
||||||
} else if (groupIdx && *groupIdx > polyphonyGroups_.size()) {
|
} else if (groupIdx) {
|
||||||
setGroupPolyphony(*groupIdx, config::maxVoices);
|
voiceList_.ensureNumPolyphonyGroups(*groupIdx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -749,22 +677,13 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
case hash("hint_stealing"):
|
case hash("hint_stealing"):
|
||||||
switch(hash(member.value)) {
|
switch(hash(member.value)) {
|
||||||
case hash("first"):
|
case hash("first"):
|
||||||
for (auto& voice : voiceList_)
|
voiceList_.setStealingAlgorithm(StealingAlgorithm::First);
|
||||||
voice.disablePowerFollower();
|
|
||||||
|
|
||||||
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::First);
|
|
||||||
break;
|
break;
|
||||||
case hash("oldest"):
|
case hash("oldest"):
|
||||||
for (auto& voice : voiceList_)
|
voiceList_.setStealingAlgorithm(StealingAlgorithm::Oldest);
|
||||||
voice.disablePowerFollower();
|
|
||||||
|
|
||||||
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest);
|
|
||||||
break;
|
break;
|
||||||
case hash("envelope_and_age"):
|
case hash("envelope_and_age"):
|
||||||
for (auto& voice : voiceList_)
|
voiceList_.setStealingAlgorithm(StealingAlgorithm::EnvelopeAndAge);
|
||||||
voice.enablePowerFollower();
|
|
||||||
|
|
||||||
stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::EnvelopeAndAge);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DBG("Unsupported value for hint_stealing: " << member.value);
|
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++) {
|
for (auto note = 0; note < 128; note++) {
|
||||||
if (region->keyRange.containsWithEnd(note))
|
if (region->keyRange.containsWithEnd(note))
|
||||||
noteActivationLists_[note].push_back(region);
|
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
|
void Synth::Impl::startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept
|
||||||
{
|
{
|
||||||
checkNotePolyphony(region, delay, triggerEvent);
|
voiceList_.checkPolyphony(region, delay, triggerEvent);
|
||||||
checkRegionPolyphony(region, delay);
|
|
||||||
checkGroupPolyphony(region, delay);
|
|
||||||
checkSetPolyphony(region, delay);
|
|
||||||
checkEnginePolyphony(delay);
|
|
||||||
|
|
||||||
Voice* selectedVoice = findFreeVoice();
|
Voice* selectedVoice = findFreeVoice();
|
||||||
if (selectedVoice == nullptr)
|
if (selectedVoice == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
@ -1387,9 +1295,6 @@ void Synth::Impl::startVoice(Region* region, int delay, const TriggerEvent& trig
|
||||||
ASSERT(selectedVoice->isFree());
|
ASSERT(selectedVoice->isFree());
|
||||||
selectedVoice->startVoice(region, delay, triggerEvent);
|
selectedVoice->startVoice(region, delay, triggerEvent);
|
||||||
ring.addVoiceToRing(selectedVoice);
|
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
|
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
|
void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noexcept
|
||||||
{
|
{
|
||||||
const auto randValue = randNoteDistribution_(Random::randomGenerator);
|
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
|
const PolyphonyGroup* Synth::getPolyphonyGroupView(int idx) const noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
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
|
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
|
unsigned Synth::getNumPolyphonyGroups() const noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
return impl.polyphonyGroups_.size();
|
return impl.voiceList_.getNumPolyphonyGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string>& Synth::getUnknownOpcodes() const noexcept
|
const std::vector<std::string>& Synth::getUnknownOpcodes() const noexcept
|
||||||
|
|
@ -1971,7 +1782,7 @@ void Synth::Impl::resetVoices(int numVoices)
|
||||||
Voice& lastVoice = voiceList_.back();
|
Voice& lastVoice = voiceList_.back();
|
||||||
lastVoice.setSampleRate(this->sampleRate_);
|
lastVoice.setSampleRate(this->sampleRate_);
|
||||||
lastVoice.setSamplesPerBlock(this->samplesPerBlock_);
|
lastVoice.setSamplesPerBlock(this->samplesPerBlock_);
|
||||||
lastVoice.setStateListener(this);
|
lastVoice.setStateListener(&voiceList_);
|
||||||
voiceViewArray_.push_back(&lastVoice);
|
voiceViewArray_.push_back(&lastVoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1988,15 +1799,6 @@ void Synth::Impl::applySettingsPerVoice()
|
||||||
voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG);
|
voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG);
|
||||||
voice.setFilterEGEnabledPerVoice(settingsPerVoice_.haveFilterEG);
|
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()
|
void Synth::Impl::setupModMatrix()
|
||||||
|
|
@ -2076,7 +1878,8 @@ void Synth::setOversamplingFactor(Oversampling factor) noexcept
|
||||||
if (factor == impl.oversamplingFactor_)
|
if (factor == impl.oversamplingFactor_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
impl.voiceList_.reset();
|
for (auto& voice : impl.voiceList_)
|
||||||
|
voice.reset();
|
||||||
|
|
||||||
impl.resources_.filePool.emptyFileLoadingQueues();
|
impl.resources_.filePool.emptyFileLoadingQueues();
|
||||||
impl.resources_.filePool.setOversamplingFactor(factor);
|
impl.resources_.filePool.setOversamplingFactor(factor);
|
||||||
|
|
@ -2191,19 +1994,12 @@ void Synth::allSoundOff() noexcept
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ };
|
const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ };
|
||||||
|
|
||||||
impl.voiceList_.reset();
|
for (auto& voice : impl.voiceList_)
|
||||||
|
voice.reset();
|
||||||
for (auto& effectBus : impl.effectBuses_)
|
for (auto& effectBus : impl.effectBuses_)
|
||||||
effectBus->clear();
|
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
|
std::bitset<config::numCCs> Synth::getUsedCCs() const noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
|
||||||
|
|
@ -1582,7 +1582,7 @@ void Voice::Impl::switchState(State s)
|
||||||
if (s != state_) {
|
if (s != state_) {
|
||||||
state_ = s;
|
state_ = s;
|
||||||
if (stateListener_)
|
if (stateListener_)
|
||||||
stateListener_->onVoiceStateChanged(id_, s);
|
stateListener_->onVoiceStateChanging(id_, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public:
|
||||||
|
|
||||||
class StateListener {
|
class StateListener {
|
||||||
public:
|
public:
|
||||||
virtual void onVoiceStateChanged(NumericId<Voice> /*id*/, State /*state*/) {}
|
virtual void onVoiceStateChanging(NumericId<Voice> /*id*/, State /*state*/) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,37 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Voice.h"
|
#include "Voice.h"
|
||||||
|
#include "Config.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
|
#include "SisterVoiceRing.h"
|
||||||
|
#include "PolyphonyGroup.h"
|
||||||
|
#include "RegionSet.h"
|
||||||
|
#include "VoiceStealing.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <absl/algorithm/container.h>
|
#include <absl/algorithm/container.h>
|
||||||
|
|
||||||
namespace sfz {
|
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.
|
* @brief Find the voice which is associated with the given identifier.
|
||||||
*
|
*
|
||||||
|
|
@ -48,6 +71,11 @@ struct VoiceList
|
||||||
{
|
{
|
||||||
for (auto& voice : list_)
|
for (auto& voice : list_)
|
||||||
voice.reset();
|
voice.reset();
|
||||||
|
|
||||||
|
polyphonyGroups_.clear();
|
||||||
|
polyphonyGroups_.emplace_back();
|
||||||
|
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
|
||||||
|
setStealingAlgorithm(StealingAlgorithm::Oldest);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool playingAttackVoice(const Region* releaseRegion) noexcept
|
bool playingAttackVoice(const Region* releaseRegion) noexcept
|
||||||
|
|
@ -68,21 +96,187 @@ struct VoiceList
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
typename std::vector<Voice>::iterator begin() { return list_.begin(); }
|
void ensureNumPolyphonyGroups(unsigned groupIdx) noexcept
|
||||||
typename std::vector<Voice>::const_iterator cbegin() const { return list_.cbegin(); }
|
{
|
||||||
typename std::vector<Voice>::iterator end() { return list_.end(); }
|
while (polyphonyGroups_.size() <= groupIdx)
|
||||||
typename std::vector<Voice>::const_iterator cend() const { return list_.cend(); }
|
polyphonyGroups_.emplace_back();
|
||||||
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(); }
|
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept
|
||||||
typename std::vector<Voice>::const_reference back() const { return list_.back(); }
|
{
|
||||||
|
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(); }
|
size_t size() const { return list_.size(); }
|
||||||
void clear() { list_.clear(); }
|
|
||||||
void reserve(size_t n) { list_.reserve(n); }
|
void reserve(size_t n) { list_.reserve(n); }
|
||||||
template< class... Args >
|
template< class... Args >
|
||||||
void emplace_back(Args&&... args) { list_.emplace_back(std::forward<Args>(args)...); }
|
void emplace_back(Args&&... args) { list_.emplace_back(std::forward<Args>(args)...); }
|
||||||
private:
|
|
||||||
std::vector<Voice> list_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,102 @@
|
||||||
#include "VoiceStealing.h"
|
#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);
|
Voice* candidate = nullptr;
|
||||||
}
|
unsigned numPlaying = 0;
|
||||||
|
for (const auto& voice : candidates) {
|
||||||
sfz::Voice* sfz::VoiceStealing::steal(absl::Span<sfz::Voice*> voices) noexcept
|
if (voiceCond(voice)) {
|
||||||
{
|
if (candidateCond(voice, candidate))
|
||||||
if (voices.empty())
|
candidate = voice;
|
||||||
return {};
|
numPlaying += 1;
|
||||||
|
}
|
||||||
switch(stealingAlgorithm) {
|
|
||||||
case StealingAlgorithm::First:
|
|
||||||
return stealFirst(voices);
|
|
||||||
case StealingAlgorithm::EnvelopeAndAge:
|
|
||||||
return stealEnvelopeAndAge(voices);
|
|
||||||
case StealingAlgorithm::Oldest:
|
|
||||||
default:
|
|
||||||
return stealOldest(voices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 genericPolyphonyCheck(candidates, maxPolyphony,
|
||||||
return voices.front();
|
[=](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);
|
absl::c_sort(voices, voiceOrdering);
|
||||||
|
|
||||||
const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {
|
const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {
|
||||||
return sum + v->getAveragePower();
|
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
|
const auto powerThreshold = sumPower
|
||||||
/ static_cast<float>(voices.size()) * config::stealingPowerCoeff;
|
/ 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 =
|
const auto ageThreshold =
|
||||||
static_cast<int>(voices.front()->getAge() * config::stealingAgeCoeff);
|
static_cast<int>(voices.front()->getAge() * config::stealingAgeCoeff);
|
||||||
|
|
||||||
|
|
@ -82,3 +127,37 @@ sfz::Voice* sfz::VoiceStealing::stealEnvelopeAndAge(absl::Span<Voice*> voices) n
|
||||||
|
|
||||||
return returnedVoice;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "Region.h"
|
||||||
#include "Voice.h"
|
#include "Voice.h"
|
||||||
#include "SisterVoiceRing.h"
|
#include "SisterVoiceRing.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -14,64 +15,56 @@
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
class VoiceStealing
|
|
||||||
|
enum class StealingAlgorithm {
|
||||||
|
First,
|
||||||
|
Oldest,
|
||||||
|
EnvelopeAndAge
|
||||||
|
};
|
||||||
|
|
||||||
|
class VoiceStealer
|
||||||
{
|
{
|
||||||
public:
|
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;
|
virtual Voice* checkPolyphony(absl::Span<Voice*> candidates, unsigned maxPolyphony) = 0;
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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