Files
suaveplan/packages/media/audio-fx/audio-fx-plugin-base/src/base/plugin-internals/plugin-internals.md
2026-05-27 23:47:53 +01:00

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) — 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

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.