Final refactor into VoiceManager

Cleanups..
This commit is contained in:
Paul Ferrand 2020-10-31 20:11:23 +01:00
parent 1b87fa3764
commit 81a4ed22eb
12 changed files with 497 additions and 381 deletions

View file

@ -115,6 +115,7 @@ SFIZZ_SOURCES = \
src/sfizz/Tuning.cpp \ src/sfizz/Tuning.cpp \
src/sfizz/utility/SpinMutex.cpp \ src/sfizz/utility/SpinMutex.cpp \
src/sfizz/Voice.cpp \ src/sfizz/Voice.cpp \
src/sfizz/VoiceManager.cpp \
src/sfizz/VoiceStealing.cpp \ src/sfizz/VoiceStealing.cpp \
src/sfizz/Wavetables.cpp src/sfizz/Wavetables.cpp

View file

@ -109,6 +109,7 @@ set (SFIZZ_HEADERS
sfizz/SynthConfig.h sfizz/SynthConfig.h
sfizz/Tuning.h sfizz/Tuning.h
sfizz/Voice.h sfizz/Voice.h
sfizz/VoiceManager.h
sfizz/VoiceStealing.h sfizz/VoiceStealing.h
sfizz/Wavetables.h sfizz/Wavetables.h
sfizz.h sfizz.h
@ -137,6 +138,7 @@ set (SFIZZ_SOURCES
sfizz/Tuning.cpp sfizz/Tuning.cpp
sfizz/RegionSet.cpp sfizz/RegionSet.cpp
sfizz/PolyphonyGroup.cpp sfizz/PolyphonyGroup.cpp
sfizz/VoiceManager.cpp
sfizz/VoiceStealing.cpp sfizz/VoiceStealing.cpp
sfizz/RTSemaphore.cpp sfizz/RTSemaphore.cpp
sfizz/Panning.cpp sfizz/Panning.cpp

View file

@ -30,7 +30,7 @@
#include "TriggerEvent.h" #include "TriggerEvent.h"
#include "utility/SpinMutex.h" #include "utility/SpinMutex.h"
#include "utility/XmlHelpers.h" #include "utility/XmlHelpers.h"
#include "VoiceList.h" #include "VoiceManager.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>
@ -236,7 +236,7 @@ struct Synth::Impl: public Parser::Listener {
using RegionPtr = std::unique_ptr<Region>; using RegionPtr = std::unique_ptr<Region>;
using RegionSetPtr = std::unique_ptr<RegionSet>; using RegionSetPtr = std::unique_ptr<RegionSet>;
std::vector<RegionPtr> regions_; std::vector<RegionPtr> regions_;
VoiceList voiceList_; VoiceManager voiceManager_;
// These are more general "groups" than sfz and encapsulates the full hierarchy // These are more general "groups" than sfz and encapsulates the full hierarchy
RegionSet* currentSet_ { nullptr }; RegionSet* currentSet_ { nullptr };
@ -327,16 +327,16 @@ Synth::Impl::Impl()
// modulation sources // modulation sources
genController_.reset(new ControllerSource(resources_)); genController_.reset(new ControllerSource(resources_));
genLFO_.reset(new LFOSource(voiceList_)); genLFO_.reset(new LFOSource(voiceManager_));
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceList_)); genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceList_, resources_.midiState)); genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
} }
Synth::Impl::~Impl() Synth::Impl::~Impl()
{ {
const std::lock_guard<SpinMutex> disableCallback { callbackGuard_ }; const std::lock_guard<SpinMutex> disableCallback { callbackGuard_ };
voiceList_.reset(); voiceManager_.reset();
resources_.filePool.emptyFileLoadingQueues(); resources_.filePool.emptyFileLoadingQueues();
} }
@ -461,10 +461,10 @@ void Synth::Impl::buildRegion(const std::vector<Opcode>& regionOpcodes)
// 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) {
voiceList_.setGroupPolyphony(lastRegion->group, lastRegion->polyphony); voiceManager_.setGroupPolyphony(lastRegion->group, lastRegion->polyphony);
} else { } else {
// Just check that there are enough polyphony groups // Just check that there are enough polyphony groups
voiceList_.ensureNumPolyphonyGroups(lastRegion->group); voiceManager_.ensureNumPolyphonyGroups(lastRegion->group);
} }
if (currentSet_ != nullptr) { if (currentSet_ != nullptr) {
@ -483,7 +483,7 @@ void Synth::Impl::clear()
// Clear the background queues before removing everyone // Clear the background queues before removing everyone
resources_.filePool.waitForBackgroundLoading(); resources_.filePool.waitForBackgroundLoading();
voiceList_.reset(); voiceManager_.reset();
for (auto& list : lastKeyswitchLists_) for (auto& list : lastKeyswitchLists_)
list.clear(); list.clear();
for (auto& list : downKeyswitchLists_) for (auto& list : downKeyswitchLists_)
@ -603,12 +603,12 @@ void Synth::Impl::handleGroupOpcodes(const std::vector<Opcode>& members, const s
parseOpcode(member); parseOpcode(member);
if (groupIdx && maxPolyphony) { if (groupIdx && maxPolyphony) {
voiceList_.setGroupPolyphony(*groupIdx, *maxPolyphony); voiceManager_.setGroupPolyphony(*groupIdx, *maxPolyphony);
} else if (maxPolyphony) { } else if (maxPolyphony) {
ASSERT(currentSet_ != nullptr); ASSERT(currentSet_ != nullptr);
currentSet_->setPolyphonyLimit(*maxPolyphony); currentSet_->setPolyphonyLimit(*maxPolyphony);
} else if (groupIdx) { } else if (groupIdx) {
voiceList_.ensureNumPolyphonyGroups(*groupIdx); voiceManager_.ensureNumPolyphonyGroups(*groupIdx);
} }
} }
@ -663,13 +663,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"):
voiceList_.setStealingAlgorithm(StealingAlgorithm::First); voiceManager_.setStealingAlgorithm(StealingAlgorithm::First);
break; break;
case hash("oldest"): case hash("oldest"):
voiceList_.setStealingAlgorithm(StealingAlgorithm::Oldest); voiceManager_.setStealingAlgorithm(StealingAlgorithm::Oldest);
break; break;
case hash("envelope_and_age"): case hash("envelope_and_age"):
voiceList_.setStealingAlgorithm(StealingAlgorithm::EnvelopeAndAge); voiceManager_.setStealingAlgorithm(StealingAlgorithm::EnvelopeAndAge);
break; break;
default: default:
DBG("Unsupported value for hint_stealing: " << member.value); DBG("Unsupported value for hint_stealing: " << member.value);
@ -1045,7 +1045,7 @@ void Synth::loadStretchTuningByRatio(float ratio)
int Synth::getNumActiveVoices() const noexcept int Synth::getNumActiveVoices() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
return static_cast<int>(impl.voiceList_.getNumActiveVoices()); return static_cast<int>(impl.voiceManager_.getNumActiveVoices());
} }
void Synth::setSamplesPerBlock(int samplesPerBlock) noexcept void Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
@ -1056,7 +1056,7 @@ void Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ }; const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ };
impl.samplesPerBlock_ = samplesPerBlock; impl.samplesPerBlock_ = samplesPerBlock;
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.setSamplesPerBlock(samplesPerBlock); voice.setSamplesPerBlock(samplesPerBlock);
impl.resources_.setSamplesPerBlock(samplesPerBlock); impl.resources_.setSamplesPerBlock(samplesPerBlock);
@ -1073,7 +1073,7 @@ void Synth::setSampleRate(float sampleRate) noexcept
const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ }; const std::lock_guard<SpinMutex> disableCallback { impl.callbackGuard_ };
impl.sampleRate_ = sampleRate; impl.sampleRate_ = sampleRate;
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.setSampleRate(sampleRate); voice.setSampleRate(sampleRate);
impl.resources_.setSampleRate(sampleRate); impl.resources_.setSampleRate(sampleRate);
@ -1136,7 +1136,7 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration };
tempMixSpan->fill(0.0f); tempMixSpan->fill(0.0f);
for (auto& voice : impl.voiceList_) { for (auto& voice : impl.voiceManager_) {
if (voice.isFree()) if (voice.isFree())
continue; continue;
@ -1243,7 +1243,7 @@ void Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept
// auto replacedVelocity = (velocity == 0 ? getNoteVelocity(noteNumber) : velocity); // auto replacedVelocity = (velocity == 0 ? getNoteVelocity(noteNumber) : velocity);
const auto replacedVelocity = impl.resources_.midiState.getNoteVelocity(noteNumber); const auto replacedVelocity = impl.resources_.midiState.getNoteVelocity(noteNumber);
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.registerNoteOff(delay, noteNumber, replacedVelocity); voice.registerNoteOff(delay, noteNumber, replacedVelocity);
impl.noteOffDispatch(delay, noteNumber, replacedVelocity); impl.noteOffDispatch(delay, noteNumber, replacedVelocity);
@ -1251,8 +1251,8 @@ 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
{ {
voiceList_.checkPolyphony(region, delay, triggerEvent); voiceManager_.checkPolyphony(region, delay, triggerEvent);
Voice* selectedVoice = voiceList_.findFreeVoice(); Voice* selectedVoice = voiceManager_.findFreeVoice();
if (selectedVoice == nullptr) if (selectedVoice == nullptr)
return; return;
@ -1275,7 +1275,7 @@ void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noe
for (auto& region : noteActivationLists_[noteNumber]) { for (auto& region : noteActivationLists_[noteNumber]) {
if (region->registerNoteOff(noteNumber, velocity, randValue)) { if (region->registerNoteOff(noteNumber, velocity, randValue)) {
if (region->trigger == SfzTrigger::release && !region->rtDead && !voiceList_.playingAttackVoice(region)) if (region->trigger == SfzTrigger::release && !region->rtDead && !voiceManager_.playingAttackVoice(region))
continue; continue;
startVoice(region, delay, triggerEvent, ring); startVoice(region, delay, triggerEvent, ring);
@ -1308,7 +1308,7 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex
for (auto& region : noteActivationLists_[noteNumber]) { for (auto& region : noteActivationLists_[noteNumber]) {
if (region->registerNoteOn(noteNumber, velocity, randValue)) { if (region->registerNoteOn(noteNumber, velocity, randValue)) {
for (auto& voice : voiceList_) { for (auto& voice : voiceManager_) {
if (voice.checkOffGroup(region, delay, noteNumber)) { if (voice.checkOffGroup(region, delay, noteNumber)) {
const TriggerEvent& event = voice.getTriggerEvent(); const TriggerEvent& event = voice.getTriggerEvent();
noteOffDispatch(delay, event.number, event.value); noteOffDispatch(delay, event.number, event.value);
@ -1325,7 +1325,7 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex
void Synth::Impl::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept void Synth::Impl::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept
{ {
if (!region->rtDead && !voiceList_.playingAttackVoice(region)) { if (!region->rtDead && !voiceManager_.playingAttackVoice(region)) {
region->delayedReleases.clear(); region->delayedReleases.clear();
return; return;
} }
@ -1377,13 +1377,13 @@ void Synth::hdcc(int delay, int ccNumber, float normValue) noexcept
} }
if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) { if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) {
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.reset(); voice.reset();
impl.resources_.midiState.allNotesOff(delay); impl.resources_.midiState.allNotesOff(delay);
return; return;
} }
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.registerCC(delay, ccNumber, normValue); voice.registerCC(delay, ccNumber, normValue);
impl.ccDispatch(delay, ccNumber, normValue); impl.ccDispatch(delay, ccNumber, normValue);
@ -1427,7 +1427,7 @@ void Synth::pitchWheel(int delay, int pitch) noexcept
region->registerPitchWheel(normalizedPitch); region->registerPitchWheel(normalizedPitch);
} }
for (auto& voice : impl.voiceList_) { for (auto& voice : impl.voiceManager_) {
voice.registerPitchWheel(delay, normalizedPitch); voice.registerPitchWheel(delay, normalizedPitch);
} }
} }
@ -1614,7 +1614,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 impl.voiceList_.getPolyphonyGroupView(idx); return impl.voiceManager_.getPolyphonyGroupView(idx);
} }
const Region* Synth::getRegionById(NumericId<Region> id) const noexcept const Region* Synth::getRegionById(NumericId<Region> id) const noexcept
@ -1638,13 +1638,13 @@ const Region* Synth::getRegionById(NumericId<Region> id) const noexcept
const Voice* Synth::getVoiceView(int idx) const noexcept const Voice* Synth::getVoiceView(int idx) const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
return idx < impl.numVoices_ ? &impl.voiceList_[idx] : nullptr; return idx < impl.numVoices_ ? &impl.voiceManager_[idx] : nullptr;
} }
unsigned Synth::getNumPolyphonyGroups() const noexcept unsigned Synth::getNumPolyphonyGroups() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
return impl.voiceList_.getNumPolyphonyGroups(); return impl.voiceManager_.getNumPolyphonyGroups();
} }
const std::vector<std::string>& Synth::getUnknownOpcodes() const noexcept const std::vector<std::string>& Synth::getUnknownOpcodes() const noexcept
@ -1730,9 +1730,9 @@ void Synth::Impl::resetVoices(int numVoices)
engineSet_->removeAllVoices(); engineSet_->removeAllVoices();
engineSet_->setPolyphonyLimit(numVoices_); engineSet_->setPolyphonyLimit(numVoices_);
voiceList_.requireNumVoices(numVoices_, resources_); voiceManager_.requireNumVoices(numVoices_, resources_);
for (auto& voice : voiceList_) { for (auto& voice : voiceManager_) {
voice.setSampleRate(this->sampleRate_); voice.setSampleRate(this->sampleRate_);
voice.setSamplesPerBlock(this->samplesPerBlock_); voice.setSamplesPerBlock(this->samplesPerBlock_);
} }
@ -1742,7 +1742,7 @@ void Synth::Impl::resetVoices(int numVoices)
void Synth::Impl::applySettingsPerVoice() void Synth::Impl::applySettingsPerVoice()
{ {
for (auto& voice : voiceList_) { for (auto& voice : voiceManager_) {
voice.setMaxFiltersPerVoice(settingsPerVoice_.maxFilters); voice.setMaxFiltersPerVoice(settingsPerVoice_.maxFilters);
voice.setMaxEQsPerVoice(settingsPerVoice_.maxEQs); voice.setMaxEQsPerVoice(settingsPerVoice_.maxEQs);
voice.setMaxLFOsPerVoice(settingsPerVoice_.maxLFOs); voice.setMaxLFOsPerVoice(settingsPerVoice_.maxLFOs);
@ -1829,7 +1829,7 @@ void Synth::setOversamplingFactor(Oversampling factor) noexcept
if (factor == impl.oversamplingFactor_) if (factor == impl.oversamplingFactor_)
return; return;
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.reset(); voice.reset();
impl.resources_.filePool.emptyFileLoadingQueues(); impl.resources_.filePool.emptyFileLoadingQueues();
@ -1886,7 +1886,7 @@ void Synth::Impl::resetAllControllers(int delay) noexcept
if (!lock.owns_lock()) if (!lock.owns_lock())
return; return;
for (auto& voice : voiceList_) { for (auto& voice : voiceManager_) {
voice.registerPitchWheel(delay, 0); voice.registerPitchWheel(delay, 0);
for (int cc = 0; cc < config::numCCs; ++cc) for (int cc = 0; cc < config::numCCs; ++cc)
voice.registerCC(delay, cc, 0.0f); voice.registerCC(delay, cc, 0.0f);
@ -1945,7 +1945,7 @@ 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_ };
for (auto& voice : impl.voiceList_) for (auto& voice : impl.voiceManager_)
voice.reset(); voice.reset();
for (auto& effectBus : impl.effectBuses_) for (auto& effectBus : impl.effectBuses_)
effectBus->clear(); effectBus->clear();

View file

@ -1,322 +0,0 @@
// 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
#pragma once
#include "Voice.h"
#include "Resources.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 : 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.
*
* @param id
* @return const Voice*
*/
const Voice* 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* getVoiceById(NumericId<Voice> id) noexcept
{
return const_cast<Voice*>(
const_cast<const VoiceList*>(this)->getVoiceById(id));
}
void reset()
{
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
{
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;
}
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);
}
/**
* @brief Get the number of active voices
*
* @return unsigned
*/
unsigned getNumActiveVoices() const
{
return activeVoices_.size();
}
/**
* @brief Find a voice that is not currently playing
*
* @return Voice*
*/
Voice* findFreeVoice() noexcept
{
auto freeVoice = absl::c_find_if(list_, [](const Voice& voice) {
return voice.isFree();
});
if (freeVoice != list_.end())
return &*freeVoice;
DBG("Engine hard polyphony reached");
return {};
}
void requireNumVoices(int numVoices, Resources& resources)
{
numActualVoices_ =
static_cast<int>(config::overflowVoiceMultiplier * numVoices);
numRequiredVoices_ = numVoices;
clear();
list_.reserve(numActualVoices_);
activeVoices_.reserve(numActualVoices_);
for (int i = 0; i < numActualVoices_; ++i) {
list_.emplace_back(i, resources);
Voice& lastVoice = list_.back();
lastVoice.setStateListener(this);
}
}
private:
int numRequiredVoices_ { config::numVoices };
int numActualVoices_ { static_cast<int>(config::numVoices * config::overflowVoiceMultiplier) };
std::vector<Voice> list_;
std::vector<Voice*> activeVoices_;
// 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
{
Voice* candidate = stealer_->checkPolyphony(
absl::MakeSpan(activeVoices_), numRequiredVoices_);
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]; }
};
} // namespace sfz

242
src/sfizz/VoiceManager.cpp Normal file
View file

@ -0,0 +1,242 @@
// 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)
{
(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);
}
}
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();
polyphonyGroups_.emplace_back();
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
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;
}
void VoiceManager::ensureNumPolyphonyGroups(unsigned groupIdx) noexcept
{
while (polyphonyGroups_.size() <= groupIdx)
polyphonyGroups_.emplace_back();
}
void VoiceManager::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept
{
ensureNumPolyphonyGroups(groupIdx);
polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony);
}
const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) const noexcept
{
return (size_t)idx < polyphonyGroups_.size() ? &polyphonyGroups_[idx] : nullptr;
}
void VoiceManager::clear()
{
reset();
list_.clear();
activeVoices_.clear();
}
void VoiceManager::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 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
{
auto freeVoice = absl::c_find_if(list_, [](const Voice& voice) {
return voice.isFree();
});
if (freeVoice != list_.end())
return &*freeVoice;
DBG("Engine hard polyphony reached");
return {};
}
void VoiceManager::requireNumVoices(int numVoices, Resources& resources)
{
numActualVoices_ =
static_cast<int>(config::overflowVoiceMultiplier * numVoices);
numRequiredVoices_ = numVoices;
clear();
list_.reserve(numActualVoices_);
activeVoices_.reserve(numActualVoices_);
for (int i = 0; i < numActualVoices_; ++i) {
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 };
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);
}
}
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_);
SisterVoiceRing::offAllSisters(candidate, delay);
}
} // namespace sfz

193
src/sfizz/VoiceManager.h Normal file
View file

@ -0,0 +1,193 @@
// 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
#pragma once
#include "Voice.h"
#include "Resources.h"
#include "Config.h"
#include "Region.h"
#include "PolyphonyGroup.h"
#include "VoiceStealing.h"
#include <vector>
namespace sfz {
struct VoiceManager : public Voice::StateListener
{
/**
* @brief The voice callback which is called during a change of state.
*/
void onVoiceStateChanging(NumericId<Voice> id, Voice::State state) final;
/**
* @brief Find the voice which is associated with the given identifier.
*
* @param id
* @return const Voice*
*/
const Voice* getVoiceById(NumericId<Voice> id) const noexcept;
/**
* @brief Find the voice which is associated with the given identifier.
*
* @param id
* @return Voice*
*/
Voice* getVoiceById(NumericId<Voice> id) noexcept;
/**
* @brief Reset all voices and clear the polyphony groups
*/
void reset();
/**
* @brief Check if a compatible attack voice is playing for the release region.
*
* @param releaseRegion
* @return true
* @return false
*/
bool playingAttackVoice(const Region* releaseRegion) noexcept;
/**
* @brief Ensures that the polyphony groups are at least this size.
* Call this each time a new `group=N` is given in an sfz file.
*
* @param groupIdx
*/
void ensureNumPolyphonyGroups(unsigned groupIdx) noexcept;
/**
* @brief Set the polyphony for a given group
* If the number of polyphony groups is too small, it will
* be increased.
*
* @param groupIdx
* @param polyphony
*/
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept;
/**
* @brief Get a view into a given polyphony group
*
* @param idx
* @return const PolyphonyGroup*
*/
const PolyphonyGroup* getPolyphonyGroupView(int idx) const noexcept;
/**
* @brief Clear all voices and polyphony groups.
* Also resets the stealing algorithm to default.
*/
void clear();
/**
* @brief Set the stealing algorithm
*
* @param algorithm
*/
void setStealingAlgorithm(StealingAlgorithm algorithm);
/**
* @brief Off voices as necessary depending on the trigger event and started region
*
* @param region
* @param delay
* @param triggerEvent
*/
void checkPolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept;
/**
* @brief Get the number of active voices
*
* @return size_t
*/
size_t getNumActiveVoices() const { return activeVoices_.size(); }
/**
* @brief Get the number of polyphony groups
*
* @return size_t
*/
size_t getNumPolyphonyGroups() const noexcept { return polyphonyGroups_.size(); }
/**
* @brief Find a voice that is not currently playing
*
* @return Voice*
*/
Voice* findFreeVoice() noexcept;
/**
* @brief Require a number of voices from this manager.
* In practice, the manager will handle slightly more, in order to
* allow voices to die off upon reaching higher polyphony count.
*
* @param numVoices
* @param resources
*/
void requireNumVoices(int numVoices, Resources& resources);
private:
int numRequiredVoices_ { config::numVoices };
int numActualVoices_ { static_cast<int>(config::numVoices * config::overflowVoiceMultiplier) };
std::vector<Voice> list_;
std::vector<Voice*> activeVoices_;
// 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;
/**
* @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;
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]; }
};
} // namespace sfz

View file

@ -15,14 +15,14 @@
namespace sfz { namespace sfz {
ADSREnvelopeSource::ADSREnvelopeSource(VoiceList& list, MidiState& state) ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager, MidiState& state)
: voiceList_(list), midiState_(state) : voiceManager_(manager), midiState_(state)
{ {
} }
void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
{ {
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;
@ -60,7 +60,7 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
{ {
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;
@ -91,7 +91,7 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
{ {
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;

View file

@ -6,7 +6,7 @@
#pragma once #pragma once
#include "../ModGenerator.h" #include "../ModGenerator.h"
#include "../../VoiceList.h" #include "../../VoiceManager.h"
#include "../../MidiState.h" #include "../../MidiState.h"
namespace sfz { namespace sfz {
@ -14,13 +14,13 @@ class Synth;
class ADSREnvelopeSource : public ModGenerator { class ADSREnvelopeSource : public ModGenerator {
public: public:
explicit ADSREnvelopeSource(VoiceList &synth, MidiState& state); explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state);
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override; void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
private: private:
VoiceList& voiceList_; VoiceManager& voiceManager_;
MidiState& midiState_; MidiState& midiState_;
}; };

View file

@ -14,8 +14,8 @@
namespace sfz { namespace sfz {
FlexEnvelopeSource::FlexEnvelopeSource(VoiceList& list) FlexEnvelopeSource::FlexEnvelopeSource(VoiceManager& manager)
: voiceList_(list) : voiceManager_(manager)
{ {
} }
@ -23,7 +23,7 @@ void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
{ {
unsigned egIndex = sourceKey.parameters().N; unsigned egIndex = sourceKey.parameters().N;
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;
@ -50,7 +50,7 @@ void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
{ {
unsigned egIndex = sourceKey.parameters().N; unsigned egIndex = sourceKey.parameters().N;
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;
@ -70,7 +70,7 @@ void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voic
{ {
unsigned egIndex = sourceKey.parameters().N; unsigned egIndex = sourceKey.parameters().N;
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;

View file

@ -6,20 +6,20 @@
#pragma once #pragma once
#include "../ModGenerator.h" #include "../ModGenerator.h"
#include "../../VoiceList.h" #include "../../VoiceManager.h"
namespace sfz { namespace sfz {
class Synth; class Synth;
class FlexEnvelopeSource : public ModGenerator { class FlexEnvelopeSource : public ModGenerator {
public: public:
explicit FlexEnvelopeSource(VoiceList& list); explicit FlexEnvelopeSource(VoiceManager& manager);
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override; void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
private: private:
VoiceList& voiceList_; VoiceManager& voiceManager_;
}; };
} // namespace sfz } // namespace sfz

View file

@ -14,8 +14,8 @@
namespace sfz { namespace sfz {
LFOSource::LFOSource(VoiceList& list) LFOSource::LFOSource(VoiceManager& manager)
: voiceList_(list) : voiceManager_(manager)
{ {
} }
@ -23,7 +23,7 @@ void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned
{ {
unsigned lfoIndex = sourceKey.parameters().N; unsigned lfoIndex = sourceKey.parameters().N;
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
return; return;
@ -44,7 +44,7 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl
{ {
const unsigned lfoIndex = sourceKey.parameters().N; const unsigned lfoIndex = sourceKey.parameters().N;
Voice* voice = voiceList_.getVoiceById(voiceId); Voice* voice = voiceManager_.getVoiceById(voiceId);
if (!voice) { if (!voice) {
ASSERTFALSE; ASSERTFALSE;
fill(buffer, 0.0f); fill(buffer, 0.0f);

View file

@ -6,18 +6,18 @@
#pragma once #pragma once
#include "../ModGenerator.h" #include "../ModGenerator.h"
#include "../../VoiceList.h" #include "../../VoiceManager.h"
namespace sfz { namespace sfz {
class Synth; class Synth;
class LFOSource : public ModGenerator { class LFOSource : public ModGenerator {
public: public:
explicit LFOSource(VoiceList &list); explicit LFOSource(VoiceManager &manager);
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override; void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override; void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
private: private:
VoiceList& voiceList_; VoiceManager& voiceManager_;
}; };
} // namespace sfz } // namespace sfz