fix(voice): add WSL audio warmup to eliminate RDP crackling - #38896
fix(voice): add WSL audio warmup to eliminate RDP crackling#38896kharitonov-ivan wants to merge 1 commit into
Conversation
WSLg RDP audio has two issues causing crackling: 1. systemd-timesyncd clock adjustments jitter PulseAudio timing (microsoft/wslg#1257) — user action: stop the service 2. Cold-start RDP connection drops first ~100ms of audio packets before the virtual channel stabilises Fix (automated, WSL-only): - Detect WSL via /proc/version 'microsoft' marker - Prepend 100ms silence + apply 100ms fade-in to audio - Append 50ms silence tail for clean stream teardown - Set blocksize=4096 (default auto ~1024 is too small for RDP) - All in a single continuous sd.play() buffer Non-WSL paths unchanged. Closes NousResearch#38893
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating this to the existing WAV/sounddevice playback path. Current main still makes the unmitigated sd.play() call at tools/voice_mode.py:1071, so this addresses a live path.
Problems
- The new fade multiplication at proposed
tools/voice_mode.py:1089has a shape mismatch for a valid WAV shorter than 100 ms: the audio slice is shorter than the fixed-sizefadevector. That raises inside the broad playbacktry, causing the code to fall through instead of applying the WSL path. - The PR changes only
tools/voice_mode.py; existing coverage attests/tools/test_voice_mode.py:884exercises a one-second ordinary WAV and does not cover WSL padding/fade,blocksize=4096, or short clips.
Suggested changes
- Clamp the fade length to
len(audio_data)before constructing the ramp, while keeping the silence prefix independent. - Reuse the tested shared detector at
hermes_constants.py:839instead of adding another/proc/versionimplementation. - Add mocked WSL/non-WSL and short-WAV regression tests in
tests/tools/test_voice_mode.py.
This is an automated hermes-sweeper review.
| fade_samples = int(0.1 * sample_rate) | ||
| fade = np.linspace(0.0, 1.0, fade_samples, dtype=np.float64) | ||
| audio_float = audio_data.astype(np.float64) | ||
| audio_float[:fade_samples] *= fade |
There was a problem hiding this comment.
For WAVs shorter than 100 ms this slice is shorter than fade, so NumPy raises a shape-mismatch ValueError and the outer handler skips the sounddevice path. Clamp fade_samples to len(audio_data) before constructing and applying the ramp.
| pass | ||
|
|
||
|
|
||
| def _is_wsl() -> bool: |
There was a problem hiding this comment.
Please reuse hermes_constants.is_wsl() here. It implements the same marker check, caches the result, and already has WSL1/WSL2 coverage in tests/hermes_cli/test_gateway_wsl.py.
|
Merged into main via consolidated salvage PR #73520 (merge Your contribution is credited to you in git history. Thank you! Closing this PR as merged-via-salvage. |
Summary
WSLg RDP audio playback produces crackling/popping artifacts even when the generated WAV file is clean. Two interacting issues:
systemd-timesyncdclock adjustments — hyperv_clocksource drift causes PulseAudio RDP sink timing jitter → buffer underruns throughout playback (microsoft/wslg#1257)sd.play(); the RDP virtual channel needs ~100ms to stabilise → first packets droppedFix
In
play_audio_file()(tools/voice_mode.py), WAV branch (sounddevice path):/proc/version→"microsoft"markerblocksize=4096explicitly (default auto ~1024 is too small for RDP jitter)sd.play()buffer — no stop/start gapGated behind
_is_wsl()→ Linux/macOS paths are unchanged.Testing
Empirical A/B tests on WSL2 Ubuntu 24.04 with RTX 4060:
The timesyncd fix is a documented user action (
sudo systemctl stop systemd-timesyncd). The warmup is automated in code.Related