Files
skills/docs/media/audio/audio-engine-browser/src/graph/web-audio-graph.md
2026-05-29 08:20:56 +01:00

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 / 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

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