namespace ytLive.Services.Audio; /// /// Supplies the live-mix gain values (mic + loopback) as Func seams for /// , sourced from the VM's volume/mute state. Read live /// on every mix tick so slider drags and mutes take effect immediately on the /// stream. Mute and zero volume can never disagree: 0 always reads as muted. /// public sealed class AudioGainProvider { private readonly Func _micMuted; private readonly Func _micVolume; private readonly Func _gameMuted; private readonly Func _gameVolume; public AudioGainProvider( Func micMuted, Func micVolume, Func gameMuted, Func gameVolume) { _micMuted = micMuted; _micVolume = micVolume; _gameMuted = gameMuted; _gameVolume = gameVolume; } /// Mic gain for the live mix (0 = muted). public double MicGain() => _micMuted() ? 0 : _micVolume(); /// Loopback (desktop/game) gain for the live mix (0 = muted). /// Honesty rule (2026-09-01): WASAPI loopback capture does NOT shrink with the /// endpoint volume — the creator dropped system volume to 20% and the captured /// level stayed pegged — so the mixer must multiply by GameAudioVolume itself /// for the stream to follow the knob (the old unity assumption left the stream /// at full desktop volume while the headphones went quiet; ai.md always /// specified loopbackGain = GameAudioVolume — the code had drifted). public double LoopbackGain() => _gameMuted() ? 0 : _gameVolume(); }