dc91c855ef
- Elements scrollbar no longer deselects element on click (check ScrollViewer, not StackPanel) - Web source: URI + check/revert buttons merged into single DockPanel row, consistent order (✓ rightmost) - Countdown timer moved from Elements panel to inline in source list row, range clamped to 1-30 min - Volume slider click-to-position restored: e.Handled prevents IncreaseLarge from overriding position, Thumb drag still works - Desktop audio meter: asymmetric smoothing (attack 0.2, decay 0.08) for faster response to silence - 207 tests passing, 0 warnings
63 lines
2.2 KiB
C#
63 lines
2.2 KiB
C#
namespace ytLive.Services.Audio;
|
|
|
|
/// <summary>
|
|
/// Computes the smoothed 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. Supports asymmetric smoothing: fast attack (rise) and
|
|
/// configurable decay (fall) so the meter responds quickly to silence.
|
|
/// </summary>
|
|
public sealed class AudioLevelMeter
|
|
{
|
|
private float _level;
|
|
|
|
public float Level => _level;
|
|
|
|
public AudioLevelMeter(float attackSmoothing = 0.2f, float decaySmoothing = 0.08f)
|
|
{
|
|
AttackSmoothing = attackSmoothing;
|
|
DecaySmoothing = decaySmoothing;
|
|
}
|
|
|
|
public float AttackSmoothing { get; }
|
|
public float DecaySmoothing { get; }
|
|
|
|
/// <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 smoothing = rms > _level ? AttackSmoothing : DecaySmoothing;
|
|
_level = _level * (1 - smoothing) + rms * smoothing;
|
|
return _level;
|
|
}
|
|
|
|
public void Reset() => _level = 0;
|
|
}
|