46#include "../Core/AudioBuffer.h"
47#include "../Core/AudioSpec.h"
48#include "../Core/DspMath.h"
73template <FloatType T,
int MaxChannels = 16>
91 sampleRate_ = spec.sampleRate;
92 numChannels_ = std::min(spec.numChannels, MaxChannels);
104 if (!std::isfinite(ms))
106 attackMs_.store(std::max(T(0.001),
static_cast<T
>(ms)), std::memory_order_relaxed);
107 updateCoefficients();
117 if (!std::isfinite(ms))
119 releaseMs_.store(std::max(T(0.001),
static_cast<T
>(ms)), std::memory_order_relaxed);
120 updateCoefficients();
135 if (!std::isfinite(ms))
137 rmsWindowMs_.store(std::max(T(0.001),
static_cast<T
>(ms)), std::memory_order_relaxed);
138 updateCoefficients();
144 return attackMs_.load(std::memory_order_relaxed);
150 return releaseMs_.load(std::memory_order_relaxed);
156 return rmsWindowMs_.load(std::memory_order_relaxed);
162 for (
auto& s : state_)
164 s.peak.store(T(0), std::memory_order_relaxed);
165 s.rmsAccum.store(T(0), std::memory_order_relaxed);
179 const int nCh = std::min(buffer.getNumChannels(), numChannels_);
180 const int nS = buffer.getNumSamples();
185 static constexpr T kAntiDenormal = std::is_same_v<T, float> ? T(1e-15) : T(1e-30);
188 const T attackCoeff = attackCoeff_.load(std::memory_order_relaxed);
189 const T releaseCoeff = releaseCoeff_.load(std::memory_order_relaxed);
190 const T rmsCoeff = rmsCoeff_.load(std::memory_order_relaxed);
192 for (
int ch = 0; ch < nCh; ++ch)
194 const T* data = buffer.getChannel(ch);
195 auto& s = state_[ch];
198 T localPeak = s.peak.load(std::memory_order_relaxed);
199 T localRms = s.rmsAccum.load(std::memory_order_relaxed);
201 for (
int i = 0; i < nS; ++i)
203 const T absSample = std::abs(data[i]);
207 const T isAttack =
static_cast<T
>(absSample > localPeak);
208 const T peakCoeff = isAttack * attackCoeff + (T(1) - isAttack) * releaseCoeff;
210 localPeak = absSample + peakCoeff * (localPeak - absSample) + kAntiDenormal;
213 const T squared = data[i] * data[i];
214 localRms = squared + rmsCoeff * (localRms - squared) + kAntiDenormal;
221 if (!std::isfinite(localPeak)) localPeak = T(0);
222 if (!std::isfinite(localRms)) localRms = T(0);
225 s.peak.store(localPeak, std::memory_order_relaxed);
226 s.rmsAccum.store(localRms, std::memory_order_relaxed);
237 if (channel < 0 || channel >= MaxChannels)
return T(0);
238 return state_[channel].peak.load(std::memory_order_relaxed);
248 if (channel < 0 || channel >= MaxChannels)
return T(0);
249 const T squaredRms = state_[channel].rmsAccum.load(std::memory_order_relaxed);
250 return std::sqrt(std::max(squaredRms, T(0)));
270 if (channel < 0 || channel >= MaxChannels)
return T(-100);
271 const T squaredRms = state_[channel].rmsAccum.load(std::memory_order_relaxed);
274 if (squaredRms <= T(1e-10))
return T(-100);
275 return T(10) * std::log10(squaredRms);
279 void updateCoefficients() noexcept
281 if (!(sampleRate_ > 0.0))
283 const auto fs =
static_cast<T
>(sampleRate_);
285 const T attackMs = attackMs_.load(std::memory_order_relaxed);
286 const T releaseMs = releaseMs_.load(std::memory_order_relaxed);
287 const T rmsMs = rmsWindowMs_.load(std::memory_order_relaxed);
289 attackCoeff_.store(std::exp(T(-1) / (fs * attackMs / T(1000))), std::memory_order_relaxed);
290 releaseCoeff_.store(std::exp(T(-1) / (fs * releaseMs / T(1000))), std::memory_order_relaxed);
291 rmsCoeff_.store(std::exp(T(-1) / (fs * rmsMs / T(1000))), std::memory_order_relaxed);
298 std::atomic<T> peak{ T(0) };
299 std::atomic<T> rmsAccum{ T(0) };
302 double sampleRate_ = 44100.0;
303 int numChannels_ = 0;
305 std::atomic<T> attackMs_ { T(1) };
306 std::atomic<T> releaseMs_ { T(100) };
307 std::atomic<T> rmsWindowMs_ { T(300) };
309 std::atomic<T> attackCoeff_ { T(0) };
310 std::atomic<T> releaseCoeff_ { T(0) };
311 std::atomic<T> rmsCoeff_ { T(0) };
313 std::array<ChannelState, MaxChannels> state_{};
Non-owning view over audio channel data.
Per-channel peak and RMS envelope follower with lock-free readout.
T getAttackMs() const noexcept
Returns the peak attack time in milliseconds.
T getRmsWindowMs() const noexcept
Returns the RMS integration time constant in milliseconds.
void prepare(const AudioSpec &spec) noexcept
Prepares the follower for the given audio environment.
T getRmsLevelDb(int channel) const noexcept
Returns the current RMS level in decibels (optimized).
void setReleaseMs(float ms) noexcept
Sets the release time for peak metering.
void setRmsWindowMs(float ms) noexcept
Sets the integration time constant for RMS metering.
T getPeakLevelDb(int channel) const noexcept
Returns the current peak level in decibels.
void process(AudioBufferView< const T > buffer) noexcept
Processes a block of audio and updates level tracking.
T getPeakLevel(int channel) const noexcept
Returns the current peak level for the given channel safely.
void setAttackMs(float ms) noexcept
Sets the attack time for peak metering.
void reset() noexcept
Resets all envelope states to zero safely.
T getRmsLevel(int channel) const noexcept
Returns the current RMS level for the given channel safely.
T getReleaseMs() const noexcept
Returns the peak release time in milliseconds.
Main namespace for the DSPark framework.
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
Describes the audio environment for a DSP processor.