Files
LlamaCasty/Helpers/MeterScale.cs
T
gramps 2607ecf210 fix(dock): TRAX ladder gets the mic's ballistics (and a 33 Hz beat)
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.
2026-10-30 13:28:32 -07:00

55 lines
2.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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));
}
}