@audio/neural-separate 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +5 -3
- package/README.md +94 -10
- package/index.d.ts +11 -2
- package/package.json +6 -2
- package/roformer.js +396 -0
- package/scripts/compact.py +351 -0
- package/scripts/engine.mjs +58 -0
- package/scripts/export-roformer.py +481 -0
- package/scripts/export-scnet.py +6 -6
- package/scripts/reference.py +5 -2
- package/separate.js +144 -31
package/NOTICE
CHANGED
|
@@ -56,9 +56,11 @@ The MIT License, for each notice above:
|
|
|
56
56
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
57
57
|
DEALINGS IN THE SOFTWARE.
|
|
58
58
|
|
|
59
|
-
No model weights are included. The export scripts write them to the local cache
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
No model weights are included. The export scripts write them to the local cache, and the compact
|
|
60
|
+
files of SCNet-large, SCNet, MRX and TIGER are fetched from huggingface.co/audiojs (scnet-large, scnet,
|
|
61
|
+
mrx, tiger-dnr, each with its licence and model card); their terms are their authors' (Open-Unmix
|
|
62
|
+
umxhq: MIT; SCNet and SCNet-large: MIT, Copyright (c) 2024 starrytong, redistributable converted with
|
|
63
|
+
attribution, github.com/starrytong/SCNet/issues/35; Demucs htdemucs
|
|
62
64
|
weights: see the README; MRX: MIT, Copyright 2023 Mitsubishi Electric Research Laboratories (MERL), the
|
|
63
65
|
repository's .reuse/dep5 naming each checkpoint; TIGER's DnR model: Apache 2.0, its model card,
|
|
64
66
|
huggingface.co/JusperLee/TIGER-DnR).
|
package/README.md
CHANGED
|
@@ -26,8 +26,9 @@ In the browser `@audio/neural-runtime` runs `onnxruntime-web` instead (`device:
|
|
|
26
26
|
| `'umxhq'` | Open-Unmix: bi-LSTM on magnitude spectrograms, multichannel Wiener EM | 4 × 35.6 MB (fp16: 4 × 17.8 MB) | MIT | 6.75 · 6.11 · 5.00 · 3.36 | 0.19 (vocals only: 0.11) · 0.12 · 0.29 |
|
|
27
27
|
| `'htdemucs'` | Hybrid Transformer Demucs: waveform and spectrogram U-Nets joined by a cross-domain transformer | 174 MB | research only | 8.86 · 9.55 · 9.30 · 5.69 | 1.4 (vocals only: 1.2) · 0.70 · 0.21 |
|
|
28
28
|
| `'htdemucs_ft'` | four fine-tuned HTDemucs, one per source | 4 × 174 MB | research only | 8.67 · 9.45 · 9.68 · 5.58 | vocals only: 1.1 · not measured · not measured |
|
|
29
|
-
| `'scnet-large'` | SCNet-large: band-split convolutions around dual-path LSTMs, on the complex spectrogram |
|
|
30
|
-
| `'scnet'` | SCNet: the same at half the width | 43 MB | MIT | 9.88 · 9.43 · 8.35 · 6.15 | 0.70 ·
|
|
29
|
+
| `'scnet-large'` | SCNet-large: band-split convolutions around dual-path LSTMs, on the complex spectrogram | 45 MB (export 169 MB) | MIT | 11.00 · 10.27 · 8.21 · 6.87 | 2.1 · 7.8 · 5.0 |
|
|
30
|
+
| `'scnet'` | SCNet: the same at half the width | 13 MB (export 43 MB) | MIT | 9.88 · 9.43 · 8.35 · 6.15 | 0.70 · 1.7 · 0.91 |
|
|
31
|
+
| `'mel-roformer'` | Mel-Band RoFormer (Kimberley Jensen's vocals model): axial transformers over time and mel bands, on the complex spectrogram; written for the GPU | 464 MB, float16 weights (export 928 MB) | MIT | vocals only: 12.08 | 1.4 · not measured · 0.56 (Node's WebGPU; the export 0.44) |
|
|
31
32
|
|
|
32
33
|
SDR as measured below (Verification), identical to the Python originals'. The previews are 7 s excerpts, so these numbers sit apart from full-track results in scale, not in order: the Demucs README reports overall SDR 9.0 for fine-tuned HT Demucs against 5.3 for Open-Unmix on the MUSDB18-HQ test set.
|
|
33
34
|
|
|
@@ -35,10 +36,10 @@ SDR as measured below (Verification), identical to the Python originals'. The pr
|
|
|
35
36
|
|
|
36
37
|
A soundtrack's stems, Divide and Remaster's three (the cocktail fork problem: Petermann, Wichern, Wang, Le Roux, ICASSP 2022):
|
|
37
38
|
|
|
38
|
-
| `model` | Architecture | Files | Weights | Targets | Real-time factor
|
|
39
|
+
| `model` | Architecture | Files | Weights | Targets | Real-time factor: Node CPU · browser wasm · WebGPU |
|
|
39
40
|
|---|---|---|---|---|---|
|
|
40
|
-
| `'mrx'` | MRX: magnitudes at three STFT resolutions (Hann 1024, 2048, 8192, hop 256) into one hidden layer, a BLSTM per source, their mean, a real mask per source and resolution | 122 MB | MIT | `dialogue`, `music`, `effects` | 0.21 |
|
|
41
|
-
| `'tiger'` | TIGER: three band-split models (57 bands, multi-scale convolutions and frame and frequency attention, 1.4 M parameters each), a complex mask each, one source kept from each | 29 MB | Apache 2.0 | `dialogue`, `music`, `effects` | 7.5–23 |
|
|
41
|
+
| `'mrx'` | MRX: magnitudes at three STFT resolutions (Hann 1024, 2048, 8192, hop 256) into one hidden layer, a BLSTM per source, their mean, a real mask per source and resolution | 31 MB (export 122 MB) | MIT | `dialogue`, `music`, `effects` | 0.21 · 0.31 · 0.75 |
|
|
42
|
+
| `'tiger'` | TIGER: three band-split models (57 bands, multi-scale convolutions and frame and frequency attention, 1.4 M parameters each), a complex mask each, one source kept from each | 7.3 MB (export 29 MB) | Apache 2.0 | `dialogue`, `music`, `effects` | 7.5–23 · 9.2 · 3.1 |
|
|
42
43
|
|
|
43
44
|
Measured on Divide and Remaster v3's English test set (Watcharasupat, Wu, Orife, 2024; CC BY-SA 4.0, built from sources that allow commercial use), 150 of its 1200 clips of 60 s, every 8th ([`audio`](https://github.com/audiojs/audio)'s `bench/rx/scene.mjs`): each stem against its reference over the whole clip, SNR the median over clips (the measure Bandit v2's paper reports on this set) and SI-SDR the mean (MRX's on v2); remixes, a stem 6 dB up or down as the input plus (g − 1) times it, against the true remix, SNR, the median.
|
|
44
45
|
|
|
@@ -53,11 +54,25 @@ Measured on Divide and Remaster v3's English test set (Watcharasupat, Wu, Orife,
|
|
|
53
54
|
|
|
54
55
|
TIGER, at about 50 times MRX's time (RTF 10.6 against 0.21 here), is ahead on 29, 28 and 29 of the 30 clips (dialogue, music, effects), by a median 2.3, 3.7 and 3.4 dB. MRX's own README reports 12.5 · 4.2 · 5.7 dB SI-SDR on DnR v2, the set it trained on, whose music holds singing; v3's music holds none, and its loudness and languages differ (v3's paper). DnR v2's test split comes only inside a 116 GB gzip of the whole set; v3's clips are fetched one by one. The real-time factors are of one run on a 14-core M4 Max shared with other jobs (load averages 25 to 60), so upper bounds: `mrx` a 60 s clip in 13 s; `tiger` three models of 15,000 small operators each, every sample in three 12 s segments.
|
|
55
56
|
|
|
57
|
+
The browser's, the shipped files (Size, below) on 30 s (a song; a Divide and Remaster clip), model load included:
|
|
58
|
+
onnxruntime-web 1.30 in headless Chromium 153, wasm on its default 4 threads, WebGPU on Metal, the faster of two runs
|
|
59
|
+
on a 14-core M4 Max shared with other jobs (load averages 110 to 300; the other run, at 9 to 140 with another job
|
|
60
|
+
training on the GPU, took 1.5 to 2 times as long), so upper bounds. Peak memory, the browser's processes
|
|
61
|
+
together (0.9 GB of it the browser idle): wasm `scnet` 3.0 GB, `scnet-large` 4.6, `mrx` 3.0, `tiger` 3.5; WebGPU 2.1,
|
|
62
|
+
2.4, 3.4, 1.5. On WebGPU `tiger`'s session takes 13 s to make (38,817 nodes), a 60 s clip about 3 minutes, MRX's 45 s;
|
|
63
|
+
on wasm 9 and 0.3 minutes. Float16 compute does not pay: `tiger`'s on WebGPU is 13 % faster and its music 4.7 dB from
|
|
64
|
+
the float32 graph's.
|
|
65
|
+
|
|
56
66
|
`targets` picks a subset: `targets: ['vocals']` runs only the vocals graph of `umxhq` (against the residual, see Algorithm: 2.4× faster, vocals SDR 6.50 instead of 6.75) and of `htdemucs_ft`, and skips the other sources' iSTFT for `htdemucs`. Presets resample to their 44.1 kHz and back.
|
|
57
67
|
|
|
58
68
|
## Weights
|
|
59
69
|
|
|
60
|
-
Nothing ships in this package
|
|
70
|
+
Nothing ships in this package. `scnet-large`, `scnet`, `mrx` and `tiger` read a compact file each, `<model>.int8.onnx`
|
|
71
|
+
(Size, below), and `mel-roformer` its float16 weights, `mel-roformer.fp16.onnx` (or `roformer.js`'s `mel-roformer.engine.bin`), hosted on Hugging Face
|
|
72
|
+
(`audiojs/scnet-large`, `audiojs/scnet`, `audiojs/mrx`, `audiojs/tiger-dnr`, `audiojs/mel-roformer`, each with its model
|
|
73
|
+
card and licence): without `weights`, `https://huggingface.co/<repo>/resolve/<revision>/<file>` at the
|
|
74
|
+
revision `REVISIONS` pins, fetched once and checked against its SHA-256 (the browser keeps it in the Cache API, Node in
|
|
75
|
+
the neural cache, below). Every model can also be exported from its original checkpoint:
|
|
61
76
|
|
|
62
77
|
```sh
|
|
63
78
|
pip install torch openunmix onnx onnxruntime onnxscript
|
|
@@ -74,16 +89,81 @@ python3 node_modules/@audio/neural-separate/scripts/export-mrx.py --verify
|
|
|
74
89
|
|
|
75
90
|
pip install safetensors
|
|
76
91
|
python3 node_modules/@audio/neural-separate/scripts/export-tiger.py --verify
|
|
92
|
+
|
|
93
|
+
pip install einops beartype rotary-embedding-torch librosa
|
|
94
|
+
python3 node_modules/@audio/neural-separate/scripts/export-roformer.py --verify --fp16 --engine
|
|
95
|
+
node node_modules/@audio/neural-separate/scripts/engine.mjs # roformer.js in Chromium against onnxruntime (playwright)
|
|
96
|
+
|
|
97
|
+
python3 node_modules/@audio/neural-separate/scripts/compact.py --model tiger --verify # tiger.int8.onnx from tiger.onnx
|
|
77
98
|
```
|
|
78
99
|
|
|
79
|
-
They land in `$AUDIO_NEURAL_CACHE/<model>/`, default `~/.cache/audiojs/neural/<model>/`, where a preset looks in Node; a missing file throws, naming itself and the script. Elsewhere, serve that directory and pass its URL: `separate(audio, { model: 'umxhq', weights: 'https://…/' })`; `@audio/neural-runtime` fetches each file once and caches it (Cache API in the browser). `--verify` compares the ONNX graphs with the PyTorch modules and writes the reference separation `test.js` checks the JS pipeline against. `export-openunmix.py --fp16` also writes float16 weights with float32 I/O, half the size.
|
|
100
|
+
They land in `$AUDIO_NEURAL_CACHE/<model>/`, default `~/.cache/audiojs/neural/<model>/`, where a preset looks in Node: its compact file when it is the hosted one (its SHA-256), else its export (float32, the numbers the Python original gives), else the hosted file, fetched there once and written whole (offline, a compact file of another SHA-256, `compact.py`'s own build, is read as it is); a missing file throws, naming itself and the script. `weights` a directory: its files as they are, the compact one first. Elsewhere, serve that directory and pass its URL: `separate(audio, { model: 'umxhq', weights: 'https://…/' })`; `@audio/neural-runtime` fetches each file once and caches it (Cache API in the browser). `--verify` compares the ONNX graphs with the PyTorch modules and writes the reference separation `test.js` checks the JS pipeline against. `export-openunmix.py --fp16` also writes float16 weights with float32 I/O, half the size; `export-scnet.py --fp16` float16 weights with float32 compute (`compact.py --as fp16`).
|
|
80
101
|
|
|
81
102
|
- **`export-openunmix.py`** exports each target's `OpenUnmix` through a wrapper that takes the frame count from the traced tensor (upstream reads it off `x.data.shape`, which export freezes), with the TorchScript exporter (the `torch.export` one bakes the LSTM's sequence length into a reshape). Magnitude `[1, C, F, T]` in and out, T dynamic. `umxhq`'s bandwidth restriction (`max_bin`, 1487 of 2049 bins for `n_fft=4096`@44.1kHz) crops the network's *input* below 16 kHz for efficiency; the final dense layer regresses the **full** bin range from that reduced representation (a learned extrapolation, not a literal zero-fill).
|
|
82
103
|
- **`export-scnet.py`** fetches SCNet's code from ZFTurbo/Music-Source-Separation-Training at a pinned commit and the author's MUSDB18-HQ checkpoint from that repository's releases, and exports the network between its STFT and iSTFT (the same split as demucs.onnx's): `mix_spec [1, 4, 2049, 476]` in, `stems_spec [1, 16, 2049, 476]` out, one 11 s segment. Two changes to the graph, neither to its function: the rFFT and irFFT over time in its separation network become products with cosine and sine matrices (torch 2.14 exports no `fft_rfft`), and each `GroupNorm(1, C)` reduces its mean and variance one axis at a time: exported whole, onnxruntime summed a 10-million-value group in float32 and the stems came out 52 to 55 dB from PyTorch's, against 110 dB now (PyTorch's own float32 against float64: 110 dB). `--fp16` also writes float16 weights.
|
|
83
104
|
- **`export-mrx.py`** fetches MRX's code and MERL's checkpoint (`default_mrx_pre_trained_weights.pth`, trained with the SNR loss so its stems keep the mixture's level; the three others are scale-invariant or trained on loudness- or EQ-adapted stems, and their stems come out at another level: on Divide and Remaster v3 their SNR is negative where their SI-SDR is up to 1 dB higher) from merlresearch/cocktail-fork-separation at a pinned commit, and exports the network between its STFTs and iSTFTs: each channel's three magnitude spectrograms `mag_1024 [B, 513, T]`, `mag_2048 [B, 1025, T]`, `mag_8192 [B, 4097, T]` in (scaled 1/√n, torch.stft's `normalized=True`), the masks `mask_<n> [B, 3, F, T]` out, music, speech and sfx; B and T free.
|
|
84
105
|
- **`export-tiger.py`** fetches TIGER's model code (JusperLee/TIGER, `look2hear/models/tiger_dnr.py` and the two layer modules it reads) at a pinned commit and its Divide and Remaster weights (huggingface.co/JusperLee/TIGER-DnR) at a pinned revision, and exports TIGERDNR's three models between their STFT and iSTFT as one graph, each its kept source (the first's dialogue, the second's effects, the third's music): `mix_spec [1, 2, 1025, 1034]` (one channel's STFT, n 2048, hop 512, Hann, unnormalized) in, `stems_spec [1, 6, 1025, 1034]` (dialogue, music, effects, re and im) out, one 12 s segment. Three changes to the graph, none to its function: `F.adaptive_avg_pool1d`, which the exporter takes only for output sizes dividing the input's, becomes a cumulative sum read at torch's windows; UConvBlock's global sum starts at its first term, not at a zeros tensor exported as a constant the size of the features; each `GroupNorm(1, C)` reduces axis by axis, as SCNet's. ONNX Runtime runs the graph in 12 s segments at about twice real time on a CPU: three models of 15,000 small operators each, every sample in three segments.
|
|
106
|
+
- **`export-roformer.py`** fetches Mel-Band RoFormer's code and Kim's config from ZFTurbo/Music-Source-Separation-Training at a pinned commit and Kim's checkpoint (huggingface.co/KimberleyJSN/melbandroformer, MIT) at a pinned revision, checked by its SHA-256, and exports the network between its STFT and iSTFT: `mix_spec [1, 4, 1025, 801]` in, `stems_spec [1, 4, 1025, 801]` (the vocals) out, one 8 s segment every 4 s. It is written for the GPU, its function unchanged (`--verify`: 1e-5 to 2e-4 of the peak from MelBandRoformer.forward, whose own float32 stands 5e-3 from its float64 on tones): the 60 bands' projections and mask MLPs as batched products, bands of near widths grouped and zero-padded; each RMSNorm's scale folded into the weights it feeds, the attention's scale into the queries, its gates' projection joined to q, k, v; the rotary embedding's interleaved pairs turned to halves (the same permutation of q's and k's features), so it is two products with cos and sin tables and one with a fixed matrix; the overlapping mel bands' masks averaged by two gathers, not a scatter. 826 operators where an export of BS-RoFormer as written has about 3,000, and SCNet's LSTMs none: on Node's WebGPU (Metal, M4 Max) 2.4 s a segment against 9 s on its CPU; SCNet-large's LSTMs take 19 of its 26 s a segment there. And no tensor passes 100 MB, each transformer run on slices of its sequences: onnxruntime 1.30's WebGPU Softmax over a tensor past WebGPU's default 128 MiB storage binding came out wrong one run in two (36 dB from the CPU's at 307 MB, every run right at 123 MB), and the stems with it. `--fp16`: float16 weights, float32 compute, half the download, 3.0 s a segment and 48 dB from the float32 file's stems.
|
|
107
|
+
- **`roformer.js`**: in a browser whose GPU offers subgroup matrices (`chromium-experimental-subgroup-matrix`: Chromium on Metal), `mel-roformer` runs on kernels of its own instead of onnxruntime, from `mel-roformer.engine.bin` and `.json` (`export-roformer.py --engine`: the same tensors, hosted beside the ONNX file; a browser downloads one or the other). Matrix products and attention on the GPU's matrix units, float32: band split, norms, the q, k, v projection with the rotation and gates, attention in two passes over the keys (the rows' maxima, then the output in registers), feed-forward with GELU, the mask MLP with tanh and GLU, the masks averaged and applied; 163 dispatches a segment, recorded once; kept on the GPU a minute after a separation, so the next starts at once (its weights and 2 GB of buffers made in about a second the first time). Against onnxruntime's CPU on the same weights 101.5 dB (`scripts/engine.mjs`); in Chromium on an M4 Max under the same load as onnxruntime-web's WebGPU there, 1.5 to 1.8 s a segment against 2.6 to 3.3; GPU time by kernel: the time attention a third, the matrix products half (about 3 TFLOPS each, against onnxruntime's 1.8 for its float32 products and 0.5 to 0.75 for its attention). Elsewhere (Node, Firefox, Safari, a GPU without them) onnxruntime runs the ONNX file as before.
|
|
85
108
|
- **`export-htdemucs.py`** follows [sevagh/demucs.onnx](https://github.com/sevagh/demucs.onnx): the STFT and iSTFT, which ONNX export cannot carry, move out of the graph. Rather than a vendored copy of `htdemucs.py`, it runs upstream's own `forward` with its four transform methods swapped on the instance. One graph per 7.8 s segment: `mix [1, 2, 343980]` and `mix_spec [1, 4, 2048, 336]` (complex as channels) in, `stems_spec [1, 4, 4, 2048, 336]` and `stems_wave [1, 4, 2, 343980]` out; a source is the iSTFT of the first plus the second.
|
|
86
109
|
|
|
110
|
+
## Size
|
|
111
|
+
|
|
112
|
+
`scnet-large`, `scnet`, `mrx` and `tiger` each read one compact file, `<model>.int8.onnx`, that `scripts/compact.py`
|
|
113
|
+
makes from the export: the weights stored in 8 bits (symmetric, a scale per output channel; an LSTM's per gate row and
|
|
114
|
+
direction), the few whose rounding costs most in 16, computed in float32: onnxruntime folds each weight's Cast and scale
|
|
115
|
+
at load, on every backend, so the session holds float32 weights and runs as fast as the export. A variant is kept when
|
|
116
|
+
its cost is negligible: every stem's median within 0.05 dB of the export's (SDR on the 50 MUSDB18 test previews, SNR on
|
|
117
|
+
Divide and Remaster v3 test clips) and no song or clip worse by 0.5 dB or more; the smallest such is shipped.
|
|
118
|
+
|
|
119
|
+
| `model` | export (float32) | float16 weights | int8, every weight | **shipped**: int8, the costliest float16 | its gzip · brotli |
|
|
120
|
+
|---|---|---|---|---|---|
|
|
121
|
+
| `'scnet-large'` | 169.2 MB | 86.5 MB | 44.7 MB | **45.1 MB**: 83 of 88 weights int8 | 39.8 · 39.0 MB |
|
|
122
|
+
| `'scnet'` | 42.8 MB | 23.2 MB | 12.8 MB | **12.9 MB**: 77 of 82 | 10.7 · 10.2 MB |
|
|
123
|
+
| `'mrx'` | 122.3 MB | 61.5 MB | 31.3 MB | **31.3 MB**: all 39 | 27.5 · 26.6 MB |
|
|
124
|
+
| `'tiger'` | 28.6 MB (18.8 folded) | 10.9 MB | 7.3 MB | **7.3 MB**: all 444 | 5.2 · 2.6 MB |
|
|
125
|
+
|
|
126
|
+
Against the export, on the same songs or clips: the median per stem, then the largest loss on one song or clip, dB; and
|
|
127
|
+
the graph's own output against the export's on a calibration excerpt (two MUSDB18 training previews; a Divide and
|
|
128
|
+
Remaster v3 tuning clip), SNR.
|
|
129
|
+
|
|
130
|
+
| `model`, measure | export | float16 weights | int8, every weight | shipped |
|
|
131
|
+
|---|---|---|---|---|
|
|
132
|
+
| `'scnet-large'`, SDR: vocals · drums · bass · other | 11.00 · 10.27 · 8.21 · 6.87 | 10.99 · 10.27 · 8.21 · 6.87 | | **10.96 · 10.26 · 8.20 · 6.92** |
|
|
133
|
+
| … the song that lost most | | −0.004 | | −0.43 |
|
|
134
|
+
| … output, calibration | | 62.3 dB | 24.0 dB | 43.7 dB |
|
|
135
|
+
| `'scnet'`, SDR | 9.88 · 9.43 · 8.35 · 6.15 | 9.88 · 9.43 · 8.35 · 6.15 | 9.87 · 9.44 · 8.32 · 6.13 | **9.88 · 9.44 · 8.35 · 6.14** |
|
|
136
|
+
| … the song that lost most | | −0.004 | −2.61 | −0.29 |
|
|
137
|
+
| … output, calibration | | 60.6 dB | 23.4 dB | 42.4 dB |
|
|
138
|
+
| `'mrx'`, SNR, 30 clips: dialogue · music · effects | 10.92 · 5.17 · 5.72 | 10.92 · 5.17 · 5.72 | = shipped | **10.92 · 5.17 · 5.70** |
|
|
139
|
+
| … the clip that lost most | | −0.002 | | −0.04 |
|
|
140
|
+
| … output (masked spectra), calibration | | 73.8 dB | 40.4 dB | 40.4 dB |
|
|
141
|
+
| `'tiger'`, SNR, the same 30 clips | 12.68 · 10.24 · 8.06 | | = shipped | **12.66 · 10.23 · 8.03** |
|
|
142
|
+
| … the clip that lost most | | | | −0.10 |
|
|
143
|
+
| … output, calibration | | 73.9 dB | 41.4 dB | 41.4 dB |
|
|
144
|
+
|
|
145
|
+
Per song or clip the median change is within 0.015 dB everywhere; the largest losses fall where a stem is near silence:
|
|
146
|
+
SCNet's and SCNet-large's on PR - Happy Daze, whose vocals they separate at −2 dB SDR as exported (`scnet` with all 82
|
|
147
|
+
weights in int8: −2.6 dB there).
|
|
148
|
+
The remixes (`rebalance`'s, `scene`'s) move as little: vocals +6 dB 20.21 → 20.22 (`scnet-large`), dialogue +6 dB
|
|
149
|
+
18.29 → 18.30 (`mrx`).
|
|
150
|
+
|
|
151
|
+
Which weights stay float16: each weight rounded alone to int8 on a calibration excerpt (two MUSDB18 training previews;
|
|
152
|
+
a Divide and Remaster v3 tuning clip), the others float32, its cost the error it adds over the output's power; the
|
|
153
|
+
weights go to int8 in order of that cost per byte saved while the costs' sum stays 40 dB under the output (separate
|
|
154
|
+
weights' costs add: the sum predicted the whole to 0.2 dB). SCNet keeps the five layers ending its decoder in float16,
|
|
155
|
+
1 % of its values (`decoder.2.0`'s convolution, `decoder.1.1`'s three transposed convolutions, `decoder.2.1`'s first:
|
|
156
|
+
each alone at int8 26.8 to 39.0 dB); MRX (40.4 dB) and TIGER (41.4 dB) keep none.
|
|
157
|
+
|
|
158
|
+
Tried and left out: 4-bit weights (blocks of 32: SCNet's stems 7 dB from float32's; its LSTMs and linear layers alone,
|
|
159
|
+
18 dB); int8 scales per block of 32 values rather than per channel (31.5 dB against 26.3: still the same decoder
|
|
160
|
+
layers); DequantizeLinear for the int8 weights (onnxruntime 1.30 fuses it with the MatMul it feeds into MatMulNBits,
|
|
161
|
+
which quantizes the activations to int8 on a CPU: MRX 38.4 dB from float32 so, 40.4 as stored).
|
|
162
|
+
|
|
163
|
+
Hosts send these files as they are: Hugging Face's CDN and raw.githubusercontent.com set no Content-Encoding on a binary
|
|
164
|
+
(both answer any origin, `Access-Control-Allow-Origin: *`). gzip would save 12 to 17 % of SCNet's and MRX's, 29 % of
|
|
165
|
+
TIGER's, brotli 14 to 21 % and 65 % (TIGER's graph, 38,817 nodes, is most of its file): not worth a decoder in the page.
|
|
166
|
+
|
|
87
167
|
## Algorithm
|
|
88
168
|
|
|
89
169
|
**`'openunmix'`, `'mask'`** (spectrogram models):
|
|
@@ -111,12 +191,16 @@ TIGER's DnR model runs as `'complex'` with its own segments, those of `TIGERDNR.
|
|
|
111
191
|
| ONNX vs PyTorch module, random input (`--verify`) | umxhq max \|diff\| ≤ 5.7e-6 of max \|y\| (fp16: ≤ 5.3e-3); stems of htdemucs ≤ 1.6e-4, of htdemucs_ft ≤ 2.5e-4 |
|
|
112
192
|
| Pipeline vs `openunmix.Separator` in float64, 9 s reference mix (`test.js`) | 112–134 dB SNR per stem |
|
|
113
193
|
| Pipeline vs `demucs.apply.apply_model`, same mix (`test.js`) | htdemucs 80–84 dB, htdemucs_ft 78–89 dB SNR per stem |
|
|
114
|
-
| ONNX vs `SCNet.forward`, noise and tones (`export-scnet.py --verify`) | scnet-large max \|diff\| ≤ 2.8e-6 of max \|y
|
|
194
|
+
| ONNX vs `SCNet.forward`, noise and tones (`export-scnet.py --verify`) | scnet-large max \|diff\| ≤ 2.8e-6 of max \|y\|, scnet ≤ 2.2e-6 |
|
|
115
195
|
| Pipeline vs `SCNet.forward` on `reference.py`'s segments, a 20 s mix (`test.js`) | scnet-large 114–134 dB, scnet 123–133 dB SNR per stem |
|
|
116
196
|
| ONNX vs `MRX.forward`, noise and tones (`export-mrx.py --verify`) | max \|diff\| ≤ 1.2e-6 of max \|y\| |
|
|
117
197
|
| Pipeline vs `separate.separate_soundtrack` (pyloudnorm -27 LUFS in and back), a 20 s mix (`test.js`) | 101–134 dB SNR per stem, in one chunk; in 8 s chunks 12–50 dB |
|
|
118
198
|
| ONNX vs the three `TIGER.forward`, one 12 s segment of tones and noise (`export-tiger.py --verify`) | max \|diff\| ≤ 4.2e-7 of max \|y\| |
|
|
119
199
|
| Pipeline vs `TIGERDNR.wav_chunk_inference`, its segments through the verified graph, a 20 s mix (`test.js`) | 95–140 dB SNR per stem |
|
|
200
|
+
| Compact file vs export, a calibration excerpt (`compact.py --verify`) | scnet-large 43.7 dB, scnet 42.4, mrx 40.4, tiger 41.4 SNR (the worst output); folding alone: bit for bit |
|
|
201
|
+
| Pipeline, compact file vs `reference.py`'s stems, the same 20 s mix (`test.js`) | each stem's error under the mixture: scnet-large 25.7 dB (its other and vocals, which trade the synthetic voice; drums and bass 51), scnet 47.0, mrx 45.6, tiger 58.9 |
|
|
202
|
+
| Hosted files: the pinned revisions' SHA-256 (`test.js`, online); a fetched file of another refused | as `models[name].sha256` |
|
|
203
|
+
| Browser (onnxruntime-web, Chromium) vs Node, the same compact file, 30 s | `rebalance` and `scene` through `audio`, 8 s, the files fetched from Hugging Face: the output 135 and 145 dB from Node's on wasm, 134 and 148 dB on WebGPU |
|
|
120
204
|
| `wienerFilter` vs open-unmix's `wiener()`, float64 fixture (`test.js`) | ratio mask: 3e-12; mixture phase: 1.7e-7, upstream's `atan2` adds a float32 π |
|
|
121
205
|
|
|
122
206
|
The 50 test tracks of the MUSDB18 7 s previews (`musdb.DB(download=True)`; its terms are educational and non-commercial, so it serves measurement only), SDR by [museval](https://github.com/sigsep/sigsep-mus-eval) (BSSEval v4, 1 s windows, median over windows, then over tracks: the SiSEC 2018 aggregation):
|
|
@@ -174,7 +258,7 @@ let vocals = complex.vocals.map(ch => istft(ch, { length: left.length }))
|
|
|
174
258
|
| `sampleRate` | — | required unless `audio` is `{ channelData, sampleRate }` |
|
|
175
259
|
| `model` | — | required. Preset `'scnet-large' \| 'scnet' \| 'umxhq' \| 'htdemucs' \| 'htdemucs_ft' \| 'mrx' \| 'tiger'` · `url \| bytes` (single target, named `'stem'`) · `{ target: url \| { url, targets }, ... }` (one graph per target, Open-Unmix's own layout; a multi-source graph contributes its own target) · `{ url, targets: [...] }` (one multi-target graph, stacks a target axis) |
|
|
176
260
|
| `targets` | all | the targets to return |
|
|
177
|
-
| `weights` | `$AUDIO_NEURAL_CACHE` or `~/.cache/audiojs/neural` (Node) | where a preset's files are: URL, or a directory in Node |
|
|
261
|
+
| `weights` | `$AUDIO_NEURAL_CACHE` or `~/.cache/audiojs/neural` (Node); a compact preset's hosted file (`REVISIONS`) | where a preset's files are: URL, or a directory in Node |
|
|
178
262
|
| `modelType` | `'openunmix'` | `'openunmix'` (magnitude out) · `'mask'` (`[0,1]` mask out, multiplied by mixture magnitude) · `'hybrid'` (demucs.onnx contract) · `'complex'` (complex spectrogram in and out, SCNet's) · `'multires'` (magnitudes at several resolutions in, masks out, MRX's) · `'waveform'` (Demucs v2-class); presets know theirs |
|
|
179
263
|
| `wiener` | `1` | EM iterations; `0` = raw masks. Ignored for `'hybrid'` and `'waveform'` |
|
|
180
264
|
| `wienerWindow` | `300` | frames per EM window |
|
|
@@ -210,7 +294,7 @@ Audit any weight source yourself before shipping it — this table reflects what
|
|
|
210
294
|
|
|
211
295
|
## Reference
|
|
212
296
|
|
|
213
|
-
Stöter, Uhlich, Liutkus, Mitsufuji, "Open-Unmix - A Reference Implementation for Music Source Separation," *JOSS* 4(41), 2019. · Rouard, Massa, Défossez, "Hybrid Transformers for Music Source Separation," *ICASSP* 2023. · Xu, Li, Chen, Hu, "TIGER: Time-frequency Interleaved Gain Extraction and Reconstruction for Efficient Speech Separation," *ICLR* 2025. · Petermann, Wichern, Wang, Le Roux, "The Cocktail Fork Problem: Three-Stem Audio Separation for Real-World Soundtracks," *ICASSP* 2022. · Watcharasupat, Wu, Orife, "Remastering Divide and Remaster: A Cinematic Audio Source Separation Dataset with Multilingual Support," 2024. · Tong,
|
|
297
|
+
Stöter, Uhlich, Liutkus, Mitsufuji, "Open-Unmix - A Reference Implementation for Music Source Separation," *JOSS* 4(41), 2019. · Rouard, Massa, Défossez, "Hybrid Transformers for Music Source Separation," *ICASSP* 2023. · Xu, Li, Chen, Hu, "TIGER: Time-frequency Interleaved Gain Extraction and Reconstruction for Efficient Speech Separation," *ICLR* 2025. · Petermann, Wichern, Wang, Le Roux, "The Cocktail Fork Problem: Three-Stem Audio Separation for Real-World Soundtracks," *ICASSP* 2022. · Watcharasupat, Wu, Orife, "Remastering Divide and Remaster: A Cinematic Audio Source Separation Dataset with Multilingual Support," 2024. · Tong, Zhu, Chen, Kang, Jiang, Li, Wu, Meng, "SCNet: Sparse Compression Network for Music Source Separation," *ICASSP* 2024 (arXiv:2401.13276). · Duong, Vincent, Gribonval, "Under-determined reverberant audio source separation using a full-rank spatial covariance model," *IEEE TASLP* 18(7), 2010. · Rafii, Liutkus, Stöter, Mimilakis, Bittner, "The MUSDB18 corpus for music separation," 2017. · [norbert](https://github.com/sigsep/norbert) (Liutkus & Stöter) · [open-unmix-pytorch](https://github.com/sigsep/open-unmix-pytorch) · [Demucs](https://github.com/facebookresearch/demucs) · [demucs.onnx](https://github.com/sevagh/demucs.onnx) · [SCNet](https://github.com/starrytong/SCNet) · [cocktail-fork-separation](https://github.com/merlresearch/cocktail-fork-separation) · [TIGER](https://github.com/JusperLee/TIGER) · [pyloudnorm](https://github.com/csteinmetz1/pyloudnorm) · [Music-Source-Separation-Training](https://github.com/ZFTurbo/Music-Source-Separation-Training) · [museval](https://github.com/sigsep/sigsep-mus-eval) · [Spleeter](https://github.com/deezer/spleeter).
|
|
214
298
|
|
|
215
299
|
**Use when:** you have (or can license) an ONNX-exported spectrogram-mask, Hybrid-Demucs or waveform separation model and want to run it — with proper multichannel Wiener refinement, not just the raw mask — dependency-free, in Node or the browser; `scnet-large` for the highest SDR here under MIT weights, `scnet` for a quarter of its size and a third of its time, `htdemucs` under research-only terms.<br>
|
|
216
300
|
**Not for:** the classical, model-free case — reach for [`@audio/vocals`](https://github.com/audiojs/vocals) when a center-panned M/S trick is all you need; training a model (this is inference-only); real-time streaming (none of Open-Unmix's bi-LSTM, Hybrid Transformer Demucs or SCNet's dual-path LSTMs is causal: offline and chunked only, as upstream).
|
package/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type AudioInput = Float32Array[] | { channelData: Float32Array[]; sampleR
|
|
|
12
12
|
export type ModelSpec = string | Uint8Array
|
|
13
13
|
|
|
14
14
|
/** Presets whose files the export scripts write: <weights>/<name>/<target>.onnx or <weights>/<name>/<name>.onnx */
|
|
15
|
-
export type ModelName = 'umxhq' | 'htdemucs' | 'htdemucs_ft' | 'scnet-large' | 'scnet' | 'mrx' | 'tiger'
|
|
15
|
+
export type ModelName = 'umxhq' | 'htdemucs' | 'htdemucs_ft' | 'scnet-large' | 'scnet' | 'mrx' | 'tiger' | 'mel-roformer'
|
|
16
16
|
|
|
17
17
|
/** One graph whose output stacks several sources on its S axis */
|
|
18
18
|
export type MultiGraph = { url: ModelSpec; targets: string[] }
|
|
@@ -62,11 +62,20 @@ export interface ModelPreset {
|
|
|
62
62
|
chunk?: number
|
|
63
63
|
/** the export script that writes its weights */
|
|
64
64
|
script?: string
|
|
65
|
+
/** its compact file (scripts/compact.py: int8 weights, a few float16, float32 compute), read before <name>.onnx */
|
|
66
|
+
file?: string
|
|
67
|
+
/** the Hugging Face repository hosting it */
|
|
68
|
+
repo?: string
|
|
69
|
+
/** the compact file's SHA-256, checked when it is fetched */
|
|
70
|
+
sha256?: string
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/** Model presets by name */
|
|
68
74
|
export const models: Record<ModelName, ModelPreset>
|
|
69
75
|
|
|
76
|
+
/** The hosted revision (commit) of each compact preset's repository: https://huggingface.co/<repo>/resolve/<revision>/<file> is read without opts.weights in the browser, and in Node when the cache holds neither the file nor its export. Empty: not hosted. */
|
|
77
|
+
export const REVISIONS: Record<'scnet-large' | 'scnet' | 'mrx' | 'tiger' | 'mel-roformer', string>
|
|
78
|
+
|
|
70
79
|
/** A neural-runtime-shaped session — enough of it to drive separate() with a test double. */
|
|
71
80
|
export interface Session {
|
|
72
81
|
run(feeds: Record<string, { data: Float32Array; dims: number[]; type: string }>): Promise<Record<string, { data: Float32Array; dims: number[] }>>
|
|
@@ -84,7 +93,7 @@ export interface SeparateOptions {
|
|
|
84
93
|
modelType?: ModelType
|
|
85
94
|
/** subset of the model's targets to return; spectral models skip the other graphs, and one target runs Wiener EM against the residual */
|
|
86
95
|
targets?: string[]
|
|
87
|
-
/** where a preset's files are: URL, or a directory in Node (default $AUDIO_NEURAL_CACHE or ~/.cache/audiojs/neural) */
|
|
96
|
+
/** where a preset's files are: URL, or a directory in Node (default $AUDIO_NEURAL_CACHE or ~/.cache/audiojs/neural in Node; a compact preset's hosted file, REVISIONS) */
|
|
88
97
|
weights?: string
|
|
89
98
|
/** iterations of multichannel Wiener EM refinement (default 1); 0 = raw masks. Ignored for modelType 'hybrid' and 'waveform'. */
|
|
90
99
|
wiener?: number
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@audio/neural-separate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Source separation (stems): a song's by SCNet (MIT weights), Hybrid Transformer Demucs, Open-Unmix with multichannel Wiener refinement; a soundtrack's dialogue, music and effects by MRX (MIT weights) and TIGER (Apache-2.0); model-agnostic ONNX",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,17 +15,21 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"separate.js",
|
|
18
|
+
"roformer.js",
|
|
18
19
|
"index.d.ts",
|
|
19
20
|
"scripts/export-openunmix.py",
|
|
20
21
|
"scripts/export-htdemucs.py",
|
|
21
22
|
"scripts/export-scnet.py",
|
|
22
23
|
"scripts/export-mrx.py",
|
|
23
24
|
"scripts/export-tiger.py",
|
|
25
|
+
"scripts/export-roformer.py",
|
|
26
|
+
"scripts/engine.mjs",
|
|
24
27
|
"scripts/reference.py",
|
|
28
|
+
"scripts/compact.py",
|
|
25
29
|
"NOTICE"
|
|
26
30
|
],
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@audio/neural-runtime": "^0.1.
|
|
32
|
+
"@audio/neural-runtime": "^0.1.1",
|
|
29
33
|
"@audio/resample-sinc": "^1.1.2",
|
|
30
34
|
"fourier-transform": "^2.4.1"
|
|
31
35
|
},
|