4.4 KiB
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
PluginCapabilitiesinterface 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 withpreservesChannelCountderived from the declaredPluginIo.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
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
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;preservesChannelCountderived fromioPreservesChannelCount(io).capabilitiesAgreeWithMembers(cap, members): boolean— returns true iff everyhas*flag matchestypeof members.<m> === 'function'.withCapabilities(base, overrides): PluginCapabilities— returns{ ...base, ...overrides }; never mutatesbase.
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.