Files
LlamaCasty/ytLive.Tests/MeterScaleTests.cs
T
gramps e92ce4bb63 fix(dock): the master ladder moves when no session is running
Reported: no activity in the master meter.

Cause: `MasterLevel` measures the buffer the ENCODER receives, and that buffer only exists while the
mix is being pulled — streaming or recording. Idle, nothing fills it, so the master read zero while
the bays below it moved. A master that is dead between sessions is a gap in the design.

Now: while a session runs the master shows the measured mix (unchanged); idle it shows an estimate
computed from the same measured sources and the same gains the mix applies
(`MeterScale.MonitorFraction`, pure + tested). Two ladders can therefore never tell contradictory
stories about whether audio exists.

Guard: MeterScaleTests pins the estimate — silence reads zero, a quiet source at unity is visible,
two sources add, and gain is applied in the LINEAR domain like the mix (scaling the display fraction
would be a different, wrong curve).
2026-10-30 14:01:29 -07:00

50 lines
2.7 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 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));
// Level ballistics: a jump is followed quickly, a drop falls gently — the same curve that
// makes the mic meter look fluid. Raw peaks at a 30 Hz UI tick stutter without it.
Assert.Equal(0.6f, MeterScale.NextLevel(0f, 1f), 3); // attack
Assert.True(MeterScale.NextLevel(0.9f, 0.1f) > 0.8f); // decay is gentle
Assert.True(MeterScale.NextLevel(0.1f, 0.9f) > 0.5f); // rise is fast
// The idle master estimate: sources × their gains, on the same display scale. With nothing
// playing it is zero; a quiet source at unity is visible; two sources add.
Assert.Equal(0f, MeterScale.MonitorFraction(0f, 1, 0f, 1));
Assert.True(MeterScale.MonitorFraction(0.05f, 1, 0f, 1) > 0.3f);
Assert.True(MeterScale.MonitorFraction(0.05f, 1, 0.05f, 1) > MeterScale.MonitorFraction(0.05f, 1, 0f, 1));
// Gain is applied in the LINEAR domain, like the mix does — not to the display fraction.
Assert.True(MeterScale.MonitorFraction(0.05f, 0.5, 0f, 1) < MeterScale.MonitorFraction(0.05f, 1, 0f, 1));
// 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
}
}