4.1 KiB
schemas
(no submodules)
Purpose
The compressor.schemas module owns the Zod boundary guards for CompressorPlugin — both the constructor options and the plugin-specific preset payload. 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 these schemas are how CompressorPlugin keeps that promise. A malformed initialRatio, an out-of-range initialAttackMs, or a missing field on applyPreset data all surface as AUDIO_FX.INVALID_OPTIONS / AUDIO_FX.INVALID_PRESET with the Zod issue list in context.issues.
Keeping the schemas in a sibling file (rather than inline in compressor.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
CompressorPluginOptionsSchema— every constructor field is optional but, when present, must be finite and inside the per-parameter[min, max]range that the plugin'sparametersdeclare (threshold ∈ [−60, 0],knee ∈ [0, 24],ratio ∈ [1, 20],attack ∈ [0.1, 100] ms,release ∈ [1, 1000] ms).CompressorPresetDataSchema— the plugin-specific preset payload: justmidSideandsidechainEnabledbooleans. The five continuous parameters round-trip through the universalparametersmap on thePresetSnapshotenvelope, so they do not need to appear here.
Usage: Basic Example
import { CompressorPluginOptionsSchema } from "@suaveplan/audio-fx-plugin-compressor";
const result = CompressorPluginOptionsSchema.safeParse(formValues);
if (!result.success) {
return showFieldErrors(result.error.issues);
}
const compressor = new CompressorPlugin(result.data);
Usage: Advanced Example
import {
CompressorPluginOptionsSchema,
CompressorPresetDataSchema,
} from "@suaveplan/audio-fx-plugin-compressor";
// Server-side validation of an untrusted preset payload before it
// hits the plugin's applyPreset (which would throw with the same
// code, but the host wants to surface a friendlier error first).
const envelope = parseChainSnapshot(payload); // base envelope check
for (const entry of envelope.plugins.filter((p) => p.pluginType === "compressor")) {
const data = CompressorPresetDataSchema.parse(entry.preset.data);
if (data.sidechainEnabled && !allowSidechain) {
return reject("Sidechain compression is disabled for this tier.");
}
}
API Reference
CompressorPluginOptionsSchema—z.ZodObjectmatchingCompressorPluginOptions. Every field is optional and ranged.CompressorPresetDataSchema—z.ZodObjectmatchingCompressorPresetData. Both fields required.
Implementation Notes
The schemas use .finite() so NaN and ±Infinity are rejected at the boundary; the envelope follower's exp(-1 / (τ · sr)) calculation would otherwise propagate non-finite alphas into the smoothing state and contaminate every subsequent block. Time constants use .min(0.1) for attack and .min(1) for release — values smaller than this break the one-pole smoothing assumption (alpha approaches 1 from below) and produce numerical artefacts in single-precision float.
Ratio is .min(1) (rather than .positive()) because a ratio below 1 would expand rather than compress, which the plugin's curve does not model. A future expander plugin would live as a separate plugin with its own schema. The maxima (.max(20) for ratio, .max(24) for knee, etc.) are the values the plugin's parameter declarations advertise — keeping the schema and the declaration in sync means consumers can safely take the parameter's min / max as authoritative without re-deriving them from the schema.
The preset data deliberately omits the continuous parameters because they live in PresetSnapshot.parameters (the universal map). Duplicating them here would risk drift if a future refactor changes one source but not the other; keeping the data slim makes the preset round-trip obviously correct.