132 lines
4.8 KiB
Markdown
132 lines
4.8 KiB
Markdown
# `WebAudioGraph`
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
|
|
## Purpose
|
|
|
|
`WebAudioGraph` is the thin id-keyed registry that sits between
|
|
`BrowserTimelineAdapter` and a `BaseAudioContext`. It exists so the adapter
|
|
can address `AudioNode`s by stable string id (e.g. `track:42:gain`) rather
|
|
than juggling raw node references, and so a populated graph can be
|
|
snapshotted to a JSON-safe object and restored into a fresh context.
|
|
|
|
The registry is intentionally narrow: it owns the node table and the edge
|
|
list, validates ids on every operation, and produces a snapshot whose
|
|
shape (node-types + edges) is independent of any live `AudioNode`
|
|
instance. Snapshots round-trip through `JSON.stringify` and survive
|
|
context replacement — the same snapshot can be restored into a realtime
|
|
`AudioContext`, an `OfflineAudioContext`, or a deterministic test shim.
|
|
|
|
## Features
|
|
|
|
- `addNode` / `removeNode` keyed by string id.
|
|
- `connect` / `disconnect` wrapping `AudioNode.connect` / `disconnect`
|
|
with full optional `outputIndex`/`inputIndex` support.
|
|
- `getNode` and `listIds` for read-side inspection.
|
|
- `snapshot()` returning a `Readonly<AudioGraphSnapshot>` containing
|
|
node-type tags and the edge list.
|
|
- `restore(snapshot)` Zod-validates the snapshot and rebuilds the graph
|
|
against the bound context, raising
|
|
`AUDIO_ENGINE_BROWSER.NODE_NOT_FOUND` for orphan edges.
|
|
- `classifyNode(node)` exported helper that maps a live `AudioNode` to
|
|
its `AudioNodeType` tag using its `constructor.name`.
|
|
- Defensive: every id mismatch raises a typed
|
|
`BrowserAudioError(NODE_NOT_FOUND)` carrying the offending id.
|
|
|
|
## Usage: Basic Example
|
|
|
|
```ts
|
|
import { WebAudioGraph } from "@suaveplan/audio-engine-browser/graph/web-audio-graph";
|
|
|
|
const graph = new WebAudioGraph(audioContext);
|
|
graph.addNode("master", audioContext.createGain());
|
|
graph.addNode("dest", audioContext.destination);
|
|
graph.connect("master", "dest");
|
|
```
|
|
|
|
## Usage: Advanced Example
|
|
|
|
```ts
|
|
import {
|
|
WebAudioGraph,
|
|
} from "@suaveplan/audio-engine-browser/graph/web-audio-graph";
|
|
|
|
const graph = new WebAudioGraph(audioContext);
|
|
graph.addNode("src", audioContext.createBufferSource());
|
|
graph.addNode("gain", audioContext.createGain());
|
|
graph.addNode("dest", audioContext.destination);
|
|
graph.connect("src", "gain");
|
|
graph.connect("gain", "dest");
|
|
|
|
// Persist for an undo step
|
|
const snap = graph.snapshot();
|
|
localStorage.setItem("graph", JSON.stringify(snap));
|
|
|
|
// Later — possibly in a new context — restore
|
|
const restored = new WebAudioGraph(newContext);
|
|
restored.restore(JSON.parse(localStorage.getItem("graph")!));
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### `new WebAudioGraph(context)`
|
|
|
|
Constructs a graph bound to a `BaseAudioContext`-shaped value. The
|
|
context is the factory used by `restore()`; calls to `addNode` accept
|
|
externally-constructed nodes.
|
|
|
|
### `addNode(id, node) → void`
|
|
|
|
Registers a node under `id`. Existing entries are overwritten so the
|
|
adapter can swap nodes during reconfiguration; the edge list is left
|
|
untouched.
|
|
|
|
### `removeNode(id) → void`
|
|
|
|
Disconnects the node, removes it, and prunes every edge that referenced
|
|
it. Throws `NODE_NOT_FOUND` for an unknown id.
|
|
|
|
### `connect(sourceId, destinationId, outputIndex?, inputIndex?) → void`
|
|
|
|
Wraps `AudioNode.connect`. Throws `NODE_NOT_FOUND` if either id is
|
|
absent. Records the edge so it appears in `snapshot()`.
|
|
|
|
### `disconnect(sourceId, destinationId?) → void`
|
|
|
|
When `destinationId` is omitted, disconnects every outgoing edge from
|
|
`source`. Otherwise disconnects just that edge. Throws `NODE_NOT_FOUND`
|
|
for missing source.
|
|
|
|
### `snapshot() → AudioGraphSnapshot`
|
|
|
|
Returns `{ nodes: Record<id, AudioNodeType>, connections: Edge[] }`.
|
|
|
|
### `restore(snapshot) → void`
|
|
|
|
Zod-validates the snapshot, clears the current registry, rebuilds nodes
|
|
via the bound context's factories, and replays every edge. Throws
|
|
`NODE_NOT_FOUND` for invalid or orphan-edge snapshots.
|
|
|
|
### `classifyNode(node) → AudioNodeType`
|
|
|
|
Static helper inferring the type tag from `constructor.name`.
|
|
|
|
## Implementation Notes
|
|
|
|
- `snapshot()` records **node types**, not node instances; this is what
|
|
makes it JSON-safe.
|
|
- `restore()` calls into the context's `create*` factories
|
|
(`createGain`, `createStereoPanner`, …) so the restored graph uses
|
|
nodes native to the new context. Worklet and `ScriptProcessor` nodes
|
|
cannot be reconstructed by tag alone and are rejected at restore.
|
|
- Per-disconnect graph mutations are best-effort: the underlying
|
|
`AudioNode.disconnect` may throw when the destination is not actually
|
|
connected, which is treated as a no-op so a careless caller cannot
|
|
desynchronise the edge list from the live graph.
|
|
- The Zod validator rejects unknown node types and any edge referencing
|
|
an absent node before any factory call is issued — the graph reaches
|
|
an inconsistent state only when a downstream context factory fails,
|
|
in which case the partial state is left for the caller to observe.
|