# `schemas` _(no submodules)_ ## 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.