first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
# `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.