@sadhaka/loom-engine 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/audio/audio-asset-cache.d.ts +12 -0
  2. package/dist/audio/audio-asset-cache.d.ts.map +1 -0
  3. package/dist/audio/audio-asset-cache.js +53 -0
  4. package/dist/audio/audio-asset-cache.js.map +1 -0
  5. package/dist/audio/audio-asset-loader.d.ts +16 -0
  6. package/dist/audio/audio-asset-loader.d.ts.map +1 -0
  7. package/dist/audio/audio-asset-loader.js +100 -0
  8. package/dist/audio/audio-asset-loader.js.map +1 -0
  9. package/dist/audio/audio-listener-resource.d.ts +19 -0
  10. package/dist/audio/audio-listener-resource.d.ts.map +1 -0
  11. package/dist/audio/audio-listener-resource.js +57 -0
  12. package/dist/audio/audio-listener-resource.js.map +1 -0
  13. package/dist/audio/cue-catalog.d.ts +39 -0
  14. package/dist/audio/cue-catalog.d.ts.map +1 -0
  15. package/dist/audio/cue-catalog.js +186 -0
  16. package/dist/audio/cue-catalog.js.map +1 -0
  17. package/dist/audio/music-director.d.ts +18 -0
  18. package/dist/audio/music-director.d.ts.map +1 -0
  19. package/dist/audio/music-director.js +177 -0
  20. package/dist/audio/music-director.js.map +1 -0
  21. package/dist/audio/spatial-audio-bus.d.ts +66 -0
  22. package/dist/audio/spatial-audio-bus.d.ts.map +1 -0
  23. package/dist/audio/spatial-audio-bus.js +341 -0
  24. package/dist/audio/spatial-audio-bus.js.map +1 -0
  25. package/dist/audio/spatial-audio-system.d.ts +17 -0
  26. package/dist/audio/spatial-audio-system.d.ts.map +1 -0
  27. package/dist/audio/spatial-audio-system.js +98 -0
  28. package/dist/audio/spatial-audio-system.js.map +1 -0
  29. package/dist/audio/zone-audio-system.d.ts +78 -0
  30. package/dist/audio/zone-audio-system.d.ts.map +1 -0
  31. package/dist/audio/zone-audio-system.js +206 -0
  32. package/dist/audio/zone-audio-system.js.map +1 -0
  33. package/dist/index.d.ts +14 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +16 -1
  36. package/dist/index.js.map +1 -1
  37. package/package.json +2 -2
@@ -0,0 +1,177 @@
1
+ // MusicDirector - high-level music playback with fade + crossfade.
2
+ //
3
+ // Per LOOM-AUDIO-SPEC §4.3. Wraps AudioBufferSourceNode + GainNode
4
+ // chains and routes them through audioBus.input('music'). The 'music'
5
+ // bus is an AudioBus default (priority 'ambient'), so VE-budget gating
6
+ // already mutes it under load - the director does not duplicate that.
7
+ //
8
+ // State machine (single track at a time, except mid-crossfade):
9
+ // idle: nothing playing
10
+ // playing(track): one source -> one gain -> music bus
11
+ // crossfading(from -> to): two sources, two gains, both ramping
12
+ //
13
+ // playMusic(name, fadeIn): stops prior immediately (no fade), creates a
14
+ // new source+gain at gain=0, ramps to 1 over fadeInMs (default 500).
15
+ // Loops by default. No-op if asset not in cache.
16
+ //
17
+ // stopMusic(fadeOut): ramps current gain to 0 over fadeOutMs (default
18
+ // 800), stops source after fade. Resolves when fade completes. Safe
19
+ // when nothing playing.
20
+ //
21
+ // crossfadeMusic(name, fade): starts new track gain=0, ramps prior to 0
22
+ // + new to 1 simultaneously over fadeMs (default 1000), stops prior
23
+ // after fade. If nothing was playing, equivalent to playMusic with the
24
+ // same fade in.
25
+ const DEFAULT_FADE_IN_MS = 500;
26
+ const DEFAULT_FADE_OUT_MS = 800;
27
+ const DEFAULT_CROSSFADE_MS = 1000;
28
+ export class MusicDirector {
29
+ audioBus;
30
+ cache;
31
+ current = null;
32
+ constructor(audioBus, cache) {
33
+ this.audioBus = audioBus;
34
+ this.cache = cache;
35
+ }
36
+ static create(audioBus, cache) {
37
+ return new MusicDirector(audioBus, cache);
38
+ }
39
+ // Start a music track. If music is already playing, stops it
40
+ // immediately (no fade). Use crossfadeMusic for smooth transitions.
41
+ // No-op if asset not in cache.
42
+ playMusic(name, fadeInMs) {
43
+ var buffer = this.cache.get(name);
44
+ if (!buffer)
45
+ return;
46
+ // Stop prior immediately - no fade.
47
+ if (this.current) {
48
+ this.hardStop(this.current);
49
+ this.current = null;
50
+ }
51
+ var fade = fadeInMs !== undefined ? fadeInMs : DEFAULT_FADE_IN_MS;
52
+ this.current = this.startTrack(name, buffer, fade);
53
+ }
54
+ // Stop the current track with a fade-out. Resolves when fade
55
+ // completes. Safe to call when no music is playing.
56
+ stopMusic(fadeOutMs) {
57
+ var track = this.current;
58
+ if (!track)
59
+ return Promise.resolve();
60
+ var fade = fadeOutMs !== undefined ? fadeOutMs : DEFAULT_FADE_OUT_MS;
61
+ this.current = null;
62
+ return this.fadeOutAndStop(track, fade);
63
+ }
64
+ // Smoothly transition to a different track. If no track is playing,
65
+ // equivalent to playMusic with fadeMs as the fade-in. Same name as
66
+ // current is treated as a fresh play (the new buffer plays, prior
67
+ // fades; useful when the buffer was reloaded with a new variant).
68
+ crossfadeMusic(name, fadeMs) {
69
+ var buffer = this.cache.get(name);
70
+ if (!buffer)
71
+ return;
72
+ var fade = fadeMs !== undefined ? fadeMs : DEFAULT_CROSSFADE_MS;
73
+ var prior = this.current;
74
+ var next = this.startTrack(name, buffer, fade);
75
+ this.current = next;
76
+ if (prior) {
77
+ // Fade prior out; clean up after fade. We do not await - crossfade
78
+ // is fire-and-forget. If a NEW playMusic / crossfadeMusic call
79
+ // arrives mid-fade, hardStop on the prior (which is no longer
80
+ // referenced as `current`) is safe; the in-flight setTimeout
81
+ // closure will then call stop() on an already-stopped source,
82
+ // which the standard tolerates.
83
+ this.fadeOutAndStop(prior, fade);
84
+ }
85
+ }
86
+ // Currently-playing track name; null if silent. Mid-crossfade returns
87
+ // the NEW (incoming) track name - the prior is fading out and is no
88
+ // longer the canonical "current".
89
+ currentMusic() {
90
+ return this.current ? this.current.name : null;
91
+ }
92
+ // ---------- private ----------
93
+ startTrack(name, buffer, fadeInMs) {
94
+ var ctx = this.audioBus.ctx;
95
+ var source = ctx.createBufferSource();
96
+ source.buffer = buffer;
97
+ source.loop = true;
98
+ var gain = ctx.createGain();
99
+ var now = ctx.currentTime;
100
+ var fadeSec = Math.max(0, fadeInMs) / 1000;
101
+ if (fadeSec <= 0) {
102
+ gain.gain.value = 1;
103
+ }
104
+ else {
105
+ // setValueAtTime + linearRampToValueAtTime is the canonical
106
+ // Web Audio fade pattern. Browsers schedule the ramp on the
107
+ // audio thread; no JS timer needed for the audio side.
108
+ gain.gain.setValueAtTime(0, now);
109
+ gain.gain.linearRampToValueAtTime(1, now + fadeSec);
110
+ }
111
+ source.connect(gain).connect(this.audioBus.input('music'));
112
+ source.start();
113
+ return { name: name, source: source, gain: gain };
114
+ }
115
+ fadeOutAndStop(track, fadeOutMs) {
116
+ var ctx = this.audioBus.ctx;
117
+ var now = ctx.currentTime;
118
+ var fadeSec = Math.max(0, fadeOutMs) / 1000;
119
+ if (fadeSec <= 0) {
120
+ // Immediate stop.
121
+ this.hardStop(track);
122
+ return Promise.resolve();
123
+ }
124
+ // Cancel any prior ramps on this gain (e.g. an in-progress fade-in)
125
+ // so the fade-out starts from the actual current value rather than
126
+ // a stale ramp target. cancelScheduledValues + setValueAtTime at
127
+ // the live gain.value is the standard pattern.
128
+ try {
129
+ track.gain.gain.cancelScheduledValues(now);
130
+ }
131
+ catch {
132
+ // some implementations / mocks may lack cancelScheduledValues;
133
+ // best-effort
134
+ }
135
+ var startVal = track.gain.gain.value;
136
+ track.gain.gain.setValueAtTime(startVal, now);
137
+ track.gain.gain.linearRampToValueAtTime(0, now + fadeSec);
138
+ return new Promise((resolve) => {
139
+ // Timer in JS-land mirrors the audio-thread ramp duration. After
140
+ // the fade window, hardStop disconnects the nodes and stops the
141
+ // source. setTimeout in tests runs against fake timers if the
142
+ // test installs them, but in production the duration matches the
143
+ // ramp.
144
+ setTimeout(() => {
145
+ this.hardStop(track);
146
+ resolve();
147
+ }, fadeOutMs);
148
+ });
149
+ }
150
+ hardStop(track) {
151
+ try {
152
+ track.source.stop();
153
+ }
154
+ catch {
155
+ // source.stop() throws if already-stopped on some implementations
156
+ // or if start() was never called - we don't treat that as an
157
+ // error.
158
+ }
159
+ try {
160
+ track.source.disconnect();
161
+ }
162
+ catch {
163
+ // ignore
164
+ }
165
+ try {
166
+ track.gain.disconnect();
167
+ }
168
+ catch {
169
+ // ignore
170
+ }
171
+ }
172
+ }
173
+ // Resource key for the world's resource registry. Engine consumers
174
+ // register a MusicDirector instance under this key alongside the audio
175
+ // bus + asset cache.
176
+ export const RESOURCE_MUSIC_DIRECTOR = 'music_director';
177
+ //# sourceMappingURL=music-director.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"music-director.js","sourceRoot":"","sources":["../../src/audio/music-director.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type { AudioBus } from './audio-bus.js';
2
+ export interface PositionalPlayOptions {
3
+ x: number;
4
+ y: number;
5
+ z?: number;
6
+ distanceModel?: 'linear' | 'inverse' | 'exponential';
7
+ refDistance?: number;
8
+ maxDistance?: number;
9
+ rolloffFactor?: number;
10
+ gain?: number;
11
+ rate?: number;
12
+ loop?: boolean;
13
+ }
14
+ export interface AudioListenerPose {
15
+ x: number;
16
+ y: number;
17
+ z?: number;
18
+ forward?: {
19
+ x: number;
20
+ y: number;
21
+ z: number;
22
+ };
23
+ up?: {
24
+ x: number;
25
+ y: number;
26
+ z: number;
27
+ };
28
+ }
29
+ export interface SpatialSourceHandle {
30
+ stop(): void;
31
+ setPosition(x: number, y: number, z?: number): void;
32
+ fadeOut(durationMs: number): Promise<void>;
33
+ isPlaying(): boolean;
34
+ }
35
+ export declare const SPATIAL_BUS_NAME = "spatial";
36
+ export declare function spatialDistance(listener: {
37
+ x: number;
38
+ y: number;
39
+ z?: number;
40
+ }, source: {
41
+ x: number;
42
+ y: number;
43
+ z?: number;
44
+ }): number;
45
+ export declare class SpatialAudioBus {
46
+ private readonly audioBus;
47
+ private readonly ctx;
48
+ private active;
49
+ private lastPose;
50
+ private constructor();
51
+ static create(audioBus: AudioBus): SpatialAudioBus;
52
+ getAudioBus(): AudioBus;
53
+ getListenerPose(): AudioListenerPose;
54
+ setListener(pose: AudioListenerPose): void;
55
+ playPositional(buffer: AudioBuffer, options: PositionalPlayOptions): SpatialSourceHandle | null;
56
+ playPositionalTone(freq: number, durationMs: number, options: PositionalPlayOptions & {
57
+ type?: OscillatorType;
58
+ }): SpatialSourceHandle | null;
59
+ stopAll(): void;
60
+ dispose(): void;
61
+ private canPlay;
62
+ private startSource;
63
+ private applyPositionTo;
64
+ private releaseSource;
65
+ }
66
+ //# sourceMappingURL=spatial-audio-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial-audio-bus.d.ts","sourceRoot":"","sources":["../../src/audio/spatial-audio-bus.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI/C,MAAM,WAAW,qBAAqB;IAGpC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IAEV,CAAC,CAAC,EAAE,MAAM,CAAC;IAKX,aAAa,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAID,MAAM,WAAW,iBAAiB;IAChC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,CAAC,EAAE,MAAM,CAAC;IAEX,OAAO,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE9C,EAAE,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAKD,MAAM,WAAW,mBAAmB;IAGlC,IAAI,IAAI,IAAI,CAAC;IAGb,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpD,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C,SAAS,IAAI,OAAO,CAAC;CACtB;AAKD,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAM1C,wBAAgB,eAAe,CAC7B,QAAQ,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9C,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,MAAM,CAeR;AAYD,qBAAa,eAAe;IAG1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAGpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAGnC,OAAO,CAAC,MAAM,CAAgC;IAI9C,OAAO,CAAC,QAAQ,CAMd;IAEF,OAAO;IAgBP,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,eAAe;IAMlD,WAAW,IAAI,QAAQ;IAKvB,eAAe,IAAI,iBAAiB;IAOpC,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI;IA6C1C,cAAc,CACZ,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,qBAAqB,GAC7B,mBAAmB,GAAG,IAAI;IAa7B,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,qBAAqB,GAAG;QAAE,IAAI,CAAC,EAAE,cAAc,CAAA;KAAE,GACzD,mBAAmB,GAAG,IAAI;IAiB7B,OAAO,IAAI,IAAI;IAaf,OAAO,IAAI,IAAI;IAcf,OAAO,CAAC,OAAO;IAcf,OAAO,CAAC,WAAW;IA2FnB,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,aAAa;CAQtB"}
@@ -0,0 +1,341 @@
1
+ // SpatialAudioBus - positional 3D audio for the Loom Engine.
2
+ //
3
+ // Composes the existing AudioBus (Phase 5 lock) and adds a 'spatial'
4
+ // sub-bus whose sources route through Web Audio PannerNodes. The
5
+ // existing 'sfx' / 'music' / 'voice' / 'ui' buses are untouched.
6
+ //
7
+ // Architecture per LOOM-AUDIO-SPEC.md Section 3.1:
8
+ //
9
+ // AudioContext.destination
10
+ // <- AudioBus.master (existing, Phase 5)
11
+ // <- 'sfx', 'music', 'voice', 'ui' (existing AudioBus sub-buses)
12
+ // <- 'spatial' (NEW: ambient priority, VE-budget gated)
13
+ // <- per-source PannerNode
14
+ // <- per-source GainNode
15
+ // <- AudioBufferSourceNode | OscillatorNode
16
+ //
17
+ // Each call to playPositional / playPositionalTone allocates ONE
18
+ // PannerNode + ONE GainNode + ONE source node. The PannerNode is reused
19
+ // when handle.setPosition is called - no realloc per frame for moving
20
+ // sources. fadeOut animates the gain to 0 over the requested duration
21
+ // then disconnects.
22
+ //
23
+ // VE-budget integration: SpatialAudioBus.create adds a 'spatial' bus on
24
+ // the underlying AudioBus with priority 'ambient'. Under VE-budget
25
+ // pressure (audioBudget < AUDIO_BUDGET_AMBIENT_FLOOR per audio-bus.ts)
26
+ // the entire spatial bus mutes; we surface that as null returns from
27
+ // playPositional so callers don't waste cycles building handles for
28
+ // inaudible sounds.
29
+ //
30
+ // Listener pose: setListener writes to AudioContext.listener
31
+ // (positionX/Y/Z + forwardX/Y/Z + upX/Y/Z). Per spec §8.4 the listener
32
+ // orientation is fixed in v1 to forward=(0,0,-1), up=(0,1,0); only
33
+ // position is meaningful. Modern AudioParam-style API is preferred;
34
+ // we fall back to the deprecated setPosition/setOrientation if the
35
+ // runtime exposes those instead (some test mocks).
36
+ //
37
+ // Inspirations (per PRIOR-ART.md):
38
+ // Web Audio PannerNode HRTF spec - W3C public technique
39
+ // Distance falloff models (linear / inverse / exponential) - Web
40
+ // Audio canonical, also FMOD / Wwise standard
41
+ // No patented techniques used.
42
+ // Name of the new sub-bus this class adds to the underlying AudioBus.
43
+ // Exported so consumers can adjust gain/mute via audioBus.setBusGain
44
+ // without depending on the literal.
45
+ export const SPATIAL_BUS_NAME = 'spatial';
46
+ // Distance helper shared with tests. Returns the Euclidean distance
47
+ // from the listener pose to a source. Treats undefined z as 0 and
48
+ // guards against NaN inputs (returns Infinity for any NaN coordinate
49
+ // so a defensive caller can skip muted sources cleanly).
50
+ export function spatialDistance(listener, source) {
51
+ if (Number.isNaN(listener.x) || Number.isNaN(listener.y) ||
52
+ Number.isNaN(source.x) || Number.isNaN(source.y) ||
53
+ (listener.z !== undefined && Number.isNaN(listener.z)) ||
54
+ (source.z !== undefined && Number.isNaN(source.z))) {
55
+ return Number.POSITIVE_INFINITY;
56
+ }
57
+ const dx = source.x - listener.x;
58
+ const dy = source.y - listener.y;
59
+ const lz = listener.z ?? 0;
60
+ const sz = source.z ?? 0;
61
+ const dz = sz - lz;
62
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
63
+ }
64
+ export class SpatialAudioBus {
65
+ // The composed AudioBus. We don't alter its master/sub-buses; we
66
+ // just add a 'spatial' sub-bus on it via addBus.
67
+ audioBus;
68
+ // The AudioContext from the underlying bus. Cached for cheap access.
69
+ ctx;
70
+ // Tracks live sources so dispose() / debug introspection can sweep.
71
+ active = new Set();
72
+ // Latest listener pose; remembered so handlers can read it without
73
+ // re-traversing the AudioContext.
74
+ lastPose = {
75
+ x: 0,
76
+ y: 0,
77
+ z: 0,
78
+ forward: { x: 0, y: 0, z: -1 },
79
+ up: { x: 0, y: 1, z: 0 },
80
+ };
81
+ constructor(audioBus) {
82
+ this.audioBus = audioBus;
83
+ this.ctx = audioBus.ctx;
84
+ // Idempotent: if a 'spatial' bus already exists (engine consumer
85
+ // wired one early), AudioBus.addBus is a no-op and we reuse it.
86
+ if (!audioBus.hasBus(SPATIAL_BUS_NAME)) {
87
+ audioBus.addBus(SPATIAL_BUS_NAME, {
88
+ initialGain: 1.0,
89
+ priority: 'ambient',
90
+ });
91
+ }
92
+ }
93
+ // Construct on top of an existing AudioBus. The AudioBus owns the
94
+ // AudioContext lifecycle (unlock, dispose). v1 does not own a second
95
+ // master gain; the spatial bus IS the master path through AudioBus.
96
+ static create(audioBus) {
97
+ return new SpatialAudioBus(audioBus);
98
+ }
99
+ // Read-only accessor for the underlying AudioBus, useful for tests
100
+ // and consumers that want to tweak the spatial sub-bus gain.
101
+ getAudioBus() {
102
+ return this.audioBus;
103
+ }
104
+ // Read-only accessor for the cached listener pose.
105
+ getListenerPose() {
106
+ return this.lastPose;
107
+ }
108
+ // Update the listener pose. Renderer pushes this each frame from the
109
+ // local character's transform via SpatialAudioSystem. Pose is global
110
+ // to the AudioContext - one listener per context.
111
+ setListener(pose) {
112
+ this.lastPose = pose;
113
+ var listener = this.ctx.listener;
114
+ if (!listener)
115
+ return;
116
+ var z = pose.z ?? 0;
117
+ var fwd = pose.forward ?? { x: 0, y: 0, z: -1 };
118
+ var up = pose.up ?? { x: 0, y: 1, z: 0 };
119
+ // Modern AudioParam API (Chrome 64+, Firefox 58+, Safari 14+).
120
+ if (listener.positionX !== undefined &&
121
+ typeof listener.positionX.setValueAtTime === 'function') {
122
+ var asParam = listener;
123
+ var now = this.ctx.currentTime;
124
+ asParam.positionX.setValueAtTime(pose.x, now);
125
+ asParam.positionY.setValueAtTime(pose.y, now);
126
+ asParam.positionZ.setValueAtTime(z, now);
127
+ asParam.forwardX.setValueAtTime(fwd.x, now);
128
+ asParam.forwardY.setValueAtTime(fwd.y, now);
129
+ asParam.forwardZ.setValueAtTime(fwd.z, now);
130
+ asParam.upX.setValueAtTime(up.x, now);
131
+ asParam.upY.setValueAtTime(up.y, now);
132
+ asParam.upZ.setValueAtTime(up.z, now);
133
+ return;
134
+ }
135
+ // Deprecated fallback (older Safari, some test mocks).
136
+ var legacy = listener;
137
+ if (typeof legacy.setPosition === 'function') {
138
+ legacy.setPosition(pose.x, pose.y, z);
139
+ }
140
+ if (typeof legacy.setOrientation === 'function') {
141
+ legacy.setOrientation(fwd.x, fwd.y, fwd.z, up.x, up.y, up.z);
142
+ }
143
+ }
144
+ // Play a one-shot positional sound. Returns null if the AudioContext
145
+ // is suspended (AudioBus.unlock not yet called) or if the 'spatial'
146
+ // bus is muted by VE-budget pressure.
147
+ playPositional(buffer, options) {
148
+ if (!this.canPlay())
149
+ return null;
150
+ var src = this.ctx.createBufferSource();
151
+ src.buffer = buffer;
152
+ src.playbackRate.value = options.rate ?? 1;
153
+ src.loop = options.loop ?? false;
154
+ return this.startSource(src, false, options);
155
+ }
156
+ // Convenience: play a positional tone (no asset needed). Useful for
157
+ // tests and code-only demos. Honours options.type for the oscillator
158
+ // waveform; envelope is left to the consumer (or the natural source
159
+ // duration).
160
+ playPositionalTone(freq, durationMs, options) {
161
+ if (!this.canPlay())
162
+ return null;
163
+ var osc = this.ctx.createOscillator();
164
+ osc.type = options.type ?? 'sine';
165
+ osc.frequency.value = freq;
166
+ var handle = this.startSource(osc, true, options);
167
+ if (handle) {
168
+ // Schedule a stop at the end of the requested duration so the
169
+ // tone doesn't run forever. Tiny tail keeps the click low.
170
+ var stopAt = this.ctx.currentTime + Math.max(0.01, durationMs / 1000) + 0.05;
171
+ try {
172
+ osc.stop(stopAt);
173
+ }
174
+ catch { /* runtime may not support timestamped stop */ }
175
+ }
176
+ return handle;
177
+ }
178
+ // Stop every live spatial source and disconnect their nodes. Called
179
+ // by dispose() and useful for "scene change, kill everything" flows.
180
+ stopAll() {
181
+ // Snapshot to a list because handle.stop mutates the set.
182
+ var sources = [];
183
+ this.active.forEach(function (s) { sources.push(s); });
184
+ for (var i = 0; i < sources.length; i++) {
185
+ this.releaseSource(sources[i]);
186
+ }
187
+ this.active.clear();
188
+ }
189
+ // Tear down. Useful in tests; production demo lives the lifetime of
190
+ // the page. Does NOT dispose the underlying AudioBus - the consumer
191
+ // may still want sfx/music/etc.
192
+ dispose() {
193
+ this.stopAll();
194
+ }
195
+ // -------- private --------
196
+ // Returns true iff a play call would have a chance of producing
197
+ // audible output: context unlocked AND spatial bus not muted by
198
+ // VE-budget. We do NOT introspect AudioBus's internal state directly;
199
+ // we mirror the public contract:
200
+ // - audioBus.isUnlocked() must be true
201
+ // - audioBus.isBusMuted('spatial') must be false (explicit mute)
202
+ // - audioBus.getBusGain('spatial') must be > 0 after budget gating
203
+ // (the bus's input GainNode reflects this; we check the live gain)
204
+ canPlay() {
205
+ if (!this.audioBus.isUnlocked())
206
+ return false;
207
+ if (this.audioBus.isBusMuted(SPATIAL_BUS_NAME))
208
+ return false;
209
+ // Live gain after budget gating - if VE budget knocked it to 0,
210
+ // there's no point creating a source we can't hear.
211
+ var input = this.audioBus.input(SPATIAL_BUS_NAME);
212
+ if (input && input.gain && input.gain.value === 0)
213
+ return false;
214
+ return true;
215
+ }
216
+ // Wires a fresh source -> gain -> panner -> spatial bus input,
217
+ // applies position + falloff fields, registers the active source,
218
+ // and returns a handle. Caller is responsible for src.start() in the
219
+ // BufferSource case (we do it here so paths converge).
220
+ startSource(source, isOscillator, options) {
221
+ var panner = this.ctx.createPanner();
222
+ var gain = this.ctx.createGain();
223
+ gain.gain.value = options.gain ?? 1.0;
224
+ // Falloff fields. PannerNode defaults are ref=1, max=10000,
225
+ // rolloff=1, model='inverse'. Our defaults are documented in
226
+ // PositionalPlayOptions.
227
+ panner.distanceModel = options.distanceModel ?? 'inverse';
228
+ panner.refDistance = options.refDistance ?? 1;
229
+ panner.maxDistance = options.maxDistance ?? 32;
230
+ panner.rolloffFactor = options.rolloffFactor ?? 1;
231
+ panner.panningModel = 'HRTF';
232
+ var z = options.z ?? 0;
233
+ this.applyPositionTo(panner, options.x, options.y, z);
234
+ // Wire: source -> gain -> panner -> spatial-bus input.
235
+ source.connect(gain);
236
+ gain.connect(panner);
237
+ var spatialInput = this.audioBus.input(SPATIAL_BUS_NAME);
238
+ panner.connect(spatialInput);
239
+ // Track for cleanup BEFORE start() so an immediate end-of-buffer
240
+ // event still finds us in the active set.
241
+ var record = {
242
+ panner: panner,
243
+ gain: gain,
244
+ source: source,
245
+ isOscillator: isOscillator,
246
+ stopped: false,
247
+ };
248
+ this.active.add(record);
249
+ // BufferSourceNode.onended fires when the buffer plays out OR
250
+ // stop() is called. Use it to auto-cleanup so we don't leak nodes.
251
+ var self = this;
252
+ source.onended = function () {
253
+ self.releaseSource(record);
254
+ };
255
+ try {
256
+ source.start();
257
+ }
258
+ catch {
259
+ // Some runtimes throw if start() called twice; harmless here.
260
+ }
261
+ var handle = {
262
+ stop: function () {
263
+ if (record.stopped)
264
+ return;
265
+ try {
266
+ source.stop();
267
+ }
268
+ catch { /* already stopped */ }
269
+ self.releaseSource(record);
270
+ },
271
+ setPosition: function (nx, ny, nz) {
272
+ if (record.stopped)
273
+ return;
274
+ self.applyPositionTo(record.panner, nx, ny, nz ?? 0);
275
+ },
276
+ fadeOut: function (durationMs) {
277
+ if (record.stopped)
278
+ return Promise.resolve();
279
+ var dur = Math.max(0, durationMs) / 1000;
280
+ var now = self.ctx.currentTime;
281
+ try {
282
+ // setValueAtTime to current then ramp to 0 - prevents the
283
+ // ramp from interpolating from whatever the previous
284
+ // setValueAtTime scheduled (could be a jump).
285
+ record.gain.gain.setValueAtTime(record.gain.gain.value, now);
286
+ record.gain.gain.linearRampToValueAtTime(0, now + dur);
287
+ }
288
+ catch { /* fake AudioParam may not support */ }
289
+ return new Promise(function (resolve) {
290
+ var ms = Math.max(0, durationMs);
291
+ // setTimeout in test environments without a real audio clock.
292
+ setTimeout(function () {
293
+ handle.stop();
294
+ resolve();
295
+ }, ms);
296
+ });
297
+ },
298
+ isPlaying: function () {
299
+ return !record.stopped;
300
+ },
301
+ };
302
+ return handle;
303
+ }
304
+ // Apply x/y/z to a PannerNode. Prefers AudioParam.setValueAtTime when
305
+ // available (modern API); falls back to the deprecated direct
306
+ // assignment via setPosition for older runtimes / mocks.
307
+ applyPositionTo(panner, x, y, z) {
308
+ var asParam = panner;
309
+ if (asParam.positionX && typeof asParam.positionX.setValueAtTime === 'function') {
310
+ var now = this.ctx.currentTime;
311
+ asParam.positionX.setValueAtTime(x, now);
312
+ asParam.positionY.setValueAtTime(y, now);
313
+ asParam.positionZ.setValueAtTime(z, now);
314
+ return;
315
+ }
316
+ var legacy = panner;
317
+ if (typeof legacy.setPosition === 'function') {
318
+ legacy.setPosition(x, y, z);
319
+ }
320
+ }
321
+ // Disconnect + drop one active source. Idempotent.
322
+ releaseSource(record) {
323
+ if (record.stopped)
324
+ return;
325
+ record.stopped = true;
326
+ this.active.delete(record);
327
+ try {
328
+ record.source.disconnect();
329
+ }
330
+ catch { /* ignore */ }
331
+ try {
332
+ record.gain.disconnect();
333
+ }
334
+ catch { /* ignore */ }
335
+ try {
336
+ record.panner.disconnect();
337
+ }
338
+ catch { /* ignore */ }
339
+ }
340
+ }
341
+ //# sourceMappingURL=spatial-audio-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial-audio-bus.js","sourceRoot":"","sources":["../../src/audio/spatial-audio-bus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { System } from '../system.js';
2
+ import type { World } from '../world.js';
3
+ import type { EntityId } from '../entity.js';
4
+ import type { SpatialAudioBus } from './spatial-audio-bus.js';
5
+ export declare class SpatialAudioSystem implements System {
6
+ readonly name: string;
7
+ private localCharacter;
8
+ private spatialBus;
9
+ constructor(opts?: {
10
+ spatialBus?: SpatialAudioBus;
11
+ });
12
+ setSpatialBus(bus: SpatialAudioBus | null): void;
13
+ setLocalCharacterEntity(entity: EntityId | null): void;
14
+ getLocalCharacterEntity(): EntityId | null;
15
+ update(world: World, _dt: number): void;
16
+ }
17
+ //# sourceMappingURL=spatial-audio-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial-audio-system.d.ts","sourceRoot":"","sources":["../../src/audio/spatial-audio-system.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAY7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,qBAAa,kBAAmB,YAAW,MAAM;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAmB;IAExC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,UAAU,CAAgC;gBAMtC,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,eAAe,CAAA;KAAO;IAOvD,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI;IAQhD,uBAAuB,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI;IAItD,uBAAuB,IAAI,QAAQ,GAAG,IAAI;IAI1C,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CA4BxC"}