Files
skills/docs/media/audio-fx/audio-fx-plugin-reverb/src/engines/engines.md
2026-05-29 08:20:56 +01:00

98 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `engines`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `reverb.engines` module owns the **per-sample DSP kernels** that `ReverbPlugin` calls every block: the Schroeder-Moorer algorithmic engine (comb-bank + all-pass diffusion), the direct-convolution engine (used when an IR is loaded), the pre-delay tap, and the per-channel state shape they all consume. Splitting these out of the plugin file keeps `reverb.ts` focused on the plugin lifecycle (parameters, setup, presets, loadImpulseResponse) and concentrates the inner-loop hot paths in one allocation-free helper that other reverb-shaped plugins (a future plate-only reverb, a Hadamard-matrix FDN) can reuse without dragging the full `ReverbPlugin` in.
The engines are pure functions over a mutable `ChannelState`: every per-sample call updates the state's circular buffer and indices in place, so the plugin's `processBlock` allocates nothing.
## Features
- `ChannelState`, `CombState`, `AllPassState` interfaces — the full per-channel state shape, sized once at `setup()`.
- `COMB_DELAYS_MS`, `ALLPASS_DELAYS_MS` — fixed Freeverb-style delay tables, scaled per session sample rate.
- `MAX_FEEDBACK`, `ALLPASS_FEEDBACK` — clamping and diffusion constants used by both engines.
- `createChannelState(sampleRate, preDelayMaxMs, irLength)` — fresh per-channel state allocator.
- `readPreDelay(state, sample, delaySamples)` — pre-delay tap; returns `sample` directly when `delaySamples === 0`.
- `algorithmicStep(state, input)` — Schroeder-Moorer per-sample step.
- `convolveStep(state, ir, input)` — direct time-domain convolution per-sample step.
- `updateCombFeedbacks(state, sampleRate, decaySec)` — recompute comb feedbacks from the current decay time (called once per block by the plugin).
- `resetChannelState(state)` — clear every buffer + index in place; no re-allocation.
## Usage: Basic Example
```ts
import {
createChannelState,
algorithmicStep,
updateCombFeedbacks,
} from "@suaveplan/audio-fx-plugin-reverb";
const state = createChannelState(48_000, 200, 0);
updateCombFeedbacks(state, 48_000, 2.5);
for (let i = 0; i < input.length; i++) {
output[i] = algorithmicStep(state, input[i]);
}
```
## Usage: Advanced Example
```ts
import {
createChannelState,
algorithmicStep,
convolveStep,
readPreDelay,
updateCombFeedbacks,
resetChannelState,
} from "@suaveplan/audio-fx-plugin-reverb";
// Build a custom dual-path reverb: pre-delay → convolver (if loaded)
// or algorithmic fallback, mixed wet/dry.
const states = Array.from({ length: channels }, () =>
createChannelState(sampleRate, 200, impulseResponse?.length ?? 0)
);
for (let ch = 0; ch < channels; ch++) {
const state = states[ch]!;
updateCombFeedbacks(state, sampleRate, decaySec);
for (let i = 0; i < blockSize; i++) {
const sample = input[ch]![i]!;
const wetIn = readPreDelay(state, sample, preDelaySamples);
const wetOut = impulseResponse
? convolveStep(state, impulseResponse, wetIn)
: algorithmicStep(state, wetIn);
output[ch]![i] = dry * sample + wet * wetOut;
}
}
// Transport stop: clear all state without reallocating.
for (const state of states) resetChannelState(state);
```
## API Reference
- `CombState`, `AllPassState`, `ChannelState` — state interfaces consumed by every engine function.
- `COMB_DELAYS_MS: readonly [29.7, 37.1, 41.1, 43.7]`, `ALLPASS_DELAYS_MS: readonly [5, 1.7]` — delay tables.
- `MAX_FEEDBACK = 0.997`, `ALLPASS_FEEDBACK = 0.5`.
- `createChannelState(sampleRate, preDelayMaxMs, irLength): ChannelState`.
- `readPreDelay(state, sample, delaySamples): number`.
- `algorithmicStep(state, input): number`.
- `convolveStep(state, ir, input): number`.
- `updateCombFeedbacks(state, sampleRate, decaySec): void`.
- `resetChannelState(state): void`.
## Implementation Notes
The algorithmic step parallels Freeverb's topology: four parallel comb filters with sample-rate-scaled delays, their outputs averaged, then fed through two series all-pass diffusers. The comb feedback formula `exp(-3 · ln10 · τ / T)` (where τ is the delay in seconds and T is the RT60) is mathematically equivalent to `10^(-3·τ/T)` and gives an analytically-correct RT60 in the linear-decay limit; the explicit `MAX_FEEDBACK = 0.997` clamp guarantees the engine cannot diverge regardless of the configured `decay` value.
`convolveStep` is direct time-domain convolution; it costs `O(blockSize · irLength)` per channel, which is appropriate for short room and plate IRs (up to a few thousand samples). Long cathedral IRs benefit from a partitioned FFT convolver — that is deliberately deferred until a real bottleneck demonstrates the need, since the algorithmic path covers long tails efficiently and the convolution path stays simple and obviously correct.
`readPreDelay` short-circuits when `delaySamples === 0` so the bit-transparent dry path stays tight at zero pre-delay. The buffer is sized to the maximum allowed pre-delay (200 ms × sample rate) at setup and never reallocated; the current pre-delay length is read per-block from the live parameter so automation lanes stay continuous.
`resetChannelState` mutates buffers in place (`fill(0)`) rather than reallocating so the plugin's `reset()` honours the spec's "buffers are the same instances as before reset" guarantee.