124 static_assert(std::is_trivially_copyable_v<BandConfig>,
"BandConfig must be trivially copyable for std::atomic");
125 static_assert(std::atomic<T>::is_always_lock_free,
126 "audio-thread stores must not lock");
130 for (
int i = 0; i < MaxBands; ++i) {
132 staged_[
static_cast<size_t>(i)].publish(masterConfigs_[i]);
133 paramsDirty_[i].store(
true, std::memory_order_relaxed);
144 isPrepared_.store(
false, std::memory_order_relaxed);
148 if (oversamplingFactor_ > 1)
153 oversampler_ = std::make_unique<Oversampling<T>>(oversamplingFactor_);
154 oversamplerSc_ = std::make_unique<Oversampling<T>>(oversamplingFactor_);
155 oversampler_ ->prepare(spec);
156 oversamplerSc_->prepare(spec);
160 oversampler_.reset();
161 oversamplerSc_.reset();
164 analytic_.assign(
static_cast<size_t>(MaxBands * kMaxChannels),
HilbertIIR<double> {});
166 int maxLaSamples =
static_cast<int>(sampleRate_ * oversamplingFactor_ * 0.01) + 1;
167 for (
int ch = 0; ch < kMaxChannels; ++ch)
168 lookaheadBuf_[ch].
prepare(maxLaSamples);
172 isPrepared_.store(
true, std::memory_order_relaxed);
191 if (!isPrepared_.load(std::memory_order_relaxed))
return;
195 if (sidechain.getNumChannels() <= 0 ||
196 sidechain.getNumSamples() < audio.getNumSamples())
198 if (audio.getNumChannels() <= 0)
return;
204 if (oversamplingFactor_ > 1 && oversampler_ && oversamplerSc_)
210 auto upAudio = oversampler_->upsample(audio);
211 auto upSc = oversamplerSc_->upsample(sidechain);
213 processCore(upAudio, upSc, sampleRate_ * oversamplingFactor_);
215 oversampler_->downsample(audio);
220 processCore(audio, sidechain, sampleRate_);
234 if (band < 0 || band >= MaxBands)
return;
240 const BandConfig& prev = masterConfigs_[
static_cast<size_t>(band)];
241 auto keep = [](T v, T fallback) {
return std::isfinite(v) ? v : fallback; };
243 c.
q = keep(c.
q, prev.
q);
255 masterConfigs_[
static_cast<size_t>(band)] = c;
256 staged_[
static_cast<size_t>(band)].publish(c);
257 paramsDirty_[band].store(
true, std::memory_order_release);
262 numBands_.store(std::clamp(n, 1, MaxBands), std::memory_order_relaxed);
286 oversamplingFactor_ = std::clamp(factor, 1, 4);
287 if (oversamplingFactor_ == 3) oversamplingFactor_ = 4;
288 isPrepared_.store(
false, std::memory_order_relaxed);
295 if (!std::isfinite(ms))
return;
296 lookaheadMs_ = std::clamp(ms, T(0), T(10));
310 const int factor = std::max(oversamplingFactor_, 1);
311 const int la = lookaheadSamples_.load(std::memory_order_relaxed) / factor;
312 const int os = oversampler_ ? oversampler_->getLatency() : 0;
318 if (band < 0 || band >= MaxBands)
return T(0);
319 return meterGainDb_[band].load(std::memory_order_relaxed);
324 for (
int b = 0; b < MaxBands; ++b)
326 bandDetector_[b].reset();
327 bandFilter_[b].reset();
328 currentGainDb_[b] = T(0);
329 meterGainDb_[b].store(T(0), std::memory_order_relaxed);
330 paramsDirty_[b].store(
true, std::memory_order_relaxed);
332 for (
int ch = 0; ch < kMaxChannels; ++ch)
333 lookaheadBuf_[ch].
reset();
338 [[nodiscard]] std::vector<uint8_t>
getState()
const
341 const int n = numBands_.load(std::memory_order_relaxed);
342 w.
write(
"numBands", n);
344 for (
int i = 0; i < n; ++i)
349 const BandConfig c = staged_[
static_cast<size_t>(i)].read();
350 std::snprintf(key,
sizeof(key),
"b%d.freq", i);
352 std::snprintf(key,
sizeof(key),
"b%d.q", i);
353 w.
write(key,
static_cast<float>(c.
q));
354 std::snprintf(key,
sizeof(key),
"b%d.thresh", i);
356 std::snprintf(key,
sizeof(key),
"b%d.shape", i);
358 std::snprintf(key,
sizeof(key),
"b%d.on", i);
360 std::snprintf(key,
sizeof(key),
"b%d.aRatio", i);
362 std::snprintf(key,
sizeof(key),
"b%d.aAtk", i);
364 std::snprintf(key,
sizeof(key),
"b%d.aRel", i);
366 std::snprintf(key,
sizeof(key),
"b%d.aRange", i);
368 std::snprintf(key,
sizeof(key),
"b%d.aBoost", i);
370 std::snprintf(key,
sizeof(key),
"b%d.bRatio", i);
372 std::snprintf(key,
sizeof(key),
"b%d.bAtk", i);
374 std::snprintf(key,
sizeof(key),
"b%d.bRel", i);
376 std::snprintf(key,
sizeof(key),
"b%d.bRange", i);
378 std::snprintf(key,
sizeof(key),
"b%d.bBoost", i);
389 const int n = std::clamp(r.
read(
"numBands", 0), 0, MaxBands);
391 for (
int i = 0; i < n; ++i)
394 std::snprintf(key,
sizeof(key),
"b%d.freq", i);
396 std::snprintf(key,
sizeof(key),
"b%d.q", i);
397 c.
q =
static_cast<T
>(r.
read(key, 1.0f));
398 std::snprintf(key,
sizeof(key),
"b%d.thresh", i);
400 std::snprintf(key,
sizeof(key),
"b%d.shape", i);
402 std::snprintf(key,
sizeof(key),
"b%d.on", i);
404 std::snprintf(key,
sizeof(key),
"b%d.aRatio", i);
406 std::snprintf(key,
sizeof(key),
"b%d.aAtk", i);
408 std::snprintf(key,
sizeof(key),
"b%d.aRel", i);
410 std::snprintf(key,
sizeof(key),
"b%d.aRange", i);
412 std::snprintf(key,
sizeof(key),
"b%d.aBoost", i);
414 std::snprintf(key,
sizeof(key),
"b%d.bRatio", i);
416 std::snprintf(key,
sizeof(key),
"b%d.bAtk", i);
418 std::snprintf(key,
sizeof(key),
"b%d.bRel", i);
420 std::snprintf(key,
sizeof(key),
"b%d.bRange", i);
422 std::snprintf(key,
sizeof(key),
"b%d.bBoost", i);
431 static constexpr int kMaxChannels = 16;
432 static constexpr T kMinLevelDb = T(-100.0);
433 static constexpr T kMinEnvelope = T(1e-12);
438 T aboveAtkCoeff, aboveRelCoeff;
439 T belowAtkCoeff, belowRelCoeff;
442 void processCore(AudioBufferView<T>& audio, AudioBufferView<T>& sidechain,
double currentFs)
noexcept
444 const int nCh = std::min(audio.getNumChannels(), kMaxChannels);
445 const int scCh = sidechain.getNumChannels();
446 const int nS = audio.getNumSamples();
447 if (scCh <= 0)
return;
448 const int nb = numBands_.load(std::memory_order_relaxed);
449 const int laSamples = lookaheadSamples_.load(std::memory_order_relaxed);
452 for (
int b = 0; b < nb; ++b)
454 if (paramsDirty_[b].exchange(
false, std::memory_order_acquire))
455 updateBandInternalState(b, currentFs);
462 for (
int chunkStart = 0; chunkStart < nS; chunkStart += kDetChunk)
464 const int chunkLen = std::min(kDetChunk, nS - chunkStart);
466 for (
int b = 0; b < nb; ++b)
468 if (!states_[b].cfg.enabled)
continue;
469 T* levels = detLevelDb_[
static_cast<size_t>(b)].data();
470 std::fill(levels, levels + chunkLen, kMinLevelDb);
473 for (
int ch = 0; ch < nCh; ++ch)
475 const T* sc = sidechain.getChannel(std::min(ch, scCh - 1)) + chunkStart;
476 for (
int j = 0; j < chunkLen; ++j)
477 detBand_[
static_cast<size_t>(j)] =
478 static_cast<double>(bandDetector_[b].processSample(sc[j], ch));
479 analytic_[
static_cast<size_t>(b * kMaxChannels + ch)].magnitudeBlock(
480 detBand_.data(), detMag_.data(), chunkLen);
481 for (
int j = 0; j < chunkLen; ++j)
485 const T detected = std::max(
static_cast<T
>(std::abs(detBand_[
static_cast<size_t>(j)])),
486 static_cast<T
>(detMag_[
static_cast<size_t>(j)]));
487 const T levelDb =
gainToDecibels(std::max(detected, kMinEnvelope));
488 if (levelDb > levels[j]) levels[j] = levelDb;
493 for (
int j = 0; j < chunkLen; ++j)
495 const int i = chunkStart + j;
496 for (
int b = 0; b < nb; ++b)
498 if (!states_[b].cfg.enabled)
continue;
500 const T maxLevelDb = detLevelDb_[
static_cast<size_t>(b)][
static_cast<size_t>(j)];
503 T targetGainDb = computeTargetGain(states_[b].cfg, maxLevelDb);
506 T& currentGain = currentGainDb_[b];
507 T diff = targetGainDb - currentGain;
510 if (maxLevelDb > states_[b].cfg.threshold) {
511 coeff = (std::abs(targetGainDb) > std::abs(currentGain))
512 ? states_[b].aboveAtkCoeff : states_[b].aboveRelCoeff;
514 coeff = (std::abs(targetGainDb) > std::abs(currentGain))
515 ? states_[b].belowAtkCoeff : states_[b].belowRelCoeff;
518 currentGain += coeff * diff;
530 if (std::abs(currentGain) > T(0.01))
532 switch (states_[b].cfg.shape)
535 updateDynamicPeakCoeffs(b, currentGain);
539 currentFs,
static_cast<double>(states_[b].cfg.frequency),
540 static_cast<double>(currentGain)));
544 currentFs,
static_cast<double>(states_[b].cfg.frequency),
545 static_cast<double>(currentGain)));
550 bandFilter_[b].setCoeffsNow(BiquadCoeffs{});
554 meterGainDb_[b].store(currentGain, std::memory_order_relaxed);
558 for (
int ch = 0; ch < nCh; ++ch)
560 T audioSample = audio.getChannel(ch)[i];
563 lookaheadBuf_[ch].push(audioSample);
564 audioSample = lookaheadBuf_[ch].read(laSamples);
567 for (
int b = 0; b < nb; ++b) {
568 if (states_[b].cfg.enabled) {
569 audioSample = bandFilter_[b].processSample(audioSample, ch);
572 audio.getChannel(ch)[i] = audioSample;
578 [[nodiscard]] T computeTargetGain(
const BandConfig& cfg, T levelDb)
const noexcept
582 if (levelDb > cfg.threshold)
584 if (cfg.aboveRatio > T(1.001)) {
585 T overDb = levelDb - cfg.threshold;
586 T amount = std::min(overDb * (T(1) - T(1) / cfg.aboveRatio), cfg.aboveRangeDb);
587 gainDb += cfg.aboveBoost ? amount : -amount;
592 if (cfg.belowRatio > T(1.001)) {
593 T underDb = cfg.threshold - levelDb;
594 T amount = std::min(underDb * (T(1) - T(1) / cfg.belowRatio), cfg.belowRangeDb);
595 gainDb += cfg.belowBoost ? amount : -amount;
601 void updateBandInternalState(
int b,
double fs)
noexcept
611 if (!staged_[
static_cast<size_t>(b)].tryRead(cfg))
616 paramsDirty_[b].store(
true, std::memory_order_release);
622 const bool wasEnabled = states_[b].cfg.enabled;
623 if (cfg.enabled && !wasEnabled)
625 bandDetector_[b].reset();
626 bandFilter_[b].reset();
627 currentGainDb_[b] = T(0);
629 states_[b].cfg = cfg;
654 const double w0 = 2.0 * 3.14159265358979323846 *
static_cast<double>(cfg.frequency) / fs;
655 precomputedCos_[b] = std::cos(w0);
656 precomputedAlpha_[b] = std::sin(w0) / (2.0 * std::max(
static_cast<double>(cfg.q), 0.001));
658 auto calcCoeff = [fs](T ms) -> T {
659 const double tauSec = std::max(
static_cast<double>(ms), 0.01) / 1000.0;
660 return static_cast<T
>(1.0 - std::exp(-1.0 / (fs * tauSec)));
663 states_[b].aboveAtkCoeff = calcCoeff(cfg.aboveAttackMs);
664 states_[b].aboveRelCoeff = calcCoeff(cfg.aboveReleaseMs);
665 states_[b].belowAtkCoeff = calcCoeff(cfg.belowAttackMs);
666 states_[b].belowRelCoeff = calcCoeff(cfg.belowReleaseMs);
670 void updateDynamicPeakCoeffs(
int b, T gainDb)
noexcept
672 const double A = std::pow(10.0,
static_cast<double>(gainDb) / 40.0);
673 const double cosw = precomputedCos_[b];
674 const double alpha = precomputedAlpha_[b];
675 const double a0Inv = 1.0 / (1.0 + alpha / A);
678 c.b0 = (1.0 + alpha * A) * a0Inv;
679 c.b1 = (-2.0 * cosw) * a0Inv;
680 c.b2 = (1.0 - alpha * A) * a0Inv;
681 c.a1 = (-2.0 * cosw) * a0Inv;
682 c.a2 = (1.0 - alpha / A) * a0Inv;
685 bandFilter_[b].setCoeffsNow(c);
688 void updateLookahead() noexcept
690 if (sampleRate_ > 0) {
691 int samples =
static_cast<int>(sampleRate_ * oversamplingFactor_ * lookaheadMs_ / T(1000));
692 lookaheadSamples_.store(samples, std::memory_order_relaxed);
697 std::atomic<bool> isPrepared_ {
false };
699 double sampleRate_ = 0;
701 std::atomic<int> numBands_ { 0 };
715 std::atomic<T> frequency { T(1000) };
716 std::atomic<T> q { T(1.0) };
717 std::atomic<T> threshold { T(-20) };
719 std::atomic<bool> enabled {
true };
721 std::atomic<T> aboveRatio { T(1) };
722 std::atomic<T> aboveAttackMs { T(5) };
723 std::atomic<T> aboveReleaseMs { T(50) };
724 std::atomic<T> aboveRangeDb { T(12) };
725 std::atomic<bool> aboveBoost {
false };
727 std::atomic<T> belowRatio { T(1) };
728 std::atomic<T> belowAttackMs { T(10) };
729 std::atomic<T> belowReleaseMs { T(100) };
730 std::atomic<T> belowRangeDb { T(12) };
731 std::atomic<bool> belowBoost {
false };
733 std::atomic<unsigned> seq { 0 };
746 void publish(
const BandConfig& c)
noexcept
748 seq.fetch_add(1, std::memory_order_acq_rel);
749 std::atomic_thread_fence(std::memory_order_release);
750 frequency.store(c.frequency, std::memory_order_relaxed);
751 q.store(c.q, std::memory_order_relaxed);
752 threshold.store(c.threshold, std::memory_order_relaxed);
753 shape.store(
static_cast<int>(c.shape), std::memory_order_relaxed);
754 enabled.store(c.enabled, std::memory_order_relaxed);
755 aboveRatio.store(c.aboveRatio, std::memory_order_relaxed);
756 aboveAttackMs.store(c.aboveAttackMs, std::memory_order_relaxed);
757 aboveReleaseMs.store(c.aboveReleaseMs, std::memory_order_relaxed);
758 aboveRangeDb.store(c.aboveRangeDb, std::memory_order_relaxed);
759 aboveBoost.store(c.aboveBoost, std::memory_order_relaxed);
760 belowRatio.store(c.belowRatio, std::memory_order_relaxed);
761 belowAttackMs.store(c.belowAttackMs, std::memory_order_relaxed);
762 belowReleaseMs.store(c.belowReleaseMs, std::memory_order_relaxed);
763 belowRangeDb.store(c.belowRangeDb, std::memory_order_relaxed);
764 belowBoost.store(c.belowBoost, std::memory_order_relaxed);
765 seq.fetch_add(1, std::memory_order_release);
773 static constexpr int kSeqlockMaxAttempts = 3;
781 void loadWordsRelaxed(BandConfig& c)
const noexcept
783 c.frequency = frequency.load(std::memory_order_relaxed);
784 c.q = q.load(std::memory_order_relaxed);
785 c.threshold = threshold.load(std::memory_order_relaxed);
786 c.shape =
static_cast<BandShape>(shape.load(std::memory_order_relaxed));
787 c.enabled = enabled.load(std::memory_order_relaxed);
788 c.aboveRatio = aboveRatio.load(std::memory_order_relaxed);
789 c.aboveAttackMs = aboveAttackMs.load(std::memory_order_relaxed);
790 c.aboveReleaseMs = aboveReleaseMs.load(std::memory_order_relaxed);
791 c.aboveRangeDb = aboveRangeDb.load(std::memory_order_relaxed);
792 c.aboveBoost = aboveBoost.load(std::memory_order_relaxed);
793 c.belowRatio = belowRatio.load(std::memory_order_relaxed);
794 c.belowAttackMs = belowAttackMs.load(std::memory_order_relaxed);
795 c.belowReleaseMs = belowReleaseMs.load(std::memory_order_relaxed);
796 c.belowRangeDb = belowRangeDb.load(std::memory_order_relaxed);
797 c.belowBoost = belowBoost.load(std::memory_order_relaxed);
820 [[nodiscard]]
bool tryRead(BandConfig& out)
const noexcept
822 for (
int attempt = 0; attempt < kSeqlockMaxAttempts; ++attempt)
824 const unsigned s0 = seq.load(std::memory_order_acquire);
825 if ((s0 & 1u) != 0u)
continue;
828 std::atomic_thread_fence(std::memory_order_acquire);
829 if (s0 == seq.load(std::memory_order_relaxed))
853 [[nodiscard]] BandConfig read() const noexcept
858 s0 = seq.load(std::memory_order_acquire);
860 std::atomic_thread_fence(std::memory_order_acquire);
861 s1 = seq.load(std::memory_order_relaxed);
862 }
while ((s0 & 1u) != 0u || s0 != s1);
868 std::array<StagedBand, MaxBands> staged_ {};
874 std::array<BandConfig, MaxBands> masterConfigs_ {};
875 std::array<std::atomic<bool>, MaxBands> paramsDirty_ {};
876 std::array<BandState, MaxBands> states_ {};
879 std::array<double, MaxBands> precomputedCos_ {};
880 std::array<double, MaxBands> precomputedAlpha_ {};
885 std::array<Biquad<T, kMaxChannels>, MaxBands> bandDetector_ {};
886 std::array<Biquad<T, kMaxChannels>, MaxBands> bandFilter_ {};
887 std::array<T, MaxBands> currentGainDb_ {};
888 std::array<std::atomic<T>, MaxBands> meterGainDb_ {};
890 int oversamplingFactor_ = 1;
891 std::unique_ptr<Oversampling<T>> oversampler_;
892 std::unique_ptr<Oversampling<T>> oversamplerSc_;
894 T lookaheadMs_ = T(0);
895 std::atomic<int> lookaheadSamples_ { 0 };
896 std::array<RingBuffer<T>, kMaxChannels> lookaheadBuf_ {};
897 std::vector<HilbertIIR<double>> analytic_;
898 static constexpr int kDetChunk = 64;
899 std::array<double, kDetChunk> detBand_ {};
900 std::array<double, kDetChunk> detMag_ {};
901 std::array<std::array<T, kDetChunk>, MaxBands> detLevelDb_ {};