open-standards/wai/extensions/spatial-audio.md
Transaction Science 161d147767
Some checks failed
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 9m59s
spec citations resolve / Code cites no section its SPEC lacks (pull_request) Successful in 4s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m12s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m15s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m48s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m10s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m13s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m35s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m14s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m35s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
no-vanity-metrics / No new orphan source files (pull_request) Has been cancelled
standards workspaces build + test / jouleclaw-rs (pull_request) Has been cancelled
standards workspaces build + test / aac-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (pull_request) Has been cancelled
standards workspaces build + test / joulecontract-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / map-rs (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
tests in CI / every test file is run by a job or listed with its reason (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Has been cancelled
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Has been cancelled
wai-pack / wai-pack · does this change touch it (pull_request) Has been cancelled
wai-pack / wai-pack · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Has been cancelled
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Has been cancelled
WAI section references / cited sections exist (pull_request) Has been cancelled
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Has been cancelled
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
workflow hygiene / every workflow supersedes its own queue (pull_request) Has been cancelled
lockfiles / every Cargo.lock is current (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
wai: reader limits for the extension formats' durations
A score, haptic signal, audio scene, splat4d clip or film declares its length in its
contract, and rendering allocates in proportion: a score's mixdown allocated `duration`
samples outright, a haptic channel `duration` × 2 bytes, a scene's stereo mix 20 bytes a
sample, and the splat4d and film receipts a hash a frame. Nothing bounded the
declaration, so a few bytes could ask a renderer for more memory than any machine has.
SECURITY.md recorded it as a gap when the fuzzing PR found the reader-side reservations.

Each extension's contract section (score, haptics, spatial-audio, volumetric,
linear-film and its reel) now has a reader limit shared with SPEC §2, through a new
always-on module `wai::limits`:
- score and haptics: `duration × channels` at most 2^27 samples;
- audio scene: `duration` at most 2^26, so the stereo floor is at most 2^27 samples;
- splat4d `n_frames`, film `frames`, a reel's shots together: at most 2^20 frames.
A reader may refuse past them (`duration_too_long`) when it reads the contract, before
anything is rendered; a conforming encoder writes none.

The film schedule's tick, `start_tick + f × ticks_per_frame`, wrapped in release builds
and panicked in debug ones. A reader must now refuse a schedule whose last tick passes
2^64 - 1 (a film's, and each shot's of a reel); `Film::frame_tick` saturates and
`Reel::frame_tick` uses checked arithmetic.

tests/extension_duration_limits.rs rewrites a committed vector of each format to declare
exactly its limit (it reads) and one past it (refused by name), and checks the film
schedule ending at 2^64 - 1 (reads) and past it (refused). Falsified: with the readers
as they were, all three tests fail. The world, audio, haptic, splat4d, score and film
corpora verify unchanged; the 275 lib tests with the container features pass. CI runs
the test beside container_reservations on every leg.

SECURITY.md drops the gap and keeps the one this exposes: replaying a world to a far
tick costs time in proportion (memory is bounded, time is not).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 11:07:16 -04:00

176 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WAI Extension: Object-Based Spatial Audio
> Status: Draft. Phase 1 of the cargo roadmap. The audio analog of
> interactive-worlds: ship sound *objects* + a scene, render to the
> listener's own layout. Conformance is **mixdown-equivalence** (bit-exact
> PCM for a declared layout), the audio counterpart of replay-equivalence.
> Reference impl: the `audio_scene` feature of `wai-rs` (mixer + receipt);
> conformance corpus: `audio-conformance/`; live sink: `/audio`.
Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
## 1. Scope and model
A `wai.audio.scene` object describes a set of **audio objects** — each a
content-addressed source plus a position trajectory and gain — that a
sink renders to *its own* speaker layout. Today's audio ships a fixed
channel mix (stereo, 5.1); this ships the scene and lets the sink make
the mix it can actually play. It is lever 3 (instructions-at-the-sink)
for hearing.
In scope: the scene container, the deterministic mixdown render
contract, the **mixdown-equivalence** conformance criterion, the
fallback floor, and the receipt profile.
Out of scope: a transport; the source codecs (sources are ordinary
`wai.audio.*` WAI objects, decoded by the existing path); HRTF design
(an HRTF is named as a requirement-of-existence, never shipped).
Relationship to the core spec: this mirrors interactive-worlds exactly.
A scene is a content-addressed artifact; sources are content-addressed
WAI assets (§4.1 discipline, domain `wai:asset\x01`); the render is
deterministic under a declared numeric profile; receipts are a JWP
group profile. `media` takes the informational value `"audio-scene"`;
the capability `wai.audio.scene` is authoritative.
## 2. Container (`WSCN`)
```
"WSCN" | u16 n_sections | section table | sections
| n × (u8 kind, u32 off, u32 len), 8-byte-aligned bodies
```
| kind | section | required |
|---|---|---|
| `0x01` | scene contract (canonical JSON) | REQUIRED |
| `0x02` | object table | REQUIRED |
| `0x03` | asset table (§4.1, the source `wai.audio.*` objects) | REQUIRED |
| `0x04` | seal: duration + final mixdown hash + Ed25519 sig | OPTIONAL (a sealed scene pins its reference mix) |
Unknown section kinds MUST be ignored (forward compat within MINOR).
### 2.1 Scene contract (canonical JSON)
```json
{
"sample_rate": 48000,
"numeric": "wai.det.fixed64",
"pan_law": "wai.audio.pan.sqrt_constpower",
"distance": "wai.audio.dist.inverse_clamped",
"duration": 240000,
"floor_layout":"stereo"
}
```
`numeric` is the same Q32.32 fixed-point floor as worlds — the mixdown
is bit-exact on every machine. `floor_layout` (`stereo`) is the
mandatory render target; richer layouts (`5.1`, `binaural@<hrtf-id>`)
are OPTIONAL and, for binaural, perceptual (the HRTF is a
requirement-of-existence, not bit-exact across HRTFs).
**Reader limit.** A reader MUST NOT refuse a scene for its length while `duration` is
at most 67 108 864 (2²⁶): its stereo floor mixdown is then at most 2²⁷ samples, the audio
limit of SPEC §2. It MAY refuse one past it (`duration_too_long`), when it reads the
contract, before it renders anything. A richer layout is a render the sink chooses, at
its own cost. A conforming encoder writes no scene past the limit.
### 2.2 Object table
`n_objects u32`, then per object:
```
source_hash (32 B) // content hash of a wai.audio.* WAI envelope (asset table)
gain (i64 Fx)
n_keys (u16)
keys: n × ( pos_sample u64 | x i64 | y i64 | z i64 ) // listener-relative position, Fx metres
```
Position between keyframes is linear-interpolated in `Fx` (so it is
reproducible). One keyframe ⇒ a static source.
## 3. Render contract (mixdown)
For output channel `c` and sample `t`, in `Fx`:
```
out[c][t] = Σ_objects src_o[t] · gain_o · dist_atten(|p_o(t)|) · pan_gain(p_o(t), c)
```
- `src_o[t]`: object `o`'s source as mono PCM at `sample_rate`. v0
requires sources already at `sample_rate` (a deterministic resampler
is a registered sub-detail, not yet in the reference impl).
- `dist_atten(d) = 1 / (1 + d)` clamped to `[0,1]` (`inverse_clamped`),
where `d = sqrt(x² + y² + z²)`.
- `pan_gain` for `sqrt_constpower` stereo — chosen to be exactly
computable in the `Fx` floor (**sqrt only, no transcendental
tables**): `pan = clamp(x / (|x| + |z|), -1, 1)` (0 when `|x|+|z|=0`),
then `gL = sqrt((1 - pan)/2)`, `gR = sqrt((1 + pan)/2)` — constant
power (`gL² + gR² = 1`).
- Accumulate in `Fx`, then quantize to the canonical output (interleaved
**i16 LE**) by round-half-away-from-zero in integer space.
## 4. Conformance — mixdown-equivalence
> Given the same scene and the same **declared output layout**, a
> conforming sink MUST produce the identical canonical PCM, hence the
> identical `BLAKE3` over it — on every machine, with no tolerance
> parameter.
```
mixdown_hash = BLAKE3("wai:audio-mixdown\x01" || layout_id || pcm_i16le)
```
The mixdown is a function of the sources' decoded PCM, so the hash is portable
only as far as that PCM is. A source whose capability promises the same bytes on
every sink (`wai.audio.flac`, an integer codec) keeps it portable; a source decoded
through a codec whose standard conforms decoders to a tolerance (`wai.audio.opus`,
SPEC §7, `StandardTolerance`) may decode differently on two conforming sinks, and
then so does the mixdown. The reference registry reports this:
`codecs::is_cross_decoder_exact("wai.audio.scene")` is false, because a scene's
payload names its sources by hash and not by capability, and
`codecs::is_cross_decoder_exact_with_sources` is true once the capability of
every source envelope is known and each promises the same bytes on every sink. It
answers false when it cannot tell: when no source is given (a scene with no
objects included), or when a source is not a `wai.audio.*` capability (§2.2).
The `stereo` floor is mandatory and bit-exact. `binaural@<hrtf>` is
perceptual (conformance is similarity, not bytes — a sink advertising
it states the metric, like the `wai.semantic.*` path). The conformance
corpus ships scenes + the expected `stereo` mixdown hash; pass = hash
equality.
## 5. Fallback floor
`model_requirement.fallback` SHOULD name `wai.audio.opus` (or `flac`),
and the encoder SHOULD ship the `WAI2` multi-rendition form: the scene
plus a pre-rendered stereo recording. A sink without the scene runtime
plays the recording. Scene-capable sinks render to their real layout;
below-floor sinks hear a fixed mix — both opened one envelope.
## 6. Session receipts (JWP profile)
A render session receipts as a JWP group (jwp-receipts.md) — the worlds
`SessionReceipt` transposed to audio (`SceneReceipt` in the reference
impl):
- identity `scene_hash` = `BLAKE3("wai:audio-scene\x01" || contract_bytes
|| object_table_bytes)` (the `world_hash` analog).
- the group's objects = the rendered mix's **PCM blocks** (fixed span of
frames), each `BLAKE3("wai:audio-block\x01" || block_index || i16le)`,
**Merkle-bound** with JWP's exact leaf/node domains.
- one Ed25519 signature (verified as SPEC §7 *Signatures* requires) over a
profile payload carrying `scene_hash`,
the Merkle root, the block list, `work_mac` (= output samples ×
objects mixed — exact, signed) and `joules_micro` (carried; the native
`wai_world_meter` pattern supplies a measured figure, `0` if
unmetered), with the figure's acquisition class as an optional signed
label ([energy-measurement](energy-measurement.md) §2.8) — absent, the
receipt keeps its legacy bytes and the figure is unlabelled.
- `parent_receipt_hash` chains successive renders into an append-only
signed timeline, exactly as worlds' receipts do.
## Appendix — why this is the first cargo class
Mixdown-equivalence is replay-equivalence with the tick replaced by the
audio sample; the `Fx` floor, the content-addressed assets, the seal,
the receipt profile, and the fallback floor are all reused verbatim. A
spatial-audio scene is, structurally, a world whose "sim" is a mixer,
so the interactive-worlds rules carry over to a second sense.