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,75 @@
# `biquad`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `eq.biquad` module owns the **RBJ Audio EQ Cookbook coefficient maths** and the per-band biquad state shapes consumed by `EQPlugin`. Splitting the coefficient computation out of the plugin file keeps `eq.ts` focused on the plugin lifecycle (parameters, setup, presets, processBlock) and concentrates the dense numeric formulae in one well-tested helper that other plugins (a future shelf-only EQ, a tilt EQ) can reuse without dragging the full plugin in.
The formulae match Robert Bristow-Johnson's canonical reference verbatim: the `a0` term is folded into the numerator so the per-sample inner loop in the plugin's `processBlock` costs only five multiplies and four adds per band per channel.
## Features
- `BiquadCoeffs` interface — five-field record `{ b0, b1, b2, a1, a2 }` representing one normalised biquad section.
- `BiquadState` interface — `{ x1, x2, y1, y2 }` direct-form-I state. The EQ creates one per channel per band and clears them in-place from `resetState`.
- `identityCoeffs()` — returns pass-through coefficients (`b0 = 1`, others zero). Used as a placeholder before `setup()` runs the real RBJ formulae.
- `computeBiquad(band, sampleRate)` — returns coefficients for any of the five shipped band types (`peaking`, `low-shelf`, `high-shelf`, `low-pass`, `high-pass`).
## Usage: Basic Example
```ts
import { computeBiquad } from "@suaveplan/audio-fx-plugin-eq";
const coeffs = computeBiquad(
{ frequency: 1_000, gain: 6, q: 1, type: "peaking" },
48_000
);
// coeffs.b0, coeffs.b1, ...
```
## Usage: Advanced Example
```ts
import { computeBiquad, type BiquadState } from "@suaveplan/audio-fx-plugin-eq";
// Roll your own cascade using just the coefficient helper.
const states: BiquadState[] = bands.map(() => ({ x1: 0, x2: 0, y1: 0, y2: 0 }));
const coeffs = bands.map((b) => computeBiquad(b, sampleRate));
for (let i = 0; i < blockSize; i++) {
let sample = input[i]!;
for (let k = 0; k < bands.length; k++) {
const c = coeffs[k]!;
const s = states[k]!;
const y =
c.b0 * sample +
c.b1 * s.x1 +
c.b2 * s.x2 -
c.a1 * s.y1 -
c.a2 * s.y2;
s.x2 = s.x1;
s.x1 = sample;
s.y2 = s.y1;
s.y1 = y;
sample = y;
}
output[i] = sample;
}
```
## API Reference
- `BiquadCoeffs` (type) — `{ b0: number; b1: number; b2: number; a1: number; a2: number }`.
- `BiquadState` (type) — `{ x1: number; x2: number; y1: number; y2: number }`.
- `identityCoeffs(): BiquadCoeffs` — returns `{ b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 }`.
- `computeBiquad(band: EQBand, sampleRate: number): BiquadCoeffs` — RBJ Cookbook formulae for peaking / low-shelf / high-shelf / low-pass / high-pass.
## Implementation Notes
A flat band — `peaking` with `gain === 0` — collapses to identity: `A = 10^(0/40) = 1`, so `b0 = a0 = 1 + alpha`, `b1 = a1 = -2·cosW`, `b2 = a2 = 1 - alpha`. After normalising by `a0` the transfer function `H(z) = 1`, which makes the EQ's `flat` preset bit-transparent at the sample-by-sample level (the plugin's test suite asserts equality within `1e-6` against mixed-frequency input).
The chain of `if / else if` branches over `band.type` is intentional: a single `switch` would trip biome's `lexical-let-in-switch` rule for the `beta = 2·sqrt(A)·alpha` shelf intermediate, and the alternative — declaring `beta` once at the top and reusing it across cases — would compute the shelf coefficient unnecessarily for the three non-shelf branches. The current structure keeps each branch self-contained while staying lint-clean.
`computeBiquad` does not validate `band.frequency` against the Nyquist limit because the plugin instance does not yet know its target `sampleRate` at construction time and the engine's `setup()` call carries the rate; out-of-Nyquist frequencies still produce numerically stable (if musically meaningless) coefficients via `cos(omega) / sin(omega)`, and the cost of a per-band check at every recompute is not worth the protection.