first commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# `capabilities`
|
||||
|
||||
## Purpose
|
||||
|
||||
`PluginCapabilities` is the **explicit feature-flag declaration** every `AudioFxPlugin` carries. Engines route audio through one of three render paths (browser-native `AudioNode`, `AudioWorklet` fallback, universal `BlockProcessor`); each requires different optional members on the plugin. Rather than feature-detecting via `typeof plugin.createAudioNode === 'function'` and other opaque probes scattered across the engine adapters, plugins declare what they support up front, and the chain + engine read the typed flags directly. The two `defaultCapabilities(io)` and `capabilitiesAgreeWithMembers(cap, members)` helpers enforce honesty: plugin construction can call them to ensure the flag set matches reality.
|
||||
|
||||
## Features
|
||||
|
||||
- A `PluginCapabilities` interface covering eight static flags: `hasAudioNode`, `hasWorklet`, `hasSidechain`, `sampleAccurate`, `stateful`, `asyncSetup`, `preservesChannelCount`, `latencySamples` (initial).
|
||||
- `defaultCapabilities(io)` returns a stateless / sample-accurate / no-async / universal-only baseline with `preservesChannelCount` derived from the declared `PluginIo`.
|
||||
- `capabilitiesAgreeWithMembers(cap, members)` confirms flag/method symmetry — the basis for an automated lint-style assertion in tests.
|
||||
- `withCapabilities(base, overrides)` produces a merged copy (no mutation) so plugin authors can express only their deltas.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { defaultCapabilities, withCapabilities } from "@suaveplan/audio-fx-plugin-base/base";
|
||||
|
||||
const io = { inputs: [{ count: 2 }], outputs: [{ count: "preserve" as const }] };
|
||||
const capabilities = withCapabilities(defaultCapabilities(io), {
|
||||
stateful: true, // delay lines persist across blocks
|
||||
latencySamples: 64, // lookahead limiter
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
capabilitiesAgreeWithMembers,
|
||||
defaultCapabilities,
|
||||
withCapabilities,
|
||||
} from "@suaveplan/audio-fx-plugin-base/base";
|
||||
|
||||
class PitchShifterPlugin {
|
||||
readonly io = { inputs: [{ count: 2 }], outputs: [{ count: "preserve" as const }] };
|
||||
readonly capabilities = withCapabilities(defaultCapabilities(this.io), {
|
||||
hasWorklet: true,
|
||||
stateful: true,
|
||||
asyncSetup: true,
|
||||
latencySamples: 2048, // one FFT frame
|
||||
});
|
||||
|
||||
getWorkletProcessor() { return PITCH_SHIFTER_WORKLET_SOURCE; }
|
||||
|
||||
// Constructor asserts in dev to catch drift between flags and members:
|
||||
constructor() {
|
||||
if (!capabilitiesAgreeWithMembers(this.capabilities, this)) {
|
||||
throw new Error("capability flags do not agree with method presence");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `PluginCapabilities` — readonly record of eight flags. All members required at plugin construction.
|
||||
- `OptionalPluginMembers` — the three optional plugin members the flags cross-reference (`createAudioNode`, `getWorkletProcessor`, `setSidechain`).
|
||||
- `defaultCapabilities(io: PluginIo): PluginCapabilities` — returns the universal-only baseline; `preservesChannelCount` derived from `ioPreservesChannelCount(io)`.
|
||||
- `capabilitiesAgreeWithMembers(cap, members): boolean` — returns true iff every `has*` flag matches `typeof members.<m> === 'function'`.
|
||||
- `withCapabilities(base, overrides): PluginCapabilities` — returns `{ ...base, ...overrides }`; never mutates `base`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`latencySamples` on `PluginCapabilities` is the **initial** value — plugins whose latency changes at runtime (lookahead limiters with variable lookahead, dynamic FFT-size pitch shifters) emit `onLatencyChanged(samples)` and the chain re-reads `getLatencySamples()`. The static flag is the value an engine should assume at first `setup()` call; dynamic recomputation happens through the event surface.
|
||||
|
||||
`capabilitiesAgreeWithMembers` is intentionally cheap (three `typeof` checks). The `AbstractAudioFxPlugin` reference class calls it in its constructor under a debug-only branch (no perf cost in production), and test suites call it once per concrete plugin to gate releases. A mismatch is a bug in the plugin author's declaration, not a runtime condition; failing fast in test is the right call.
|
||||
|
||||
`defaultCapabilities` ties `preservesChannelCount` to `ioPreservesChannelCount(io)` so plugin authors who change their IO (e.g. add a downmix output) automatically get the right capability without restating it; the test suite catches the symmetry by running both helpers against every exported plugin.
|
||||
Reference in New Issue
Block a user