57 lines
2.0 KiB
C#
57 lines
2.0 KiB
C#
namespace ytLive.Services.Audio;
|
||
|
||
/// <summary>
|
||
/// Computes the smoothed mic level (0..1) from captured samples. Pure —
|
||
/// unit-tested; the <see cref="AudioMixer"/> only feeds it and forwards the
|
||
/// result. RMS-based so it tracks perceived loudness, smoothed so the meter
|
||
/// does not flicker.
|
||
/// </summary>
|
||
public sealed class AudioLevelMeter
|
||
{
|
||
private const float Smoothing = 0.2f;
|
||
private const float DecaySmoothing = 0.5f;
|
||
private float _level;
|
||
|
||
public float Level => _level;
|
||
|
||
/// <summary>
|
||
/// Maps a linear level (0..1) onto the meter's display scale: -60 dBFS..0 dBFS
|
||
/// spread linearly across 0..1 — an HONEST dBFS scale. Linear RMS of real speech is
|
||
/// ~0.01..0.1 (-40..-20 dBFS), which leaves a flat (linear) meter looking dead; the
|
||
/// log scale makes typical levels occupy the bar. Inputs at or below -60 dBFS
|
||
/// (0.001 linear) read as zero — the meter never idles on background noise.
|
||
/// <para>2026-10-09: the +10 dB "input amplification" that used to sit here is gone.
|
||
/// It made a −30 dBFS voice look dialed in, and a creator's upload came out at
|
||
/// −28.5 LUFS. The speech leveler now puts voice at −20 dBFS, which lands at 2/3 of
|
||
/// the bar (the green/yellow edge); loud peaks reach the red.</para>
|
||
/// </summary>
|
||
public static float ToDisplay(float linear)
|
||
{
|
||
if (linear <= 0.001f)
|
||
return 0f;
|
||
var db = 20f * MathF.Log10(linear);
|
||
return Math.Clamp(1f + db / 60f, 0f, 1f);
|
||
}
|
||
|
||
public float Push(AudioSample sample)
|
||
{
|
||
if (sample.Samples.Length == 0)
|
||
return _level;
|
||
|
||
double sumSquares = 0;
|
||
var count = 0;
|
||
foreach (var value in sample.Samples)
|
||
{
|
||
sumSquares += value * value;
|
||
count++;
|
||
}
|
||
|
||
var rms = (float)Math.Sqrt(sumSquares / count);
|
||
var alpha = rms > _level ? Smoothing : DecaySmoothing;
|
||
_level = _level * (1 - alpha) + rms * alpha;
|
||
return _level;
|
||
}
|
||
|
||
public void Reset() => _level = 0;
|
||
}
|