PrivatelistenerPrivatebusPrivateduckPrivate ReadonlynodesPrivate ReadonlygatePrivategestureTrue while a one-shot gesture listener is waiting to retry the resume.
PrivatedisarmPrivateunsubscribePrivateunsubscribePrivatemasterPrivatemutedPrivatescenePrivatepanningPrivatebusPrivateduckPrivateactivePrivatedisposedPrivate ReadonlyfoaAmbisonic decoders to rotate against the camera (see ensureGraph).
Private ReadonlylistenerPrivate ReadonlydepsPrivate ReadonlycapturePrivateensureBuild the listener, buses and ducker on first use.
PrivateattachPrivateeffectiveFind every sound node under root (their placeholders carry
userData.sound), build the graph if needed, evaluate the slab once,
and start decoding clips. Idempotent per scene: call detachScene first
when a new dataset loads.
Stop and drop every node; keep the context and buses for the next scene.
PrivateresolveWhere an attach_to source sits: the target's live bounding-box centre
when its geometry is loaded, else the centre of the position_bounds its
scene-graph node carries (a story-bound cluster has no geometry until the
slice reaches its story, but its authored bounds are on disk from the
start). The fallback reads the displayed dimensions and assumes an identity
target transform; the live box takes over once the node loads.
PrivaterefreshRe-place every attach_to source (its target may have loaded since).
PrivaterotatePoint every ambisonic field at the camera the listener sits on.
PrivateevaluateThe waypoint driver left (depart) or reached (arrive) the waypoint whose
when clause this is. Every on_depart / on_arrive node whose rows belong
to that waypoint fires (SOUND_SPEC.md §4.3); a node without rows belongs
to every waypoint. Called by the app from the driver's event port.
PrivatebelongingThe rows of node that belong to the waypoint: null for a row-less node
(it belongs to every waypoint), a mask otherwise, undefined when no row
belongs (or the dims are not ready) — nothing to trigger.
PrivatedecodePrivatecheckPrivatearmRetry the resume on the next pointer or key event anywhere on the page.
The overlay arms its own dismissal, but it is not the only way a listener
interacts first — they may click the canvas, press a navigation key, or
tap the rail. Without this the context stays suspended for the whole
session and the only route to sound is a mute toggle, because that is the
one other call site that re-enters checkGate.
PrivateonPrivateopenPrivateonPrivateonPrivatesetApply a scene's authored viewer_config.audio. The listener's own
persisted preferences (mute, master gain) win over the scene's defaults.
PrivateapplyMute everything (with each node's fade-out) or unmute. Unmuting also
re-enables a scene that authored enabled: false for this session, since
the listener asked for sound explicitly. A muted scene never shows the gate.
A MediaStream carrying the mix the listener hears (post master gain, so a
muted viewer records silence), for the Recording panel to add to its
canvas capture (SOUND_SPEC.md §6, Phase 3). Null before the graph exists
— a scene without sound nodes records a silent video as before. Release
with releaseCaptureStream when the recording ends.
Detach a capture stream from the master gain and stop its tracks.
Mute one node (path names it) or every node under a group (path is an
ancestor). The node's own eye and an ancestor's eye are tracked separately,
as a hidden group hides its children whatever their own flag says.
Live per-node linear gain ([0, 2]); ramps a playing node.
The live per-node gain, or undefined for an unknown path.
PrivatefindStart name (basename or full path) regardless of the slab.
Stop name with its fade-out.
Sound is wanted but the browser has not let the context start.
Distinct from isMuted: nothing is audible in either case, but this one is not the listener's choice and is cleared by a gesture rather than by unmuting. The rail renders it as its own state so the control does not read "on" over silence.
Resume the context from a user gesture and open the gate. Safe to call when already running. Returns whether sound is live afterwards.
The sound layer's engine: one per
LuxarApp, nodes attached per scene. See the module docs for the graph it builds and the ports it takes.