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)
## 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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
If the DeepFilterNet model (
DeepFilterNet3_onnx.tar.gz) is served withContent-Encoding: gzip,fetch()returns the already-unpacked tar. Vite preview does this, and so do some CDNs and nginxgzip_staticsetups. The library's AudioWorklet then fails insidedf_create. It catches its own error and passes the raw mic straight through, while Lotus Call still reportsio.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 aftercore.initialize():ustarmagic), they're re-gzipped in the browser withCompressionStream;Verification
tscand oxlint are clean; 147/147 lotus tests pass.Content-Encoding: gzip), noise removed in speech pauses:🤖 Generated with Claude Code
https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5