diff --git a/ViewModels/MainViewModel.Audio.cs b/ViewModels/MainViewModel.Audio.cs index ba347a4..412c427 100644 --- a/ViewModels/MainViewModel.Audio.cs +++ b/ViewModels/MainViewModel.Audio.cs @@ -13,6 +13,49 @@ namespace ytLive.ViewModels; public partial class MainViewModel { + private readonly DispatcherTimer _meterTimer; + private int _traxSilentTicks; + private bool _traxSilenceLogged; + + /// The dock's heartbeat: every meter readout is refreshed here, 20 times a second, + /// from whatever the audio threads last measured. + 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(); + } + + /// 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. + 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; diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 86b3303..3ed8928 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -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();