Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 31 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,85 +1,48 @@
Audio Graph JS — KHR_audio_graph Runtime (Node/Web)
Audio Graph JS - Layered glTF Audio Runtime (Node/Web)

**What It Is**
- **Runtime:** A minimal Web Audiobased runtime for graphs shaped like the glTF KHR_audio_graph proposal.
- **Parity tools:** Side‑by‑side runner + comparator to verify parity between native runtime GraphSpecs and KHR containers.
- **Emitters:** Placementagnostic emitters with instance expansion from glTF node bindings.
- **Runtime:** A minimal Web Audio-based runtime for layered glTF audio proposals built on `KHR_audio_emitter`, with compatibility support for legacy `KHR_audio_graph` containers.
- **Parity tools:** Side-by-side runner and comparator to verify parity between native runtime GraphSpecs and glTF containers.
- **Emitters:** Placement-agnostic emitters with instance expansion from glTF node or scene bindings.

**Requirements**
- **Node.js:** 18+ (includes `globalThis.fetch`).
- Optional: macOS/Linux/Windows shell to run example scripts.
- **Node.js:** 18+.

**Install & Build**
- Install deps: `npm install`
- TypeScript build: `npm run build`

**Quick Start: Run Examples**
- Render one example (writes WAV + trace): `npm run example:run-graph -- examples/graphs/osc.json`
- Run all core examples: `npm run examples`
- Compare KHR vs runtime graphs (traces only): `npm run example:compare-khr`
- Strict compare (traces + WAV checksums): `npm run example:compare-khr:strict`
- Note: “seven-nation-army” WAV strict is intentionally skipped; traces still match.
**Quick Start**
- Render one example: `npm run example:run-graph -- examples/graphs/osc.json`
- Run tests: `npm test`

**Emitter Instance Expansion**
- The base graph builds `emitter` nodes as shared input buses (no auto‑connect to destination).
- Instance expansion creates per‑instance panner and post‑gain, connecting bus → panner → gain → destination.
- Two ways to provide instances:
- **glTF node bindings:** Attach `extensions.KHR_audio_graph = { emitter: <emitterId> }` or `{ emitters: [<id>, ...] }` to glTF nodes. The example runner extracts node T/R/S “as is” and expands instances at runtime.
- **Runtime test harness:** Add a top‑level `__emitterInstances` to a runtime GraphSpec, e.g. `{ emitterNodeId: "emit", translation: [1,0,0] }`.

**Run The New Spatial Emitter Parity Example**
- Runtime graph with instance expansion hint: `node examples/run-graph.mjs examples/graphs/spatial-emitter-instanced.json`
- glTF + KHR graph with node binding: `node examples/run-graph.mjs examples/graphs-khr/spatial-emitter-instanced-khr.json`
- Comparator picks both up automatically.

**API Usage (Node)**
- Build a graph and apply instances programmatically:

```
import wae from 'web-audio-engine';
import { buildGraphAsync, applyEmitterInstances } from './dist/index.js';

const { OfflineAudioContext } = wae;
const spec = {
nodes: [
{ id: 'src', kind: 'oscillator', params: { type: 'sine', frequency: 330, startTime: 0 } },
{ id: 'emit', kind: 'emitter', params: { emitterType: 'spatial', gain: 1 } },
],
connections: [ { from: { node: 'src' }, to: { node: 'emit' } } ],
};

const sr = 48000;
const ctx = new OfflineAudioContext(2, sr * 2, sr);
const built = await buildGraphAsync(ctx, spec);

applyEmitterInstances(built, spec, [
{ emitterNodeId: 'emit', translation: [1, 0, 0] },
]);

const audioBuffer = await ctx.startRendering();
```

**glTF/KHR Mapping In Runner**
- The example runner (`examples/run-graph.mjs`) accepts either:
- A runtime GraphSpec: `{ nodes, connections, outputs? }`
- A glTF JSON with `extensions.KHR_audio_graph` container and optional node bindings (scalar `emitter` or array `emitters`).
- For mappings, it also:
- Synthesizes deterministic noise or IR where needed to ensure parity.
- Applies simple musical automation for select presets.

**Testing & Linting**
- Unit tests: `npm test`
- Graph lint: enforced by `buildGraph(Async)` usage in the runner; you can invoke `lintGraph(spec)` manually.
- The base graph builds `emitter` nodes as shared input buses with no direct auto-connect to the destination.
- Instance expansion creates the final panner and post-gain stages for each binding.
- Multiple upstream graph outputs may target the same emitter bus. The runtime mixes those inputs by default before spatialization or final gain. This is informative runtime guidance and matches the intended layered spec behavior.
- Layered glTF bindings:
- Node-level: `extensions.KHR_audio_emitter = { emitter: <id> }` or `{ emitters: [<id>, ...] }`
- Scene-level for global emitters: `extensions.KHR_audio_emitter = { emitters: [<id>, ...] }`
- Runtime test harness:
- Add `__emitterInstances` to a runtime GraphSpec, for example `{ emitterNodeId: "emit", translation: [1, 0, 0] }`

**Runner Input Modes**
- Runtime GraphSpec: `{ nodes, connections, outputs? }`
- Layered glTF: `KHR_audio_emitter`, optionally `KHR_audio_graph` and `KHR_audio_environment`
- Legacy glTF: `extensions.KHR_audio_graph`

**Useful Scripts**
- `npm run spec:validate` — validate KHR graphs using the included schema and examples.
- `npm run spec:validate:gltf` — validate glTF examples.
- `npm run spec:validate`
- `npm run spec:validate:gltf`
- `node tools/spec-validate/validate-layered.mjs <files...>`

**Notes**
- Listener design and KHR_animation_pointer integration are TODO (documented under `docs/` and spec repo notes).
- Stereo panner uses an approximation in this runtime; parity tests account for it.
- Listener application is available for layered inputs.
- `KHR_animation_pointer` integration is still TODO.
- Stereo panner uses an approximation in this runtime where needed.

**Where Things Live**
- Runtime code: `src/runtime`, nodes under `src/nodes`.
- Examples and tools: `examples/*`, `tools/*`.
- Doc updates: `docs/USAGE.md`, `docs/PLAN_AND_STATUS.md`, `docs/STATE_OF_PROJECT.md`.
- Runtime code: `src/runtime`
- Serialization and parsing: `src/serialization`
- Examples: `examples`
- Notes: `docs`
86 changes: 39 additions & 47 deletions docs/KHR_AUDIO_GRAPH_NODE_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,39 @@
# KHR_audio_graph Node Mapping (Tracking)

- Source → AudioBufferSourceNode (implemented)
- id, data(audio|osc), priority, gain, state, autoPlay, loop, loopStart(ms), loopEnd(ms), playbackSpeed, duration(ms), offset(ms), when(s), channelInterpretation
- Audio Data (partial)
- bufferView, uri, mimeType, encodingProperties(bitsPerSample, duration, samples, sampleRate, channels)
- Oscillator Data → OscillatorNode (implemented)
- type, frequency, pulseWidth (static via PeriodicWave; no modulation)
- Emitter (implemented)
- id, emitterType(global|spatial), gain, spatialProperties(spatializationModel, attenuation)
- Maps to GainNode + (PannerNode/StereoPanner)
- Listener → AudioListener (skipped)

Processors
- Gain → GainNode (implemented)
- gain, interpolation, duration(ms)
- Delay → DelayNode (implemented)
- delayTime(ms)
- Pitch Shifter (deferred)
- pitch(semitones)
- Channel Splitter → ChannelSplitterNode (implemented)
- Channel Merger → ChannelMergerNode (implemented)
- Channel Mixer → GainNode with channelCount (implemented)
- outputChannels
- Audio Mixer (composite) (not_implemented)
- Audio Mixer → GainNode (summing) (implemented)
- Filters (implemented via BiquadFilterNode)
- lowpass: frequency, qualityFactor, bypass
- highpass: frequency, qualityFactor, bypass
- bandpass: frequency, qualityFactor, bypass
- lowshelf: frequency, gain, bypass
- highshelf: frequency, gain, bypass
- peaking: frequency, qualityFactor, gain, bypass
- notch: frequency, qualityFactor, bypass
- allpass: frequency, qualityFactor, bypass
- Reverb (IR-based) (implemented)
- ConvolverNode + wet/dry mix; algorithmic parameters out of scope for now

Notes
- Unit conversions: ms ↔ s for Delay/Source timing.
- Bypass: requires routing toggle; not native on Web Audio nodes.
- Bypass: supported two ways — build-time rewiring (spec JSON) and runtime toggling via wrapper (setBypass). For filters/delay/convolver/waveshaper, graphs are built with a dry/wet wrapper for runtime bypass.
- Pitch shifter & Reverb: require custom DSP or Worklets; not standard nodes.
- Spatialization: Emitter maps to PannerNode; listener bound to camera.
- Gain smoothing: honors `interpolation` (linear/custom) + `duration` (ms) when provided.

See docs/KHR_AUDIO_GRAPH_NODE_MAP.json for machine-readable detail.
# Layered Audio Runtime Mapping

- `KHR_audio_emitter.audio[]`
- asset-level audio payloads
- optional `KHR_audio_graph.encoding` metadata

- `KHR_audio_emitter.sources[]`
- maps to runtime audio-buffer-source setup
- extended playback fields from `extensions.KHR_audio_graph`

- `KHR_audio_emitter.emitters[]`
- maps to shared runtime emitter buses
- `type: global` -> gain-only output instance
- `type: positional` -> panner plus post-gain output instance

- `KHR_audio_graph.graphs[]`
- parsed individually, then merged for execution
- shared emitter targets remain shared across merged graphs
- when multiple outputs target the same emitter, signals are summed by default

- `KHR_audio_environment.listeners[]`
- maps to `AudioListener`
- transform comes from the bound glTF node

- `KHR_audio_environment.environments[]`
- scene-level environment currently supported
- reverb is implemented through a real wet/dry environment bus

## Important Conversions

- glTF time in the layered runtime path is interpreted in seconds
- glTF cone angles are interpreted in radians
- Web Audio panner cone angles use degrees, so the runtime converts radians to degrees

## Current Non-Goals

- node-localized environment zones
- `KHR_animation_pointer` integration
- full spec-sync cleanup of older historical notes
50 changes: 25 additions & 25 deletions docs/PLAN_AND_STATUS.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
# Plan & Status — Listener/Emitter Spec + Parser
# Plan And Status

Status: in progress
Status: current layered runtime milestone complete

Phases
## Completed

- [x] Docs: Complex Graph (USAGE section)
- [ ] Spec: Listener language (README; KHR_animation_pointer TODO)
- [ ] Spec: Emitter → Node binding (README + node-level extension schema)
- [ ] Spec: Commit + spec validation (no example breakage)
- [ ] Runtime: glTF parser for emitter bindings (separate class/file)
- [ ] Test: Focused spatial emitter parity (one case; identical transforms)
- [ ] Docs: Spatial Emitter Binding (USAGE) + STATE_OF_PROJECT update
- layered parser for `KHR_audio_emitter`, `KHR_audio_graph`, and `KHR_audio_environment`
- node-level and scene-level emitter binding extraction
- merged execution for multiple layered graphs
- additive default mixing on shared emitter buses
- listener application in the runner
- scene-level environment routing in the runner
- radians-to-degrees conversion for positional emitter cone angles
- layered validator wildcard support
- real glTF fixture tests for layered examples

Listener — Scope (this pass)
- Not a graph node; scene/node-level concept; single Listener per scene.
- KHR_animation_pointer applicability is deferred (TODO). We will specify accessible properties and pointer paths in a later pass.
## Current Scope

Emitter Binding — Node-level (this pass)
- Do not add node ids inside graph emitter objects.
- Bind emitters at glTF node level via a node extension:
- Scalar form: `extensions.KHR_audio_graph = { "emitter": <emitterId> }`
- Array form: `extensions.KHR_audio_graph = { "emitters": [<emitterId>, ...] }`
- Each glTF node creates instances for each referenced emitter id. Multiple nodes may reference the same emitter id.
- Runtime optimization: share upstream audio graph routing; instantiate only the final per-instance spatial stage (PannerNode and any post‑panner gain).
- layered format is the preferred implementation path
- legacy `KHR_audio_graph` runner support remains for compatibility only
- scene-level environment is supported
- node-localized environment zones are deferred

Parser (this pass)
- Separate glTF parser external to audio graph parsing.
- For examples, read node transforms (translation/rotation/scale) “as is” — no world/hierarchy computation.
## Open Items

Test (next step)
- Add one focused spatial emitter test to compare KHR-bound instances vs a native baseline with identical transforms.
- `KHR_animation_pointer` integration
- node-localized environment zones and overlap rules
- additional layered fixture coverage for more complex mixed assets

## Reviewer Notes

- informative runtime behavior now explicitly supports default signal summing when multiple graph outputs target the same emitter
- this should be mirrored in spec prose as informative guidance, not as hidden implementation behavior
Loading
Loading