4.5 KiB
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 smuggleNaNs into the convolution delay line.REVERB_PRESET_NAMES— the canonical four preset names exported as areadonlytuple so consumers can type-narrow against it and iterate UI menus deterministically.
Usage: Basic Example
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
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.ZodObjectmatchingReverbPluginOptions. Every field is optional and ranged to match the parameter declarations inreverb.ts.ReverbPresetDataSchema—z.ZodObjectmatchingReverbPresetData.impulseResponseis 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.