mirror of
https://codeberg.org/armin/ambivalence.git
synced 2026-09-01 04:10:48 +02:00
79 lines
3.1 KiB
C
79 lines
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
|