fix(calls): DTLN at 16kHz + raw-capture A/B; explains weak/robotic results
CI / Build & Quality Checks (push) Successful in 10m23s
CI / Trigger Desktop Build (push) Successful in 5s

Two issues found from real testing of the in-app tester:

1. Raw ≈ RNNoise ≈ Speex sounded identical in Record & compare because the clip
   was captured with browser noise suppression ON (the user's native-NS
   setting), so "Raw" was already cleaned and the models had nothing left to
   remove. Record & compare now captures fully raw audio (noiseSuppression /
   AGC / echoCancellation off) so each model's effect on real noise is audible.
   (Friends still heard differences in calls — the models work; the test was
   feeding them pre-cleaned audio.)

2. DTLN was robotic/choppy/quiet because @workadventure/noise-suppression
   targets 16 kHz (AUDIO_CONFIG.sampleRate) and does NOT resample internally,
   while we ran it at 48 kHz. Run DTLN's whole graph in a 16 kHz context:
   - denoisePipeline: add sampleRateFor(model) (16k for dtln, 48k otherwise);
     tester live-monitor + playback contexts use it (bufferSource resamples the
     48k clip down for DTLN).
   - shim (build/lotus-denoise.js): SAMPLE_RATE is now model-aware, so DTLN is
     correct in real calls too (it was previously broken at 48 kHz). The 16 kHz
     processed track is still published to LiveKit (WebRTC/Opus resamples).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 17:27:15 -04:00
parent 14cfa021c5
commit abb7f743b8
3 changed files with 37 additions and 11 deletions
+7 -2
View File
@@ -13,8 +13,13 @@ import { DenoiseModelId } from '../state/settings';
// Mirror CallEmbed's widget-base resolution so assets resolve under any base.
const BASE = `${import.meta.env.BASE_URL.replace(/\/+$/, '')}/public/element-call/denoise/`;
/** RNNoise/Speex/DTLN all assume mono 48 kHz, matching the call pipeline. */
export const DENOISE_SAMPLE_RATE = 48000;
/**
* Required AudioContext sample rate per model. RNNoise/Speex (sapphi) assume
* 48 kHz. DTLN (@workadventure) targets 16 kHz and does NOT resample internally
* — running it at 48 kHz produces robotic/choppy/quiet output, so its whole
* graph must run in a 16 kHz context.
*/
export const sampleRateFor = (model: DenoiseModelId): number => (model === 'dtln' ? 16000 : 48000);
export type DenoiseNode = {
node: AudioWorkletNode;