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).
This commit is contained in:
2026-10-30 14:01:29 -07:00
parent ec293e54b0
commit e92ce4bb63
3 changed files with 30 additions and 2 deletions
+14
View File
@@ -34,6 +34,20 @@ public static class MeterScale
: dbfs >= CautionDb ? MeterBand.Caution
: MeterBand.Nominal;
/// <summary>The master ladder when no session is running.
///
/// <para>`AudioMixer.MasterLevel` measures the buffer the encoder receives, and that buffer only
/// exists while the mix is being pulled (streaming or recording). Idle, the master would read
/// zero while the source ladders move — so the monitor level is computed from the same measured
/// sources and the same gains the mix applies. It is an estimate of the mix, not a substitute
/// for it: once a session starts, the real measured value takes over.</para></summary>
public static float MonitorFraction(float micLevel, double micGain, float gameLevel, double gameGain)
{
var linear = Math.Max(0f, (float)(micLevel * micGain) + (float)(gameLevel * gameGain));
if (linear <= 0.001f) return 0f;
return ToFraction(20f * MathF.Log10(linear));
}
/// <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>