feat(dock): the GAME bay — dB fader, measured ladder, mute, real duck badge

Unit 6. Mirrors the mic bay for the desktop/game bed, plus the badge that matters to a creator who
cannot hear the duck: it shows while the ducker is actually attenuating, and it prints the gain the
mixer applied (−12.0 DUCK by design).

- `AudioMixer.DuckGain` exposes `AutoDucker.CurrentGain` instead of leaving it internal.
- VM: `GameVolumeDb` / `GameVolumeDbLabel` (fader face), `GameMeterFraction` (measured level),
  `IsGameDucking` + `GameDuckLabel` refreshed on the level tick — the same moment the ducker runs,
  so the badge cannot lag behind the mix.
- The bay: header chip + source, ladder, mute dot (uses the meter band, so mute reads at a glance),
  dB readout, fader.

Guard: GameBayTests — the dock binds all four, `AutoDucker.DuckGain` is −12.04 dB and the label is
produced through the same dB scale as the faders, and the mixer property exists so the badge cannot
become a hardcoded number.
This commit is contained in:
2026-10-30 13:12:10 -07:00
parent 3524ede480
commit a1d8e761ea
4 changed files with 122 additions and 4 deletions
+46 -4
View File
@@ -66,10 +66,52 @@
</Grid>
</Border>
<Border Grid.Column="1" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0" Padding="10,6">
<StackPanel>
<TextBlock Text="GAME / PC" FontSize="10" FontWeight="SemiBold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
<TextBlock x:Name="GameBayBody" Text="—" FontSize="11" Foreground="{StaticResource Brush.OnSurfaceDisabled}"/>
</StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<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>
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="11" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Foreground="{StaticResource Brush.OnSurfaceVariant}"
Text="Desktop loopback"/>
<!-- 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}}">
<TextBlock FontSize="9" FontWeight="Bold" FontFamily="{StaticResource FontFamily.Mono}"
Foreground="{StaticResource Brush.Secondary}" Text="{Binding GameDuckLabel}"/>
</Border>
</Grid>
<controls:SegmentMeter Grid.Row="1" Height="10" Margin="0,6,0,0" Orientation="Horizontal"
Level="{Binding GameMeterFraction}" 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"
Command="{Binding ToggleGameMuteCommand}" ToolTip="Mute desktop audio">
<Ellipse Width="9" Height="9" Fill="{Binding GameMeterBrush}"/>
</Button>
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right" FontSize="10"
FontFamily="{StaticResource FontFamily.Mono}" VerticalAlignment="Center"
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>
</Grid>
</Border>
<Border Grid.Column="2" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0" Padding="10,6">
<StackPanel>