namespace ytLive.Services.Audio; /// /// Computes the smoothed mic level (0..1) from captured samples. Pure — /// unit-tested; the only feeds it and forwards the /// result. RMS-based so it tracks perceived loudness, smoothed so the meter /// does not flicker. /// public sealed class AudioLevelMeter { private const float Smoothing = 0.2f; private const float DecaySmoothing = 0.5f; private float _level; public float Level => _level; /// /// 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. /// 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. /// 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; }