Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
# `schemas`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `reverb.schemas` module owns the Zod boundary guards for `ReverbPlugin` — both the constructor options (`preDelay`, `decay`, `wet`, `dry` initial values) and the plugin-specific preset payload (`impulseResponse`). Per the package's "Zod boundary validation" requirement, every public options object on every plugin SHALL be validated against a sibling `*.schemas.ts` before the plugin touches any state, and `ReverbPlugin` is no exception: malformed options surface as `AUDIO_FX.INVALID_OPTIONS` and malformed preset data as `AUDIO_FX.INVALID_PRESET`, both carrying the Zod issue list in `context.issues`.
Keeping the schemas in a sibling file (rather than inline in `reverb.ts`) is the package-wide convention: consumers can import the schemas directly and run their own `safeParse` against UI input, surfacing field-level errors before the plugin instance exists.
## Features
- `ReverbPluginOptionsSchema` — every initial value is optional but, when present, must be finite and inside the parameter declaration's `[min, max]` range (`preDelay ∈ [0, 200] ms`, `decay ∈ [0.1, 10] s`, `wet ∈ [0, 1]`, `dry ∈ [0, 1]`).
- `ReverbPresetDataSchema` — the plugin-specific preset payload. Just one optional field, `impulseResponse: number[]`, validated as an array of finite numbers so a hostile payload cannot smuggle `NaN`s into the convolution delay line.
- `REVERB_PRESET_NAMES` — the canonical four preset names exported as a `readonly` tuple so consumers can type-narrow against it and iterate UI menus deterministically.
## Usage: Basic Example
```ts
import { ReverbPluginOptionsSchema } from "@suaveplan/audio-fx-plugin-reverb";
const result = ReverbPluginOptionsSchema.safeParse(formValues);
if (!result.success) {
return showFieldErrors(result.error.issues);
}
const reverb = new ReverbPlugin(result.data);
```
## Usage: Advanced Example
```ts
import {
REVERB_PRESET_NAMES,
ReverbPluginOptionsSchema,
ReverbPresetDataSchema,
} from "@suaveplan/audio-fx-plugin-reverb";
// Render a UI preset picker driven by the schema-exported tuple.
const select = document.createElement("select");
for (const name of REVERB_PRESET_NAMES) {
select.append(new Option(name, name));
}
// Validate an untrusted preset payload (e.g. from cloud sync) before
// handing it to the plugin's applyPreset. The plugin would catch the
// same issue, but the host wants to render a friendlier error first.
const result = ReverbPresetDataSchema.safeParse(cloudPayload.data);
if (!result.success) {
return reject("Preset is corrupt; not loading.");
}
if (result.data.impulseResponse && result.data.impulseResponse.length > 65_536) {
return reject("Impulse response too long for the current build.");
}
```
## API Reference
- `ReverbPluginOptionsSchema` — `z.ZodObject` matching `ReverbPluginOptions`. Every field is optional and ranged to match the parameter declarations in `reverb.ts`.
- `ReverbPresetDataSchema` — `z.ZodObject` matching `ReverbPresetData`. `impulseResponse` is optional; when present, must be an array of finite numbers.
- `REVERB_PRESET_NAMES: readonly ["room", "hall", "plate", "cathedral"]`.
## Implementation Notes
`ReverbPluginOptionsSchema` uses `.finite()` on every numeric field so `NaN` and `±Infinity` are rejected at the boundary; the comb feedback formula `exp(-3 · LN10 · τ / T)` would otherwise propagate non-finite values into every comb's accumulator and contaminate every subsequent block. Time bounds (`min(0.1)` for decay, `max(200)` for pre-delay) match the parameter declarations in `reverb.ts` exactly so consumers can take either the schema or the parameter declaration as authoritative without having to cross-reference.
`ReverbPresetDataSchema.impulseResponse` is stored as `number[]` rather than a `Float32Array` because the universal `PresetSnapshot` envelope round-trips through `JSON.stringify`; `Float32Array` would lose its element-type during serialisation, so the schema accepts the JSON-native shape and the plugin converts back to `Float32Array` on load. Every element is `.finite()`-checked so a malicious payload cannot inject `NaN` into the convolution kernel.
The preset names tuple is exported as a `readonly` const assertion so consumers writing exhaustive switch statements get compile-time exhaustiveness checking — adding a new preset name to the plugin requires updating this tuple, which in turn surfaces every consumer that needs to handle the new case.