From ec293e54b09e9abcf72d3db27d5396090e3d62ab Mon Sep 17 00:00:00 2001 From: gramps Date: Fri, 30 Oct 2026 13:46:05 -0700 Subject: [PATCH] feat(dock): TRAX becomes a background level, not a channel strip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creator ruling (2026-10-30): TRAX is background music and is controlled separately from desktop and mic — so KISS it: - TRAX loses its own ladder and slider. It is one number plus ±5 dB buttons, disabled at the ends (−60 dB floor, +6 dB ceiling). - It STARTS at −50 dB, taken from the creator's own meter readings, and moves in 5 dB clicks. If that proves inaudible on their setup, six clicks lands at −20 — the buttons exist for exactly that, and `TraxDefaultDb` is one constant. - Layout follows: Desktop's volume now sits directly beneath its ladder, and the TRAX row (name, play/pause, level) sits beneath that, inside the GAME bay. The bays are MIC | GAME+TRAX | LIP-SYNC. - The metering tap on the player stays (it is how "is the music producing sound" is answered) but has no ladder of its own; the desktop bed already shows the music. Guards updated: TraxBayTests now asserts the stepper wiring and that the row has NO SegmentMeter; GameBayTests asserts the desktop fader sits below the desktop ladder. --- Controls/AudioDock.xaml | 155 +++++++++++++------------------ Controls/AudioDock.xaml.cs | 10 ++ ViewModels/MainViewModel.Trax.cs | 18 +++- ViewModels/MainViewModel.cs | 1 + ytLive.Tests/GameBayTests.cs | 4 + ytLive.Tests/TraxBayTests.cs | 21 +++-- 6 files changed, 112 insertions(+), 97 deletions(-) diff --git a/Controls/AudioDock.xaml b/Controls/AudioDock.xaml index 2c74c56..41c4a37 100644 --- a/Controls/AudioDock.xaml +++ b/Controls/AudioDock.xaml @@ -18,11 +18,11 @@ - - + + @@ -30,10 +30,7 @@ - - - + @@ -42,14 +39,11 @@ - - + Foreground="{StaticResource Brush.OnSurfaceVariant}" Text="{Binding GameVolumeDbLabel}"/> + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - @@ -193,13 +174,9 @@ + Foreground="{StaticResource Brush.Tertiary}" Text="{Binding SyncOffsetLabel}"/> - - diff --git a/Controls/AudioDock.xaml.cs b/Controls/AudioDock.xaml.cs index 2a7bff9..21c910e 100644 --- a/Controls/AudioDock.xaml.cs +++ b/Controls/AudioDock.xaml.cs @@ -39,6 +39,16 @@ public partial class AudioDock : UserControl vm.OpenMicPickerCommand.Execute(null); } + private void TraxStepDown_Click(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.MainViewModel vm) vm.StepTrax(-1); + } + + private void TraxStepUp_Click(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.MainViewModel vm) vm.StepTrax(+1); + } + private void SyncPreset_Click(object sender, RoutedEventArgs e) { if (DataContext is not ViewModels.MainViewModel vm) return; diff --git a/ViewModels/MainViewModel.Trax.cs b/ViewModels/MainViewModel.Trax.cs index cf24c13..900e4b8 100644 --- a/ViewModels/MainViewModel.Trax.cs +++ b/ViewModels/MainViewModel.Trax.cs @@ -63,7 +63,21 @@ public partial class MainViewModel ? BarOnBrush : TraxError != null ? BarOffBrush : TraxIdleBrush; - /// The dock's TRAX fader, in dB. Unity means the bed's design level, not the raw file. + /// Where TRAX starts: quiet background under the desktop bed. The ±5 dB buttons are the + /// control surface — a creator who wants it louder clicks up three times rather than hunting a + /// 6px thumb on a 148px dock. (Creator ruling 2026-10-30, from their own meter readings.) + public const double TraxDefaultDb = -50; + + public const double TraxStepDb = 5; + + /// +6 dB is the ceiling; −60 dB is silence. The buttons disable at both ends. + public bool CanRaiseTrax => TraxVolumeDb < Helpers.GainDb.MaxDb; + + public bool CanLowerTrax => TraxVolumeDb > Helpers.GainDb.FloorDb; + + public void StepTrax(int steps) => TraxVolumeDb += steps * TraxStepDb; + + /// The dock's TRAX level, in dB. Unity means the bed's design level, not the raw file. public double TraxVolumeDb { get => Helpers.GainDb.ToDb(_musicPlayer.UserGain); @@ -74,6 +88,8 @@ public partial class MainViewModel _musicPlayer.UserGain = (float)gain; OnPropertyChanged(nameof(TraxVolumeDb)); OnPropertyChanged(nameof(TraxVolumeDbLabel)); + OnPropertyChanged(nameof(CanRaiseTrax)); + OnPropertyChanged(nameof(CanLowerTrax)); } } diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 6bc4ac5..8d3d047 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -277,6 +277,7 @@ public partial class MainViewModel : ViewModelBase MicSourceName = _layoutStore.LoadMicSourceName(); AudioSyncOffsetMs = _layoutStore.LoadAudioSyncOffsetMs(); MonoOutput = _layoutStore.LoadMonoOutput(); // applied to the mixer by the setter + _musicPlayer.UserGain = (float)Helpers.GainDb.FromDb(TraxDefaultDb); // background by default try { var s = _layoutStore.GetSetting("ZoomShape"); diff --git a/ytLive.Tests/GameBayTests.cs b/ytLive.Tests/GameBayTests.cs index 240b647..708b498 100644 --- a/ytLive.Tests/GameBayTests.cs +++ b/ytLive.Tests/GameBayTests.cs @@ -15,6 +15,10 @@ public class GameBayTests { var dock = SourceAssert.Text("Controls", "AudioDock.xaml"); Assert.Contains("Value=\"{Binding GameVolumeDb, Mode=TwoWay}\"", dock); + // Desktop volume sits directly beneath its meter (creator's layout ruling). + var desktopMeter = dock.IndexOf("Level=\"{Binding GameMeterFraction}\"", System.StringComparison.Ordinal); + var desktopFader = dock.IndexOf("Value=\"{Binding GameVolumeDb, Mode=TwoWay}\"", System.StringComparison.Ordinal); + Assert.True(desktopFader > desktopMeter, "the desktop fader belongs under the desktop ladder"); Assert.Contains("Level=\"{Binding GameMeterFraction}\"", dock); Assert.Contains("Text=\"{Binding GameDuckLabel}\"", dock); Assert.Contains("Visibility=\"{Binding IsGameDucking, Converter={StaticResource BoolToVis}}\"", dock); diff --git a/ytLive.Tests/TraxBayTests.cs b/ytLive.Tests/TraxBayTests.cs index 517098e..f5da2da 100644 --- a/ytLive.Tests/TraxBayTests.cs +++ b/ytLive.Tests/TraxBayTests.cs @@ -16,20 +16,25 @@ public class TraxBayTests { var dock = SourceAssert.Text("Controls", "AudioDock.xaml"); Assert.Contains("Text=\"{Binding TraxTrackLabel}\"", dock); - Assert.Contains("Value=\"{Binding TraxVolumeDb, Mode=TwoWay}\"", dock); + // KISS (creator 2026-10-30): background music is one number and two ±5 dB buttons, not a + // channel strip — and it has no ladder of its own, because the desktop bed already carries it. + Assert.Contains("Click=\"TraxStepDown_Click\"", dock); + Assert.Contains("Click=\"TraxStepUp_Click\"", dock); + Assert.Contains("IsEnabled=\"{Binding CanLowerTrax}\"", dock); + Assert.Contains("IsEnabled=\"{Binding CanRaiseTrax}\"", dock); Assert.Contains("Text=\"{Binding TraxVolumeDbLabel}\"", dock); Assert.Contains("Binding=\"{Binding IsMusicPlaying}\" Value=\"True\"", dock); Assert.Contains("Click=\"TraxPlayPause_Click\"", dock); // The bay DOES carry a ladder now — the player measures its own output, so "is music making // sound" is answerable without hearing it (a play glyph cannot answer that). - var traxBay = dock[dock.IndexOf("Text=\"TRAX\"", System.StringComparison.Ordinal) + // The TRAX row lives under the desktop volume, inside the GAME bay. + var traxRow = dock[dock.IndexOf("Text=\"TRAX\"", System.StringComparison.Ordinal) ..dock.IndexOf("Text=\"LIP-SYNC\"", System.StringComparison.Ordinal)]; - Assert.Contains("TraxTrackLabel", traxBay); - Assert.Contains("SegmentMeter", traxBay); - Assert.Contains("TraxMeterFraction", traxBay); + Assert.Contains("TraxTrackLabel", traxRow); + Assert.DoesNotContain("SegmentMeter", traxRow); - foreach (var name in new[] { "TraxVolumeDb", "TraxVolumeDbLabel", "TraxTrackLabel", "IsTraxDucking", "TraxMeterFraction" }) + foreach (var name in new[] { "TraxVolumeDb", "TraxVolumeDbLabel", "TraxTrackLabel", "CanRaiseTrax", "CanLowerTrax" }) Assert.NotNull(typeof(MainViewModel).GetProperty(name)); // The player gained a real user level, separate from the mute-follow LocalGain. @@ -37,7 +42,9 @@ public class TraxBayTests Assert.NotNull(typeof(ITraxPlayer).GetProperty("Level")); Assert.NotNull(typeof(MusicPlayer).GetProperty("UserGain")); - // Unity means the bed's design level, and the fader's scale is the same one as the others. + // It starts as background: −50 dB, and the ±5 dB steps are how it moves. + Assert.Equal(-50, MainViewModel.TraxDefaultDb); + Assert.Equal(5, MainViewModel.TraxStepDb); Assert.Equal(0, GainDb.ToDb(1f), 6); Assert.Contains("∞", GainDb.Label(GainDb.FloorDb)); // true silence, not "-60.0" }