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,113 @@
# `bus-orchestrator`
## Purpose
`BusOrchestrator` is the per-adapter owner of the project-level sidechain
bus graph. It wraps a single `DefaultBusRegistry` instance (or any
caller-supplied `BusRegistry`), tracks every `(publisher track, bus)` and
`(subscriber track, bus)` declaration, and refuses any new declaration
that would close a directed cycle in the publish-then-subscribe DAG. It
also exposes a topological ordering of tracks so the adapter can process
publishers before subscribers in the same render block — the property
the spec calls out under "publishers process before subscribers".
Cycle detection happens **at declaration time**, not at first render.
That keeps the failure visible at the call site that introduced the
cycle and avoids a half-built render pipeline aborting mid-block. Every
faulted declaration raises a `BrowserAudioError(BUS_CYCLE_DETECTED)`.
## Features
- Wraps an injectable `BusRegistry` — defaults to `DefaultBusRegistry`
from `@suaveplan/audio-fx-plugin-base`.
- `declarePublish(trackId, bus, channels)` registers a publisher and
caches the `SidechainPublisher` so `dispose()` can close it.
- `declareSubscribe(trackId, bus)` records a subscriber edge.
- Edges build a directed graph `publisher → subscriber` keyed on shared
bus name; a cycle in this graph is rejected immediately.
- `processOrder(allTracks)` returns a topologically-sorted track id
list — tracks with no bus edges keep their insertion order (Kahn's
algorithm).
- `dispose()` closes every active publisher.
## Usage: Basic Example
```ts
import { BusOrchestrator } from "@suaveplan/audio-engine-browser/adapter/bus-orchestrator";
const orch = new BusOrchestrator();
orch.declarePublish("kick", "kick-bus", 2);
orch.declareSubscribe("bass", "kick-bus");
const order = orch.processOrder(["kick", "bass"]);
// → ["kick", "bass"]
```
## Usage: Advanced Example
```ts
import { BusOrchestrator } from "@suaveplan/audio-engine-browser/adapter/bus-orchestrator";
const orch = new BusOrchestrator();
orch.declarePublish("a", "x", 2);
orch.declareSubscribe("b", "x");
try {
orch.declarePublish("b", "y", 2);
orch.declareSubscribe("a", "y"); // closes a cycle
} catch (e) {
if (e instanceof BrowserAudioError) {
// e.code === "AUDIO_ENGINE_BROWSER.BUS_CYCLE_DETECTED"
}
}
orch.dispose(); // closes every publisher
```
## API Reference
### `new BusOrchestrator(registry?)`
Constructs the orchestrator. The default registry is a fresh
`DefaultBusRegistry`. Tests can inject any conforming `BusRegistry`.
### `declarePublish(trackId, bus, channels) → void`
Opens a publisher in the registry, caches the handle, and stores the
edge. Throws `BUS_CYCLE_DETECTED` when the new edge would close a
cycle.
### `declareSubscribe(trackId, bus) → void`
Records a subscriber edge and runs the same cycle check.
### `processOrder(allTracks) → ReadonlyArray<string>`
Returns the topological order of `allTracks` honouring every recorded
publisher→subscriber edge.
### `bus → BusRegistry`
Read accessor for the underlying registry — handed to every
`PluginChain` as `ChainContext.buses`.
### `dispose() → void`
Closes every active publisher and clears internal state.
## Implementation Notes
- The DFS cycle check uses three colours (white / grey / black) so the
declaration-time check is `O(V + E)`. The `processOrder` step uses
Kahn's algorithm and detects any residual cycle by checking that
every node was emitted.
- Edges are `(publisher_track, subscriber_track)` filtered on the
shared bus name. Self-loops (a track publishing and subscribing to
the same bus on itself) are intentionally excluded from the cycle
graph because a single chain processes left-to-right within one
track — self-feedback is the plugin's own responsibility to handle.
- The cached `processOrder` is invalidated on every declaration so the
adapter sees fresh orderings as tracks are added.
- `dispose()` is best-effort about publisher closes — a misbehaving
registry implementation does not prevent the rest from being
released.