Auto-Chain — the meta-atom that turns (audio + content-type preset | reference track)
into a configured, executed, and explained processing chain. Reverse-engineered from
Dolby.io Media Enhance-class products (classical DSP, no ML): an analysis pass measures
the input, an adaptive stage list is assembled entirely from shipped @audio/* atoms
with parameters set by those measurements, the chain runs, and you get back the result
and the recipe — every stage's exact parameters plus a one-line reason citing the
measurement that put it there. Dolby and iZotope hide the chain behind one "Enhance"
button; this shows it. The recipe is the product.
import chain from '@audio/chain'
const { channels, recipe, analysis } = chain([left, right], { type: 'speech' })
for (const s of recipe.stages) console.log(`${s.name.padEnd(9)} ${s.why}`)
// a voice clipped, in a room, under white noise 16 dB down (bench/rx/assistant.mjs in audio, a tuning take):
// declip clipped: 1075 runs at the rails, 0.0 dBFS, 2.95 % of samples
// hpf DC/rumble guard, always applied — 40 Hz highpass (speech convention)
// denoise noise bed 15.5 dB under the program (< 25 dB trigger)
// dereverb late reverberation: a diffuse tail, -8.8 dB of the take (dereverb's dry, diffuse and pauses checks)
// eq spectral deviation from speech target curve, max 7.1 dB (>=1 dB trigger)
// gain loudness normalization: measured -8.5 LUFS -> target -16 LUFS
// limiter peak ceiling -1.0 dB, always applied| export | signature | role |
|---|---|---|
analyze |
analyze(channels, { fs=44100, type }) |
Read-only measurement pass. Returns a plain object: fs, duration, channels, lufs, truePeakDb, lra, snr, ltas, sibilanceDb, hum, clipping, clicks, voicedRatio, reverb, pops, plus width when channels.length >= 2. snr: program over noise bed, dB (Infinity: no bed); hum: { freq, harmonics, level, taken } or null (taken: what dehum's own detection took, dB re the take; freq, harmonics, level: denoise-detect's measured series, its A-weighted level re the program, when it found one); clipping: { count, ratio, level, kind }, declip's evidence (kind 'rail', 'band' or null; level the lowest rail, dBFS); clicks: impulses a second; voicedRatio: share of voiced frames (@audio/vad); reverb, pops: { db, spans } that dereverb and deplosive take from the take, or null (read for type 'speech' only, the default). |
plan |
plan(analysis, opts) |
Analysis → recipe: { fs, type, intensity, targetLufs, stages }. Each stage is { atom, name, params, why }. JSON.stringify-safe (no typed arrays). |
apply |
apply(channels, recipe, { fs=44100 }) |
Executes recipe.stages in order over copies (input untouched), then, where a limiter runs, one refinement pass (see below). Returns Float32Array[]. Throws on a neural stage (neural, below): it runs asynchronously, so its caller applies the stages before it, awaits it, and applies the rest. |
chain (default) |
chain(channels, opts) |
One-shot analyze → plan → apply → { channels, recipe, analysis }. |
code |
code(recipe) |
Recipe → copy-paste runnable ESM: imports of every stage's atom + the exact params, executed in order, then apply()'s refinement pass. Embeds derived data (the EQ correction curve), so the script renders what apply() renders, bit for bit, without re-analyzing anything. A neural stage is awaited (await denoise(channel, { sampleRate, model, limit, floor }), top-level await): the script names the denoiser that ran. |
plan() options:
| opt | default | meaning |
|---|---|---|
type |
'speech' |
'speech' | 'music' | 'voice-music' |
intensity |
1 |
0..2: scales how hard the adaptive stages work: denoise's a priori SNR floor (−15 dB × intensity, −2..−30; with neural, how far DeepFilterNet3 takes the bed: 60 dB × intensity, 2 at least), dereverb's late estimate (strength × intensity), the deesser's deepest cut (−8 dB × intensity), the EQ's correction past the spread, the multiband ratio, the limiting allowed to reach the loudness target (6 dB × intensity). Does not change whether a stage fires (that's measurement-only, see below); declip, declick, deplosive and dehum have no strength |
targetLufs |
per-type | override the loudness target (speech/voice-music -16, music -14 — EBU-informed conventions) |
reference |
— | { ltas, lufs, truePeakDb, width }, typically analyze() of a reference track — see Reference mode |
ceiling |
-1 |
peak ceiling override, dB |
neural |
false |
its caller runs @audio/neural-denoise (audio's auto() does where it is installed): a bed under speech goes to DeepFilterNet3 instead of OM-LSA (see denoise below). { music }: the share of the take the model's guard passed as music where the stage runs, as its caller saw it; over half, OM-LSA (the model would leave the take as it came). This package neither imports nor runs it |
Execution order: the repairs first, in the order the damage is undone, the last done first (iZotope's own,
RX 12 Repair Assistant's: de-clip, de-click, de-hum, de-noise, de-reverb, de-ess), then the tone and level stages
(gain, then limiter where it may be needed). Every stage acts on measured need: each repair on its own evidence, the
tone and level stages only by how far the take is from what finished programme of its type keeps, so a take that needs
nothing comes back unchanged but for its level. speechLike = type ≠ 'music'; musicLike = type ≠ 'speech' –
'voice-music' is hybrid and lands on whichever side matters for that specific stage's own concern (vocal content →
speech-side; full-mix bass/dynamics → music-side); dereverb and deplosive are 'speech''s alone (a held note's sustain
reads as a room to one, a kick drum as a pop to the other).
| stage | atom | inclusion trigger | measurement cited |
|---|---|---|---|
declip |
@audio/denoise-declip |
clipping.kind |
declip's own evidence (rails, bands): the samples a hard clip piled onto one level, in runs, or a rail lossy coding spread. First: every other stage moves the samples off the rails it rebuilds from. The kernel's defaults |
declick |
@audio/denoise-declick |
clicks > CLICK_RATE (1 a second) |
@audio/denoise-detect's click rate (isolated impulses standing 32σ out of the AR error), with the threshold it picks its own declick branch at. Before the highpass, which rings on a click and spreads it. The kernel's defaults |
deplosive |
@audio/denoise-deplosive |
type === 'speech' && pops.db > -22 |
deplosive's own detection, run in analyze() (on the take past declick, as the plan hands it on, here and for dehum and dereverb: a click reads to dereverb as a fall no room allows): a thump under 80 Hz rising out of nothing over the voice's band, without a period; what it takes, over −22 dB of the take (a voice's own low end at a word's onset takes less). The kernel's defaults |
hpf |
@audio/filter-biquad (highpass) |
always | DC/rumble guard; 40 Hz for speech, 20 Hz for musicLike, the bottom of hearing (0.3's 25 Hz took the sub-bass out of bass-heavy mixes: PEAQ ODG under −0.5 for 4 of 32 tuning mixes) |
dehum |
@audio/denoise-dehum |
hum |
dehum's own detection, run in analyze(): its measurement over the take, else lines tracked along the 50 and 60 Hz series (hum under music); what it took. The kernel finds the series again, tracks its exact frequency, takes every harmonic to 1 kHz and every line over it to 8 kHz |
denoise |
@audio/denoise-omlsa; with neural, for 'speech', @audio/neural-denoise |
speechLike && snr < BED_SNR (25 dB) |
@audio/denoise-detect's noise bed: a floor shown in the program's pauses or held at its bands' floor, its level re the program. OM-LSA, the bed tracked by IMCRA, so one that changes over the take is followed; { gMin }, the floor the noise is taken to, dB. With neural: DeepFilterNet3 (Schröter et al., Interspeech 2023), { model: 'deepfilternet3', limit, floor }, its output mixed back by the package's mixback(): the noise 60 dB × intensity down, no floor (at 60 dB no bed reaches one), a trace of the room left rather than digital silence; as much as its whole removal on the tuning takes (see Measured). Voice-music keeps OM-LSA (the model takes a music bed under a voice for noise), and so does a take the model's guard passes as music in its larger part (neural: { music }). Not on music: tracking a dense mix it takes held parts of it for the bed (see Measured) |
dereverb |
@audio/denoise-dereverb |
type === 'speech' && reverb.db > -20 |
dereverb's own checks, run in analyze(): a diffuse tail falling slower than the voice (dry, diffuse, pauses); what it takes, over −20 dB of the take (a reading's own mild room at home takes less). After the denoiser: a bed reads as a tail that never falls. { strength }, the late estimate's scale × intensity |
deesser |
@audio/dynamics-deesser |
speechLike && sibilanceDb > -6 |
sibilanceDb: 5-9 kHz vs. 1-4 kHz LTAS band ratio, dB. { range }, its default mode (split): the kernel judges each 's' (its band over the voice body, against its own threshold) and cuts the band over 3.5 kHz; range −8 dB × intensity |
eq |
@audio/eq-fir |
speech or reference mode, max|correction| >= 1 dB (post-intensity) |
spectral-target's deviation(), Byrne's LTASS less the LTAS over 100 Hz–10 kHz, levelled per octave, octave-smoothed: only what lies past the spread clean voices keep about it (the largest deviation, per octave, of the tuning split's 61 clean speech takes: 6.9–12 dB), × intensity. Not on music: Pestana's slope is the average of commercial pop mixes, not of music (a solo trumpet lies 6–7 dB past their spread). A reference (reference mode) is matched whole |
multiband |
@audio/dynamics-multiband |
lra > 12 |
the loudness range (EBU Tech 3342) wider than finished programme keeps it (no clean tuning take came within 7 LU of 12); ratio 1 + intensity × (LRA − 12)/12, to 2; threshold at the take's integrated loudness; 2-band split for speech, 3-band where music is; downward only |
width |
@audio/spatial-midside |
reference mode, both ≥2ch | analysis.width vs reference.width (side/mid RMS ratio) — reference mode only, see below |
gain |
(inline) | always | toward targetLufs, measured on the sound reaching it (after the repairs and the EQ): min(target − LUFS, ceiling − true peak + limit), ±20 dB; limit 6 dB × intensity where the take's peaks, declip's rebuilt ones or the EQ's lift may go over the ceiling, else 0. A loud take is turned down; one needing more than limit stops short by the rest |
limiter |
@audio/dynamics-limiter |
limit > 0 or reference mode |
true-peak ceiling, default -1 dBTP (BS.1770-4 Annex 2: the waveform between samples held under it; tightened to min(ceiling, reference.truePeakDb) in reference mode), taking at most limit dB; idle under the ceiling. The clean tuning takes needed 2.6 dB of it to reach their targets (median; 95th percentile speech 6.9, music 5.3), at PESQ 4.64 → 4.60 and music ODG 0.21 → −0.03 for up to 6 dB |
Corrective stages fire on the input's own analysis in either mode: reference mode changes the tone/loudness/width target, not whether the input's own defects get fixed.
apply()'s refinement pass, where a limiter stage runs: after every stage, loudness is re-measured once; if the limiter
took it off what the gain gave, a ±2 dB trim back is applied and the limiter re-run (a trim can otherwise punch a
new peak through the ceiling limiter already enforced). One pass, not a
loop — documented here because it's the one place apply() re-measures rather than
just executing the recipe literally.
node bench/rx/assistant.mjs test in audio (2026-10): takes with several defects at
once, each made on a clean recording, so a repair is scored against the sound before the damage. Speech: VoiceBank's
test speakers, four utterances to a clip (52 clips), and 20 s of eight Spoken Wikipedia readings, twice (16); music: 25
MUSDB18 test previews and four longer pieces (29). Each clip makes three takes: clean; one defect, the kinds in turn; two
to four at random. The defects (seeded; every setting in the script's header): a steady bed (white, pink, brown, or under
150 Hz) 5–25 dB under the voice; a DEMAND recording 0–20 dB under; mains hum or buzz 20–35 dB under; clicks 1–4 a
second at 3–10× the sound around them; hard clipping to 3–15 dB SDR, the rails at full scale; a MIT IR Survey room (the
reference: the take's early sound in it); sibilants 4–12 dB brighter; plosive pops. Music gets the first five, gentler.
Every setting, RX's and this package's thresholds, was chosen on tuning takes from VoiceBank's training speakers, seven
other readings and MUSDB18's training songs.
RX: Repair Assistant (VST3, a new instance per render; Voice for speech, Music for music). Offline it renders what it is
set to: its analysis is its window's Learn, which no parameter reaches, and its state after a render holds nothing
learned but De-hum's harmonics, all zero; at its defaults it returns the input (Voice −136 dB, Music bit for bit). So it
cannot de-hum (De-hum needs learned harmonics), and it has no de-plosive. Tuned: each module's setting chosen on the
tuning takes with its defect, then on for every take each module whose dropping lowered the tuning mean: speech De-noise
80 %, De-reverb 25 %, De-clip at −0.1 dB without its limiter, De-ess at its defaults (De-click off: on, it lowered the
mean); music De-click and De-clip (De-noise lowered PEAQ's grade at every amount, −2.26 → −2.34 at 20 %, −2.90 at
100 %). Oracle: the tuned modules on for each take's own defects, what a perfect analysis would set. Ours: the recipe's
repairs alone (RX's scope), and auto() whole (with the EQ, glue, loudness and limiter), as audio's auto() runs them:
0.5.0 with @audio/neural-denoise installed (a speech bed to DeepFilterNet3), 0.4.0 without it (OM-LSA), both on the
current dependencies.
Speech: PESQ (P.862.2), mean over the takes:
| takes | input | RX tuned | RX oracle | 0.4.0 repairs | 0.4.0 auto() |
0.5.0 repairs | 0.5.0 auto() |
|---|---|---|---|---|---|---|---|
| clean (68) | 4.64 | 3.81 | 4.64 | 4.46 | 4.45 | 4.46 | 4.45 |
| steady bed (9) | 1.81 | 2.94 | 2.95 | 2.39 | 2.38 | 2.97 | 2.96 |
| DEMAND (9) | 1.84 | 2.90 | 2.93 | 2.07 | 2.04 | 2.53 | 2.50 |
| hum, buzz (9) | 2.78 | 3.60 | 2.78 | 3.28 | 3.22 | 3.29 | 3.23 |
| clicks (9) | 2.25 | 2.98 | 2.97 | 3.86 | 3.87 | 3.86 | 3.87 |
| clipped (8) | 2.38 | 2.66 | 3.07 | 3.87 | 3.88 | 3.87 | 3.88 |
| room (8) | 2.06 | 2.09 | 2.41 | 2.60 | 2.50 | 2.60 | 2.50 |
| sibilance (8) | 4.44 | 3.74 | 4.58 | 4.52 | 4.49 | 4.52 | 4.49 |
| pops (8) | 2.52 | 2.50 | 2.52 | 3.77 | 3.75 | 3.77 | 3.75 |
| 2–4 defects (68) | 1.60 | 2.18 | 2.20 | 2.31 | 2.26 | 2.57 | 2.53 |
| all with defects (136) | 2.04 | 2.56 | 2.61 | 2.79 | 2.75 | 2.99 | 2.96 |
| … STOI | 0.90 | 0.91 | 0.92 | 0.93 | 0.93 | 0.94 | 0.93 |
| … SI-SDR, dB | 9.3 | 13.0 | 13.7 | 18.2 | 14.9 | 18.4 | 15.1 |
| … DNSMOS OVRL | 2.77 | 3.12 | 3.07 | 3.00 | 3.00 | 3.12 | 3.12 |
Music: PEAQ Basic ODG (ITU-R BS.1387), mean (0.5.0 renders every music take as 0.4.0 does, sample for sample: no neural stage on music):
| takes | input | RX tuned | RX oracle | 0.3.0 repairs | 0.3.0 auto() |
0.4.0 repairs | 0.4.0 auto() |
|---|---|---|---|---|---|---|---|
| clean (29) | 0.21 | 0.21 | 0.21 | −0.28 | −2.18 | 0.09 | −0.22 |
| steady bed (6) | −0.65 | −0.65 | −0.74 | −0.83 | −2.11 | −0.63 | −0.84 |
| DEMAND (6) | −0.55 | −0.55 | −0.68 | −0.54 | −2.15 | −0.53 | −0.83 |
| hum, buzz (6) | −0.67 | −0.67 | −0.67 | −0.96 | −2.51 | −0.66 | −0.95 |
| clicks (6) | −2.38 | −2.35 | −2.38 | −1.03 | −2.38 | −0.28 | −0.59 |
| clipped (5) | −3.22 | −3.12 | −3.12 | −3.20 | −3.70 | −2.26 | −2.26 |
| 2–4 defects (29) | −3.07 | −3.05 | −3.06 | −2.88 | −3.23 | −2.56 | −2.66 |
| all with defects (58) | −2.25 | −2.23 | −2.26 | −2.06 | −2.88 | −1.69 | −1.85 |
| … SI-SDR, dB | 16.0 | 15.0 | 14.0 | 18.3 | −14.9 | 22.3 | 17.8 |
| … NMR, dB | −7.2 | −7.9 | −7.2 | −7.9 | 0.7 | −9.8 | −9.2 |
Paired over the takes with defects, 0.5.0's repairs against RX tuned: speech PESQ +0.43 ± 0.13 (95 %), STOI +0.02 ± 0.01, SI-SDR +5.4 ± 1.0 dB, DNSMOS OVRL +0.00 ± 0.04; music ODG +0.54 ± 0.25, SI-SDR +7.3 ± 1.8 dB, NMR 1.9 ± 2.5 dB lower. Against RX oracle: speech PESQ +0.38 ± 0.11, SI-SDR +4.7 ± 1.1, OVRL +0.05 ± 0.04; music ODG +0.57 ± 0.25. 0.4.0 trailed RX where a bed is the defect: PESQ −0.56 ± 0.16 on the steady beds, −0.86 ± 0.35 on DEMAND's (OVRL −0.22, −0.35), OM-LSA's −15 dB floor leaving more of a bed than RX's De-noise at 80 % (on the tuning takes a −20 dB floor or the noise read 4 dB louder gained 0.01–0.03 PESQ a take for 0.01 of STOI). 0.5.0's DeepFilterNet3 takes the beds the plan finds: on the 49 takes it ran on, PESQ 1.94 → 2.50 against RX tuned's 2.28 (+0.22 ± 0.10), DNSMOS OVRL 2.73 → 3.08 against 3.01 (+0.07 ± 0.05), STOI and SI-SDR level; against 0.4.0 over every take with defects PESQ +0.20 ± 0.06, OVRL +0.13 ± 0.04, STOI +0.01 ± 0.00, SI-SDR +0.3 ± 0.2 dB, clean takes and music the same. The steady beds now level with RX (+0.03 ± 0.21). RX still leads on DEMAND's (−0.37 ± 0.42): of the nine the plan finds five (ours ahead there, 2.43 against 2.35) and misses four, three hallways and a river read 25.5 to 34 dB under the voice, which RX's De-noise, on every take, takes. It takes hum as part of the bed too (PESQ +0.31 ± 0.57 over ours, which keeps the voice: SI-SDR 10.0 ± 4.3 dB over RX's). Clean takes: RX tuned runs its De-noise on every take (PESQ 4.64 → 3.81); the repairs change them by the 40 Hz guard and the deesser, deplosive and dereverb where their evidence holds (SDR to the input, median 12.1 dB); the guard costs music ODG 0.12.
auto()'s tone and level stages act on measured need (the rules above): the EQ, the glue and 0.3's limiter on every
take cost a clean mix 1.9 of ODG and a clean voice 0.25 of PESQ against its repairs alone. Its loudness is delivered:
speech at −16.1 LUFS (median; 89 % of takes within 1 LU of −16), music at −14.0 (98 % within 1 LU of −14), the highest
true peak −1.00 dBTP; on these takes, set to −26 dB active level (speech) and −20 dBFS RMS (music), that takes up to
6 dB of limiting. Against the repairs alone that limiting costs a clean voice PESQ 0.00 ± 0.01, speech with defects
0.04 ± 0.01, a clean mix ODG 0.30 ± 0.09 (−0.22 against the input's 0.21; the release and look-ahead tried on the
tuning mixes, 50–400 ms and 5–10 ms, all cost as much or more), music with defects 0.16 ± 0.06; intensity: 0
stops the gain at the ceiling instead (clean mixes 0.09). Against RX tuned, auto(): speech with defects PESQ
+0.40 ± 0.13 (0.4.0: +0.19 ± 0.15), DNSMOS OVRL +0.00 ± 0.04 (−0.12 ± 0.05), music ODG +0.38 ± 0.24. A first gated build kept the music EQ past the spread of MUSDB18 mixes: a solo
trumpet lay 6–7 dB past it and lost 1.9 of ODG to it; music now gets no target EQ. The tuning takes through this build
end to end, auto() against its repairs: clean speech PESQ 4.52 against 4.55, with defects 2.81 against 2.86; clean
mixes ODG −0.08 against 0.17 (input 0.21), with defects −1.52 against −1.40; speech at −16.2 LUFS (91 % within 1 LU),
music at −14.0 (95 %), the highest true peak −0.99 dBTP.
What the plan turns on, per defect, recall · false alarms (takes with the defect · without it):
| defect | 0.3.0 | 0.4.0, 0.5.0 |
|---|---|---|
| speech: steady bed → denoise (33 · 137) | 79 · 1 % | 79 · 1 % |
| speech: DEMAND → denoise (43 · 137) | 70 · 1 % | 70 · 1 % |
| speech: hum → dehum (34 · 170) | 26 · 0 % | 47 · 0 % |
| speech: clicks → declick (30 · 174) | 77 · 0 % | 77 · 0 % |
| speech: clipped → declip (35 · 169) | 0 · 0 % | 100 · 0 % |
| speech: room → dereverb (30 · 174) | 0 · 0 % | 97 · 6 % |
| speech: sibilance → deesser (31 · 173) | 48 · 18 % | 35 · 12 % |
| speech: pops → deplosive (35 · 169) | 0 · 0 % | 100 · 2 % |
| music: steady bed, DEMAND → denoise (51 · 48) | 25 · 13 % | 0 · 0 % |
| music: hum → dehum (25 · 62) | 0 · 0 % | 0 · 0 % |
| music: clicks → declick (22 · 65) | 45 · 0 % | 45 · 0 % |
| music: clipped → declip (20 · 67) | 0 · 0 % | 100 · 0 % |
0.5.0 puts in what 0.4.0 does; its denoise stage on speech is DeepFilterNet3's wherever the plan finds a bed (49 of the
136 speech takes with defects; its guard heard none of them as music). The beds missed sit 25 dB or more under the
voice above 63 Hz, most of a brown bed's power under it (the 40 Hz guard takes it), or are DEMAND's hallway and river,
read 25.5 to 34 dB under where they lie 2.6 to 18 dB under; dereverb's false alarms are one reading's own room (both its clean excerpts) and beds it read as a tail. Music beds go unrepaired by design (see denoise
above); hum under a mix stays too faint for dehum's detection. Through audio, 0.3's auto() normalized every take to
−6 LUFS (its targetLufs sentinel clamped into the declared range; see Manifest), not the type's −16.
node scripts/plan.js [tune|test] [chain.js] plans every take of labelled material and counts the
repair stages each recipe holds (sources and mixtures in the script): VoiceBank+DEMAND's test set
(Valentini-Botinhao 2017), ten Spoken Wikipedia narrations, Slakh2100 mixes and VocalSet singers,
clean and with white or pink noise 10 and 20 dB under, mains hum 20 dB under, clicks at 5× the level
around. 0.3.0 → 0.4.0 on the test set, share of takes:
| material | n | declip | declick | deplosive | dehum | denoise | dereverb | deesser | nothing repaired |
|---|---|---|---|---|---|---|---|---|---|
| clean speech (VoiceBank) | 138 | · | 4 → 4% | · | · | · | · | 4 → 0% | 96 → 96% |
| narrations | 20 | · | · | 0 → 5% | 0 → 15% | 10 → 10% | 0 → 10% | 55 → 40% | 90 → 70% |
| music | 30 | · | · | · | 3 → 0% | 3 → 0% | · | · | 93 → 100% |
| VoiceBank+DEMAND noisy | 138 | 0 → 1% | · | 0 → 11% | · | 76 → 76% | 0 → 7% | 4 → 1% | 24 → 18% |
| speech + white, pink noise | 184 | · | · | · | · | 100 → 100% | 0 → 12% | 28 → 13% | 0 → 0% |
| music + white noise | 30 | · | · | · | 7 → 0% | 100 → 0% | · | · | 0 → 100% |
| speech + hum | 23 | · | 4 → 4% | · | 96 → 91% | 61 → 61% | · | 4 → 0% | 0 → 0% |
| music + hum | 10 | · | · | · | 80 → 90% | 10 → 0% | · | · | 20 → 10% |
| speech + clicks | 46 | · | 93 → 93% | · | · | · | · | 4 → 0% | 7 → 7% |
| music + clicks | 20 | · | 100 → 100% | · | · | 10 → 0% | · | · | 0 → 0% |
The narrations' dehum is their own mains hum (two of the ten carry a line 20 dB over its
surroundings, bench/rx/dehum.mjs in audio), their dereverb a reader's room; the noisy takes'
deplosive and dereverb are bursts of DEMAND's noise under 80 Hz and beds read as a tail. Music is
no longer denoised (see denoise).
Missed: beds that sit 25 dB and more under the voice above 63 Hz (the test set's bus and office
noise at 12.5–17.5 dB, most of whose power lies under 40 Hz, which the guard highpass takes), as
denoise-detect's README measures. 0.1.1 → 0.2.0: clean speech untouched 0 → 96 %, narrations 20 →
90 %, music 63 → 97 % (0.1.1 denoised every speech take on an absolute floor and put declick into
38 % of clean VoiceBank takes).
plan(analysis, { reference }) — Matchering-style mastering. reference is normally
just analyze() run on a reference track (its ltas, lufs, truePeakDb, and — for
stereo input — width are all it needs):
- EQ target becomes the reference's own LTAS (linear → dB) instead of a
content-type preset curve —
deviation(analysis.ltas, ltasToDb(reference.ltas)). targetLufsdefaults toreference.lufs(explicitopts.targetLufsstill wins).- Limiter ceiling tightens to
min(ceiling, reference.truePeakDb). widthstage is added when both input and reference are stereo: mid/side matrix via@audio/spatial-midside, side gain =reference.width / analysis.width(clamped0..4).
Two host-facing surfaces per @audio/compile CONTRACT.md:
auto— processor atom,streaming: false(whole-render — the entire signal in oneprocesscall),channels: 'any'. Runsanalyze → plan → applyinside that one call: the open "God Particle," adaptive by measurement rather than a fixed preset. Params:type(enum, default'speech'),intensity(0..2, default1),targetLufs(-30..0LUFS, default0:0is a sentinel for "no override, use the per-type default", since the param system has no null and 0 LUFS is never a sensible target; the range holds it, as a host clamps a default into it: 0.3's-30..-6made it −6 LUFS inaudio),ceiling(-6..0dB, default-1).chain— stat atom,{ stat: 'chain', compute(channels, opts) }.analyze+planonly, no processing — the recipe for a report/preview UI (Mix Analyser's feed, a "show me the chain before you render" panel) without paying for a render.- For a host that runs the recipe itself (audio's
auto(), which runs the neural denoise stage betweenapply()'s parts):analyze,plan,apply,code, andoptions(params), plan()'s options from the params as a host hands them over (thetargetLufssentinel resolved).
audio.d.ts is hand-written, not tool-generated: @audio/compile/tools/dts.js only
walks monorepo <family>/packages/* layouts (readdirSync(FAM, fam, 'packages')) — it
has no root-package mode, and this repo is a root package with no packages/ dir. Content matches the tool's own generated format otherwise.
Classical DSP only. Every processing stage this package runs is a deterministic, published @audio/*
atom — BS.1770 loudness, minimum-statistics noise PSD (Martin 2001), Welch LTAS,
biquad/FIR filters, feedforward compressors and a lookahead limiter. No ML, no black
box: two runs on the same input are bit-identical, and code() turns any recipe into a
plain script you can read, edit, and re-run without this package at all. That's the
whole point — Dolby.io Media Enhance and iZotope's mastering assistants apply a chain
exactly like this one and never show it. @audio/chain always does. The one exception is
named, not run: with neural, the plan hands a speech bed to @audio/neural-denoise's
DeepFilterNet3, which its caller runs and the recipe (and code()) names.
npm testAll fixtures are synthetic and seeded (no bundled audio assets, no Math.random) —
deterministic in, deterministic out. Thresholds in test.js were calibrated against
this pipeline's own measured output where no external reference exists (e.g. the
fixture's noise is set for @audio/denoise-detect to read its bed 10 to 25 dB under the
program; it reads 17.2); see the comments above each fixture and assertion for the
reasoning and the specific numbers.