`Controls/SegmentMeter` draws a 24-segment ladder in ONE OnRender pass — no per-segment elements updating on the UI thread, which is the difference between a meter and a UI thread tax. Level and peak are display fractions (what was MEASURED), never gain settings; vertical for the bay ladders, horizontal for the master strip. Bands come from `Helpers/MeterScale` (nominal/caution/peak, DESIGN.md thresholds) and live as dependencies of the control, so the painting logic is pure and testable: the band a segment paints is the level it measured. Brushes resolve from the theme tokens via SetResourceReference, so the ladder cannot drift from DESIGN.md. Peak-hold is the reason the header exists: a bar that follows the moment hides clipping, and clipping is the one audio fault that cannot be fixed after the stream. Guard: MeterScaleTests — band edges (inclusive at −12 and −3, as the standard states the ranges), the −60…0 display scale, and peak-hold rising instantly then decaying at 0.35/s.
46 lines
1.9 KiB
C#
46 lines
1.9 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>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));
|
||
}
|
||
}
|