feat(dock): TRAX becomes a background level, not a channel strip

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.
This commit is contained in:
2026-10-30 13:46:05 -07:00
parent 93dc6592ac
commit ec293e54b0
6 changed files with 112 additions and 97 deletions
+66 -89
View File
@@ -18,11 +18,11 @@
<Grid x:Name="Bays" Grid.Row="1" Margin="8,4,8,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.4*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- MIC -->
<Border Grid.Column="0" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="0,0,4,0" Padding="10,6">
<Grid>
<Grid.RowDefinitions>
@@ -30,10 +30,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Clicking the name opens the mic picker: the label is the control. -->
<Grid Grid.Row="0" Cursor="Hand" ToolTip="Change microphone"
MouseLeftButtonUp="MicLabel_MouseLeftButtonUp">
<Grid Grid.Row="0" Cursor="Hand" ToolTip="Change microphone" MouseLeftButtonUp="MicLabel_MouseLeftButtonUp">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
@@ -42,14 +39,11 @@
<TextBlock Text="MIC 1" FontSize="9" FontWeight="Bold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
</Border>
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="11" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
Text="{Binding MicSourceName, TargetNullValue='No microphone'}"
TextTrimming="CharacterEllipsis" Text="{Binding MicSourceName, TargetNullValue='No microphone'}"
Foreground="{StaticResource Brush.OnSurface}"/>
</Grid>
<controls:SegmentMeter Grid.Row="1" Height="10" Margin="0,6,0,0" Orientation="Horizontal"
Level="{Binding MicMeterFraction}" Segments="24"/>
<DockPanel Grid.Row="2" Margin="0,6,0,0" VerticalAlignment="Bottom">
<Button DockPanel.Dock="Left" Width="24" Height="20" Margin="0,0,6,0"
Style="{StaticResource YtButtonSecondary}" Padding="0"
@@ -58,21 +52,22 @@
</Button>
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right" FontSize="10"
FontFamily="{StaticResource FontFamily.Mono}" VerticalAlignment="Center"
Foreground="{StaticResource Brush.OnSurfaceVariant}"
Text="{Binding MicVolumeDbLabel}"/>
Foreground="{StaticResource Brush.OnSurfaceVariant}" Text="{Binding MicVolumeDbLabel}"/>
<Slider Minimum="-60" Maximum="6" SmallChange="0.5" LargeChange="3"
Value="{Binding MicVolumeDb, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel>
</Grid>
</Border>
<!-- GAME / PC, then TRAX beneath it: meter -> desktop volume -> TRAX line -->
<Border Grid.Column="1" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0" Padding="10,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -82,15 +77,10 @@
<Border Background="{StaticResource Brush.SurfaceContainerHighest}" CornerRadius="{StaticResource Radius.Sm}" Padding="5,1">
<TextBlock Text="GAME / PC" FontSize="9" FontWeight="Bold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
</Border>
<!-- TRAX plays through the default device, so the loopback bed carries it:
this ladder shows everything the viewer hears from the desktop,
music included. The TRAX bay's ladder is a tap on the player. -->
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="11" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Foreground="{StaticResource Brush.OnSurfaceVariant}"
Text="Desktop loopback · incl. TRAX"
ToolTip="Everything playing on the desktop — including TRAX, which rides the same device."/>
<!-- The duck badge only exists while the duck is happening, and it carries
the gain the mixer actually applied. -->
<Border Grid.Column="2" Background="{StaticResource Brush.SurfaceContainerHighest}"
CornerRadius="{StaticResource Radius.Sm}" Padding="5,1"
Visibility="{Binding IsGameDucking, Converter={StaticResource BoolToVis}}">
@@ -99,10 +89,11 @@
</Border>
</Grid>
<controls:SegmentMeter Grid.Row="1" Height="10" Margin="0,6,0,0" Orientation="Horizontal"
<controls:SegmentMeter Grid.Row="1" Height="10" Margin="0,6,0,4" Orientation="Horizontal"
Level="{Binding GameMeterFraction}" Segments="24"/>
<DockPanel Grid.Row="2" Margin="0,6,0,0" VerticalAlignment="Bottom">
<!-- Desktop volume, directly beneath its meter. -->
<DockPanel Grid.Row="2" VerticalAlignment="Center">
<Button DockPanel.Dock="Left" Width="24" Height="20" Margin="0,0,6,0"
Style="{StaticResource YtButtonSecondary}" Padding="0"
Command="{Binding ToggleGameMuteCommand}" ToolTip="Mute desktop audio">
@@ -110,79 +101,69 @@
</Button>
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right" FontSize="10"
FontFamily="{StaticResource FontFamily.Mono}" VerticalAlignment="Center"
Foreground="{StaticResource Brush.OnSurfaceVariant}"
Text="{Binding GameVolumeDbLabel}"/>
Foreground="{StaticResource Brush.OnSurfaceVariant}" Text="{Binding GameVolumeDbLabel}"/>
<Slider Minimum="-60" Maximum="6" SmallChange="0.5" LargeChange="3"
Value="{Binding GameVolumeDb, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel>
<!-- TRAX: background music — one number, two buttons. No meter: it is
background, and the desktop ladder already carries it. -->
<Border Grid.Row="3" Margin="0,4,0,0" Padding="6,3" CornerRadius="{StaticResource Radius.Sm}"
Background="{StaticResource Brush.SurfaceContainerLowest}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="TRAX" FontSize="9" FontWeight="Bold" VerticalAlignment="Center"
Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="10" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Foreground="{StaticResource Brush.OnSurface}"
Text="{Binding TraxTrackLabel}" Cursor="Hand"
MouseLeftButtonUp="TraxLabel_MouseLeftButtonUp"
MouseRightButtonUp="TraxLabel_MouseRightButtonUp"/>
<Button Grid.Column="2" Width="20" Height="16" Margin="4,0,0,0" Padding="0"
Style="{StaticResource YtButtonSecondary}" Click="TraxPlayPause_Click"
ToolTip="{Binding TraxToolTip}">
<TextBlock FontSize="9" Foreground="{StaticResource Brush.OnSurface}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="▶"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMusicPlaying}" Value="True">
<Setter Property="Text" Value="❚❚"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<StackPanel Grid.Column="3" Orientation="Horizontal" Margin="6,0,0,0" VerticalAlignment="Center">
<Button Content="−" Width="18" Height="16" Padding="0" FontSize="11"
Style="{StaticResource YtButtonSecondary}" Click="TraxStepDown_Click"
IsEnabled="{Binding CanLowerTrax}" ToolTip="5 dB quieter"/>
<TextBlock Width="52" TextAlignment="Center" FontSize="10" VerticalAlignment="Center"
FontFamily="{StaticResource FontFamily.Mono}"
Foreground="{StaticResource Brush.Tertiary}"
Text="{Binding TraxVolumeDbLabel}"/>
<Button Content="+" Width="18" Height="16" Padding="0" FontSize="11"
Style="{StaticResource YtButtonSecondary}" Click="TraxStepUp_Click"
IsEnabled="{Binding CanRaiseTrax}" ToolTip="5 dB louder"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</Border>
<Border Grid.Column="2" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0" Padding="10,6">
<!-- LIP-SYNC -->
<Border Grid.Column="2" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0,0,0" Padding="10,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Background="{StaticResource Brush.SurfaceContainerHighest}" CornerRadius="{StaticResource Radius.Sm}" Padding="5,1">
<TextBlock Text="TRAX" FontSize="9" FontWeight="Bold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
</Border>
<!-- The name is the control: left-click toggles play, right-click picks (§TRAX). -->
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="11" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Foreground="{StaticResource Brush.OnSurface}"
Text="{Binding TraxTrackLabel}"
MouseLeftButtonUp="TraxLabel_MouseLeftButtonUp"
MouseRightButtonUp="TraxLabel_MouseRightButtonUp" Cursor="Hand"/>
<Button Grid.Column="2" Width="22" Height="18" Style="{StaticResource YtButtonSecondary}" Padding="0"
Margin="4,0,0,0" Click="TraxPlayPause_Click" ToolTip="{Binding TraxToolTip}">
<TextBlock FontSize="10" Foreground="{StaticResource Brush.OnSurface}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="▶"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMusicPlaying}" Value="True">
<Setter Property="Text" Value="❚❚"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<Border Grid.Column="3" Margin="4,0,0,0" Background="{StaticResource Brush.SurfaceContainerHighest}"
CornerRadius="{StaticResource Radius.Sm}" Padding="5,1"
Visibility="{Binding IsTraxDucking, Converter={StaticResource BoolToVis}}">
<TextBlock FontSize="9" FontWeight="Bold" FontFamily="{StaticResource FontFamily.Mono}"
Foreground="{StaticResource Brush.Secondary}" Text="DUCK"/>
</Border>
</Grid>
<controls:SegmentMeter Grid.Row="1" Height="10" Margin="0,6,0,6" Orientation="Horizontal"
Level="{Binding TraxMeterFraction}" Segments="24"/>
<DockPanel Grid.Row="1" Margin="0,22,0,0" VerticalAlignment="Bottom">
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right" FontSize="10"
FontFamily="{StaticResource FontFamily.Mono}" VerticalAlignment="Center"
Foreground="{StaticResource Brush.OnSurfaceVariant}"
Text="{Binding TraxVolumeDbLabel}"/>
<Slider Minimum="-60" Maximum="6" SmallChange="0.5" LargeChange="3"
Value="{Binding TraxVolumeDb, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel>
</Grid>
</Border>
<Border Grid.Column="3" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0,0,0" Padding="10,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -193,13 +174,9 @@
</Border>
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="10" VerticalAlignment="Center"
FontFamily="{StaticResource FontFamily.Mono}" TextAlignment="Right"
Foreground="{StaticResource Brush.Tertiary}"
Text="{Binding SyncOffsetLabel}"/>
Foreground="{StaticResource Brush.Tertiary}" Text="{Binding SyncOffsetLabel}"/>
</Grid>
<StackPanel Grid.Row="1" VerticalAlignment="Bottom">
<!-- A detent marker would need a custom track; the 0 ms preset is the
detent as a button, which is also easier to hit than a snap. -->
<Slider Minimum="-200" Maximum="200" SmallChange="5" LargeChange="25"
TickFrequency="5" IsSnapToTickEnabled="True"
Value="{Binding AudioSyncOffsetMs, Mode=TwoWay}" VerticalAlignment="Center"/>
+10
View File
@@ -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;