4.8 KiB
WebAudioGraph
(no submodules)
Purpose
WebAudioGraph is the thin id-keyed registry that sits between
BrowserTimelineAdapter and a BaseAudioContext. It exists so the adapter
can address AudioNodes 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/removeNodekeyed by string id.connect/disconnectwrappingAudioNode.connect/disconnectwith full optionaloutputIndex/inputIndexsupport.getNodeandlistIdsfor read-side inspection.snapshot()returning aReadonly<AudioGraphSnapshot>containing node-type tags and the edge list.restore(snapshot)Zod-validates the snapshot and rebuilds the graph against the bound context, raisingAUDIO_ENGINE_BROWSER.NODE_NOT_FOUNDfor orphan edges.classifyNode(node)exported helper that maps a liveAudioNodeto itsAudioNodeTypetag using itsconstructor.name.- Defensive: every id mismatch raises a typed
BrowserAudioError(NODE_NOT_FOUND)carrying the offending id.
Usage: Basic Example
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
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'screate*factories (createGain,createStereoPanner, …) so the restored graph uses nodes native to the new context. Worklet andScriptProcessornodes cannot be reconstructed by tag alone and are rejected at restore.- Per-disconnect graph mutations are best-effort: the underlying
AudioNode.disconnectmay 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.