Dynamics processing — compressor, limiter, gate, expander, de-esser, ducker, softclip, compand. All built on a single branching envelope follower; differences are purely in the gain curve. Part of audiojs.
| Kind | Gain function | Typical use | |
|---|---|---|---|
| compressor | envelope | soft-knee above threshold | leveling vocals, mix glue |
| limiter | lookahead | brickwall at ceiling | master bus, peak control |
| gate | envelope | hard cut below threshold | silence between phrases |
| expander | envelope | gentle below-threshold reduction | soft gating, noise bed shaping |
| deesser | sidechain | compressor on sibilance band | harsh 's' / 't' in voice |
| ducker | ext. sidechain | compressor keyed by side signal | music-under-voice, podcast |
| softclip | waveshaper | static transfer curve | gentle peak limiting + coloration |
| compand | envelope | piecewise-linear transfer | SoX-style multi-segment |
npm install dynamics-processor
import { compressor, limiter, gate, ducker } from 'dynamics-processor'
let glued = compressor(samples, { threshold: -18, ratio: 4, attack: 5, release: 100 })
let safe = limiter(glued, { ceiling: -0.3, lookahead: 5 })
let write = compressor({ threshold: -18, ratio: 4 }) // streaming
write(block1)
write(block2)
write() // → remaining samples
let ducked = ducker(music, voice, { threshold: -30, range: -12 })Mono
Float32Arrayin/out. For stereo, process channels independently or feed a linked detector. Sample rate defaults to 44100; passsampleRatefor anything else.
Every processor except softclip is built on this: a branching one-pole follower with separate attack/release time constants, peak or RMS detection.
import { envelope } from 'dynamics-processor'
let follow = envelope({ attack: 5, release: 100, detector: 'peak' })
for (let x of samples) level.push(follow(x))| Param | Default | |
|---|---|---|
sampleRate |
44100 |
— |
attack |
5 |
ms |
release |
50 |
ms |
detector |
'peak' |
'peak' or 'rms' |
rmsWindow |
256 |
samples, for RMS detector |
Feed-forward soft-knee downward compressor — Giannoulis-Massberg topology. Envelope → log domain → quadratic soft-knee gain curve → linear gain applied to input.
import { compressor } from 'dynamics-processor'
compressor(data, { threshold: -18, ratio: 4 })
compressor(data, { threshold: -24, ratio: 2, knee: 12, attack: 10, release: 200, makeup: 6 })| Param | Default | |
|---|---|---|
threshold |
-20 |
dB |
ratio |
4 |
— |
knee |
6 |
dB (soft-knee width) |
attack |
5 |
ms |
release |
100 |
ms |
makeup |
0 |
dB |
Use when: vocals, bass, drum bus, mix glue.
Not for: peak control at the master bus — use limiter. Transparent loudness shaping — use compand with gentle slope.
Lookahead brickwall limiter. Peak-hold envelope with exponential release; input delayed by lookahead ms so gain reduction lands before the peak emerges.
import { limiter } from 'dynamics-processor'
limiter(data, { ceiling: -0.3 })
limiter(data, { ceiling: -1, lookahead: 10, release: 100 })| Param | Default | |
|---|---|---|
ceiling |
-0.3 |
dB (brickwall) |
lookahead |
5 |
ms (introduces delay) |
release |
50 |
ms |
Use when: master bus ceiling, true-peak safety, preventing inter-sample clipping.
Not for: musical dynamics shaping — use compressor. Low-latency paths — use softclip.
Noise gate with hold-then-close logic. Below threshold, signal is attenuated by range dB. A hold timer keeps the gate open after a drop-out to avoid chatter; attack/release smooth the gain transitions.
import { gate } from 'dynamics-processor'
gate(data, { threshold: -40 })
gate(data, { threshold: -35, range: -80, hold: 20, attack: 1, release: 150 })| Param | Default | |
|---|---|---|
threshold |
-40 |
dB |
range |
-60 |
dB attenuation when closed |
hold |
10 |
ms |
attack |
0.1 |
ms (opening) |
release |
100 |
ms (closing) |
Use when: drum mics, voice dialogue with ambient noise, removing hiss between phrases.
Not for: subtle low-level reduction — use expander.
Downward expander — a softer gate. Below threshold, gain is reduced by (threshold − level) × (ratio − 1) dB, clamped at range.
import { expander } from 'dynamics-processor'
expander(data, { threshold: -30, ratio: 2 })| Param | Default | |
|---|---|---|
threshold |
-30 |
dB |
ratio |
2 |
— |
knee |
6 |
dB |
range |
-40 |
dB, max reduction |
attack |
5 |
ms |
release |
50 |
ms |
Use when: gentle noise-floor suppression without the abruptness of a gate.
Not for: hard removal of sound between phrases — use gate.
Sibilance compressor. A biquad bandpass drives the envelope follower; the resulting gain reduction is applied broadband. Simple and transparent.
import { deesser } from 'dynamics-processor'
deesser(data, { freq: 6500, threshold: -20 })
deesser(data, { freq: 5500, q: 3, threshold: -24, ratio: 6 })| Param | Default | |
|---|---|---|
freq |
6500 |
Hz, sibilance center |
q |
2 |
bandpass Q |
threshold |
-20 |
dB (on sidechain level) |
ratio |
4 |
— |
knee |
6 |
dB |
attack |
1 |
ms |
release |
40 |
ms |
Use when: harsh 's' / 't' / 'sh' in close-miked voice, bright vocal takes.
Not for: broadband brightness — use an EQ. Generic compression — use compressor.
External-sidechain compressor. Main signal's gain tracks the level of a separate side signal.
import { ducker } from 'dynamics-processor'
// batch
let podcast = ducker(music, voice, { threshold: -30, range: -12 })
// streaming — callable takes (main, side); call with no args to flush
let duck = ducker({ threshold: -30, range: -15 })
let out1 = duck(musicBlock1, voiceBlock1)
let out2 = duck(musicBlock2, voiceBlock2)
let tail = duck()| Param | Default | |
|---|---|---|
threshold |
-30 |
dB (on side level) |
ratio |
4 |
— |
knee |
6 |
dB |
range |
-24 |
dB, max reduction |
attack |
20 |
ms |
release |
300 |
ms |
Use when: music-under-voice podcasts, dialogue ducking, sidechain-pumped mixes.
Not for: sidechain from the same signal — use compressor.
Static waveshaping — no time state, no pumping. Maps input through a fixed transfer curve; peaks saturate smoothly, introducing controlled harmonic content.
import { softclip } from 'dynamics-processor'
softclip(data, { curve: 'tanh', drive: 1.5 })
softclip(data, { curve: 'cubic', drive: 2, ceiling: 0.9 })| Param | Default | |
|---|---|---|
curve |
'tanh' |
'tanh', 'atan', 'cubic', 'sin', 'hard' |
drive |
1 |
input pre-gain |
ceiling |
1 |
output asymptote |
Use when: gentle peak control with musical saturation, avoiding pumping artifacts of a limiter, lo-fi character.
Not for: transparent true-peak safety — use limiter. Clean gain reduction — use compressor.
SoX-style multi-segment compander. Arbitrary piecewise-linear transfer in dB unifies compression, expansion, and gating under one curve — points below the identity line compress; above, they expand.
import { compand } from 'dynamics-processor'
// Default: compress above -20 dB
compand(data)
// Broadcast leveler: lift -40..-20 dB, compress above -10 dB
compand(data, {
points: [[-90, -90], [-40, -30], [-20, -18], [-10, -10], [0, -4]],
attack: 20, release: 500
})| Param | Default | |
|---|---|---|
points |
[[-90,-90],[-60,-60],[-20,-20],[0,-8]] |
[[inDb, outDb], ...] |
attack |
5 |
ms |
release |
200 |
ms |
Use when: broadcast leveling, speech normalization, any time a single compressor's fixed ratio is too rigid.
Not for: simple threshold compression — use compressor.
- noise-reduction — gate belongs here too; umbrella for everything noise
- audio-filter — biquads for deesser sidechain
- audio-effect — modulation effects
- time-stretch — sibling package
- Giannoulis, D., Massberg, M. & Reiss, J.D. (2012). "Digital dynamic range compressor design — a tutorial and analysis." JAES, 60(6).
- Zölzer, U. (ed., 2011). DAFX — Digital Audio Effects (2nd ed.), chapter on dynamics processing.
- Reiss, J.D. & McPherson, A. (2014). Audio Effects — Theory, Implementation and Application, Ch. 6.
- Bristow-Johnson, R. (2005). "Audio EQ Cookbook." (RBJ biquad formulae, used in deesser sidechain.)
- SoX manual —
compand(piecewise-linear compander semantics).