35#include "../Core/AudioBuffer.h"
36#include "../Core/AudioSpec.h"
37#include "../Core/DspMath.h"
54template <FloatType T,
int MaxChannels = 16>
74 if (!spec.isValid() || !std::isfinite(spec.sampleRate))
return;
75 sampleRate_.store(spec.sampleRate, std::memory_order_relaxed);
76 numChannels_.store(std::clamp(spec.numChannels, 1, MaxChannels),
77 std::memory_order_relaxed);
91 for (
auto& s : state_) s = 0.0;
92 for (
auto& e : published_) e.store(T(0), std::memory_order_relaxed);
101 const double v =
static_cast<double>(ms);
102 if (!std::isfinite(v))
return;
103 attackMs_.store(std::max(v, 0.01), std::memory_order_relaxed);
111 const double v =
static_cast<double>(ms);
112 if (!std::isfinite(v))
return;
113 releaseMs_.store(std::max(v, 0.01), std::memory_order_relaxed);
120 const int v = std::clamp(
static_cast<int>(m), 0, 1);
121 mode_.store(
static_cast<Mode>(v), std::memory_order_relaxed);
133 const int nCh = std::min(buffer.getNumChannels(),
134 numChannels_.load(std::memory_order_relaxed));
135 const int nS = buffer.getNumSamples();
136 const double aAtt = attackA_.load(std::memory_order_relaxed);
137 const double aRel = releaseA_.load(std::memory_order_relaxed);
138 const bool peak = mode_.load(std::memory_order_relaxed) ==
Mode::Peak;
140 for (
int ch = 0; ch < nCh; ++ch)
142 const T* d = buffer.getChannel(ch);
143 double env = state_[
static_cast<size_t>(ch)];
146 for (
int i = 0; i < nS; ++i)
148 const double x = std::abs(
static_cast<double>(d[i]));
149 const double a = (x > env) ? aAtt : aRel;
150 env = a * env + (1.0 - a) * x;
155 for (
int i = 0; i < nS; ++i)
157 const double x =
static_cast<double>(d[i]) * d[i];
158 const double a = (x > env) ? aAtt : aRel;
159 env = a * env + (1.0 - a) * x;
167 if (!std::isfinite(env) || env < 1e-100) env = 0.0;
168 state_[
static_cast<size_t>(ch)] = env;
169 published_[
static_cast<size_t>(ch)].store(readout(env, peak),
170 std::memory_order_relaxed);
184 const double aAtt = attackA_.load(std::memory_order_relaxed);
185 const double aRel = releaseA_.load(std::memory_order_relaxed);
186 const bool peak = mode_.load(std::memory_order_relaxed) ==
Mode::Peak;
187 double env = state_[0];
188 const double x = peak ? std::abs(
static_cast<double>(input))
189 :
static_cast<double>(input) * input;
190 const double a = (x > env) ? aAtt : aRel;
191 env = a * env + (1.0 - a) * x;
192 if (!std::isfinite(env) || env < 1e-100) env = 0.0;
194 const T out = readout(env, peak);
195 published_[0].store(out, std::memory_order_relaxed);
204 const int n = numChannels_.load(std::memory_order_relaxed);
205 channel = std::clamp(channel, 0, n - 1);
206 return published_[
static_cast<size_t>(channel)].load(std::memory_order_relaxed);
212 const int n = numChannels_.load(std::memory_order_relaxed);
214 for (
int ch = 0; ch < n; ++ch)
215 m = std::max(m, published_[
static_cast<size_t>(ch)].load(std::memory_order_relaxed));
222 const double e =
static_cast<double>(
getEnvelope(channel));
223 return static_cast<T
>(20.0 * std::log10(std::max(e, 1e-6)));
229 return static_cast<T
>(attackMs_.load(std::memory_order_relaxed));
235 return static_cast<T
>(releaseMs_.load(std::memory_order_relaxed));
241 return mode_.load(std::memory_order_relaxed);
245 static_assert(MaxChannels >= 1,
"EnvelopeFollower needs at least one channel");
247 [[nodiscard]] T readout(
double env,
bool peak)
const noexcept
249 return static_cast<T
>(peak ? env : std::sqrt(std::max(env, 0.0)));
252 void updateCoeffs() noexcept
254 const double fs = sampleRate_.load(std::memory_order_relaxed);
255 attackA_.store(std::exp(-1.0 / (attackMs_.load(std::memory_order_relaxed) * 0.001 * fs)),
256 std::memory_order_relaxed);
257 releaseA_.store(std::exp(-1.0 / (releaseMs_.load(std::memory_order_relaxed) * 0.001 * fs)),
258 std::memory_order_relaxed);
261 std::atomic<double> sampleRate_ { 48000.0 };
262 std::atomic<int> numChannels_ { 1 };
263 std::atomic<double> attackMs_ { 10.0 };
264 std::atomic<double> releaseMs_ { 150.0 };
265 std::atomic<double> attackA_ { 0.99 };
266 std::atomic<double> releaseA_ { 0.999 };
269 std::array<double, MaxChannels> state_ {};
270 std::array<std::atomic<T>, MaxChannels> published_ {};
Non-owning view over audio channel data.
Attack/release envelope detector (Peak or RMS).
T processSample(T input) noexcept
Single-sample path on channel 0 (for embedding in processors).
void setAttack(T ms) noexcept
Attack time in milliseconds (default 10, floor 0.01; non-finite values are ignored).
void setRelease(T ms) noexcept
Release time in milliseconds (default 150, floor 0.01; non-finite values are ignored).
T getAttack() const noexcept
EnvelopeFollower() noexcept
T getEnvelopeDb(int channel=0) const noexcept
T getRelease() const noexcept
void prepare(const AudioSpec &spec) noexcept
Prepares the follower. Allocation-free.
Mode getMode() const noexcept
T getEnvelope(int channel=0) const noexcept
void processBlock(AudioBufferView< const T > buffer) noexcept
Analyzes a block (read-only) and updates per-channel envelopes.
T getEnvelopeMax() const noexcept
void setMode(Mode m) noexcept
Peak (default) or RMS detection. Out-of-range values clamp.
void reset() noexcept
Clears all envelopes.
Main namespace for the DSPark framework.
Describes the audio environment for a DSP processor.