Files
gramps f9ad7c2cb1 fix: asymmetric meter decay for faster desktop level response
Issue 3: Desktop meter now decays 2.5x faster than it rises
(DecaySmoothing=0.5 vs Smoothing=0.2). When rms drops below current
level the higher alpha pulls the bar down quickly; when audio returns
it rises smoothly. 207 tests pass, 0 warnings.
2026-08-18 12:15:44 -07:00

55 lines
1.8 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, with +10 dB of input amplification. Linear RMS
/// of real speech or game audio 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 and the amplification pushes real speech peaks into the red zone
/// (0.8+) at maxed volume instead of hovering at its edge. Inputs at or below
/// -60 dBFS (0.001 linear) read as zero — the meter never idles on background noise.
/// </summary>
public static float ToDisplay(float linear)
{
if (linear <= 0.001f)
return 0f;
var db = 20f * MathF.Log10(linear) + 10f;
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;
}