46 lines
1.5 KiB
C#
46 lines
1.5 KiB
C#
namespace ytLive.Services.Audio;
|
|
|
|
/// <summary>
|
|
/// Master output limiter — the last stage before the live mix reaches the
|
|
/// encoder pipe: a peak ceiling of -1 dBFS so mic + loopback (game + TRAX music)
|
|
/// can never sum past 0 dBFS and clip in the AAC encode. Instant attack per frame
|
|
/// (a hot frame is scaled exactly to the ceiling — no overshoot), smoothed release
|
|
/// toward unity so loud passages don't pump. Pure — unit-tested.
|
|
/// </summary>
|
|
public sealed class MasterLimiter
|
|
{
|
|
/// <summary>-1 dBFS: standard streaming headroom for encoder overshoot.</summary>
|
|
public const float Ceiling = 0.891f;
|
|
|
|
private const float Release = 0.005f;
|
|
private float _gain = 1f;
|
|
|
|
/// <summary>The current gain multiplier (≤ 1, so the limiter never boosts).</summary>
|
|
public float Gain => _gain;
|
|
|
|
/// <summary>Scales the frame by the current gain. Gain drops instantly to
|
|
/// <c>ceiling/peak</c> when the frame exceeds the ceiling and recovers
|
|
/// toward unity slowly.</summary>
|
|
public void Process(float[] samples)
|
|
{
|
|
var peak = 0f;
|
|
foreach (var s in samples)
|
|
{
|
|
var abs = MathF.Abs(s);
|
|
if (abs > peak)
|
|
peak = abs;
|
|
}
|
|
|
|
var target = peak > Ceiling ? Ceiling / peak : 1f;
|
|
if (target < _gain)
|
|
_gain = target;
|
|
else
|
|
_gain += (target - _gain) * Release;
|
|
|
|
for (var i = 0; i < samples.Length; i++)
|
|
samples[i] *= _gain;
|
|
}
|
|
|
|
public void Reset() => _gain = 1f;
|
|
}
|