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); };