5.5 KiB
preset
Purpose
The preset module owns the plugin preset envelope — PresetSnapshot — and the three boundary guards every plugin runs before applying a preset: schema validation (parsePresetSnapshot), version-compatibility (assertPresetVersionSupported), and plugin-type matching (assertPresetMatchesPluginType). Presets are the universal handshake between a plugin's serialised state and a fresh instance. Both per-plugin getPreset()/applyPreset() and chain-level snapshot()/restore() use this envelope, so a snapshot persisted from a track preset and a snapshot exported from a single plugin look the same on disk.
Features
- One Zod-validated envelope shape (
schemaVersion,pluginType,parameters, optionaldata) that every plugin shares. parsePresetSnapshot(input)validates at the boundary and throwsAUDIO_FX.INVALID_PRESETwith the Zod issue list incontexton failure.assertPresetVersionSupported(preset, supportedSchemaVersion)throwsAUDIO_FX.PRESET_VERSION_UNSUPPORTEDwhen the preset is from a newer plugin build.assertPresetMatchesPluginType(preset, pluginType)throwsAUDIO_FX.INVALID_PRESET(with apresetPluginType / expectedcontext) when a chain snapshot tries to load a preset against the wrong plugin.- The schema (
PresetSnapshotSchema) is exported for callers that want to callsafeParsethemselves.
Usage: Basic Example
import { parsePresetSnapshot, assertPresetVersionSupported } from "@suaveplan/audio-fx-plugin-base/base";
function applyPresetGuarded(plugin: AudioFxPlugin, raw: unknown, mySchemaVersion = 1) {
const preset = parsePresetSnapshot(raw);
assertPresetVersionSupported(preset, mySchemaVersion);
plugin.applyPreset(preset);
}
Usage: Advanced Example
import {
assertPresetMatchesPluginType,
assertPresetVersionSupported,
parsePresetSnapshot,
} from "@suaveplan/audio-fx-plugin-base/base";
class EQPlugin implements AudioFxPlugin {
readonly metadata = { id: "eq", name: "8-Band EQ", version: "1.0.0", category: "eq" } as const;
readonly schema = EqDataSchema;
private readonly supportedSchemaVersion = 2;
applyPreset(snapshot: PresetSnapshot): void {
// The envelope was already validated by parsePresetSnapshot in the
// caller; we re-assert the plugin-type and version locally so direct
// calls into the plugin are equally safe.
assertPresetMatchesPluginType(snapshot, this.metadata.id);
assertPresetVersionSupported(snapshot, this.supportedSchemaVersion);
// Validate the plugin-specific data blob against our own schema.
const data = EqDataSchema.parse(snapshot.data);
for (const [paramId, value] of Object.entries(snapshot.parameters)) {
this.setParameter(paramId, value);
}
this.bands = data.bands;
}
getPreset(): PresetSnapshot {
return {
schemaVersion: this.supportedSchemaVersion,
pluginType: this.metadata.id,
parameters: this.snapshotParameters(),
data: { bands: this.bands },
};
}
}
API Reference
PresetSnapshot— readonly{ schemaVersion: number; pluginType: string; parameters: Record<string, number>; data?: unknown }.PresetSnapshotSchema— Zod schema for the envelope; exported for callers that want tosafeParsedirectly without throwing.parsePresetSnapshot(input: unknown): PresetSnapshot— throwsAUDIO_FX.INVALID_PRESETon validation failure with the issue list incontext.issues.assertPresetVersionSupported(preset: PresetSnapshot, supportedSchemaVersion: number): void— throwsAUDIO_FX.PRESET_VERSION_UNSUPPORTEDwhenpreset.schemaVersion > supportedSchemaVersion.assertPresetMatchesPluginType(preset: PresetSnapshot, pluginType: string): void— throwsAUDIO_FX.INVALID_PRESETwhenpreset.pluginType !== pluginType.
Implementation Notes
The envelope keeps parameters separate from data because parameters are universally typed (always number) and can be applied generically by the AbstractAudioFxPlugin base class without any plugin-specific knowledge. The data blob is opaque from the envelope's perspective; each plugin validates it against its own Zod schema (exposed on the plugin's schema member). This split lets the chain's snapshot/restore round-trip every plugin's parameters without depending on per-plugin schemas, while still preserving plugin-specific state.
schemaVersion is a per-plugin integer: a single project may contain presets at different schemaVersions across different plugins. The version is forwards-only: a plugin at v2 can read a v1 preset (with a migration path inside applyPreset) but a plugin at v1 refuses a v2 preset. This is deliberate — back-versioning is the responsibility of the plugin author, who can write a migrateV1ToV2(data) helper inside the plugin and apply it on read.
The two assert helpers throw rather than return booleans so plugin authors don't need a separate try/throw pattern. The context payload on each error includes both the offending value and the expected value, so the UI's preset-load toast can render a useful message ("Preset is from v3 of EQ; this build supports up to v2").
assertPresetMatchesPluginType exists for chain restore: the chain's snapshot lists plugin types it expects to instantiate, but a registry swap (e.g. a fork of the EQ plugin under a different id) could load the wrong factory; the assert catches that at the apply boundary before any state mutation.