# `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)` — throws `AUDIO_FX.ABORTED` when `signal` is aborted. No-op otherwise. The plugin's `setup` method uses this twice: once before any allocation and once after any in-flight async work. - `assertChannelMatch(input, output, expectedInput, expectedOutput, pluginId)` — throws `AUDIO_FX.CHANNEL_MISMATCH` with full channel-count context when either buffer disagrees with the resolved input / output channel counts. Source plugins pass `expectedInput === 0`; fixed-output plugins (e.g. the sampler) pass their declared output count. - `requireParameter(parameterIndex, id, pluginId)` — returns the parameter record or throws `AUDIO_FX.UNKNOWN_PARAMETER`. - `requireAutomatable(parameterIndex, id, pluginId)` — returns the parameter after asserting it is automatable; throws `AUDIO_FX.PARAMETER_NOT_AUTOMATABLE` when `automatable: false`. ## Usage: Basic Example ```ts 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 ```ts import { assertChannelMatch, assertNotAborted, requireAutomatable, requireParameter, } from "@suaveplan/audio-fx-plugin-base/base"; class CustomPlugin implements AudioFxPlugin { private readonly parameterIndex: ReadonlyMap; private readonly schedules: Map; public async setup(config: ProcessorConfig, _chain: ChainContext, opts?: { signal?: AbortSignal }): Promise { 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` 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.