From 958d9bb5e87f4606f38d68b25af0421ece09d76b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Thu, 20 Aug 2020 02:23:02 +0200 Subject: [PATCH 1/9] Simpler power/envelope followerThis one is much cheaper... --- src/sfizz/Config.h | 2 +- src/sfizz/Voice.cpp | 29 ++++++++++------------------- src/sfizz/Voice.h | 9 +++------ src/sfizz/VoiceStealing.cpp | 14 +++++++------- 4 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 2956dae6..d3c301ba 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -57,7 +57,7 @@ namespace config { constexpr Oversampling defaultOversamplingFactor { Oversampling::x1 }; constexpr float A440 { 440.0 }; constexpr size_t powerHistoryLength { 16 }; - constexpr float filteredEnvelopeCutoff { 5 }; + constexpr float powerFollowerFactor { 10 }; constexpr uint16_t numCCs { 512 }; constexpr int maxCurves { 256 }; constexpr int chunkSize { 1024 }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d7517a3d..8b1a0bed 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -30,9 +30,6 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources) gainSmoother.setSmoothing(config::gainSmoothing, sampleRate); xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate); - - for (auto & filter : channelEnvelopeFilters) - filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate)); } sfz::Voice::~Voice() @@ -246,20 +243,19 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept gainSmoother.setSmoothing(config::gainSmoothing, sampleRate); xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate); - for (auto & filter : channelEnvelopeFilters) - filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate)); - for (WavetableOscillator& osc : waveOscillators) osc.init(sampleRate); for (auto& lfo : lfos) lfo->setSampleRate(sampleRate); + + trackingFactor = samplesPerBlock / sampleRate * config::powerFollowerFactor; } void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; - this->minEnvelopeDelay = samplesPerBlock / 2; + this->trackingFactor = samplesPerBlock / sampleRate * config::powerFollowerFactor; } void sfz::Voice::renderBlock(AudioSpan buffer) noexcept @@ -736,10 +732,7 @@ void sfz::Voice::reset() noexcept floatPositionOffset = 0.0f; noteIsOff = false; - for (auto& f : channelEnvelopeFilters) - f.reset(); - - for (auto& p : smoothedChannelEnvelopes) + for (auto& p : meanChannelPowers) p = 0.0f; filters.clear(); @@ -770,9 +763,9 @@ void sfz::Voice::removeVoiceFromRing() noexcept nextSisterVoice = this; } -float sfz::Voice::getAverageEnvelope() const noexcept +float sfz::Voice::getAveragePower() const noexcept { - return max(smoothedChannelEnvelopes[0], smoothedChannelEnvelopes[1]); + return max(meanChannelPowers[0], meanChannelPowers[1]); } bool sfz::Voice::releasedOrFree() const noexcept @@ -869,16 +862,14 @@ void sfz::Voice::setupOscillatorUnison() void sfz::Voice::updateChannelPowers(AudioSpan buffer) { - assert(smoothedChannelEnvelopes.size() == channelEnvelopeFilters.size()); - assert(buffer.getNumChannels() <= channelEnvelopeFilters.size()); if (buffer.getNumFrames() == 0) return; - for (unsigned i = 0; i < smoothedChannelEnvelopes.size(); ++i) { + const float factor = buffer.getNumFrames() / samplesPerBlock * trackingFactor; + for (unsigned i = 0; i < meanChannelPowers.size(); ++i) { const auto input = buffer.getConstSpan(i); - for (unsigned s = 0; s < buffer.getNumFrames(); ++s) - smoothedChannelEnvelopes[i] = - channelEnvelopeFilters[i].tickLowpass(std::abs(input[s])); + const float meanPower = sfz::meanSquared(input); + meanChannelPowers[i] = meanChannelPowers[i] * (1 - factor) + meanPower * factor; } } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 5b72e799..facd3c1d 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -261,7 +261,7 @@ public: * * @return float */ - float getAverageEnvelope() const noexcept; + float getAveragePower() const noexcept; /** * @brief Get the position of the voice in the source, in samples * @@ -450,7 +450,6 @@ private: FilePromisePtr currentPromise { nullptr }; int samplesPerBlock { config::defaultSamplesPerBlock }; - int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 }; float sampleRate { config::defaultSampleRate }; Resources& resources; @@ -486,10 +485,8 @@ private: Smoother xfadeSmoother; void resetSmoothers() noexcept; - std::array, 2> channelEnvelopeFilters; - std::array smoothedChannelEnvelopes; - - HistoricalBuffer powerHistory { config::powerHistoryLength }; + float trackingFactor { config::defaultSamplesPerBlock / config::defaultSampleRate * config::powerFollowerFactor }; + std::array meanChannelPowers; LEAK_DETECTOR(Voice); }; diff --git a/src/sfizz/VoiceStealing.cpp b/src/sfizz/VoiceStealing.cpp index 49e1b2d0..0c935df5 100644 --- a/src/sfizz/VoiceStealing.cpp +++ b/src/sfizz/VoiceStealing.cpp @@ -13,12 +13,12 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept // Start of the voice stealing algorithm absl::c_stable_sort(voices, voiceOrdering); - const auto sumEnvelope = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { - return sum + v->getAverageEnvelope(); + const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { + return sum + v->getAveragePower(); }); - // We are checking the envelope to try and kill voices with relative low contribution + // We are checking the power to try and kill voices with relative low contribution // to the output compared to the rest. - const auto envThreshold = sumEnvelope + const auto powerThreshold = sumPower / static_cast(voices.size()) * config::stealingEnvelopeCoeff; // 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 @@ -37,12 +37,12 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept break; } - float maxEnvelope { 0.0f }; + float maxPower { 0.0f }; SisterVoiceRing::applyToRing(ref, [&](Voice* v) { - maxEnvelope = max(maxEnvelope, v->getAverageEnvelope()); + maxPower = max(maxPower, v->getAveragePower()); }); - if (maxEnvelope < envThreshold) { + if (maxPower < powerThreshold) { returnedVoice = ref; break; } From afc9d72c8968f4069f8b123fe3fb4ea94f79a3b9 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 21 Aug 2020 11:17:44 +0200 Subject: [PATCH 2/9] Further changes to the envelope follower- Apply on a single channel- Track attack and release separately --- src/sfizz/Config.h | 9 +++++---- src/sfizz/Voice.cpp | 32 +++++++++++++++++++++----------- src/sfizz/Voice.h | 5 +++-- src/sfizz/VoiceStealing.cpp | 2 +- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index d3c301ba..fc48aab5 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -57,7 +57,8 @@ namespace config { constexpr Oversampling defaultOversamplingFactor { Oversampling::x1 }; constexpr float A440 { 440.0 }; constexpr size_t powerHistoryLength { 16 }; - constexpr float powerFollowerFactor { 10 }; + constexpr float powerFollowerAttackFactor { 100 }; + constexpr float powerFollowerReleaseFactor { 10 }; constexpr uint16_t numCCs { 512 }; constexpr int maxCurves { 256 }; constexpr int chunkSize { 1024 }; @@ -72,10 +73,10 @@ namespace config { */ constexpr float stealingAgeCoeff { 0.5f }; /** - * @brief The threshold for envelope stealing. - * In percentage of the sum of all envelopes. + * @brief The threshold for power stealing. + * In percentage of the sum of all powers. */ - constexpr float stealingEnvelopeCoeff { 0.5f }; + constexpr float stealingPowerCoeff { 0.5f }; constexpr int filtersPerVoice { 2 }; constexpr int eqsPerVoice { 3 }; constexpr int oscillatorsPerVoice { 9 }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 8b1a0bed..54c5ebc2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -249,13 +249,13 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept for (auto& lfo : lfos) lfo->setSampleRate(sampleRate); - trackingFactor = samplesPerBlock / sampleRate * config::powerFollowerFactor; + attackTrackingFactor = config::powerFollowerAttackFactor / sampleRate; + releaseTrackingFactor = config::powerFollowerReleaseFactor / sampleRate; } void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; - this->trackingFactor = samplesPerBlock / sampleRate * config::powerFollowerFactor; } void sfz::Voice::renderBlock(AudioSpan buffer) noexcept @@ -732,8 +732,7 @@ void sfz::Voice::reset() noexcept floatPositionOffset = 0.0f; noteIsOff = false; - for (auto& p : meanChannelPowers) - p = 0.0f; + meanChannelPower = 0.0f; filters.clear(); equalizers.clear(); @@ -765,7 +764,7 @@ void sfz::Voice::removeVoiceFromRing() noexcept float sfz::Voice::getAveragePower() const noexcept { - return max(meanChannelPowers[0], meanChannelPowers[1]); + return meanChannelPower; } bool sfz::Voice::releasedOrFree() const noexcept @@ -865,12 +864,23 @@ void sfz::Voice::updateChannelPowers(AudioSpan buffer) if (buffer.getNumFrames() == 0) return; - const float factor = buffer.getNumFrames() / samplesPerBlock * trackingFactor; - for (unsigned i = 0; i < meanChannelPowers.size(); ++i) { - const auto input = buffer.getConstSpan(i); - const float meanPower = sfz::meanSquared(input); - meanChannelPowers[i] = meanChannelPowers[i] * (1 - factor) + meanPower * factor; - } + auto tempBuffer = resources.bufferPool.getBuffer(buffer.getNumFrames()); + if (!tempBuffer) + return; + + sfz::copy(buffer.getConstSpan(0), *tempBuffer); + for (unsigned i = 1; i < buffer.getNumChannels(); ++i) + sfz::add(buffer.getConstSpan(i), *tempBuffer); + + const float meanPower = sfz::meanSquared(*tempBuffer); + + const float attackFactor = static_cast(buffer.getNumFrames()) * attackTrackingFactor; + const float releaseFactor = static_cast(buffer.getNumFrames()) * releaseTrackingFactor; + + meanChannelPower = max( + meanChannelPower * (1 - attackFactor) + meanPower * attackFactor, + meanChannelPower * (1 - releaseFactor) + meanPower * releaseFactor + ); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index facd3c1d..c2b3d1c4 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -485,8 +485,9 @@ private: Smoother xfadeSmoother; void resetSmoothers() noexcept; - float trackingFactor { config::defaultSamplesPerBlock / config::defaultSampleRate * config::powerFollowerFactor }; - std::array meanChannelPowers; + float attackTrackingFactor { config::powerFollowerAttackFactor / config::defaultSampleRate }; + float releaseTrackingFactor { config::powerFollowerReleaseFactor / config::defaultSampleRate }; + float meanChannelPower; LEAK_DETECTOR(Voice); }; diff --git a/src/sfizz/VoiceStealing.cpp b/src/sfizz/VoiceStealing.cpp index 0c935df5..0fa4f1a7 100644 --- a/src/sfizz/VoiceStealing.cpp +++ b/src/sfizz/VoiceStealing.cpp @@ -19,7 +19,7 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept // We are checking the power to try and kill voices with relative low contribution // to the output compared to the rest. const auto powerThreshold = sumPower - / static_cast(voices.size()) * config::stealingEnvelopeCoeff; + / static_cast(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 From dd8134277c45c283d89d25817aa8abfce9ca345c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 21 Aug 2020 11:36:09 +0200 Subject: [PATCH 3/9] Clamp the tracking factors to avoid blowups --- src/sfizz/Voice.cpp | 12 ++++++++++-- src/sfizz/Voice.h | 6 ++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 54c5ebc2..caaa718f 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -237,6 +237,14 @@ void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept UNUSED(secondsPerQuarter); } +void sfz::Voice::updateTrackingFactor() noexcept +{ + // Protect the envelope follower against blowups + const auto maxTrackingFactor = sampleRate / samplesPerBlock; + attackTrackingFactor = min(config::powerFollowerAttackFactor, maxTrackingFactor) / sampleRate; + releaseTrackingFactor = min(config::powerFollowerReleaseFactor, maxTrackingFactor) / sampleRate; +} + void sfz::Voice::setSampleRate(float sampleRate) noexcept { this->sampleRate = sampleRate; @@ -249,13 +257,13 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept for (auto& lfo : lfos) lfo->setSampleRate(sampleRate); - attackTrackingFactor = config::powerFollowerAttackFactor / sampleRate; - releaseTrackingFactor = config::powerFollowerReleaseFactor / sampleRate; + updateTrackingFactor(); } void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; + updateTrackingFactor(); } void sfz::Voice::renderBlock(AudioSpan buffer) noexcept diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index c2b3d1c4..5881e79c 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -485,6 +485,12 @@ private: Smoother xfadeSmoother; void resetSmoothers() noexcept; + /** + * @brief Update and clamp the tracking factors to ensure the power + * follower does not blow up. + * + */ + void updateTrackingFactor() noexcept; float attackTrackingFactor { config::powerFollowerAttackFactor / config::defaultSampleRate }; float releaseTrackingFactor { config::powerFollowerReleaseFactor / config::defaultSampleRate }; float meanChannelPower; From cb144ad42f5903ecba79fa0888eee31965c095e9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Aug 2020 04:56:56 +0200 Subject: [PATCH 4/9] Move the power follower to its own file --- dpf.mk | 1 + src/CMakeLists.txt | 2 + src/sfizz/PowerFollower.cpp | 75 +++++++++++++++++++++++++++++++++++++ src/sfizz/PowerFollower.h | 37 ++++++++++++++++++ src/sfizz/Voice.cpp | 43 +++------------------ src/sfizz/Voice.h | 12 ++---- 6 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 src/sfizz/PowerFollower.cpp create mode 100644 src/sfizz/PowerFollower.h diff --git a/dpf.mk b/dpf.mk index 5507e79b..2ee507e8 100644 --- a/dpf.mk +++ b/dpf.mk @@ -103,6 +103,7 @@ SFIZZ_SOURCES = \ src/sfizz/Parser.cpp \ src/sfizz/parser/Parser.cpp \ src/sfizz/parser/ParserPrivate.cpp \ + src/sfizz/PowerFollower.cpp \ src/sfizz/Region.cpp \ src/sfizz/RTSemaphore.cpp \ src/sfizz/ScopedFTZ.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e97af140..ed19f1b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,6 +81,7 @@ set (SFIZZ_HEADERS sfizz/Oversampler.h sfizz/Panning.h sfizz/PolyphonyGroup.h + sfizz/PowerFollower.h sfizz/railsback/2-1.h sfizz/railsback/4-1.h sfizz/railsback/4-2.h @@ -138,6 +139,7 @@ set (SFIZZ_SOURCES sfizz/Effects.cpp sfizz/LFO.cpp sfizz/LFODescription.cpp + sfizz/PowerFollower.cpp sfizz/modulations/ModId.cpp sfizz/modulations/ModKey.cpp sfizz/modulations/ModKeyHash.cpp diff --git a/src/sfizz/PowerFollower.cpp b/src/sfizz/PowerFollower.cpp new file mode 100644 index 00000000..56ccb86e --- /dev/null +++ b/src/sfizz/PowerFollower.cpp @@ -0,0 +1,75 @@ +// 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 "PowerFollower.h" +#include "Defaults.h" +#include "SIMDHelpers.h" +#include + +namespace sfz { + +PowerFollower::PowerFollower() + : sampleRate_(config::defaultSampleRate), + samplesPerBlock_(config::defaultSamplesPerBlock), + tempBuffer_(new float[config::defaultSamplesPerBlock]) +{ + updateTrackingFactor(); +} + +void PowerFollower::setSampleRate(float sampleRate) noexcept +{ + if (sampleRate_ != sampleRate) { + sampleRate_ = sampleRate; + updateTrackingFactor(); + } +} + +void PowerFollower::setSamplesPerBlock(unsigned samplesPerBlock) +{ + if (samplesPerBlock_ != samplesPerBlock) { + tempBuffer_.reset(new float[samplesPerBlock]); + samplesPerBlock_ = samplesPerBlock; + updateTrackingFactor(); + } +} + +void PowerFollower::process(AudioSpan buffer) noexcept +{ + size_t numFrames = buffer.getNumFrames(); + if (numFrames == 0) + return; + + absl::Span tempBuffer(tempBuffer_.get(), numFrames); + + copy(buffer.getConstSpan(0), tempBuffer); + for (unsigned i = 1; i < buffer.getNumChannels(); ++i) + add(buffer.getConstSpan(i), tempBuffer); + + const float meanPower = meanSquared(tempBuffer); + + const float attackFactor = static_cast(buffer.getNumFrames()) * attackTrackingFactor_; + const float releaseFactor = static_cast(buffer.getNumFrames()) * releaseTrackingFactor_; + + meanChannelPower_ = max( + meanChannelPower_ * (1 - attackFactor) + meanPower * attackFactor, + meanChannelPower_ * (1 - releaseFactor) + meanPower * releaseFactor + ); +} + +void PowerFollower::clear() noexcept +{ + meanChannelPower_ = 0; +} + +void PowerFollower::updateTrackingFactor() noexcept +{ + // Protect the envelope follower against blowups + const auto maxTrackingFactor = sampleRate_ / samplesPerBlock_; + attackTrackingFactor_ = min(config::powerFollowerAttackFactor, maxTrackingFactor) / sampleRate_; + releaseTrackingFactor_ = min(config::powerFollowerReleaseFactor, maxTrackingFactor) / sampleRate_; +} + +} // namespace sfz diff --git a/src/sfizz/PowerFollower.h b/src/sfizz/PowerFollower.h new file mode 100644 index 00000000..2deff3d5 --- /dev/null +++ b/src/sfizz/PowerFollower.h @@ -0,0 +1,37 @@ +// 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 "AudioSpan.h" +#include + +namespace sfz { + +class PowerFollower { +public: + PowerFollower(); + void setSampleRate(float sampleRate) noexcept; + void setSamplesPerBlock(unsigned samplesPerBlock); + void process(AudioSpan buffer) noexcept; + void clear() noexcept; + float getAveragePower() const noexcept { return meanChannelPower_; } + +private: + void updateTrackingFactor() noexcept; + +private: + float sampleRate_ {}; + unsigned samplesPerBlock_ {}; + + std::unique_ptr tempBuffer_; + + float attackTrackingFactor_ {}; + float releaseTrackingFactor_ {}; + + float meanChannelPower_ {}; +}; + +} // namespace sfz diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index caaa718f..53e399f4 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -237,14 +237,6 @@ void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept UNUSED(secondsPerQuarter); } -void sfz::Voice::updateTrackingFactor() noexcept -{ - // Protect the envelope follower against blowups - const auto maxTrackingFactor = sampleRate / samplesPerBlock; - attackTrackingFactor = min(config::powerFollowerAttackFactor, maxTrackingFactor) / sampleRate; - releaseTrackingFactor = min(config::powerFollowerReleaseFactor, maxTrackingFactor) / sampleRate; -} - void sfz::Voice::setSampleRate(float sampleRate) noexcept { this->sampleRate = sampleRate; @@ -257,13 +249,13 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept for (auto& lfo : lfos) lfo->setSampleRate(sampleRate); - updateTrackingFactor(); + powerFollower.setSampleRate(sampleRate); } void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; - updateTrackingFactor(); + powerFollower.setSamplesPerBlock(samplesPerBlock); } void sfz::Voice::renderBlock(AudioSpan buffer) noexcept @@ -299,7 +291,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept if (!egEnvelope.isSmoothing()) switchState(State::cleanMeUp); - updateChannelPowers(buffer); + powerFollower.process(buffer); age += buffer.getNumFrames(); if (triggerDelay) { @@ -740,7 +732,7 @@ void sfz::Voice::reset() noexcept floatPositionOffset = 0.0f; noteIsOff = false; - meanChannelPower = 0.0f; + powerFollower.clear(); filters.clear(); equalizers.clear(); @@ -772,7 +764,7 @@ void sfz::Voice::removeVoiceFromRing() noexcept float sfz::Voice::getAveragePower() const noexcept { - return meanChannelPower; + return powerFollower.getAveragePower(); } bool sfz::Voice::releasedOrFree() const noexcept @@ -867,31 +859,6 @@ void sfz::Voice::setupOscillatorUnison() #endif } -void sfz::Voice::updateChannelPowers(AudioSpan buffer) -{ - if (buffer.getNumFrames() == 0) - return; - - auto tempBuffer = resources.bufferPool.getBuffer(buffer.getNumFrames()); - if (!tempBuffer) - return; - - sfz::copy(buffer.getConstSpan(0), *tempBuffer); - for (unsigned i = 1; i < buffer.getNumChannels(); ++i) - sfz::add(buffer.getConstSpan(i), *tempBuffer); - - const float meanPower = sfz::meanSquared(*tempBuffer); - - const float attackFactor = static_cast(buffer.getNumFrames()) * attackTrackingFactor; - const float releaseFactor = static_cast(buffer.getNumFrames()) * releaseTrackingFactor; - - meanChannelPower = max( - meanChannelPower * (1 - attackFactor) + meanPower * attackFactor, - meanChannelPower * (1 - releaseFactor) + meanPower * releaseFactor - ); -} - - void sfz::Voice::switchState(State s) { if (s != state) { diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 5881e79c..0aec391e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -15,6 +15,7 @@ #include "AudioSpan.h" #include "LeakDetector.h" #include "OnePoleFilter.h" +#include "PowerFollower.h" #include "NumericId.h" #include "absl/types/span.h" #include @@ -485,15 +486,8 @@ private: Smoother xfadeSmoother; void resetSmoothers() noexcept; - /** - * @brief Update and clamp the tracking factors to ensure the power - * follower does not blow up. - * - */ - void updateTrackingFactor() noexcept; - float attackTrackingFactor { config::powerFollowerAttackFactor / config::defaultSampleRate }; - float releaseTrackingFactor { config::powerFollowerReleaseFactor / config::defaultSampleRate }; - float meanChannelPower; + PowerFollower powerFollower; + LEAK_DETECTOR(Voice); }; From 4c3db6c2c8547d75e65c0233145a091fad08f631 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Aug 2020 05:32:39 +0200 Subject: [PATCH 5/9] Add benchmark --- benchmarks/BM_powerFollower.cpp | 60 +++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 2 ++ 2 files changed, 62 insertions(+) create mode 100644 benchmarks/BM_powerFollower.cpp diff --git a/benchmarks/BM_powerFollower.cpp b/benchmarks/BM_powerFollower.cpp new file mode 100644 index 00000000..4358f596 --- /dev/null +++ b/benchmarks/BM_powerFollower.cpp @@ -0,0 +1,60 @@ +// 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 "PowerFollower.h" +#include "AudioBuffer.h" +#include "Config.h" +#include +#include + +class PowerFollowerFixture : public benchmark::Fixture { +public: + PowerFollowerFixture() + { + inputSignal_ = sfz::AudioBuffer(2, numFrames); + auto leftSignal = inputSignal_.getSpan(0); + auto rightSignal = inputSignal_.getSpan(1); + float phase = 0; + for (size_t i = 0; i < numFrames; ++i) { + constexpr float k2pi = 2.0 * M_PI; + leftSignal[i] = std::sin(k2pi * phase); + rightSignal[i] = std::cos(k2pi * phase); + phase += 440.0f / sfz::config::defaultSampleRate; + phase -= static_cast(phase); + } + } + + void SetUp(const ::benchmark::State& state) + { + auto blockSize = static_cast(state.range(0)); + follower_.setSampleRate(sfz::config::defaultSampleRate); + follower_.setSamplesPerBlock(blockSize); + follower_.clear(); + } + + void TearDown(const ::benchmark::State& /* state */) + { + } + + static constexpr size_t numFrames = 65536; + sfz::PowerFollower follower_; + sfz::AudioBuffer inputSignal_; +}; + +constexpr size_t PowerFollowerFixture::numFrames; + +BENCHMARK_DEFINE_F(PowerFollowerFixture, Follower) (benchmark::State& state) +{ + sfz::AudioSpan inputSignal(inputSignal_); + for (auto _ : state) { + auto& follower = follower_; + auto blockSize = static_cast(state.range(0)); + for (size_t i = 0; i < numFrames; i += blockSize) + follower.process(inputSignal.subspan(i, blockSize)); + } +} + +BENCHMARK_REGISTER_F(PowerFollowerFixture, Follower)->RangeMultiplier(2)->Range(1 << 5, 1 << 12); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 71fd0b14..4efb3d82 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -66,6 +66,8 @@ sfizz_add_benchmark(bm_logger BM_logger.cpp) target_link_libraries(bm_logger PRIVATE sfizz::sfizz) sfizz_add_benchmark(bm_smoothers BM_smoothers.cpp) target_link_libraries(bm_smoothers PRIVATE sfizz::sfizz) +sfizz_add_benchmark(bm_powerFollower BM_powerFollower.cpp) +target_link_libraries(bm_powerFollower PRIVATE sfizz::sfizz) if (TARGET sfizz-samplerate) sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES}) From a5c72375a984c3d4fd2202c1fb18a9c9aedf8319 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Aug 2020 08:08:37 +0200 Subject: [PATCH 6/9] Move the div operation out of the SIMD helper --- benchmarks/BM_meanSquared.cpp | 8 ++++---- src/sfizz/SIMDHelpers.cpp | 12 ++++++------ src/sfizz/SIMDHelpers.h | 30 +++++++++++++++++++++++++----- src/sfizz/simd/HelpersSSE.cpp | 4 ++-- src/sfizz/simd/HelpersSSE.h | 2 +- src/sfizz/simd/HelpersScalar.h | 4 ++-- tests/SIMDHelpersT.cpp | 8 ++++---- 7 files changed, 44 insertions(+), 24 deletions(-) diff --git a/benchmarks/BM_meanSquared.cpp b/benchmarks/BM_meanSquared.cpp index 0b1c159e..34c29a8f 100644 --- a/benchmarks/BM_meanSquared.cpp +++ b/benchmarks/BM_meanSquared.cpp @@ -34,7 +34,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar) (benchmark::State& state) { for (auto _ : state) { - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, false); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, false); auto result = sfz::meanSquared(input); benchmark::DoNotOptimize(result); } @@ -44,7 +44,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD) (benchmark::State& state) { for (auto _ : state) { - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, true); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, true); auto result = sfz::meanSquared(input); benchmark::DoNotOptimize(result); } @@ -54,7 +54,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar_Unaligned) (benchmark::State& state) { for (auto _ : state) { - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, false); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, false); auto result = sfz::meanSquared(absl::MakeSpan(input).subspan(1)); benchmark::DoNotOptimize(result); } @@ -64,7 +64,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD_Unaligned) (benchmark::State& state) { for (auto _ : state) { - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, true); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, true); auto result = sfz::meanSquared(absl::MakeSpan(input).subspan(1)); benchmark::DoNotOptimize(result); } diff --git a/src/sfizz/SIMDHelpers.cpp b/src/sfizz/SIMDHelpers.cpp index c86cecf8..c0287772 100644 --- a/src/sfizz/SIMDHelpers.cpp +++ b/src/sfizz/SIMDHelpers.cpp @@ -39,7 +39,7 @@ struct SIMDDispatch { decltype(&cumsumScalar) cumsum = &cumsumScalar; decltype(&diffScalar) diff = &diffScalar; decltype(&meanScalar) mean = &meanScalar; - decltype(&meanSquaredScalar) meanSquared = &meanSquaredScalar; + decltype(&sumSquaresScalar) sumSquares = &sumSquaresScalar; decltype(&clampAllScalar) clampAll = &clampAllScalar; decltype(&allWithinScalar) allWithin = &allWithinScalar; @@ -85,7 +85,7 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(cumsum) SIMD_OP(diff) SIMD_OP(mean) - SIMD_OP(meanSquared) + SIMD_OP(sumSquares) SIMD_OP(clampAll) SIMD_OP(allWithin) } @@ -122,7 +122,7 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(cumsum) SIMD_OP(diff) SIMD_OP(mean) - SIMD_OP(meanSquared) + SIMD_OP(sumSquares) SIMD_OP(clampAll) SIMD_OP(allWithin) } @@ -163,7 +163,7 @@ void SIMDDispatch::resetStatus() setStatus(SIMDOps::diff, false); setStatus(SIMDOps::sfzInterpolationCast, true); setStatus(SIMDOps::mean, false); - setStatus(SIMDOps::meanSquared, false); + setStatus(SIMDOps::sumSquares, false); setStatus(SIMDOps::upsampling, true); setStatus(SIMDOps::clampAll, false); setStatus(SIMDOps::allWithin, true); @@ -292,9 +292,9 @@ float mean(const float* vector, unsigned size) noexcept } template <> -float meanSquared(const float* vector, unsigned size) noexcept +float sumSquares(const float* vector, unsigned size) noexcept { - return simdDispatch().meanSquared(vector, size); + return simdDispatch().sumSquares(vector, size); } template <> diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index c0a27c09..ae34f732 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -57,7 +57,7 @@ enum class SIMDOps { diff, sfzInterpolationCast, mean, - meanSquared, + sumSquares, upsampling, clampAll, allWithin, @@ -515,7 +515,7 @@ T mean(absl::Span vector) noexcept } /** - * @brief Computes the mean squared of a span + * @brief Computes the sum of squares of a span * * @tparam T the underlying type * @tparam SIMD use the SIMD version or the scalar version @@ -523,13 +523,33 @@ T mean(absl::Span vector) noexcept * @return T */ template -T meanSquared(const T* vector, unsigned size) noexcept +T sumSquares(const T* vector, unsigned size) noexcept { - meanSquaredScalar(vector, size); + return sumSquaresScalar(vector, size); } template <> -float meanSquared(const float* vector, unsigned size) noexcept; +float sumSquares(const float* vector, unsigned size) noexcept; + +template +T sumSquares(absl::Span vector) noexcept +{ + return sumSquares(vector.data(), vector.size()); +} + +/** + * @brief Computes the mean squared of a span + * + * @tparam T the underlying type + * @param vector + * @return T + */ +template +T meanSquared(const T* vector, unsigned size) noexcept +{ + T sum = sumSquares(vector, size); + return sum / size; +} template T meanSquared(absl::Span vector) noexcept diff --git a/src/sfizz/simd/HelpersSSE.cpp b/src/sfizz/simd/HelpersSSE.cpp index 3ae8b984..d17288e7 100644 --- a/src/sfizz/simd/HelpersSSE.cpp +++ b/src/sfizz/simd/HelpersSSE.cpp @@ -370,7 +370,7 @@ float meanSSE(const float* vector, unsigned size) noexcept return result / static_cast(size); } -float meanSquaredSSE(const float* vector, unsigned size) noexcept +float sumSquaresSSE(const float* vector, unsigned size) noexcept { const auto sentinel = vector + size; @@ -404,7 +404,7 @@ float meanSquaredSSE(const float* vector, unsigned size) noexcept vector++; } - return result / static_cast(size); + return result; } void cumsumSSE(const float* input, float* output, unsigned size) noexcept diff --git a/src/sfizz/simd/HelpersSSE.h b/src/sfizz/simd/HelpersSSE.h index cff28650..5046d914 100644 --- a/src/sfizz/simd/HelpersSSE.h +++ b/src/sfizz/simd/HelpersSSE.h @@ -22,7 +22,7 @@ void subtractSSE(const float* input, float* output, unsigned size) noexcept; void subtract1SSE(float value, float* output, unsigned size) noexcept; void copySSE(const float* input, float* output, unsigned size) noexcept; float meanSSE(const float* vector, unsigned size) noexcept; -float meanSquaredSSE(const float* vector, unsigned size) noexcept; +float sumSquaresSSE(const float* vector, unsigned size) noexcept; void cumsumSSE(const float* input, float* output, unsigned size) noexcept; void diffSSE(const float* input, float* output, unsigned size) noexcept; void clampAllSSE(float* input, float low, float high, unsigned size) noexcept; diff --git a/src/sfizz/simd/HelpersScalar.h b/src/sfizz/simd/HelpersScalar.h index d5ac1162..871a9ff6 100644 --- a/src/sfizz/simd/HelpersScalar.h +++ b/src/sfizz/simd/HelpersScalar.h @@ -142,7 +142,7 @@ T meanScalar(const T* vector, unsigned size) noexcept } template -T meanSquaredScalar(const T* vector, unsigned size) noexcept +T sumSquaresScalar(const T* vector, unsigned size) noexcept { T result{ 0.0 }; if (size == 0) @@ -154,7 +154,7 @@ T meanSquaredScalar(const T* vector, unsigned size) noexcept vector++; } - return result / static_cast(size); + return result; } template diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 805bb0f9..f0ef8277 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -690,9 +690,9 @@ TEST_CASE("[Helpers] Mean (SIMD vs scalar)") TEST_CASE("[Helpers] Mean Squared") { std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f }; - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, false); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, false); REQUIRE(sfz::meanSquared(input) == 38.5f); - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, true); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, true); REQUIRE(sfz::meanSquared(input) == 38.5f); } @@ -700,9 +700,9 @@ TEST_CASE("[Helpers] Mean Squared (SIMD vs scalar)") { std::vector input(medBufferSize); absl::c_iota(input, 0.0f); - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, false); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, false); auto scalarResult = sfz::meanSquared(input); - sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, true); + sfz::setSIMDOpStatus(sfz::SIMDOps::sumSquares, true); auto simdResult = sfz::meanSquared(input); REQUIRE( scalarResult == Approx(simdResult).margin(1e-3) ); } From 732222b8d36c3e09c7896b96cef777197953c910 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Aug 2020 09:05:05 +0200 Subject: [PATCH 7/9] Add a reference follower to the benchmark --- benchmarks/BM_powerFollower.cpp | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/benchmarks/BM_powerFollower.cpp b/benchmarks/BM_powerFollower.cpp index 4358f596..6444e0e6 100644 --- a/benchmarks/BM_powerFollower.cpp +++ b/benchmarks/BM_powerFollower.cpp @@ -33,6 +33,10 @@ public: follower_.setSampleRate(sfz::config::defaultSampleRate); follower_.setSamplesPerBlock(blockSize); follower_.clear(); + + // + refFollower_.init(sfz::config::defaultSampleRate); + refFollower_.clear(); } void TearDown(const ::benchmark::State& /* state */) @@ -42,10 +46,70 @@ public: static constexpr size_t numFrames = 65536; sfz::PowerFollower follower_; sfz::AudioBuffer inputSignal_; + + // + struct ReferenceFollower { + /* + import("stdfaust.lib"); + process = (_, _) : + : an.amp_follower_ud(att, rel) with { att = 5e-3; rel = 200e-3; }; + */ + + void init(float sampleRate) + { + fConst0 = std::min(192000.0f, std::max(1.0f, float(sampleRate))); + fConst1 = std::exp((0.0f - (200.0f / fConst0))); + fConst2 = (1.0f - fConst1); + fConst3 = std::exp((0.0f - (5.0f / fConst0))); + fConst4 = (1.0f - fConst3); + } + void clear() + { + for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { + fRec1[l0] = 0.0f; + } + for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { + fRec0[l1] = 0.0f; + } + } + float process(float input0, float input1) + { + float fTemp0 = std::fabs((float(input0) + float(input1))); + fRec1[0] = std::max(fTemp0, ((fConst3 * fRec1[1]) + (fConst4 * fTemp0))); + fRec0[0] = ((fConst1 * fRec0[1]) + (fConst2 * fRec1[0])); + float output = fRec0[0]; + fRec1[1] = fRec1[0]; + fRec0[1] = fRec0[0]; + return output; + } + + float fConst0; + float fConst1; + float fConst2; + float fConst3; + float fConst4; + float fRec1[2]; + float fRec0[2]; + }; + ReferenceFollower refFollower_; }; constexpr size_t PowerFollowerFixture::numFrames; +BENCHMARK_DEFINE_F(PowerFollowerFixture, ReferenceFollower) (benchmark::State& state) +{ + sfz::AudioSpan inputSignal(inputSignal_); + auto input0 = inputSignal.getConstSpan(0); + auto input1 = inputSignal.getConstSpan(1); + + for (auto _ : state) { + auto& follower = refFollower_; + float output = 0; + for (size_t i = 0, n = inputSignal.getNumFrames(); i < n; ++i) + output = follower.process(input0[i], input1[i]); + benchmark::DoNotOptimize(output); + } +} + BENCHMARK_DEFINE_F(PowerFollowerFixture, Follower) (benchmark::State& state) { sfz::AudioSpan inputSignal(inputSignal_); @@ -57,4 +121,5 @@ BENCHMARK_DEFINE_F(PowerFollowerFixture, Follower) (benchmark::State& state) } } +BENCHMARK_REGISTER_F(PowerFollowerFixture, ReferenceFollower)->Range(1, 1); BENCHMARK_REGISTER_F(PowerFollowerFixture, Follower)->RangeMultiplier(2)->Range(1 << 5, 1 << 12); From b1f1fc40423d013d1430325b064edfa8de278fea Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Aug 2020 09:12:42 +0200 Subject: [PATCH 8/9] Power follower using a fixed block size --- src/sfizz/Config.h | 1 + src/sfizz/PowerFollower.cpp | 49 ++++++++++++++++++++++++++++--------- src/sfizz/PowerFollower.h | 6 +++-- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index fc48aab5..67821b02 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -57,6 +57,7 @@ namespace config { constexpr Oversampling defaultOversamplingFactor { Oversampling::x1 }; constexpr float A440 { 440.0 }; constexpr size_t powerHistoryLength { 16 }; + constexpr size_t powerFollowerStep { 512 }; constexpr float powerFollowerAttackFactor { 100 }; constexpr float powerFollowerReleaseFactor { 10 }; constexpr uint16_t numCCs { 512 }; diff --git a/src/sfizz/PowerFollower.cpp b/src/sfizz/PowerFollower.cpp index 56ccb86e..06807c7a 100644 --- a/src/sfizz/PowerFollower.cpp +++ b/src/sfizz/PowerFollower.cpp @@ -42,26 +42,51 @@ void PowerFollower::process(AudioSpan buffer) noexcept if (numFrames == 0) return; - absl::Span tempBuffer(tempBuffer_.get(), numFrames); + /// + constexpr size_t step = config::powerFollowerStep; + float currentPower = currentPower_; + float currentSum = currentSum_; + size_t currentCount = currentCount_; - copy(buffer.getConstSpan(0), tempBuffer); - for (unsigned i = 1; i < buffer.getNumChannels(); ++i) - add(buffer.getConstSpan(i), tempBuffer); + const float attackFactor = static_cast(numFrames) * attackTrackingFactor_; + const float releaseFactor = static_cast(numFrames) * releaseTrackingFactor_; - const float meanPower = meanSquared(tempBuffer); + /// + size_t index = 0; + while (index < numFrames) { + size_t blockSize = std::min(step - currentCount, numFrames - index); + absl::Span tempBuffer(tempBuffer_.get(), blockSize); - const float attackFactor = static_cast(buffer.getNumFrames()) * attackTrackingFactor_; - const float releaseFactor = static_cast(buffer.getNumFrames()) * releaseTrackingFactor_; + copy(buffer.getConstSpan(0).subspan(index, blockSize), tempBuffer); + for (unsigned i = 1, n = buffer.getNumChannels(); i < n; ++i) + add(buffer.getConstSpan(i).subspan(index, blockSize), tempBuffer); - meanChannelPower_ = max( - meanChannelPower_ * (1 - attackFactor) + meanPower * attackFactor, - meanChannelPower_ * (1 - releaseFactor) + meanPower * releaseFactor - ); + currentSum += sumSquares(tempBuffer); + currentCount += blockSize; + + if (currentCount == step) { + const float meanPower = currentSum / step; + currentPower = max( + currentPower * (1 - attackFactor) + meanPower * attackFactor, + currentPower * (1 - releaseFactor) + meanPower * releaseFactor); + currentSum = 0; + currentCount = 0; + } + + index += blockSize; + } + + /// + currentPower_ = currentPower; + currentSum_ = currentSum; + currentCount_ = currentCount; } void PowerFollower::clear() noexcept { - meanChannelPower_ = 0; + currentPower_ = 0; + currentSum_ = 0; + currentCount_ = 0; } void PowerFollower::updateTrackingFactor() noexcept diff --git a/src/sfizz/PowerFollower.h b/src/sfizz/PowerFollower.h index 2deff3d5..1febd9f1 100644 --- a/src/sfizz/PowerFollower.h +++ b/src/sfizz/PowerFollower.h @@ -17,7 +17,7 @@ public: void setSamplesPerBlock(unsigned samplesPerBlock); void process(AudioSpan buffer) noexcept; void clear() noexcept; - float getAveragePower() const noexcept { return meanChannelPower_; } + float getAveragePower() const noexcept { return currentPower_; } private: void updateTrackingFactor() noexcept; @@ -31,7 +31,9 @@ private: float attackTrackingFactor_ {}; float releaseTrackingFactor_ {}; - float meanChannelPower_ {}; + float currentPower_ {}; + float currentSum_ = 0; + size_t currentCount_ = 0; }; } // namespace sfz From 298befa753ad91c189c94bcbf93d04343b0b1d5c Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Aug 2020 09:39:41 +0200 Subject: [PATCH 9/9] Express follower AR in seconds --- src/sfizz/Config.h | 4 ++-- src/sfizz/PowerFollower.cpp | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 67821b02..e56548fc 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -58,8 +58,8 @@ namespace config { constexpr float A440 { 440.0 }; constexpr size_t powerHistoryLength { 16 }; constexpr size_t powerFollowerStep { 512 }; - constexpr float powerFollowerAttackFactor { 100 }; - constexpr float powerFollowerReleaseFactor { 10 }; + constexpr float powerFollowerAttackTime { 5e-3f }; + constexpr float powerFollowerReleaseTime { 200e-3f }; constexpr uint16_t numCCs { 512 }; constexpr int maxCurves { 256 }; constexpr int chunkSize { 1024 }; diff --git a/src/sfizz/PowerFollower.cpp b/src/sfizz/PowerFollower.cpp index 06807c7a..f2eb05e5 100644 --- a/src/sfizz/PowerFollower.cpp +++ b/src/sfizz/PowerFollower.cpp @@ -32,7 +32,6 @@ void PowerFollower::setSamplesPerBlock(unsigned samplesPerBlock) if (samplesPerBlock_ != samplesPerBlock) { tempBuffer_.reset(new float[samplesPerBlock]); samplesPerBlock_ = samplesPerBlock; - updateTrackingFactor(); } } @@ -48,8 +47,8 @@ void PowerFollower::process(AudioSpan buffer) noexcept float currentSum = currentSum_; size_t currentCount = currentCount_; - const float attackFactor = static_cast(numFrames) * attackTrackingFactor_; - const float releaseFactor = static_cast(numFrames) * releaseTrackingFactor_; + const float attackFactor = attackTrackingFactor_; + const float releaseFactor = releaseTrackingFactor_; /// size_t index = 0; @@ -67,8 +66,8 @@ void PowerFollower::process(AudioSpan buffer) noexcept if (currentCount == step) { const float meanPower = currentSum / step; currentPower = max( - currentPower * (1 - attackFactor) + meanPower * attackFactor, - currentPower * (1 - releaseFactor) + meanPower * releaseFactor); + currentPower * attackFactor + meanPower * (1 - attackFactor), + currentPower * releaseFactor + meanPower * (1 - releaseFactor)); currentSum = 0; currentCount = 0; } @@ -92,9 +91,8 @@ void PowerFollower::clear() noexcept void PowerFollower::updateTrackingFactor() noexcept { // Protect the envelope follower against blowups - const auto maxTrackingFactor = sampleRate_ / samplesPerBlock_; - attackTrackingFactor_ = min(config::powerFollowerAttackFactor, maxTrackingFactor) / sampleRate_; - releaseTrackingFactor_ = min(config::powerFollowerReleaseFactor, maxTrackingFactor) / sampleRate_; + attackTrackingFactor_ = std::exp(-1.0f / ((config::powerFollowerAttackTime / config::powerFollowerStep) * sampleRate_)); + releaseTrackingFactor_ = std::exp(-1.0f / ((config::powerFollowerReleaseTime / config::powerFollowerStep) * sampleRate_)); } } // namespace sfz