ambivalence/Source/DSP/OutputLimiter.h
2026-08-15 15:36:28 +02:00

79 lines
No EOL
3.1 KiB
C++

#pragma once
#include <cmath>
#include <algorithm>
namespace FDNReverb {
// -----------------------------------------------------------------------------
// OutputLimiter: safe output stage (true-peak limiter)
// -----------------------------------------------------------------------------
// design rationale :
// - parameter values are chosen conservatively for safety
// - Threshold = -0.5 dBFS (~0.944): suppresses peaks before the DAW limiter
// - Look-ahead: introduces plugin latency
// - Attack: 0.5 ms (peak-based)
// - Release: 50 ms (prevents unnatural pumping)
//
// real-time safety :
// - allocation: once in prepare(), never in processBlock
// - per-sample gain: one comparison against targetGain, SIMD-friendly
// - floating-point math: no branches or transcendental functions
//
// - layout: output stage of UniversalEngine::processBlock()
// (after Dry/Wet mix, before the stereo output)
// -----------------------------------------------------------------------------
class OutputLimiter {
public:
OutputLimiter() = default;
// --- sample-rate dependent coefficient computation ---
void prepare(double sampleRate) noexcept {
fs = sampleRate;
// 1 path filter coefficient : y[n] = y[n-1] + coeff * (x[n] - y[n-1])
// coeff = 1 - exp(-T / tau) where T = 1/fs, tau = time constant
attackCoeff = 1.0f - std::exp(-1.0f / (static_cast<float>(fs) * 0.0005f)); // 0.5ms
releaseCoeff = 1.0f - std::exp(-1.0f / (static_cast<float>(fs) * 0.050f)); // 50ms
reset();
}
void reset() noexcept {
currentGain = 1.0f;
}
// --- per-sample processing (called from the audio thread) ---
inline void process(float& l, float& r) noexcept {
// peak detection (max of L/R levels)
const float absL = std::abs(l);
const float absR = std::abs(r);
const float peak = std::max(absL, absR);
// Threshold: -0.5 dBFS ~ 0.944
// compute target gain from the signal
constexpr float threshold = 0.944f;
// target gain :
// peak <= threshold -> 1.0 (no reduction needed)
// peak > threshold -> threshold/peak (pull signal to threshold)
const float targetGain = (peak > threshold) ? (threshold / peak) : 1.0f;
// Attack/Release envelope
// when targetGain < currentGain (gain must decrease): attack
// when targetGain > currentGain (gain recovers): release
// so peaks are suppressed smoothly
const float coeff = (targetGain < currentGain) ? attackCoeff : releaseCoeff;
currentGain += (targetGain - currentGain) * coeff;
// apply the same gain to L/R to preserve the stereo image
l *= currentGain;
r *= currentGain;
}
private:
double fs{ 44100.0 };
float attackCoeff{ 0.0f };
float releaseCoeff{ 0.0f };
float currentGain{ 1.0f };
};
} // namespace FDNReverb