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,98 @@
# PluginChain + BusRegistry + SidechainPublisher
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`PluginChain` owns the ordered list of FX plugins attached to a single
track, the per-chain ambient state, the chain-level bypass flag, and the
effective plugin-delay-compensation latency for that track. `DefaultBusRegistry`
is the project-level registry passed to every chain via
`ChainContext.buses`; `SidechainPublisher` is the handle returned by
`BusRegistry.publish`. Together they implement plugin slot 5 of the audio
family's three-layer model — the "FX slot list" that the
`ServerTimelineAdapter` hosts on each track. Every plugin runs through its
`BlockProcessor` `process()` path; no `createAudioNode` or worklet path is
used on the server.
## Features
- Ordered plugin list with `add` / `remove` / `setPluginBypass`.
- Chain-level bypass that short-circuits `process` while preserving the
hosted plugins.
- Ambient state propagation — `updateAmbient` merges a patch into
`ChainContext.ambient` and fires every plugin's `onAmbient` hook before
the next block runs.
- Plugin-delay-compensation via `getEffectiveLatencySamples()`; the chain
emits `latency-changed` whenever the sum of un-bypassed plugin latencies
changes so the host engine can recompute per-track PDC.
- Typed event surface (`plugin-added`, `plugin-removed`, `bypass-changed`,
`ambient-changed`, `latency-changed`) with `on(event, listener)`
returning an unsubscribe closure.
- `DefaultBusRegistry` — `publish` returns a `SidechainPublisher`,
`subscribe` notifies listeners on registration and on every publish /
close, and `get` / `list` expose the read surface plugins consume.
## Usage: Basic
```ts
import { DefaultBusRegistry, PluginChain } from "@suaveplan/audio-engine-server";
const buses = new DefaultBusRegistry();
const chain = new PluginChain({
id: "track-1",
channels: 2,
sampleRate: 48_000,
blockSize: 512,
buses,
});
chain.process([new Float32Array(512), new Float32Array(512)], 0);
```
## Usage: Advanced
```ts
const pub = buses.publish("kick", 2);
pub.write([kickLeft, kickRight]);
const offChain = chain.on("latency-changed", ({ effectiveLatencySamples }) => {
recomputePerTrackPdc(effectiveLatencySamples);
});
chain.updateAmbient({
track: { pan: -0.5 },
transport: { state: "playing", time: 1.25 },
});
offChain();
pub.close();
```
## API Reference
- `class PluginChain` — `add`, `remove`, `setPluginBypass`,
`updateAmbient`, `process`, `getEffectiveLatencySamples`, `on`,
`dispose`, plus read accessors `id`, `bypassed`, `context`, `buses`,
`list`.
- `class DefaultBusRegistry implements BusRegistry` — `publish`,
`subscribe`, `get`, `list`.
- `interface SidechainPublisher` — `bus`, `write`, `close`.
- `interface BusRegistry`, `interface ChainContext`,
`interface ChainPlugin`, `interface AmbientState`,
`interface AmbientStatePatch`, plus the `ChainEvent`/`ChainEventPayload`
pair.
## Implementation Notes
The chain re-uses the same `Float32Array[]` block as both `input` and
`output` for each plugin's `BlockProcessor.process` call, so each plugin
writes into the channel arrays the next plugin reads from. Latency
recomputation runs after every mutation (`add` / `remove` /
`setPluginBypass`) and only fires `latency-changed` when the effective sum
actually changes — repeated no-op mutations stay quiet. The default bus
registry stores per-bus payloads as a `Float32Array[]` slot mutated by
`SidechainPublisher.write`; `close` drops the slot and notifies every
subscribed listener with `undefined` so plugins reading the bus on the
next block see silence rather than stale samples.