Initial Commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# `plugin-internals`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<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.
|
||||
Reference in New Issue
Block a user