TASK 9 audio milestone: real stream audio + voice filters + auto-duck + free TRAX music — the mixer now feeds the encoder real audio (ffmpeg reads a Windows named pipe, -f f32le -ar 48000 -ac 2 -i \.\pipe\ytllive_audio, replacing anullsrc silence; explicit -map 0:v -map 1:a via EncoderOptions.AudioPipeName), with honest gains reaching the stream (MicVolume scales mic, GameAudioVolume + mute scale loopback, Func<double> seams on AudioMixer), an always-on pure-C# voice chain on the mic BEFORE the meter and mix (LowShelfFilter 120Hz +4dB → HighShelfFilter 8kHz +3dB → NoiseGate 0.005/hysteresis 0.5 → Compressor 0.5 4:1, all TDF2), AutoDucker (mic RMS>0.02 → loopback ×0.25, attack 0.05/release 0.005), and TRAX free background music (MusicPlayer = MediaFoundationReader → VolumeWaveProvider16 at fixed 0.20 → WaveOutEvent via the new sibling NAudio.WinMM 2.2.1 package; plays to the default device so the existing loopback carries it, ducked with game; footer TRAX button with red/yellow/green status dot, left-click toggles/picks, right-click opens the OpenFileDialog picker, tooltip shows the track name; persisted via schema v9 single-row Music). Build-time deviations from the plan (recorded in ai.md + TASKS.md): WasapiCapture in NAudio 2.2.1 exposes no overridable GetDefaultMixFormat so sources run device mix format and the mixer's TinyResampler normalizes any rate to 48kHz (resampler IS the design); FfmpegEncoder.cs untouched — MainViewModel.StopStream calls _audioMixer.StopLive() (pipe EOF) before the frame pump stops; sound-bar relabelled 'Desktop Audio', IsGameAudioBarVisible = game sound OR music playing. Tests: new AudioPipelineTests (DSP/ring-buffer/ducker/resampler units + the ONE integration test Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe reading real pipe bytes; ring-buffer overwrite bug found + fixed), FfmpegEncoderTests/LayoutStorePersistenceTests updated — 196 tests passing, 0 warnings
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
namespace ytLive.Services.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Auto-duck (TASK 9): while the mic is hot the loopback (game + music) rides
|
||||
/// its volume down ~12 dB so the voice stays on top of the mix, then recovers
|
||||
/// when the creator stops talking. Smooth attack/release, always-on, no knobs.
|
||||
/// Pure — unit-tested.
|
||||
/// </summary>
|
||||
public sealed class AutoDucker
|
||||
{
|
||||
public const float Threshold = 0.02f;
|
||||
|
||||
/// <summary>-12 dB: the loopback's volume while the mic is active.</summary>
|
||||
public const float DuckGain = 0.25f;
|
||||
|
||||
private const float Attack = 0.05f;
|
||||
private const float Release = 0.005f;
|
||||
|
||||
private float _gain = 1f;
|
||||
|
||||
/// <summary>The current loopback gain to apply (1 = no duck).</summary>
|
||||
public float CurrentGain => _gain;
|
||||
|
||||
/// <summary>Feeds one mic level sample (0..1, post-filter RMS) and returns
|
||||
/// the gain to apply to the loopback for that tick.</summary>
|
||||
public float Update(float micLevel)
|
||||
{
|
||||
var target = micLevel > Threshold ? DuckGain : 1f;
|
||||
_gain += (target - _gain) * (target < _gain ? Attack : Release);
|
||||
if (MathF.Abs(_gain - target) < 0.001f)
|
||||
_gain = target;
|
||||
return _gain;
|
||||
}
|
||||
|
||||
public void Reset() => _gain = 1f;
|
||||
}
|
||||
Reference in New Issue
Block a user