36#include "../Core/AudioBuffer.h"
37#include "../Core/AudioSpec.h"
38#include "../Core/DspMath.h"
55template <FloatType T,
int MaxChannels = 16>
75 if (!spec.isValid() || !std::isfinite(spec.sampleRate))
return;
76 sampleRate_.store(spec.sampleRate, std::memory_order_relaxed);
77 numChannels_.store(std::clamp(spec.numChannels, 1, MaxChannels),
78 std::memory_order_relaxed);
92 for (
auto& s : state_) s = 0.0;
93 for (
auto& e : published_) e.store(T(0), std::memory_order_relaxed);
102 const double v =
static_cast<double>(ms);
103 if (!std::isfinite(v))
return;
104 attackMs_.store(std::max(v, 0.01), std::memory_order_relaxed);
112 const double v =
static_cast<double>(ms);
113 if (!std::isfinite(v))
return;
114 releaseMs_.store(std::max(v, 0.01), std::memory_order_relaxed);
121 const int v = std::clamp(
static_cast<int>(m), 0, 1);
122 mode_.store(
static_cast<Mode>(v), std::memory_order_relaxed);
134 const int nCh = std::min(buffer.getNumChannels(),
135 numChannels_.load(std::memory_order_relaxed));
136 const int nS = buffer.getNumSamples();
137 const double aAtt = attackA_.load(std::memory_order_relaxed);
138 const double aRel = releaseA_.load(std::memory_order_relaxed);
139 const bool peak = mode_.load(std::memory_order_relaxed) ==
Mode::Peak;
141 for (
int ch = 0; ch < nCh; ++ch)
143 const T* d = buffer.getChannel(ch);
144 double env = state_[
static_cast<size_t>(ch)];
147 for (
int i = 0; i < nS; ++i)
149 const double x = std::abs(
static_cast<double>(d[i]));
150 const double a = (x > env) ? aAtt : aRel;
151 env = a * env + (1.0 - a) * x;
156 for (
int i = 0; i < nS; ++i)
158 const double x =
static_cast<double>(d[i]) * d[i];
159 const double a = (x > env) ? aAtt : aRel;
160 env = a * env + (1.0 - a) * x;
168 if (!std::isfinite(env) || env < 1e-100) env = 0.0;
169 state_[
static_cast<size_t>(ch)] = env;
170 published_[
static_cast<size_t>(ch)].store(readout(env, peak),
171 std::memory_order_relaxed);
185 const double aAtt = attackA_.load(std::memory_order_relaxed);
186 const double aRel = releaseA_.load(std::memory_order_relaxed);
187 const bool peak = mode_.load(std::memory_order_relaxed) ==
Mode::Peak;
188 double env = state_[0];
189 const double x = peak ? std::abs(
static_cast<double>(input))
190 :
static_cast<double>(input) * input;
191 const double a = (x > env) ? aAtt : aRel;
192 env = a * env + (1.0 - a) * x;
193 if (!std::isfinite(env) || env < 1e-100) env = 0.0;
195 const T out = readout(env, peak);
196 published_[0].store(out, std::memory_order_relaxed);
205 const int n = numChannels_.load(std::memory_order_relaxed);
206 channel = std::clamp(channel, 0, n - 1);
207 return published_[
static_cast<size_t>(channel)].load(std::memory_order_relaxed);
213 const int n = numChannels_.load(std::memory_order_relaxed);
215 for (
int ch = 0; ch < n; ++ch)
216 m = std::max(m, published_[
static_cast<size_t>(ch)].load(std::memory_order_relaxed));
223 const double e =
static_cast<double>(
getEnvelope(channel));
224 return static_cast<T
>(20.0 * std::log10(std::max(e, 1e-6)));
230 return static_cast<T
>(attackMs_.load(std::memory_order_relaxed));
236 return static_cast<T
>(releaseMs_.load(std::memory_order_relaxed));
242 return mode_.load(std::memory_order_relaxed);
246 static_assert(MaxChannels >= 1,
"EnvelopeFollower needs at least one channel");
248 [[nodiscard]] T readout(
double env,
bool peak)
const noexcept
250 return static_cast<T
>(peak ? env : std::sqrt(std::max(env, 0.0)));
253 void updateCoeffs() noexcept
255 const double fs = sampleRate_.load(std::memory_order_relaxed);
256 attackA_.store(std::exp(-1.0 / (attackMs_.load(std::memory_order_relaxed) * 0.001 * fs)),
257 std::memory_order_relaxed);
258 releaseA_.store(std::exp(-1.0 / (releaseMs_.load(std::memory_order_relaxed) * 0.001 * fs)),
259 std::memory_order_relaxed);
262 std::atomic<double> sampleRate_ { 48000.0 };
263 std::atomic<int> numChannels_ { 1 };
264 std::atomic<double> attackMs_ { 10.0 };
265 std::atomic<double> releaseMs_ { 150.0 };
266 std::atomic<double> attackA_ { 0.99 };
267 std::atomic<double> releaseA_ { 0.999 };
270 std::array<double, MaxChannels> state_ {};
271 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.