46#include "../Core/AudioBuffer.h"
47#include "../Core/AudioSpec.h"
48#include "../Core/Biquad.h"
49#include "../Core/DspMath.h"
50#include "../Core/StateBlob.h"
81 static_assert(std::atomic<T>::is_always_lock_free,
82 "AutoGain requires a lock-free float type for thread safety in the audio path.");
108 refFilters_.assign(
static_cast<size_t>(numChannels_), WeightingState {});
109 outFilters_.assign(
static_cast<size_t>(numChannels_), WeightingState {});
123 if (std::min(buffer.getNumChannels(), numChannels_) <= 0 ||
124 buffer.getNumSamples() <= 0)
126 integrate(refMeanSquare_, buffer, refFilters_);
136 const int numCh = std::min(buffer.getNumChannels(), numChannels_);
137 const int numSamples = buffer.getNumSamples();
139 if (numSamples == 0 || numCh == 0)
return;
141 integrate(outMeanSquare_, buffer, outFilters_);
142 const T refLevelDb = meanSquareToDb(refMeanSquare_);
143 const T outLevelDb = meanSquareToDb(outMeanSquare_);
144 T targetDb = refLevelDb - outLevelDb;
147 const T maxComp = maxCompensation_.load(std::memory_order_relaxed);
148 targetDb = std::clamp(targetDb, -maxComp, maxComp);
151 if (refLevelDb < SILENCE_THRESH_DB && outLevelDb < SILENCE_THRESH_DB)
156 const T smoothSecs = smoothTimeSecs_.load(std::memory_order_relaxed);
157 const T alpha =
static_cast<T
>(std::exp(-
static_cast<double>(numSamples)
158 / (sampleRate_ *
static_cast<double>(smoothSecs))));
159 const T endCompensationDb = targetDb + (compensationDb_ - targetDb) * alpha;
164 const T gainStep = (endGain - startGain) /
static_cast<T
>(numSamples);
168 for (
int ch = 0; ch < numCh; ++ch)
170 T* data = buffer.getChannel(ch);
171 for (
int i = 0; i < numSamples; ++i)
173 data[i] *= (startGain +
static_cast<T
>(i) * gainStep);
178 compensationDb_ = endCompensationDb;
183 publishedCompensationDb_.store(compensationDb_, std::memory_order_relaxed);
191 refMeanSquare_ = 0.0;
192 outMeanSquare_ = 0.0;
193 for (
auto& f : refFilters_) f = {};
194 for (
auto& f : outFilters_) f = {};
195 compensationDb_ = T(0);
196 publishedCompensationDb_.store(T(0), std::memory_order_relaxed);
202 const int v = std::clamp(
static_cast<int>(w), 0,
static_cast<int>(
Weighting::Flat));
203 weighting_.store(
static_cast<Weighting>(v), std::memory_order_relaxed);
209 return weighting_.load(std::memory_order_relaxed);
223 return publishedCompensationDb_.load(std::memory_order_relaxed);
233 if (!std::isfinite(dB))
return;
234 maxCompensation_.store(std::abs(dB), std::memory_order_relaxed);
245 if (!std::isfinite(ms))
return;
246 smoothTimeSecs_.store(std::max<T>(ms * T(0.001), T(0.001)),
247 std::memory_order_relaxed);
253 return maxCompensation_.load(std::memory_order_relaxed);
259 return smoothTimeSecs_.load(std::memory_order_relaxed) * T(1000);
263 [[nodiscard]] std::vector<uint8_t>
getState()
const
268 w.
write(
"maxComp",
static_cast<float>(maxCompensation_.load(std::memory_order_relaxed)));
287 struct WeightingState {
double s1a = 0.0, s2a = 0.0, s1b = 0.0, s2b = 0.0; };
289 static inline double tdf2(
double x,
const BiquadCoeffs& c,
double& s1,
double& s2)
noexcept
291 const double y = c.b0 * x + s1;
292 s1 = c.b1 * x - c.a1 * y + s2;
293 s2 = c.b2 * x - c.a2 * y;
303 void integrate(
double& meanSquare, AudioBufferView<T> buffer,
304 std::vector<WeightingState>& filters)
noexcept
306 const int numCh = std::min({ buffer.getNumChannels(), numChannels_,
307 static_cast<int>(filters.size()) });
308 const int numSamples = buffer.getNumSamples();
309 if (numCh <= 0 || numSamples <= 0)
return;
313 for (
int ch = 0; ch < numCh; ++ch)
315 const T* x = buffer.getChannel(ch);
316 WeightingState& f = filters[
static_cast<size_t>(ch)];
319 for (
int i = 0; i < numSamples; ++i)
321 const double y = tdf2(tdf2(
static_cast<double>(x[i]), shelf_, f.s1a, f.s2a),
322 highPass_, f.s1b, f.s2b);
328 for (
int i = 0; i < numSamples; ++i)
329 sumSq +=
static_cast<double>(x[i]) *
static_cast<double>(x[i]);
333 const double blockMs = sumSq /
static_cast<double>(numSamples * numCh);
334 if (!std::isfinite(blockMs))
336 for (
auto& state : filters) state = {};
339 const double a = std::exp(-
static_cast<double>(numSamples) / (sampleRate_ * kIntegrationSeconds));
340 meanSquare = blockMs + (meanSquare - blockMs) * a;
344 [[nodiscard]]
static T meanSquareToDb(
double meanSquare)
noexcept
346 return static_cast<T
>(10.0 * std::log10(std::max(meanSquare, 1e-15)));
349 static constexpr double kIntegrationSeconds = 0.4;
350 static constexpr T SILENCE_THRESH_DB = T(-90);
352 double sampleRate_ = 44100.0;
353 int numChannels_ = 0;
355 std::atomic<T> smoothTimeSecs_{ T(0.100) };
357 double refMeanSquare_ = 0.0;
358 double outMeanSquare_ = 0.0;
361 std::vector<WeightingState> refFilters_;
362 std::vector<WeightingState> outFilters_;
363 T compensationDb_ = T(0);
364 std::atomic<T> publishedCompensationDb_{ T(0) };
366 std::atomic<T> maxCompensation_{ T(12) };
Non-owning view over audio channel data.
Block-adaptive automatic gain compensation with SIMD-friendly linear interpolation.
void reset() noexcept
Hard resets the internal state to avoid feedback loops or stale measurements.
Weighting
Level measurement used for the match.
@ KWeighted
ITU-R BS.1770 K-weighting (loudness, LUFS filter). Default.
@ Flat
Unweighted mean square (plain RMS).
T getSmoothingTime() const noexcept
void pushReference(AudioBufferView< T > buffer) noexcept
Snapshots the input level. Must be called BEFORE processing.
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
void setWeighting(Weighting w) noexcept
Selects the level measurement (K-weighted loudness by default). RT-safe.
Weighting getWeighting() const noexcept
void setSmoothingTime(T ms) noexcept
Sets the smoothing time constant.
void prepare(const AudioSpec &spec)
Prepares the auto-gain processor.
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
void compensate(AudioBufferView< T > buffer) noexcept
Measures output level and applies smoothed gain compensation. Must be called AFTER processing.
T getMaxCompensation() const noexcept
T getCompensationDb() const noexcept
Returns the current internal compensation in dB. Useful for UI metering.
void setMaxCompensation(T dB) noexcept
Thread-safe assignment of the maximum allowed compensation limit.
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
bool isValid() const noexcept
uint32_t processorId() const noexcept
Serializes key/value parameters into a versioned blob.
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
void write(const char *key, float value)
Writes a float parameter.
Main namespace for the DSPark framework.
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Describes the audio environment for a DSP processor.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
double sampleRate
Sample rate in Hz.
static BiquadCoeffs makeKWeightingHighPass(double sampleRate) noexcept
ITU-R BS.1770 K-weighting, stage 2: the RLB high-pass.
static BiquadCoeffs makeKWeightingShelf(double sampleRate) noexcept
ITU-R BS.1770 K-weighting, stage 1: the head-related high shelf.