101 static_assert(std::is_trivially_copyable_v<BandConfig>,
"BandConfig must be trivially copyable for std::atomic");
105 for (
int i = 0; i < MaxBands; ++i) {
107 configSeq_[i].store(0, std::memory_order_relaxed);
108 paramsDirty_[i].store(
true, std::memory_order_relaxed);
119 isPrepared_.store(
false, std::memory_order_relaxed);
123 if (oversamplingFactor_ > 1)
128 oversampler_ = std::make_unique<Oversampling<T>>(oversamplingFactor_);
129 oversamplerSc_ = std::make_unique<Oversampling<T>>(oversamplingFactor_);
130 oversampler_ ->prepare(spec);
131 oversamplerSc_->prepare(spec);
135 oversampler_.reset();
136 oversamplerSc_.reset();
139 int maxLaSamples =
static_cast<int>(sampleRate_ * oversamplingFactor_ * 0.01) + 1;
140 for (
int ch = 0; ch < kMaxChannels; ++ch)
141 lookaheadBuf_[ch].
prepare(maxLaSamples);
145 isPrepared_.store(
true, std::memory_order_relaxed);
164 if (!isPrepared_.load(std::memory_order_relaxed))
return;
168 if (sidechain.getNumChannels() <= 0 ||
169 sidechain.getNumSamples() < audio.getNumSamples())
171 if (audio.getNumChannels() <= 0)
return;
177 if (oversamplingFactor_ > 1 && oversampler_ && oversamplerSc_)
183 auto upAudio = oversampler_->upsample(audio);
184 auto upSc = oversamplerSc_->upsample(sidechain);
186 processCore(upAudio, upSc, sampleRate_ * oversamplingFactor_);
188 oversampler_->downsample(audio);
193 processCore(audio, sidechain, sampleRate_);
207 if (band < 0 || band >= MaxBands)
return;
210 const BandConfig& prev = configs_[
static_cast<size_t>(band)];
211 auto keep = [](T v, T fallback) {
return std::isfinite(v) ? v : fallback; };
213 c.
q = keep(c.
q, prev.
q);
226 configSeq_[band].fetch_add(1, std::memory_order_acq_rel);
227 configs_[
static_cast<size_t>(band)] = c;
228 configSeq_[band].fetch_add(1, std::memory_order_release);
229 paramsDirty_[band].store(
true, std::memory_order_release);
234 numBands_.store(std::clamp(n, 1, MaxBands), std::memory_order_relaxed);
258 oversamplingFactor_ = std::clamp(factor, 1, 4);
259 if (oversamplingFactor_ == 3) oversamplingFactor_ = 4;
260 isPrepared_.store(
false, std::memory_order_relaxed);
267 if (!std::isfinite(ms))
return;
268 lookaheadMs_ = std::clamp(ms, T(0), T(10));
282 const int factor = std::max(oversamplingFactor_, 1);
283 const int la = lookaheadSamples_.load(std::memory_order_relaxed) / factor;
284 const int os = oversampler_ ? oversampler_->getLatency() : 0;
290 if (band < 0 || band >= MaxBands)
return T(0);
291 return meterGainDb_[band].load(std::memory_order_relaxed);
296 for (
int b = 0; b < MaxBands; ++b)
298 bandDetector_[b].reset();
299 bandFilter_[b].reset();
300 currentGainDb_[b] = T(0);
301 meterGainDb_[b].store(T(0), std::memory_order_relaxed);
302 paramsDirty_[b].store(
true, std::memory_order_relaxed);
304 for (
int ch = 0; ch < kMaxChannels; ++ch)
305 lookaheadBuf_[ch].
reset();
310 [[nodiscard]] std::vector<uint8_t>
getState()
const
313 const int n = numBands_.load(std::memory_order_relaxed);
314 w.
write(
"numBands", n);
316 for (
int i = 0; i < n; ++i)
318 const BandConfig& c = configs_[
static_cast<size_t>(i)];
319 std::snprintf(key,
sizeof(key),
"b%d.freq", i);
321 std::snprintf(key,
sizeof(key),
"b%d.q", i);
322 w.
write(key,
static_cast<float>(c.
q));
323 std::snprintf(key,
sizeof(key),
"b%d.thresh", i);
325 std::snprintf(key,
sizeof(key),
"b%d.shape", i);
327 std::snprintf(key,
sizeof(key),
"b%d.on", i);
329 std::snprintf(key,
sizeof(key),
"b%d.aRatio", i);
331 std::snprintf(key,
sizeof(key),
"b%d.aAtk", i);
333 std::snprintf(key,
sizeof(key),
"b%d.aRel", i);
335 std::snprintf(key,
sizeof(key),
"b%d.aRange", i);
337 std::snprintf(key,
sizeof(key),
"b%d.aBoost", i);
339 std::snprintf(key,
sizeof(key),
"b%d.bRatio", i);
341 std::snprintf(key,
sizeof(key),
"b%d.bAtk", i);
343 std::snprintf(key,
sizeof(key),
"b%d.bRel", i);
345 std::snprintf(key,
sizeof(key),
"b%d.bRange", i);
347 std::snprintf(key,
sizeof(key),
"b%d.bBoost", i);
358 const int n = std::clamp(r.
read(
"numBands", 0), 0, MaxBands);
360 for (
int i = 0; i < n; ++i)
363 std::snprintf(key,
sizeof(key),
"b%d.freq", i);
365 std::snprintf(key,
sizeof(key),
"b%d.q", i);
366 c.
q =
static_cast<T
>(r.
read(key, 1.0f));
367 std::snprintf(key,
sizeof(key),
"b%d.thresh", i);
369 std::snprintf(key,
sizeof(key),
"b%d.shape", i);
371 std::snprintf(key,
sizeof(key),
"b%d.on", i);
373 std::snprintf(key,
sizeof(key),
"b%d.aRatio", i);
375 std::snprintf(key,
sizeof(key),
"b%d.aAtk", i);
377 std::snprintf(key,
sizeof(key),
"b%d.aRel", i);
379 std::snprintf(key,
sizeof(key),
"b%d.aRange", i);
381 std::snprintf(key,
sizeof(key),
"b%d.aBoost", i);
383 std::snprintf(key,
sizeof(key),
"b%d.bRatio", i);
385 std::snprintf(key,
sizeof(key),
"b%d.bAtk", i);
387 std::snprintf(key,
sizeof(key),
"b%d.bRel", i);
389 std::snprintf(key,
sizeof(key),
"b%d.bRange", i);
391 std::snprintf(key,
sizeof(key),
"b%d.bBoost", i);
400 static constexpr int kMaxChannels = 16;
401 static constexpr T kMinLevelDb = T(-100.0);
402 static constexpr T kMinEnvelope = T(1e-12);
407 T aboveAtkCoeff, aboveRelCoeff;
408 T belowAtkCoeff, belowRelCoeff;
411 void processCore(AudioBufferView<T>& audio, AudioBufferView<T>& sidechain,
double currentFs)
noexcept
413 const int nCh = std::min(audio.getNumChannels(), kMaxChannels);
414 const int scCh = sidechain.getNumChannels();
415 const int nS = audio.getNumSamples();
416 if (scCh <= 0)
return;
417 const int nb = numBands_.load(std::memory_order_relaxed);
418 const int laSamples = lookaheadSamples_.load(std::memory_order_relaxed);
421 for (
int b = 0; b < nb; ++b)
423 if (paramsDirty_[b].exchange(
false, std::memory_order_acquire))
424 updateBandInternalState(b, currentFs);
428 for (
int i = 0; i < nS; ++i)
430 for (
int b = 0; b < nb; ++b)
432 if (!states_[b].cfg.enabled)
continue;
434 T maxLevelDb = kMinLevelDb;
437 for (
int ch = 0; ch < nCh; ++ch)
439 int sc = std::min(ch, scCh - 1);
440 T scSample = sidechain.getChannel(sc)[i];
442 T detected = std::abs(bandDetector_[b].processSample(scSample, ch));
445 if (levelDb > maxLevelDb) maxLevelDb = levelDb;
449 T targetGainDb = computeTargetGain(states_[b].cfg, maxLevelDb);
452 T& currentGain = currentGainDb_[b];
453 T diff = targetGainDb - currentGain;
456 if (maxLevelDb > states_[b].cfg.threshold) {
457 coeff = (std::abs(targetGainDb) > std::abs(currentGain))
458 ? states_[b].aboveAtkCoeff : states_[b].aboveRelCoeff;
460 coeff = (std::abs(targetGainDb) > std::abs(currentGain))
461 ? states_[b].belowAtkCoeff : states_[b].belowRelCoeff;
464 currentGain += coeff * diff;
473 if (std::abs(currentGain) > T(0.01))
475 switch (states_[b].cfg.shape)
478 updateDynamicPeakCoeffs(b, currentGain);
482 currentFs,
static_cast<double>(states_[b].cfg.frequency),
483 static_cast<double>(currentGain)));
487 currentFs,
static_cast<double>(states_[b].cfg.frequency),
488 static_cast<double>(currentGain)));
493 bandFilter_[b].setCoeffs(BiquadCoeffs{});
497 meterGainDb_[b].store(currentGain, std::memory_order_relaxed);
501 for (
int ch = 0; ch < nCh; ++ch)
503 T audioSample = audio.getChannel(ch)[i];
506 lookaheadBuf_[ch].push(audioSample);
507 audioSample = lookaheadBuf_[ch].read(laSamples);
510 for (
int b = 0; b < nb; ++b) {
511 if (states_[b].cfg.enabled) {
512 audioSample = bandFilter_[b].processSample(audioSample, ch);
515 audio.getChannel(ch)[i] = audioSample;
520 [[nodiscard]] T computeTargetGain(
const BandConfig& cfg, T levelDb)
const noexcept
524 if (levelDb > cfg.threshold)
526 if (cfg.aboveRatio > T(1.001)) {
527 T overDb = levelDb - cfg.threshold;
528 T amount = std::min(overDb * (T(1) - T(1) / cfg.aboveRatio), cfg.aboveRangeDb);
529 gainDb += cfg.aboveBoost ? amount : -amount;
534 if (cfg.belowRatio > T(1.001)) {
535 T underDb = cfg.threshold - levelDb;
536 T amount = std::min(underDb * (T(1) - T(1) / cfg.belowRatio), cfg.belowRangeDb);
537 gainDb += cfg.belowBoost ? amount : -amount;
543 void updateBandInternalState(
int b,
double fs)
noexcept
553 s0 = configSeq_[b].load(std::memory_order_acquire);
554 cfg = configs_[
static_cast<size_t>(b)];
555 std::atomic_thread_fence(std::memory_order_acquire);
556 s1 = configSeq_[b].load(std::memory_order_relaxed);
557 }
while ((s0 & 1u) != 0u || s0 != s1);
561 const bool wasEnabled = states_[b].cfg.enabled;
562 if (cfg.enabled && !wasEnabled)
564 bandDetector_[b].reset();
565 bandFilter_[b].reset();
566 currentGainDb_[b] = T(0);
568 states_[b].cfg = cfg;
589 const double w0 = 2.0 * 3.14159265358979323846 *
static_cast<double>(cfg.frequency) / fs;
590 precomputedCos_[b] = std::cos(w0);
591 precomputedAlpha_[b] = std::sin(w0) / (2.0 * std::max(
static_cast<double>(cfg.q), 0.001));
593 auto calcCoeff = [fs](T ms) -> T {
594 const double tauSec = std::max(
static_cast<double>(ms), 0.01) / 1000.0;
595 return static_cast<T
>(1.0 - std::exp(-1.0 / (fs * tauSec)));
598 states_[b].aboveAtkCoeff = calcCoeff(cfg.aboveAttackMs);
599 states_[b].aboveRelCoeff = calcCoeff(cfg.aboveReleaseMs);
600 states_[b].belowAtkCoeff = calcCoeff(cfg.belowAttackMs);
601 states_[b].belowRelCoeff = calcCoeff(cfg.belowReleaseMs);
605 void updateDynamicPeakCoeffs(
int b, T gainDb)
noexcept
607 const double A = std::pow(10.0,
static_cast<double>(gainDb) / 40.0);
608 const double cosw = precomputedCos_[b];
609 const double alpha = precomputedAlpha_[b];
610 const double a0Inv = 1.0 / (1.0 + alpha / A);
613 c.b0 = (1.0 + alpha * A) * a0Inv;
614 c.b1 = (-2.0 * cosw) * a0Inv;
615 c.b2 = (1.0 - alpha * A) * a0Inv;
616 c.a1 = (-2.0 * cosw) * a0Inv;
617 c.a2 = (1.0 - alpha / A) * a0Inv;
618 bandFilter_[b].setCoeffs(c);
621 void updateLookahead() noexcept
623 if (sampleRate_ > 0) {
624 int samples =
static_cast<int>(sampleRate_ * oversamplingFactor_ * lookaheadMs_ / T(1000));
625 lookaheadSamples_.store(samples, std::memory_order_relaxed);
630 std::atomic<bool> isPrepared_ {
false };
632 double sampleRate_ = 0;
634 std::atomic<int> numBands_ { 0 };
638 std::array<BandConfig, MaxBands> configs_ {};
639 std::array<std::atomic<unsigned>, MaxBands> configSeq_ {};
640 std::array<std::atomic<bool>, MaxBands> paramsDirty_ {};
641 std::array<BandState, MaxBands> states_ {};
644 std::array<double, MaxBands> precomputedCos_ {};
645 std::array<double, MaxBands> precomputedAlpha_ {};
650 std::array<Biquad<T, kMaxChannels>, MaxBands> bandDetector_ {};
651 std::array<Biquad<T, kMaxChannels>, MaxBands> bandFilter_ {};
652 std::array<T, MaxBands> currentGainDb_ {};
653 std::array<std::atomic<T>, MaxBands> meterGainDb_ {};
655 int oversamplingFactor_ = 1;
656 std::unique_ptr<Oversampling<T>> oversampler_;
657 std::unique_ptr<Oversampling<T>> oversamplerSc_;
659 T lookaheadMs_ = T(0);
660 std::atomic<int> lookaheadSamples_ { 0 };
661 std::array<RingBuffer<T>, kMaxChannels> lookaheadBuf_ {};