first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.