2020-10-31 20:11:23 +01:00
|
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
|
|
|
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
|
|
|
|
// license. You should have receive a LICENSE.md file along with the code.
|
|
|
|
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
|
|
|
|
|
|
|
|
|
#include "VoiceManager.h"
|
|
|
|
|
#include "SisterVoiceRing.h"
|
|
|
|
|
#include "RegionSet.h"
|
|
|
|
|
#include <absl/algorithm/container.h>
|
|
|
|
|
|
|
|
|
|
namespace sfz {
|
|
|
|
|
|
|
|
|
|
void VoiceManager::onVoiceStateChanging(NumericId<Voice> id, Voice::State state)
|
|
|
|
|
{
|
|
|
|
|
if (state == Voice::State::idle) {
|
2021-02-02 12:28:32 +01:00
|
|
|
Voice* voice = getVoiceById(id);
|
|
|
|
|
const Region* region = voice->getRegion();
|
|
|
|
|
const uint32_t group = region->group;
|
|
|
|
|
RegionSet::removeVoiceFromHierarchy(region, voice);
|
2020-10-31 20:11:23 +01:00
|
|
|
swapAndPopFirst(activeVoices_, [voice](const Voice* v) { return v == voice; });
|
2021-06-27 22:35:41 +02:00
|
|
|
ASSERT(polyphonyGroups_.contains(group));
|
2021-02-02 12:28:32 +01:00
|
|
|
polyphonyGroups_[group].removeVoice(voice);
|
2020-10-31 20:11:23 +01:00
|
|
|
} else if (state == Voice::State::playing) {
|
2021-02-02 12:28:32 +01:00
|
|
|
Voice* voice = getVoiceById(id);
|
|
|
|
|
const Region* region = voice->getRegion();
|
|
|
|
|
const uint32_t group = region->group;
|
2020-10-31 20:11:23 +01:00
|
|
|
activeVoices_.push_back(voice);
|
2021-02-02 12:28:32 +01:00
|
|
|
RegionSet::registerVoiceInHierarchy(region, voice);
|
2021-06-27 22:35:41 +02:00
|
|
|
ASSERT(polyphonyGroups_.contains(group));
|
2021-02-02 12:28:32 +01:00
|
|
|
polyphonyGroups_[group].registerVoice(voice);
|
2020-10-31 20:11:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Voice* VoiceManager::getVoiceById(NumericId<Voice> id) const noexcept
|
|
|
|
|
{
|
|
|
|
|
const size_t size = list_.size();
|
|
|
|
|
|
|
|
|
|
if (size == 0 || !id.valid())
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
// search a sequence of ordered identifiers with potential gaps
|
|
|
|
|
size_t index = static_cast<size_t>(id.number());
|
|
|
|
|
index = std::min(index, size - 1);
|
|
|
|
|
|
|
|
|
|
while (index > 0 && list_[index].getId().number() > id.number())
|
|
|
|
|
--index;
|
|
|
|
|
|
|
|
|
|
return (list_[index].getId() == id) ? &list_[index] : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Voice* VoiceManager::getVoiceById(NumericId<Voice> id) noexcept
|
|
|
|
|
{
|
|
|
|
|
return const_cast<Voice*>(
|
|
|
|
|
const_cast<const VoiceManager*>(this)->getVoiceById(id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::reset()
|
|
|
|
|
{
|
|
|
|
|
for (auto& voice : list_)
|
|
|
|
|
voice.reset();
|
|
|
|
|
|
|
|
|
|
polyphonyGroups_.clear();
|
2021-06-27 22:35:41 +02:00
|
|
|
polyphonyGroups_.emplace(0, PolyphonyGroup{});
|
2020-10-31 20:11:23 +01:00
|
|
|
setStealingAlgorithm(StealingAlgorithm::Oldest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VoiceManager::playingAttackVoice(const Region* releaseRegion) noexcept
|
|
|
|
|
{
|
|
|
|
|
const auto compatibleVoice = [releaseRegion](const Voice& v) -> bool {
|
|
|
|
|
const TriggerEvent& event = v.getTriggerEvent();
|
|
|
|
|
return (
|
|
|
|
|
!v.isFree()
|
|
|
|
|
&& event.type == TriggerEventType::NoteOn
|
|
|
|
|
&& releaseRegion->keyRange.containsWithEnd(event.number)
|
|
|
|
|
&& releaseRegion->velocityRange.containsWithEnd(event.value)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (absl::c_find_if(list_, compatibleVoice) == list_.end())
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-27 22:35:41 +02:00
|
|
|
void VoiceManager::ensureNumPolyphonyGroups(int groupIdx) noexcept
|
2020-10-31 20:11:23 +01:00
|
|
|
{
|
2021-06-27 22:35:41 +02:00
|
|
|
if (!polyphonyGroups_.contains(groupIdx))
|
|
|
|
|
polyphonyGroups_.emplace(groupIdx, PolyphonyGroup{});
|
2020-10-31 20:11:23 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-27 22:35:41 +02:00
|
|
|
void VoiceManager::setGroupPolyphony(int groupIdx, unsigned polyphony) noexcept
|
2020-10-31 20:11:23 +01:00
|
|
|
{
|
|
|
|
|
ensureNumPolyphonyGroups(groupIdx);
|
|
|
|
|
polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-06-27 22:35:41 +02:00
|
|
|
const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) noexcept
|
2020-10-31 20:11:23 +01:00
|
|
|
{
|
2021-06-27 22:35:41 +02:00
|
|
|
if (!polyphonyGroups_.contains(idx))
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return &polyphonyGroups_[idx];
|
2020-10-31 20:11:23 +01:00
|
|
|
}
|
|
|
|
|
|
2022-05-15 01:47:30 +02:00
|
|
|
std::vector<const Voice*> VoiceManager::getActiveVoices() const noexcept
|
|
|
|
|
{
|
|
|
|
|
std::vector<const Voice*> returned;
|
|
|
|
|
std::copy(activeVoices_.begin(), activeVoices_.end(), std::back_inserter(returned));
|
|
|
|
|
return returned;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-10-31 20:11:23 +01:00
|
|
|
void VoiceManager::clear()
|
|
|
|
|
{
|
2021-06-27 22:35:41 +02:00
|
|
|
for (auto& pg : polyphonyGroups_)
|
|
|
|
|
pg.second.removeAllVoices();
|
2020-10-31 20:11:23 +01:00
|
|
|
list_.clear();
|
|
|
|
|
activeVoices_.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::setStealingAlgorithm(StealingAlgorithm algorithm)
|
|
|
|
|
{
|
|
|
|
|
switch(algorithm){
|
2021-03-02 17:10:03 +01:00
|
|
|
case StealingAlgorithm::First:
|
2020-10-31 20:11:23 +01:00
|
|
|
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 VoiceManager::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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Voice* VoiceManager::findFreeVoice() noexcept
|
|
|
|
|
{
|
2022-05-15 01:47:30 +02:00
|
|
|
Voice* freeVoice = nullptr;
|
|
|
|
|
for (auto& v: list_) {
|
|
|
|
|
if (v.isFree()) {
|
|
|
|
|
freeVoice = &v;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-10-31 20:11:23 +01:00
|
|
|
|
2022-05-15 01:47:30 +02:00
|
|
|
if (v.offedOrFree()) {
|
|
|
|
|
if (freeVoice == nullptr || v.getAge() > freeVoice->getAge())
|
|
|
|
|
freeVoice = &v;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (freeVoice != nullptr)
|
|
|
|
|
return freeVoice;
|
2020-10-31 20:11:23 +01:00
|
|
|
|
|
|
|
|
DBG("Engine hard polyphony reached");
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::requireNumVoices(int numVoices, Resources& resources)
|
|
|
|
|
{
|
|
|
|
|
numRequiredVoices_ = numVoices;
|
2022-05-15 01:47:30 +02:00
|
|
|
const int numEffectiveVoices = std::min(int(config::maxVoices), numVoices +
|
|
|
|
|
std::max(int(numVoices * config::overflowVoiceMultiplier), config::minOverflowVoices));
|
2020-10-31 20:11:23 +01:00
|
|
|
|
|
|
|
|
clear();
|
2021-02-02 14:09:35 +01:00
|
|
|
list_.reserve(numEffectiveVoices);
|
2021-06-25 17:59:48 +02:00
|
|
|
temp_.reserve(numEffectiveVoices);
|
2021-02-02 14:09:35 +01:00
|
|
|
activeVoices_.reserve(numEffectiveVoices);
|
2020-10-31 20:11:23 +01:00
|
|
|
|
2021-02-02 14:09:35 +01:00
|
|
|
for (int i = 0; i < numEffectiveVoices; ++i) {
|
2020-10-31 20:11:23 +01:00
|
|
|
list_.emplace_back(i, resources);
|
|
|
|
|
Voice& lastVoice = list_.back();
|
|
|
|
|
lastVoice.setStateListener(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::checkRegionPolyphony(const Region* region, int delay) noexcept
|
|
|
|
|
{
|
|
|
|
|
Voice* candidate = stealer_->checkRegionPolyphony(region, absl::MakeSpan(activeVoices_));
|
|
|
|
|
SisterVoiceRing::offAllSisters(candidate, delay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept
|
|
|
|
|
{
|
|
|
|
|
if (!region->notePolyphony)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
unsigned notePolyphonyCounter { 0 };
|
2021-06-25 17:59:48 +02:00
|
|
|
temp_.clear();
|
2020-10-31 20:11:23 +01:00
|
|
|
|
|
|
|
|
for (Voice* voice : activeVoices_) {
|
|
|
|
|
const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent();
|
2021-06-25 17:59:48 +02:00
|
|
|
if (!voice->offedOrFree()
|
2020-10-31 20:11:23 +01:00
|
|
|
&& voice->getRegion()->group == region->group
|
2021-02-08 19:22:56 +01:00
|
|
|
&& voiceTriggerEvent.number == triggerEvent.number) {
|
2020-10-31 20:11:23 +01:00
|
|
|
notePolyphonyCounter += 1;
|
2021-06-25 17:59:48 +02:00
|
|
|
if (region->selfMask == SelfMask::dontMask || voiceTriggerEvent.value <= triggerEvent.value)
|
|
|
|
|
temp_.push_back(voice);
|
2020-10-31 20:11:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-25 17:59:48 +02:00
|
|
|
if (region->selfMask == SelfMask::mask) {
|
|
|
|
|
absl::c_sort(temp_, [](const Voice* lhs, const Voice* rhs) {
|
|
|
|
|
const auto lhsTrigger = lhs->getTriggerEvent();
|
|
|
|
|
const auto rhsTrigger = rhs->getTriggerEvent();
|
|
|
|
|
return lhsTrigger.value < rhsTrigger.value;
|
|
|
|
|
});
|
|
|
|
|
} else if (region->selfMask == SelfMask::dontMask) {
|
|
|
|
|
absl::c_sort(temp_, [](const Voice* lhs, const Voice* rhs) {
|
|
|
|
|
return lhs->getAge() > rhs->getAge();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
ASSERTFALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto it = temp_.begin();
|
|
|
|
|
unsigned targetPolyphony { *region->notePolyphony - 1 };
|
|
|
|
|
while (notePolyphonyCounter > targetPolyphony && it < temp_.end()) {
|
|
|
|
|
Voice* voice = *it;
|
|
|
|
|
if (!voice->offedOrFree())
|
|
|
|
|
SisterVoiceRing::offAllSisters(voice, delay);
|
|
|
|
|
|
|
|
|
|
notePolyphonyCounter--;
|
|
|
|
|
it++;
|
2020-10-31 20:11:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceManager::checkEnginePolyphony(int delay) noexcept
|
|
|
|
|
{
|
|
|
|
|
Voice* candidate = stealer_->checkPolyphony(
|
|
|
|
|
absl::MakeSpan(activeVoices_), numRequiredVoices_);
|
2022-05-15 01:47:30 +02:00
|
|
|
SisterVoiceRing::offAllSisters(candidate, delay, true);
|
2020-10-31 20:11:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace sfz
|