Files
skills/docs/media/audio-fx/audio-fx-plugin-compressor/src/schemas/schemas.md
2026-05-29 08:20:56 +01:00

4.1 KiB
Raw Blame History

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's parameters declare (threshold ∈ [−60, 0], knee ∈ [0, 24], ratio ∈ [1, 20], attack ∈ [0.1, 100] ms, release ∈ [1, 1000] ms).
  • CompressorPresetDataSchema — the plugin-specific preset payload: just midSide and sidechainEnabled booleans. The five continuous parameters round-trip through the universal parameters map on the PresetSnapshot envelope, 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.ZodObject matching CompressorPluginOptions. Every field is optional and ranged.
  • CompressorPresetDataSchema — z.ZodObject matching CompressorPresetData. 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.