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:
@@ -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>
|
||||
|
||||
@@ -262,8 +262,14 @@ public partial class MainViewModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Master ladder input: the measured mix, 0…1 on the same display scale.</summary>
|
||||
public double MasterMeterFraction => AudioLevelMeter.ToDisplay(_audioMixer.MasterLevel);
|
||||
/// <summary>Master ladder input: the MEASURED mix while a session runs (that is the buffer the
|
||||
/// encoder receives), and an estimate from the measured sources when idle — otherwise the master
|
||||
/// sits at zero between sessions while every bay below it moves.</summary>
|
||||
public double MasterMeterFraction =>
|
||||
IsLive || IsRecording
|
||||
? AudioLevelMeter.ToDisplay(_audioMixer.MasterLevel)
|
||||
: Helpers.MeterScale.MonitorFraction((float)AudioLevel, MicVolume,
|
||||
(float)GameAudioLevel, GameAudioVolume);
|
||||
|
||||
public string MasterLevelLabel => Helpers.GainDb.Label(Helpers.GainDb.ToDb(_audioMixer.MasterLevel)) + " dB";
|
||||
|
||||
|
||||
@@ -33,6 +33,14 @@ public class MeterScaleTests
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user