5.4 KiB
plugin-internals
(no submodules)
Purpose
The plugin-internals module owns the boundary guards + parameter validation helpers consumed by AbstractAudioFxPlugin and any other plugin author who wants to keep their setup / process / parameter-mutation methods tight. Splitting these out of the abstract class file keeps audio-fx-plugin.ts focused on lifecycle orchestration (parameter schedules, latency listeners, preset application) while concentrating the dense if/throw validation in a small standalone surface that future plugin base classes (a more permissive LooseAudioFxPlugin for prototyping, a benchmark-only BenchmarkPlugin that skips half the checks) can reuse without inheriting from the full AbstractAudioFxPlugin.
Every helper takes pluginId as an argument and propagates it to the AudioFxError.context.pluginId payload so consumers grepping for which plugin produced a given error can pattern-match against it directly.
Features
assertNotAborted(signal, pluginId, messageKey)— throwsAUDIO_FX.ABORTEDwhensignalis aborted. No-op otherwise. The plugin'ssetupmethod uses this twice: once before any allocation and once after any in-flight async work.assertChannelMatch(input, output, expectedInput, expectedOutput, pluginId)— throwsAUDIO_FX.CHANNEL_MISMATCHwith full channel-count context when either buffer disagrees with the resolved input / output channel counts. Source plugins passexpectedInput === 0; fixed-output plugins (e.g. the sampler) pass their declared output count.requireParameter(parameterIndex, id, pluginId)— returns the parameter record or throwsAUDIO_FX.UNKNOWN_PARAMETER.requireAutomatable(parameterIndex, id, pluginId)— returns the parameter after asserting it is automatable; throwsAUDIO_FX.PARAMETER_NOT_AUTOMATABLEwhenautomatable: false.
Usage: Basic Example
import {
assertChannelMatch,
requireParameter,
} from "@suaveplan/audio-fx-plugin-base/base";
class MyPlugin extends AbstractAudioFxPlugin {
public mutateParameter(id: string, value: number): void {
const param = requireParameter(this.parameterIndex, id, this.metadata.id);
// ... use param.min / param.max ...
}
protected processBlock(input: Float32Array[], output: Float32Array[], ctx: BlockContext): void {
assertChannelMatch(
input,
output,
this.config!.channels,
this.config!.channels,
this.metadata.id
);
// ... DSP ...
}
}
Usage: Advanced Example
import {
assertChannelMatch,
assertNotAborted,
requireAutomatable,
requireParameter,
} from "@suaveplan/audio-fx-plugin-base/base";
class CustomPlugin implements AudioFxPlugin {
private readonly parameterIndex: ReadonlyMap<string, PluginParameter>;
private readonly schedules: Map<string, ParameterSchedule>;
public async setup(config: ProcessorConfig, _chain: ChainContext, opts?: { signal?: AbortSignal }): Promise<void> {
assertNotAborted(opts?.signal, this.metadata.id, "custom.setup-pre-aborted");
await this.loadResources(opts?.signal);
assertNotAborted(opts?.signal, this.metadata.id, "custom.setup-mid-aborted");
this.config = config;
}
public setParameterCurve(id: string, curve: ParameterCurve): void {
const param = requireAutomatable(this.parameterIndex, id, this.metadata.id);
const sched = this.schedules.get(id)!;
// ... schedule with clamped target against param.min / param.max ...
}
public process(input: Float32Array[], output: Float32Array[], ctx: BlockContext): void {
assertChannelMatch(
input,
output,
this.config!.channels,
this.config!.channels,
this.metadata.id
);
// ... DSP ...
}
}
API Reference
assertNotAborted(signal: AbortSignal | undefined, pluginId: string, messageKey: string): void.assertChannelMatch(input, output, expectedInput: number, expectedOutput: number, pluginId: string): void.requireParameter(parameterIndex, id: string, pluginId: string): PluginParameter.requireAutomatable(parameterIndex, id: string, pluginId: string): PluginParameter.
Implementation Notes
Every helper rejects with AudioFxError carrying the offending value in context so consumers can render UI without grepping the error message. The pluginId is always carried because production logs frequently aggregate errors across plugins and matching by metadata.id is the canonical resolution path.
assertChannelMatch accepts ReadonlyArray<unknown> rather than Float32Array[] so it can be called with either input / output buffers without forcing the caller to widen their types. The helper only reads .length from each array; the actual buffer contents are irrelevant to the channel-count check.
requireAutomatable deliberately calls requireParameter rather than duplicating the parameter lookup. The cost is one extra function call per scheduled curve / transport event, which is negligible compared to the schedule evaluation itself. This shared lookup keeps the two helpers' error messages and context payloads aligned without copy-paste.
assertNotAborted accepts the messageKey from the caller rather than baking in a fixed key so the plugin's setup can distinguish "pre-aborted" from "mid-aborted" in its logs while still using a single helper.