Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
# `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.