fix(denoise): stop DeepFilterNet silently passing raw audio through #64

Merged
jared merged 2 commits from fix/dfn-model-bytes into lotus 2026-10-30 15:16:25 -04:00
Owner

Problem

If the DeepFilterNet model (DeepFilterNet3_onnx.tar.gz) is served with Content-Encoding: gzip, fetch() returns the already-unpacked tar. Vite preview does this, and so do some CDNs and nginx gzip_static setups. The library's AudioWorklet then fails inside df_create. It catches its own error and passes the raw mic straight through, while Lotus Call still reports io.lotus.denoise_state {active: true, model: "deepfilternet"}. Users get no noise suppression and no warning.

Production nginx currently serves the file raw, so this isn't happening on chat.lotusguild.org today. Any future proxy or CDN change would silently switch DeepFilterNet off.

Fix

normalizeDfnModelBytes() runs after core.initialize():

  • if the bytes are gzip, they're used as-is;
  • if they're an unpacked tar (ustar magic), they're re-gzipped in the browser with CompressionStream;
  • anything else, such as an HTML error page, throws. The existing rnnoise fallback then runs and the host is told the truth.

Verification

  • Unit tests for all three cases.
  • tsc and oxlint are clean; 147/147 lotus tests pass.
  • Two-browser Lotus Call test (fan noise at SNR 15 dB, model served with Content-Encoding: gzip), noise removed in speech pauses:
Build Noise removed
before 4.9 dB (only gain differences; the model was bypassed)
after 30.9 dB

🤖 Generated with Claude Code

https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5

## Problem If the DeepFilterNet model (`DeepFilterNet3_onnx.tar.gz`) is served with `Content-Encoding: gzip`, `fetch()` returns the already-unpacked tar. Vite preview does this, and so do some CDNs and nginx `gzip_static` setups. The library's AudioWorklet then fails inside `df_create`. It catches its own error and **passes the raw mic straight through**, while Lotus Call still reports `io.lotus.denoise_state {active: true, model: "deepfilternet"}`. Users get no noise suppression and no warning. Production nginx currently serves the file raw, so this isn't happening on chat.lotusguild.org today. Any future proxy or CDN change would silently switch DeepFilterNet off. ## Fix `normalizeDfnModelBytes()` runs after `core.initialize()`: - if the bytes are gzip, they're used as-is; - if they're an unpacked tar (`ustar` magic), they're re-gzipped in the browser with `CompressionStream`; - anything else, such as an HTML error page, throws. The existing rnnoise fallback then runs and the host is told the truth. ## Verification - Unit tests for all three cases. - `tsc` and oxlint are clean; 147/147 lotus tests pass. - Two-browser Lotus Call test (fan noise at SNR 15 dB, model served with `Content-Encoding: gzip`), noise removed in speech pauses: | Build | Noise removed | |---|---| | before | 4.9 dB (only gain differences; the model was bypassed) | | after | **30.9 dB** | 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared added 1 commit 2026-10-30 14:18:15 -04:00
fix(denoise): stop DeepFilterNet silently passing raw audio through
CI / Build embedded bundle (push) Skipped
CI / Build embedded bundle (pull_request) Failing after 5m38s
CI / Publish to Gitea npm registry (pull_request) Skipped
16f5f3d1a3
If the DeepFilterNet model is served with `Content-Encoding: gzip` (vite
preview does this for .tar.gz; some CDNs and nginx gzip_static setups do
too), fetch() hands back the already-unpacked tar. The library's worklet
then fails inside df_create, swallows the error and passes the raw mic
through, while Lotus Call reports DeepFilterNet as active.

Check the bytes after initialize(): a gzip model is used as-is, an
unpacked tar is re-gzipped in the browser, and anything else throws so
the rnnoise fallback runs and the host is told the truth.

Measured in a two-browser call (fan noise, SNR 15 dB): before, 4.9 dB of
noise removed, i.e. none beyond gain differences; after, 30.9 dB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared added 1 commit 2026-10-30 15:11:21 -04:00
style: oxfmt
CI / Build embedded bundle (push) Skipped
CI / Build embedded bundle (pull_request) Successful in 5m2s
CI / Publish to Gitea npm registry (pull_request) Skipped
283b231c7a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared scheduled this pull request to auto merge when all checks succeed 2026-10-30 15:11:33 -04:00
jared merged commit 4ca3b0695c into lotus 2026-10-30 15:16:25 -04:00
jared deleted branch fix/dfn-model-bytes 2026-10-30 15:16:25 -04:00
Sign in to join this conversation.