Fix: TRAX meter reflection — remove double attenuation from slider+loopback

This commit is contained in:
2026-08-21 08:32:20 -07:00
parent 75ee6978d3
commit e06bfa42a8
4 changed files with 16 additions and 12 deletions
+4 -2
View File
@@ -28,6 +28,8 @@ public sealed class AudioGainProvider
/// <summary>Mic gain for the live mix (0 = muted).</summary> /// <summary>Mic gain for the live mix (0 = muted).</summary>
public double MicGain() => _micMuted() ? 0 : _micVolume(); public double MicGain() => _micMuted() ? 0 : _micVolume();
/// <summary>Loopback (desktop/game) gain for the live mix (0 = muted).</summary> /// <summary>Loopback (desktop/game) gain for the live mix (0 = muted, 1 = unity).
public double LoopbackGain() => _gameMuted() ? 0 : _gameVolume(); /// Unity because the slider now drives system volume via PushSystemVolume,
/// so the WASAPI loopback signal is already scaled by the user's volume choice.</summary>
public double LoopbackGain() => _gameMuted() ? 0 : 1.0;
} }
+2 -2
View File
@@ -10,8 +10,8 @@ namespace ytLive.Services.Audio;
/// through the voice chain (bass → treble → noise gate → compressor), THEN /// through the voice chain (bass → treble → noise gate → compressor), THEN
/// level-metered and queued; loopback samples are resampled to 48 kHz stereo /// level-metered and queued; loopback samples are resampled to 48 kHz stereo
/// and queued. While live (<see cref="StartLive"/>), a 10 ms loop drains both /// and queued. While live (<see cref="StartLive"/>), a 10 ms loop drains both
/// queues, applies the honest gains (mic = MicVolume, loopback = GameAudioVolume /// queues, applies the honest gains (mic = MicVolume, loopback = unity
/// × auto-duck when the mic is hot), mixes them to stereo float, runs the /// × auto-duck when the mic is hot — the slider drives system volume via mixes them to stereo float, runs the
/// master limiter (a -1 dBFS ceiling so the sum never clips the encoder), and /// master limiter (a -1 dBFS ceiling so the sum never clips the encoder), and
/// writes the chunk to the encoder's audio pipe. /// writes the chunk to the encoder's audio pipe.
/// </summary> /// </summary>
+2 -2
View File
@@ -652,7 +652,7 @@ public class MainViewModel : ViewModelBase
/// Called whenever the game volume/mute change and on TRAX load.</summary> /// Called whenever the game volume/mute change and on TRAX load.</summary>
private void UpdateTraxLocalGain() private void UpdateTraxLocalGain()
{ {
_musicPlayer.LocalGain = GameMuted ? 0f : (float)(GameAudioVolume * 0.2); _musicPlayer.LocalGain = GameMuted ? 0f : 1f;
} }
private static void PushSystemVolume(double volume) private static void PushSystemVolume(double volume)
@@ -684,7 +684,7 @@ public class MainViewModel : ViewModelBase
/// <summary>Displayed game meter level: 0 while muted; the volume position /// <summary>Displayed game meter level: 0 while muted; the volume position
/// while the slider is dragged (or briefly after an unmute flash); otherwise /// while the slider is dragged (or briefly after an unmute flash); otherwise
/// the realtime live level scaled by volume.</summary> /// the realtime live level scaled by volume.</summary>
private double GameMeterLevel => GameMuted ? 0 : _gameVolumeAdjusting || _gameVolumeFlash ? GameAudioVolume : Math.Min(1, AudioLevelMeter.ToDisplay((float)GameAudioLevel) * GameAudioVolume); private double GameMeterLevel => GameMuted ? 0 : _gameVolumeAdjusting || _gameVolumeFlash ? GameAudioVolume : Math.Min(1, AudioLevelMeter.ToDisplay((float)GameAudioLevel));
public double GameMeterFillWidth => GameMeterLevel * 288; public double GameMeterFillWidth => GameMeterLevel * 288;
+8 -6
View File
@@ -447,10 +447,11 @@ devices, no timers).
(mp3/wav/m4a) → `VolumeWaveProvider16` at the hardcoded **0.20** bed (no slider) → `WaveOutEvent` on (mp3/wav/m4a) → `VolumeWaveProvider16` at the hardcoded **0.20** bed (no slider) → `WaveOutEvent` on
the default device, **looping on any clean natural end** (`PlaybackStopped` with `e.Exception == null` the default device, **looping on any clean natural end** (`PlaybackStopped` with `e.Exception == null`
→ rewind + replay; the earlier `Position >= Length` check was unreliable for `MediaFoundationReader` → rewind + replay; the earlier `Position >= Length` check was unreliable for `MediaFoundationReader`
and let tracks play once then stop). The desktop/game bar scales it locally via `LocalGain` (the VM's and let tracks play once then stop). The desktop/game bar's mute toggles `LocalGain` (0 or 1;
`UpdateTraxLocalGain` runs on every `GameAudioVolume`/`GameMuted` change and on TRAX load), so the `UpdateTraxLocalGain` runs on every `GameMuted` change and on TRAX load), so muting kills TRAX
creator **hears** the game volume slider and mute work on the music in the headphones — matching the in the headphones — matching the stream, where it rides the loopback channel. The volume slider
stream, where it rides the loopback channel ducked with the game. No third mixer input. The footer drives system volume (`PushSystemVolume`), which scales the entire loopback signal including
TRAX — one knob controls everything. No third mixer input. The footer
**TRAX** button lives **inside the game audio bar** (in the preview overlay), **left of the **TRAX** button lives **inside the game audio bar** (in the preview overlay), **left of the
"Desktop Audio" label** (the creator's pick — it rides the desktop channel, so the music control "Desktop Audio" label** (the creator's pick — it rides the desktop channel, so the music control
sits with the desktop audio controls): status dot (**red** no track / **yellow** loaded stopped / sits with the desktop audio controls): status dot (**red** no track / **yellow** loaded stopped /
@@ -469,8 +470,9 @@ devices, no timers).
silently falling back to the default endpoint.** silently falling back to the default endpoint.**
- **Game audio bar** (desktop/game, **always visible** — TASK 4's `IGameAudioDetector` show/hide gating was **removed 2026-08-15**: the bar used to appear only while a full-screen game with sound was up, which kept hiding the creator's desktop meter; the detector stack is gone, desktop audio is just automatic WASAPI loopback): overlaid **at the bottom of the preview window** (bottom-center, dark translucent - **Game audio bar** (desktop/game, **always visible** — TASK 4's `IGameAudioDetector` show/hide gating was **removed 2026-08-15**: the bar used to appear only while a full-screen game with sound was up, which kept hiding the creator's desktop meter; the detector stack is gone, desktop audio is just automatic WASAPI loopback): overlaid **at the bottom of the preview window** (bottom-center, dark translucent
chip, a mirror of the mic bar: meter + mute + volume slider). It's monitoring UI **in the preview chip, a mirror of the mic bar: meter + mute + volume slider). It's monitoring UI **in the preview
grid** — the meter is display-only, but its **volume slider + mute are stream-honest** (via grid** — the meter is display-only, but its **volume slider drives system volume** (`PushSystemVolume`
`AudioGainProvider.LoopbackGain`) **and drive TRAX locally** (`MusicPlayer.LocalGain`). Relabelled scales the entire WASAPI endpoint, so both the live headphones and the loopback capture reflect the
slider) **and mute kills TRAX** (`MusicPlayer.LocalGain` = 0). Relabelled
**"Desktop Audio"** (TASK 8) since TRAX rides the same channel. **"Desktop Audio"** (TASK 8) since TRAX rides the same channel.
- **`WaveToFloat`** (pure, shared): WASAPI mix formats → interleaved float — IEEE float 32-bit direct, - **`WaveToFloat`** (pure, shared): WASAPI mix formats → interleaved float — IEEE float 32-bit direct,
PCM 16-bit normalized to -1..1, `WaveFormatExtensible` with the IEEE-float subformat GUID PCM 16-bit normalized to -1..1, `WaveFormatExtensible` with the IEEE-float subformat GUID