feat(audio): the segment meter control (unit 3 of the dock)

`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.
This commit is contained in:
2026-10-30 12:36:05 -07:00
parent 575caa7bc4
commit 0f62e55474
3 changed files with 185 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
using Xunit;
using ytLive.Helpers;
namespace ytLive.Tests;
/// <summary>A segment ladder is only useful if the band it paints is the level it measured. The
/// thresholds are DESIGN.md's (nominal / caution / peak), and peak-hold is what makes clipping
/// visible — a bar that follows the moment hides the one audio fault that cannot be fixed later.
/// </summary>
public class MeterScaleTests
{
[Fact]
public void Bands_peak_hold_and_the_display_scale_follow_the_standard()
{
// Bands at the documented thresholds.
Assert.Equal(MeterBand.Nominal, MeterScale.BandFor(-30f));
Assert.Equal(MeterBand.Nominal, MeterScale.BandFor(-12.1f));
Assert.Equal(MeterBand.Caution, MeterScale.BandFor(-12f));
Assert.Equal(MeterBand.Caution, MeterScale.BandFor(-6f));
Assert.Equal(MeterBand.Peak, MeterScale.BandFor(-3f));
Assert.Equal(MeterBand.Peak, MeterScale.BandFor(0f));
// Display fraction: −60 dBFS is empty, 0 dBFS is full, −30 is halfway, and nothing escapes.
Assert.Equal(0f, MeterScale.ToFraction(-60f));
Assert.Equal(1f, MeterScale.ToFraction(0f));
Assert.Equal(0.5f, MeterScale.ToFraction(-30f), 4);
Assert.Equal(1f, MeterScale.ToFraction(+12f));
Assert.Equal(0f, MeterScale.ToFraction(-120f));
// Peak hold rises instantly, then decays at the stated rate.
Assert.Equal(0.8f, MeterScale.NextPeak(0.2f, 0.8f, 0.05f));
Assert.Equal(0.7f, MeterScale.NextPeak(0.8f, 0.1f, 0.1f / 0.35f), 3); // 0.1 fall at 0.35/s
Assert.Equal(0f, MeterScale.NextPeak(0.1f, 0f, 10f)); // never negative
}
}