Reported: the TRAX bar moves, but its sampling looks far slower than the mic meter. Cause: the TRAX number was a raw instantaneous peak per metering block — a stutter sampled by the UI timer, with no smoothing. The mic looks fluid because it is smoothed at audio-callback rate. Fix: the player now smooths its measured level with the SAME curve the mic uses (MeterScale.NextLevel: fast attack, gentle decay), and the dock's meter clock runs at ~30 Hz instead of 20. Two ladders that move alike, because they are the same ballistics. Guard: MeterScaleTests pins the attack/decay behaviour alongside peak-hold.
55 lines
2.4 KiB
C#
55 lines
2.4 KiB
C#
using System;
|
||
|
||
namespace ytLive.Helpers;
|
||
|
||
/// <summary>Which colour band a level falls in (DESIGN.md meter thresholds).</summary>
|
||
public enum MeterBand
|
||
{
|
||
Nominal,
|
||
Caution,
|
||
Peak,
|
||
}
|
||
|
||
/// <summary>The mathematics of the dock's segment meter, kept out of the control so it can be
|
||
/// tested: a segment ladder is only honest if the band it paints is the level it measured.
|
||
///
|
||
/// <para>Bands follow DESIGN.md: nominal to −12 dB, caution to −3 dB, peak above. The display
|
||
/// fraction spreads −60…0 dBFS across 0…1 — the same scale <c>AudioLevelMeter.ToDisplay</c> uses,
|
||
/// so the ladder and the old bars cannot disagree.</para></summary>
|
||
public static class MeterScale
|
||
{
|
||
public const float FloorDb = -60f;
|
||
public const float CautionDb = -12f;
|
||
public const float PeakDb = -3f;
|
||
|
||
/// <summary>dBFS → 0…1 across the whole ladder.</summary>
|
||
public static float ToFraction(float dbfs)
|
||
=> Math.Clamp(1f + Math.Clamp(dbfs, FloorDb, 0f) / 60f, 0f, 1f);
|
||
|
||
/// <summary>The band boundaries are INCLUSIVE at the bottom edge (a meter reading exactly
|
||
/// −12.0 dB is cautious, exactly −3.0 is peaking), matching the ranges as DESIGN.md states
|
||
/// them: "−12 … −3" and "−3 … 0+".</summary>
|
||
public static MeterBand BandFor(float dbfs)
|
||
=> dbfs >= PeakDb ? MeterBand.Peak
|
||
: dbfs >= CautionDb ? MeterBand.Caution
|
||
: MeterBand.Nominal;
|
||
|
||
/// <summary>Ballistics for a level: reach a new peak quickly, fall back gently. Raw per-block
|
||
/// peaks sampled by a UI timer look like a stutter; this is what makes the mic feel fluid, and
|
||
/// TRAX gets the same curve so the two ladders move alike.</summary>
|
||
public static float NextLevel(float current, float incoming, float attack = 0.6f, float decay = 0.12f)
|
||
{
|
||
var alpha = incoming > current ? attack : decay;
|
||
return current + (incoming - current) * alpha;
|
||
}
|
||
|
||
/// <summary>Peak hold: jumps up instantly to a new maximum and falls back at
|
||
/// <paramref name="decayPerSecond"/> — a moment-to-moment bar hides clipping, which is the one
|
||
/// audio fault that cannot be fixed afterwards.</summary>
|
||
public static float NextPeak(float held, float incoming, float seconds, float decayPerSecond = 0.35f)
|
||
{
|
||
if (incoming >= held) return incoming;
|
||
return Math.Max(0f, held - decayPerSecond * Math.Max(0f, seconds));
|
||
}
|
||
}
|