Unit 8. Re-houses the shipped TASK 22 offset in the dock:
- slider −200…+200 ms, snapping to 5 ms ticks (the existing property still clamps and persists)
- readable readout: "0 ms (aligned)" when right, signed otherwise ("+50 ms", "−120 ms")
- presets 0 / +50 / +100; 0 IS the detent — a button is easier to hit than a snap, and "the lip-sync
is right" deserves one click.
Guard: SyncBayTests pins the binding, the readout, the presets and the snapping, and that the
offset stays the persisted property (no second source of truth).
222 lines
16 KiB
XML
222 lines
16 KiB
XML
<UserControl x:Class="ytLive.Controls.AudioDock"
|
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:controls="clr-namespace:ytLive.Controls">
|
|
<!-- The sound bars. Replaces the empty bottom-bar spacer; collapses to the telemetry strip
|
|
on short windows (AudioDockLayout decides the height, not this file). -->
|
|
<Border x:Name="DockRoot" Background="{StaticResource Brush.SurfaceContainer}"
|
|
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="0,1,0,0">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<!-- Master FIRST: during a live session attention is on the preview, so the
|
|
master strip sits against it and the bays hang below (creator, 2026-10-30). -->
|
|
<RowDefinition Height="36"/>
|
|
<RowDefinition Height="*"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<!-- Four bays: MIC | GAME | TRAX | SYNC. Filled in by the bay units. -->
|
|
<Grid x:Name="Bays" Grid.Row="1" Margin="8,4,8,6">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="*"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<Border Grid.Column="0" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="0,0,4,0" Padding="10,6">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<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.ColumnDefinitions>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="*"/>
|
|
</Grid.ColumnDefinitions>
|
|
<Border Background="{StaticResource Brush.SurfaceContainerHighest}" CornerRadius="{StaticResource Radius.Sm}" Padding="5,1">
|
|
<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'}"
|
|
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"
|
|
Command="{Binding ToggleMicMuteCommand}" ToolTip="{Binding MicMuteText}">
|
|
<Ellipse Width="9" Height="9" Fill="{Binding MicStatusBrush}"/>
|
|
</Button>
|
|
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right" FontSize="10"
|
|
FontFamily="{StaticResource FontFamily.Mono}" VerticalAlignment="Center"
|
|
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>
|
|
<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="*"/>
|
|
</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">
|
|
<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>
|
|
|
|
<DockPanel Grid.Row="1" Margin="0,6,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"/>
|
|
<ColumnDefinition Width="*"/>
|
|
</Grid.ColumnDefinitions>
|
|
<Border Background="{StaticResource Brush.SurfaceContainerHighest}" CornerRadius="{StaticResource Radius.Sm}" Padding="5,1">
|
|
<TextBlock Text="LIP-SYNC" FontSize="9" FontWeight="Bold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
|
|
</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}"/>
|
|
</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"/>
|
|
<StackPanel Orientation="Horizontal" Margin="0,2,0,0" HorizontalAlignment="Right">
|
|
<Button Content="0" Width="26" Height="16" FontSize="9" Padding="0" Margin="0,0,4,0"
|
|
Style="{StaticResource YtButtonSecondary}" Click="SyncPreset_Click" Tag="0"/>
|
|
<Button Content="+50" Width="30" Height="16" FontSize="9" Padding="0" Margin="0,0,4,0"
|
|
Style="{StaticResource YtButtonSecondary}" Click="SyncPreset_Click" Tag="50"/>
|
|
<Button Content="+100" Width="34" Height="16" FontSize="9" Padding="0"
|
|
Style="{StaticResource YtButtonSecondary}" Click="SyncPreset_Click" Tag="100"/>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
</Grid>
|
|
|
|
<!-- Master strip: always visible, even collapsed — it is the telemetry line. -->
|
|
<Border x:Name="MasterStrip" Grid.Row="0" Background="{StaticResource Brush.SurfaceContainerLowest}" Padding="12,0">
|
|
<DockPanel VerticalAlignment="Center">
|
|
<TextBlock DockPanel.Dock="Left" Text="MASTER" FontSize="10" FontWeight="SemiBold"
|
|
Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
|
|
</DockPanel>
|
|
</Border>
|
|
</Grid>
|
|
</Border>
|
|
</UserControl>
|