diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 8647de86..b19c6cb5 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -144,6 +144,7 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, triggerEvent.value, sampleRate); resources.modMatrix.initVoice(id, region->getId(), delay); + saveModulationTargets(region); } int sfz::Voice::getCurrentSampleQuality() const noexcept @@ -370,22 +371,20 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept const auto numSamples = modulationSpan.size(); ModMatrix& mm = resources.modMatrix; - const ModKey volumeKey = ModKey::createNXYZ(ModId::Volume, region->getId()); - const ModKey amplitudeKey = ModKey::createNXYZ(ModId::Amplitude, region->getId()); // AmpEG envelope egEnvelope.getBlock(modulationSpan); // Amplitude envelope applyGain1(baseGain, modulationSpan); - if (float* mod = mm.getModulationByKey(amplitudeKey)) { + if (float* mod = mm.getModulation(amplitudeTarget)) { for (size_t i = 0; i < numSamples; ++i) modulationSpan[i] *= normalizePercents(mod[i]); } // Volume envelope applyGain1(db2mag(baseVolumedB), modulationSpan); - if (float* mod = mm.getModulationByKey(volumeKey)) { + if (float* mod = mm.getModulation(volumeTarget)) { for (size_t i = 0; i < numSamples; ++i) modulationSpan[i] *= db2mag(mod[i]); } @@ -437,14 +436,13 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept return; ModMatrix& mm = resources.modMatrix; - const ModKey panKey = ModKey::createNXYZ(ModId::Pan, region->getId()); // Prepare for stereo output copy(leftBuffer, rightBuffer); // Apply panning fill(*modulationSpan, region->pan); - if (float* mod = mm.getModulationByKey(panKey)) { + if (float* mod = mm.getModulation(panTarget)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } @@ -463,13 +461,10 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept return; ModMatrix& mm = resources.modMatrix; - const ModKey panKey = ModKey::createNXYZ(ModId::Pan, region->getId()); - const ModKey widthKey = ModKey::createNXYZ(ModId::Width, region->getId()); - const ModKey positionKey = ModKey::createNXYZ(ModId::Position, region->getId()); // Apply panning fill(*modulationSpan, region->pan); - if (float* mod = mm.getModulationByKey(panKey)) { + if (float* mod = mm.getModulation(panTarget)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } @@ -477,14 +472,14 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // Apply the width/position process fill(*modulationSpan, region->width); - if (float* mod = mm.getModulationByKey(widthKey)) { + if (float* mod = mm.getModulation(widthTarget)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } width(*modulationSpan, leftBuffer, rightBuffer); fill(*modulationSpan, region->position); - if (float* mod = mm.getModulationByKey(positionKey)) { + if (float* mod = mm.getModulation(positionTarget)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } @@ -889,9 +884,8 @@ void sfz::Voice::pitchEnvelope(absl::Span pitchSpan) noexcept applyGain(*bends, pitchSpan); ModMatrix& mm = resources.modMatrix; - const ModKey pitchKey = ModKey::createNXYZ(ModId::Pitch, region->getId()); - if (float* mod = mm.getModulationByKey(pitchKey)) { + if (float* mod = mm.getModulation(pitchTarget)) { for (size_t i = 0; i < numFrames; ++i) pitchSpan[i] *= centsFactor(mod[i]); } @@ -902,3 +896,14 @@ void sfz::Voice::resetSmoothers() noexcept bendSmoother.reset(1.0f); gainSmoother.reset(0.0f); } + +void sfz::Voice::saveModulationTargets(const Region* region) noexcept +{ + ModMatrix& mm = resources.modMatrix; + amplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId())); + volumeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId())); + panTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pan, region->getId())); + positionTarget = mm.findTarget(ModKey::createNXYZ(ModId::Position, region->getId())); + widthTarget = mm.findTarget(ModKey::createNXYZ(ModId::Width, region->getId())); + pitchTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pitch, region->getId())); +} diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index c8ebb0c8..10e0df2a 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -406,6 +406,12 @@ private: */ void switchState(State s); + /** + * @brief Save the modulation targets to avoid recomputing them in every callback. + * Must be called during startVoice() ideally. + */ + void saveModulationTargets(const Region* region) noexcept; + const NumericId id; StateListener* stateListener = nullptr; @@ -466,6 +472,13 @@ private: Smoother xfadeSmoother; void resetSmoothers() noexcept; + ModMatrix::TargetId amplitudeTarget; + ModMatrix::TargetId volumeTarget; + ModMatrix::TargetId panTarget; + ModMatrix::TargetId positionTarget; + ModMatrix::TargetId widthTarget; + ModMatrix::TargetId pitchTarget; + PowerFollower powerFollower; LEAK_DETECTOR(Voice); diff --git a/src/sfizz/modulations/ModMatrix.cpp b/src/sfizz/modulations/ModMatrix.cpp index a8c1f8fe..4533d156 100644 --- a/src/sfizz/modulations/ModMatrix.cpp +++ b/src/sfizz/modulations/ModMatrix.cpp @@ -49,6 +49,13 @@ struct ModMatrix::Impl { absl::flat_hash_map sourceIndex_; absl::flat_hash_map targetIndex_; + std::vector sourceIndicesForGlobal_; + std::vector targetIndicesForGlobal_; + + int maxRegionIdx_ { -1 }; + std::vector> sourceIndicesForRegion_; + std::vector> targetIndicesForRegion_; + std::vector sources_; std::vector targets_; }; @@ -72,6 +79,11 @@ void ModMatrix::clear() impl.targetIndex_.clear(); impl.sources_.clear(); impl.targets_.clear(); + impl.sourceIndicesForGlobal_.clear(); + impl.targetIndicesForGlobal_.clear(); + impl.sourceIndicesForRegion_.clear(); + impl.targetIndicesForRegion_.clear(); + impl.maxRegionIdx_ = -1; } void ModMatrix::setSampleRate(double sampleRate) @@ -124,6 +136,8 @@ ModMatrix::SourceId ModMatrix::registerSource(const ModKey& key, ModGenerator& g source.buffer.resize(impl.samplesPerBlock_); impl.sourceIndex_[key] = id.number(); + if (key.region().number() > impl.maxRegionIdx_) + impl.maxRegionIdx_ = key.region().number(); gen.setSampleRate(impl.sampleRate_); gen.setSamplesPerBlock(impl.samplesPerBlock_); @@ -148,6 +162,9 @@ ModMatrix::TargetId ModMatrix::registerTarget(const ModKey& key) target.buffer.resize(impl.samplesPerBlock_); impl.targetIndex_[key] = id.number(); + if (key.region().number() > impl.maxRegionIdx_) + impl.maxRegionIdx_ = key.region().number(); + return id; } @@ -193,21 +210,50 @@ void ModMatrix::init() { Impl& impl = *impl_; - for (Impl::Source &source : impl.sources_) { + if (impl.maxRegionIdx_ >= 0) { + const size_t numRegions = impl.maxRegionIdx_ + 1; + impl.sourceIndicesForRegion_.resize(numRegions); + impl.targetIndicesForRegion_.resize(numRegions); + } + + for (unsigned i = 0; i < impl.sources_.size(); ++i) { + Impl::Source& source = impl.sources_[i]; const int flags = source.key.flags(); - if (flags & kModIsPerCycle) + if (flags & kModIsPerCycle) { + ASSERT(!source.key.region()); source.gen->init(source.key, {}, 0); + impl.sourceIndicesForGlobal_.push_back(i); + } + else if (flags & kModIsPerVoice) { + ASSERT(source.key.region()); + impl.sourceIndicesForRegion_[source.key.region().number()].push_back(i); + } + } + + for (unsigned i = 0; i < impl.targets_.size(); ++i) { + Impl::Target& target = impl.targets_[i]; + const int flags = target.key.flags(); + if (flags & kModIsPerCycle) { + ASSERT(!target.key.region()); + impl.targetIndicesForGlobal_.push_back(i); + } + else if (flags & kModIsPerVoice) { + ASSERT(target.key.region()); + impl.targetIndicesForRegion_[target.key.region().number()].push_back(i); + } } } void ModMatrix::initVoice(NumericId voiceId, NumericId regionId, unsigned delay) { Impl& impl = *impl_; + ASSERT(regionId); + ASSERT(static_cast(regionId.number()) < impl.sourceIndicesForRegion_.size()); - for (Impl::Source &source : impl.sources_) { - const int flags = source.key.flags(); - if ((flags & kModIsPerVoice) && source.key.region() == regionId) - source.gen->init(source.key, voiceId, delay); + const auto idNumber = static_cast(regionId.number()); + for (auto idx: impl.sourceIndicesForRegion_[idNumber]) { + const Impl::Source& source = impl.sources_[idx]; + source.gen->init(source.key, voiceId, delay); } } @@ -215,10 +261,12 @@ void ModMatrix::releaseVoice(NumericId voiceId, NumericId regionI { Impl& impl = *impl_; - for (Impl::Source &source : impl.sources_) { - const int flags = source.key.flags(); - if ((flags & kModIsPerVoice) && source.key.region() == regionId) - source.gen->release(source.key, voiceId, delay); + ASSERT(regionId); + + const auto idNumber = static_cast(regionId.number()); + for (auto idx: impl.sourceIndicesForRegion_[idNumber]) { + const Impl::Source& source = impl.sources_[idx]; + source.gen->release(source.key, voiceId, delay); } } @@ -228,10 +276,14 @@ void ModMatrix::beginCycle(unsigned numFrames) impl.numFrames_ = numFrames; - for (Impl::Source &source : impl.sources_) + for (auto idx: impl.sourceIndicesForGlobal_) { + Impl::Source& source = impl.sources_[idx]; source.bufferReady = false; - for (Impl::Target &target : impl.targets_) + } + for (auto idx: impl.targetIndicesForGlobal_) { + Impl::Target& target = impl.targets_[idx]; target.bufferReady = false; + } } void ModMatrix::endCycle() @@ -239,13 +291,11 @@ void ModMatrix::endCycle() Impl& impl = *impl_; const uint32_t numFrames = impl.numFrames_; - for (Impl::Source &source : impl.sources_) { + for (auto idx: impl.sourceIndicesForGlobal_) { + Impl::Source& source = impl.sources_[idx]; if (!source.bufferReady) { - const int flags = source.key.flags(); - if (flags & kModIsPerCycle) { - absl::Span buffer(source.buffer.data(), numFrames); - source.gen->generateDiscarded(source.key, {}, buffer); - } + absl::Span buffer(source.buffer.data(), numFrames); + source.gen->generateDiscarded(source.key, {}, buffer); } } @@ -259,15 +309,17 @@ void ModMatrix::beginVoice(NumericId voiceId, NumericId regionId) impl.currentVoiceId_ = voiceId; impl.currentRegionId_ = regionId; - for (Impl::Source &source : impl.sources_) { - const int flags = source.key.flags(); - if (flags & kModIsPerVoice) - source.bufferReady = false; + ASSERT(regionId); + + const auto idNumber = static_cast(regionId.number()); + for (auto idx: impl.sourceIndicesForRegion_[idNumber]) { + Impl::Source& source = impl.sources_[idx]; + source.bufferReady = false; } - for (Impl::Target &target : impl.targets_) { - const int flags = target.key.flags(); - if (flags & kModIsPerVoice) - target.bufferReady = false; + + for (auto idx: impl.targetIndicesForRegion_[idNumber]) { + Impl::Target& target = impl.targets_[idx]; + target.bufferReady = false; } } @@ -278,13 +330,16 @@ void ModMatrix::endVoice() const NumericId voiceId = impl.currentVoiceId_; const NumericId regionId = impl.currentRegionId_; - for (Impl::Source &source : impl.sources_) { + ASSERT(regionId); + ASSERT(static_cast(regionId.number()) < impl.sourceIndicesForRegion_.size()); + + const auto idNumber = static_cast(regionId.number()); + + for (auto idx: impl.sourceIndicesForRegion_[idNumber]) { + const Impl::Source& source = impl.sources_[idx]; if (!source.bufferReady) { - const int flags = source.key.flags(); - if ((flags & kModIsPerVoice) && source.key.region() == regionId) { - absl::Span buffer(source.buffer.data(), numFrames); - source.gen->generateDiscarded(source.key, voiceId, buffer); - } + absl::Span buffer(source.buffer.data(), numFrames); + source.gen->generateDiscarded(source.key, voiceId, buffer); } }