fix(dock): meters beat on their own 20 Hz clock, and TRAX silence is diagnosed

Reported: TRAX plays audibly but no ladder registers anywhere.

Two distinct faults, one of them mine:
1. The meter readouts were refreshed only from the MIC's level callback. With a silent or absent
   mic there is no tick, so a playing TRAX never updated its ladder — the level was measured, the
   UI simply never asked for it. Meters now have a 20 Hz DispatcherTimer of their own.
2. If the player's own meter still reads 0 while playing, the samples are not reaching the device
   (or metering is not attached). That now writes ONE line to startup.log after 2s — so the next
   report says which side is broken instead of another round of guessing.

Note: TRAX rides the desktop loopback, so once the mix captures it the GAME ladder should move too;
if it does not, the loopback endpoint and the music's output device differ.
This commit is contained in:
2026-10-30 13:24:29 -07:00
parent a54149bd7c
commit 839bc991a2
2 changed files with 50 additions and 0 deletions
+43
View File
@@ -13,6 +13,49 @@ namespace ytLive.ViewModels;
public partial class MainViewModel
{
private readonly DispatcherTimer _meterTimer;
private int _traxSilentTicks;
private bool _traxSilenceLogged;
/// <summary>The dock's heartbeat: every meter readout is refreshed here, 20 times a second,
/// from whatever the audio threads last measured.</summary>
private void TickMeters()
{
OnPropertyChanged(nameof(MicMeterFraction));
OnPropertyChanged(nameof(MeterFillWidth));
OnPropertyChanged(nameof(MeterBrush));
OnPropertyChanged(nameof(GameMeterFraction));
OnPropertyChanged(nameof(GameMeterFillWidth));
OnPropertyChanged(nameof(GameMeterBrush));
OnPropertyChanged(nameof(TraxMeterFraction));
RaiseMasterReadouts();
UpdateDuckState();
CheckTraxSilence();
}
/// <summary>One honest diagnostic: "playing" with a flat player meter means the samples never
/// reached the device (or the metering was not attached). Logged once per playback, so the next
/// run says which side is broken instead of another round of guessing.</summary>
private void CheckTraxSilence()
{
if (!IsMusicPlaying)
{
_traxSilentTicks = 0;
_traxSilenceLogged = false;
return;
}
if (_musicPlayer.Level > 0f)
{
_traxSilentTicks = 0;
_traxSilenceLogged = false;
return;
}
if (++_traxSilentTicks < 40 || _traxSilenceLogged) return; // 2s at 20 Hz
_traxSilenceLogged = true;
AppLog.Write("TRAX: playing but the player meter reads 0 — the output device is silent or " +
"the metering stream is not attached (the dock's ladder has nothing to show)");
}
private readonly DispatcherTimer _volumeFlashTimer;
private readonly DispatcherTimer _gameVolumeFlashTimer;
private double _audioLevel;
+7
View File
@@ -178,6 +178,13 @@ public partial class MainViewModel : ViewModelBase
_gameVolumeFlashTimer.Tick += (_, _) => EndGameVolumeFlash();
_saveDebounce = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1500) };
// Dock meters beat on their own 20 Hz clock instead of riding the mic's level callback.
// Coupling them to capture was a bug: with a silent or absent mic the ladder never
// refreshed, so a playing TRAX showed nothing (creator, 2026-10-30).
_meterTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) };
_meterTimer.Tick += (_, _) => TickMeters();
_meterTimer.Start();
_saveDebounce.Tick += (_, _) => SaveLayoutNow();
_brandFlash = new BrandFlashPresenter();