47#include "../Core/AudioBuffer.h"
48#include "../Core/AudioSpec.h"
49#include "../Core/DspMath.h"
74template <FloatType T,
int MaxChannels = 16>
92 sampleRate_ = spec.sampleRate;
93 numChannels_ = std::min(spec.numChannels, MaxChannels);
105 if (!std::isfinite(ms))
107 attackMs_.store(std::max(T(0.001),
static_cast<T
>(ms)), std::memory_order_relaxed);
108 updateCoefficients();
118 if (!std::isfinite(ms))
120 releaseMs_.store(std::max(T(0.001),
static_cast<T
>(ms)), std::memory_order_relaxed);
121 updateCoefficients();
136 if (!std::isfinite(ms))
138 rmsWindowMs_.store(std::max(T(0.001),
static_cast<T
>(ms)), std::memory_order_relaxed);
139 updateCoefficients();
145 return attackMs_.load(std::memory_order_relaxed);
151 return releaseMs_.load(std::memory_order_relaxed);
157 return rmsWindowMs_.load(std::memory_order_relaxed);
163 for (
auto& s : state_)
165 s.peak.store(T(0), std::memory_order_relaxed);
166 s.rmsAccum.store(T(0), std::memory_order_relaxed);
180 const int nCh = std::min(buffer.getNumChannels(), numChannels_);
181 const int nS = buffer.getNumSamples();
186 static constexpr T kAntiDenormal = std::is_same_v<T, float> ? T(1e-15) : T(1e-30);
189 const T attackCoeff = attackCoeff_.load(std::memory_order_relaxed);
190 const T releaseCoeff = releaseCoeff_.load(std::memory_order_relaxed);
191 const T rmsCoeff = rmsCoeff_.load(std::memory_order_relaxed);
193 for (
int ch = 0; ch < nCh; ++ch)
195 const T* data = buffer.getChannel(ch);
196 auto& s = state_[ch];
199 T localPeak = s.peak.load(std::memory_order_relaxed);
200 T localRms = s.rmsAccum.load(std::memory_order_relaxed);
202 for (
int i = 0; i < nS; ++i)
204 const T absSample = std::abs(data[i]);
208 const T isAttack =
static_cast<T
>(absSample > localPeak);
209 const T peakCoeff = isAttack * attackCoeff + (T(1) - isAttack) * releaseCoeff;
211 localPeak = absSample + peakCoeff * (localPeak - absSample) + kAntiDenormal;
214 const T squared = data[i] * data[i];
215 localRms = squared + rmsCoeff * (localRms - squared) + kAntiDenormal;
222 if (!std::isfinite(localPeak)) localPeak = T(0);
223 if (!std::isfinite(localRms)) localRms = T(0);
226 s.peak.store(localPeak, std::memory_order_relaxed);
227 s.rmsAccum.store(localRms, std::memory_order_relaxed);
238 if (channel < 0 || channel >= MaxChannels)
return T(0);
239 return state_[channel].peak.load(std::memory_order_relaxed);
249 if (channel < 0 || channel >= MaxChannels)
return T(0);
250 const T squaredRms = state_[channel].rmsAccum.load(std::memory_order_relaxed);
251 return std::sqrt(std::max(squaredRms, T(0)));
271 if (channel < 0 || channel >= MaxChannels)
return T(-100);
272 const T squaredRms = state_[channel].rmsAccum.load(std::memory_order_relaxed);
275 if (squaredRms <= T(1e-10))
return T(-100);
276 return T(10) * std::log10(squaredRms);
280 void updateCoefficients() noexcept
282 if (!(sampleRate_ > 0.0))
284 const auto fs =
static_cast<T
>(sampleRate_);
286 const T attackMs = attackMs_.load(std::memory_order_relaxed);
287 const T releaseMs = releaseMs_.load(std::memory_order_relaxed);
288 const T rmsMs = rmsWindowMs_.load(std::memory_order_relaxed);
290 attackCoeff_.store(std::exp(T(-1) / (fs * attackMs / T(1000))), std::memory_order_relaxed);
291 releaseCoeff_.store(std::exp(T(-1) / (fs * releaseMs / T(1000))), std::memory_order_relaxed);
292 rmsCoeff_.store(std::exp(T(-1) / (fs * rmsMs / T(1000))), std::memory_order_relaxed);
299 std::atomic<T> peak{ T(0) };
300 std::atomic<T> rmsAccum{ T(0) };
303 double sampleRate_ = 44100.0;
304 int numChannels_ = 0;
306 std::atomic<T> attackMs_ { T(1) };
307 std::atomic<T> releaseMs_ { T(100) };
308 std::atomic<T> rmsWindowMs_ { T(300) };
310 std::atomic<T> attackCoeff_ { T(0) };
311 std::atomic<T> releaseCoeff_ { T(0) };
312 std::atomic<T> rmsCoeff_ { T(0) };
314 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.