4.2 KiB
limiter
(no submodules)
Purpose
LimiterPlugin is the universal brick-wall limiter. Its core contract is unconditional: for any bounded input, no output sample's absolute value exceeds the linear conversion of the ceiling parameter. The plugin pairs an instantaneous (zero-lookahead) attack with a one-pole exponential release, so transients are clamped on the sample they occur — no lookahead buffer, no group delay, no latency to report.
The plugin extends AbstractAudioFxPlugin, so it runs identically across browser / server / worklet renderers. State is a single linked gain value plus a frame-peak reduction metric for telemetry. reset() snaps the gain back to unity in place.
Features
- Three parameters:
ceiling(dB, -60..0),release(ms, 1..1000), andgainReductionDb(telemetry). - Linked-stereo brick-wall: gain is shared across all input channels per sample, so a transient on one channel attenuates every channel identically — the stereo image stays phase-coherent.
- Zero-lookahead attack: when
|peak| > ceilingLinat samplei, the gain at sampleiis set toceilingLin / |peak|before writing the output, so the ceiling is a hard contract verifiable by audit. - One-pole release:
gain += releaseCoef · (1 − gain)per sample withreleaseCoef = 1 − exp(-1/(τ · sr)). gainReductionDbis updated once per block with the worst-case in-block reduction so meters get a useful peak reading without per-sample state.getGainReductionDb()accessor mirrors the parameter for low-friction metering.
Usage: Basic Example
import { LimiterPlugin } from "@suaveplan/audio-fx-plugin-limiter";
const limiter = new LimiterPlugin({
initialCeilingDb: -1,
initialReleaseMs: 50,
});
chain.add(limiter);
Usage: Advanced Example
import { LimiterPlugin } from "@suaveplan/audio-fx-plugin-limiter";
// Master-bus limiter feeding a UI meter from the live gain reduction.
const limiter = new LimiterPlugin({
initialCeilingDb: -0.3,
initialReleaseMs: 80,
});
await limiter.setup(
{ sampleRate: 48_000, channels: 2, blockSize: 512 },
ctx
);
setInterval(() => {
const gr = limiter.getGainReductionDb();
if (gr > 6) {
console.warn(`Master limiter pulling ${gr.toFixed(2)} dB`);
}
}, 100);
API Reference
LimiterPlugin(class extendsAbstractAudioFxPlugin)new LimiterPlugin(options?: LimiterPluginOptions)— throwsAUDIO_FX.INVALID_OPTIONSon malformed options.getGainReductionDb(): number— last block's peak gain reduction (positive number, in dB).- Inherited parameter accessors for
ceiling,release,gainReductionDb.
Implementation Notes
The limiter is intentionally a true brick-wall, not a "soft" or "transparent" limiter. We do not interpolate around the threshold or apply a knee. The reason: many use sites layer this plugin downstream of an output-stage compressor that already provides knee shaping, and rely on the limiter for guaranteed bound semantics. Adding a knee would weaken the contract that the test in output peak never exceeds the configured ceiling enforces.
There is no lookahead buffer. That keeps the plugin latency-free (latencySamples: 0, inherited default) and makes the bound testable by static inspection of the inner loop: at sample i, if peak > ceilingLin then gain := ceilingLin / peak before output[ch][i] = input[ch][i] * gain. The multiplication therefore can produce at most |input[ch][i]| * (ceilingLin / |peak|) ≤ ceilingLin. The downside is that an unusually fast transient (single-sample spike at a very low source sample rate) can produce visible distortion on its leading edge — for those cases a future LookaheadLimiterPlugin can sit on a separate subpath without breaking this contract.
gainReductionDb is reported as a per-block peak rather than a running average. Meters typically expect peak; if a future surface wants RMS, it can compute that from the gain-reduction stream by subscribing to a parameter automation channel. The release coefficient is recomputed every block from the current release parameter so automation works without introducing zipper noise — the one-pole step is small per sample even when the parameter changes mid-block.