#pragma once #include #include 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(fs) * 0.0005f)); // 0.5ms releaseCoeff = 1.0f - std::exp(-1.0f / (static_cast(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