@nodetool-ai/game-renderer 0.8.2 → 0.8.4-a13.1
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.
- package/dist/.nodetool-build-stamp +1 -1
- package/dist/audio/music.d.ts +38 -0
- package/dist/audio/music.d.ts.map +1 -0
- package/dist/audio/music.js +186 -0
- package/dist/audio/music.js.map +1 -0
- package/dist/audio/player.d.ts +15 -1
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +185 -19
- package/dist/audio/player.js.map +1 -1
- package/dist/build-tauri.d.ts +69 -0
- package/dist/build-tauri.d.ts.map +1 -0
- package/dist/build-tauri.js +405 -0
- package/dist/build-tauri.js.map +1 -0
- package/dist/build3d.d.ts +1 -1
- package/dist/build3d.d.ts.map +1 -1
- package/dist/build3d.js +13 -1
- package/dist/build3d.js.map +1 -1
- package/dist/game3d-capture-page.js +328 -262
- package/dist/game3d-player.js +415 -311
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/node3d.d.ts +3 -0
- package/dist/node3d.d.ts.map +1 -1
- package/dist/node3d.js +27 -1
- package/dist/node3d.js.map +1 -1
- package/dist/particles/index.d.ts +1 -0
- package/dist/particles/index.d.ts.map +1 -1
- package/dist/particles/index.js +1 -0
- package/dist/particles/index.js.map +1 -1
- package/dist/particles/particles3d.d.ts +89 -0
- package/dist/particles/particles3d.d.ts.map +1 -0
- package/dist/particles/particles3d.js +176 -0
- package/dist/particles/particles3d.js.map +1 -0
- package/dist/particles/pass3d.d.ts +63 -0
- package/dist/particles/pass3d.d.ts.map +1 -0
- package/dist/particles/pass3d.js +394 -0
- package/dist/particles/pass3d.js.map +1 -0
- package/dist/particles/simulator.d.ts +4 -0
- package/dist/particles/simulator.d.ts.map +1 -1
- package/dist/particles/simulator.js +9 -1
- package/dist/particles/simulator.js.map +1 -1
- package/dist/player-v1.js +412 -353
- package/dist/preparation3d.d.ts +2 -0
- package/dist/preparation3d.d.ts.map +1 -1
- package/dist/preparation3d.js +1 -0
- package/dist/preparation3d.js.map +1 -1
- package/dist/renderer3d/assets/image-cache.d.ts +20 -0
- package/dist/renderer3d/assets/image-cache.d.ts.map +1 -0
- package/dist/renderer3d/assets/image-cache.js +140 -0
- package/dist/renderer3d/assets/image-cache.js.map +1 -0
- package/dist/renderer3d/assets/image-contract.d.ts +14 -0
- package/dist/renderer3d/assets/image-contract.d.ts.map +1 -0
- package/dist/renderer3d/assets/image-contract.js +2 -0
- package/dist/renderer3d/assets/image-contract.js.map +1 -0
- package/dist/renderer3d/assets/image-preparation.d.ts +18 -0
- package/dist/renderer3d/assets/image-preparation.d.ts.map +1 -0
- package/dist/renderer3d/assets/image-preparation.js +167 -0
- package/dist/renderer3d/assets/image-preparation.js.map +1 -0
- package/dist/renderer3d/capture-page.d.ts +3 -0
- package/dist/renderer3d/capture-page.d.ts.map +1 -1
- package/dist/renderer3d/capture-page.js +17 -4
- package/dist/renderer3d/capture-page.js.map +1 -1
- package/dist/renderer3d/hud/paint.d.ts +1 -0
- package/dist/renderer3d/hud/paint.d.ts.map +1 -1
- package/dist/renderer3d/hud/paint.js +7 -0
- package/dist/renderer3d/hud/paint.js.map +1 -1
- package/dist/renderer3d/index.d.ts +13 -2
- package/dist/renderer3d/index.d.ts.map +1 -1
- package/dist/renderer3d/index.js +26 -4
- package/dist/renderer3d/index.js.map +1 -1
- package/dist/renderer3d/passes/editor.d.ts +59 -0
- package/dist/renderer3d/passes/editor.d.ts.map +1 -0
- package/dist/renderer3d/passes/editor.js +290 -0
- package/dist/renderer3d/passes/editor.js.map +1 -0
- package/dist/renderer3d/passes/post-processing.d.ts +2 -0
- package/dist/renderer3d/passes/post-processing.d.ts.map +1 -1
- package/dist/renderer3d/passes/post-processing.js +3 -1
- package/dist/renderer3d/passes/post-processing.js.map +1 -1
- package/dist/renderer3d/preparation.d.ts.map +1 -1
- package/dist/renderer3d/preparation.js +2 -0
- package/dist/renderer3d/preparation.js.map +1 -1
- package/dist/save-storage.d.ts +19 -0
- package/dist/save-storage.d.ts.map +1 -0
- package/dist/save-storage.js +135 -0
- package/dist/save-storage.js.map +1 -0
- package/dist/smoke.d.ts.map +1 -1
- package/dist/smoke.js +12 -2
- package/dist/smoke.js.map +1 -1
- package/dist/standalone-player.d.ts +3 -0
- package/dist/standalone-player.d.ts.map +1 -1
- package/dist/standalone-player.js +40 -30
- package/dist/standalone-player.js.map +1 -1
- package/dist/standalone-player3d.d.ts +3 -0
- package/dist/standalone-player3d.d.ts.map +1 -1
- package/dist/standalone-player3d.js +47 -13
- package/dist/standalone-player3d.js.map +1 -1
- package/dist/ui/controller.d.ts.map +1 -1
- package/dist/ui/controller.js +2 -1
- package/dist/ui/controller.js.map +1 -1
- package/dist/ui/hud3d.d.ts +5 -0
- package/dist/ui/hud3d.d.ts.map +1 -0
- package/dist/ui/hud3d.js +5 -0
- package/dist/ui/hud3d.js.map +1 -0
- package/package.json +19 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-10-
|
|
1
|
+
2026-10-15T22:44:25.338Z
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { GameScene } from "@nodetool-ai/protocol";
|
|
2
|
+
type StreamingSettings = NonNullable<NonNullable<GameScene["music"]>["streaming"]>;
|
|
3
|
+
/** Owns a streamed track and its media lifecycle. Media time never enters simulation. */
|
|
4
|
+
export declare class GameStreamingMusicVoice {
|
|
5
|
+
private readonly context;
|
|
6
|
+
private readonly url;
|
|
7
|
+
private readonly settings;
|
|
8
|
+
private readonly volume;
|
|
9
|
+
private readonly fadeSeconds;
|
|
10
|
+
private readonly offset;
|
|
11
|
+
private readonly status;
|
|
12
|
+
private readonly ended;
|
|
13
|
+
private readonly playing;
|
|
14
|
+
private readonly media;
|
|
15
|
+
private readonly source;
|
|
16
|
+
private readonly gain;
|
|
17
|
+
private readonly controller;
|
|
18
|
+
private timer;
|
|
19
|
+
private disposed;
|
|
20
|
+
private paused;
|
|
21
|
+
private started;
|
|
22
|
+
private playGeneration;
|
|
23
|
+
private fadeStart;
|
|
24
|
+
private fadeOut;
|
|
25
|
+
private end;
|
|
26
|
+
constructor(context: AudioContext, url: string, destination: AudioNode, settings: StreamingSettings, volume: number, fadeSeconds: number, offset: () => number, status: (message: string) => void, ended: () => void, playing?: () => void);
|
|
27
|
+
load(): void;
|
|
28
|
+
private ready;
|
|
29
|
+
private play;
|
|
30
|
+
private fail;
|
|
31
|
+
pause(): void;
|
|
32
|
+
resume(): void;
|
|
33
|
+
stop(fadeSeconds: number): void;
|
|
34
|
+
private scheduleFadeOut;
|
|
35
|
+
dispose(): void;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=music.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"music.d.ts","sourceRoot":"","sources":["../../src/audio/music.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,KAAK,iBAAiB,GAAG,WAAW,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAOnF,yFAAyF;AACzF,qBAAa,uBAAuB;IAehC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAEpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAvB1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAiE;IAChF,OAAO,CAAC,GAAG,CAAK;IAEhB,YACmB,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,MAAM,EAC5B,WAAW,EAAE,SAAS,EACL,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,MAAM,EACpB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACjC,KAAK,EAAE,MAAM,IAAI,EACjB,OAAO,GAAE,MAAM,IAAsB,EAsBvD;IAED,IAAI,IAAI,IAAI,CAKX;IAED,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,IAAI;IAuBZ,OAAO,CAAC,IAAI;IAKZ,KAAK,IAAI,IAAI,CAeZ;IAED,MAAM,IAAI,IAAI,CAeb;IAED,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAU9B;IAED,OAAO,CAAC,eAAe;IAUvB,OAAO,IAAI,IAAI,CAYd;CACF"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/** Logical music time includes the intro once, then repeats the authored loop. */
|
|
2
|
+
function loopOffset(seconds, start, end) {
|
|
3
|
+
return seconds < end ? seconds : start + (seconds - start) % (end - start);
|
|
4
|
+
}
|
|
5
|
+
/** Owns a streamed track and its media lifecycle. Media time never enters simulation. */
|
|
6
|
+
export class GameStreamingMusicVoice {
|
|
7
|
+
context;
|
|
8
|
+
url;
|
|
9
|
+
settings;
|
|
10
|
+
volume;
|
|
11
|
+
fadeSeconds;
|
|
12
|
+
offset;
|
|
13
|
+
status;
|
|
14
|
+
ended;
|
|
15
|
+
playing;
|
|
16
|
+
media = new Audio();
|
|
17
|
+
source;
|
|
18
|
+
gain;
|
|
19
|
+
controller = new AbortController();
|
|
20
|
+
timer;
|
|
21
|
+
disposed = false;
|
|
22
|
+
paused = false;
|
|
23
|
+
started = false;
|
|
24
|
+
playGeneration = 0;
|
|
25
|
+
fadeStart = 0;
|
|
26
|
+
fadeOut;
|
|
27
|
+
end = 0;
|
|
28
|
+
constructor(context, url, destination, settings, volume, fadeSeconds, offset, status, ended, playing = () => undefined) {
|
|
29
|
+
this.context = context;
|
|
30
|
+
this.url = url;
|
|
31
|
+
this.settings = settings;
|
|
32
|
+
this.volume = volume;
|
|
33
|
+
this.fadeSeconds = fadeSeconds;
|
|
34
|
+
this.offset = offset;
|
|
35
|
+
this.status = status;
|
|
36
|
+
this.ended = ended;
|
|
37
|
+
this.playing = playing;
|
|
38
|
+
this.media.crossOrigin = "anonymous";
|
|
39
|
+
this.media.preload = "metadata";
|
|
40
|
+
this.source = context.createMediaElementSource(this.media);
|
|
41
|
+
this.gain = context.createGain();
|
|
42
|
+
this.source.connect(this.gain).connect(destination);
|
|
43
|
+
this.gain.gain.setValueAtTime(0, context.currentTime);
|
|
44
|
+
const signal = this.controller.signal;
|
|
45
|
+
this.media.addEventListener("loadedmetadata", () => this.ready(), { signal });
|
|
46
|
+
this.media.addEventListener("timeupdate", () => {
|
|
47
|
+
if (!this.paused && this.end > 0 && this.media.currentTime >= this.end) {
|
|
48
|
+
this.media.currentTime = loopOffset(this.media.currentTime, settings.loopStartSeconds, this.end);
|
|
49
|
+
}
|
|
50
|
+
}, { signal });
|
|
51
|
+
this.media.addEventListener("ended", () => {
|
|
52
|
+
if (!this.paused && !this.disposed && this.end > 0) {
|
|
53
|
+
this.media.currentTime = settings.loopStartSeconds;
|
|
54
|
+
this.play();
|
|
55
|
+
}
|
|
56
|
+
}, { signal });
|
|
57
|
+
this.media.addEventListener("error", () => this.fail("the media could not be loaded"), { signal });
|
|
58
|
+
}
|
|
59
|
+
load() {
|
|
60
|
+
if (this.disposed)
|
|
61
|
+
return;
|
|
62
|
+
this.media.src = this.url;
|
|
63
|
+
this.media.load();
|
|
64
|
+
if (this.media.readyState >= 1)
|
|
65
|
+
this.ready();
|
|
66
|
+
}
|
|
67
|
+
ready() {
|
|
68
|
+
if (this.disposed)
|
|
69
|
+
return;
|
|
70
|
+
const duration = this.media.duration;
|
|
71
|
+
const end = this.settings.loopEndSeconds ?? duration;
|
|
72
|
+
if (!Number.isFinite(duration) || duration <= 0 || this.settings.loopStartSeconds >= end || end > duration) {
|
|
73
|
+
this.fail("loop points must fit inside the track duration");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.end = end;
|
|
77
|
+
this.media.loop = this.settings.loopStartSeconds === 0 && this.settings.loopEndSeconds === undefined;
|
|
78
|
+
if (!this.paused)
|
|
79
|
+
this.resume();
|
|
80
|
+
}
|
|
81
|
+
play() {
|
|
82
|
+
const generation = ++this.playGeneration;
|
|
83
|
+
void this.media.play().then(() => {
|
|
84
|
+
if (generation !== this.playGeneration) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.disposed || this.paused) {
|
|
88
|
+
this.media.pause();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.media.currentTime = loopOffset(Math.max(0, this.offset()), this.settings.loopStartSeconds, this.end);
|
|
92
|
+
if (!this.started) {
|
|
93
|
+
this.started = true;
|
|
94
|
+
this.playing();
|
|
95
|
+
this.fadeStart = this.context.currentTime;
|
|
96
|
+
this.gain.gain.setValueAtTime(this.fadeSeconds > 0 ? 0 : this.volume, this.fadeStart);
|
|
97
|
+
if (this.fadeSeconds > 0)
|
|
98
|
+
this.gain.gain.linearRampToValueAtTime(this.volume, this.fadeStart + this.fadeSeconds);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}).catch((error) => {
|
|
102
|
+
if (!this.disposed && !this.paused && generation === this.playGeneration) {
|
|
103
|
+
this.fail(error instanceof Error ? error.message : String(error));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
fail(message) {
|
|
108
|
+
this.status(`Streaming music could not play: ${message}`);
|
|
109
|
+
this.dispose();
|
|
110
|
+
}
|
|
111
|
+
pause() {
|
|
112
|
+
if (this.paused)
|
|
113
|
+
return;
|
|
114
|
+
if (this.fadeOut) {
|
|
115
|
+
const now = this.context.currentTime;
|
|
116
|
+
const elapsed = Math.max(0, now - this.fadeOut.start);
|
|
117
|
+
const seconds = Math.max(0, this.fadeOut.seconds - elapsed);
|
|
118
|
+
const volume = this.fadeOut.volume * Math.max(0, 1 - elapsed / this.fadeOut.seconds);
|
|
119
|
+
this.fadeOut = { start: now, seconds, volume };
|
|
120
|
+
this.gain.gain.cancelScheduledValues(now);
|
|
121
|
+
this.gain.gain.setValueAtTime(volume, now);
|
|
122
|
+
if (this.timer !== undefined)
|
|
123
|
+
clearTimeout(this.timer);
|
|
124
|
+
}
|
|
125
|
+
this.paused = true;
|
|
126
|
+
this.playGeneration += 1;
|
|
127
|
+
this.media.pause();
|
|
128
|
+
}
|
|
129
|
+
resume() {
|
|
130
|
+
if (this.disposed)
|
|
131
|
+
return;
|
|
132
|
+
const wasPaused = this.paused;
|
|
133
|
+
this.paused = false;
|
|
134
|
+
if (this.end === 0)
|
|
135
|
+
return;
|
|
136
|
+
if (wasPaused && this.fadeOut) {
|
|
137
|
+
if (this.fadeOut.seconds === 0) {
|
|
138
|
+
this.dispose();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.fadeOut.start = this.context.currentTime;
|
|
142
|
+
this.scheduleFadeOut();
|
|
143
|
+
}
|
|
144
|
+
this.media.currentTime = loopOffset(Math.max(0, this.offset()), this.settings.loopStartSeconds, this.end);
|
|
145
|
+
this.play();
|
|
146
|
+
}
|
|
147
|
+
stop(fadeSeconds) {
|
|
148
|
+
if (this.disposed)
|
|
149
|
+
return;
|
|
150
|
+
if (fadeSeconds === 0 || this.paused || !this.started) {
|
|
151
|
+
this.dispose();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const now = this.context.currentTime;
|
|
155
|
+
const current = this.fadeSeconds > 0 ? this.volume * Math.min(1, Math.max(0, (now - this.fadeStart) / this.fadeSeconds)) : this.volume;
|
|
156
|
+
this.fadeOut = { start: now, seconds: fadeSeconds, volume: current };
|
|
157
|
+
this.scheduleFadeOut();
|
|
158
|
+
}
|
|
159
|
+
scheduleFadeOut() {
|
|
160
|
+
const fade = this.fadeOut;
|
|
161
|
+
if (!fade)
|
|
162
|
+
return;
|
|
163
|
+
if (this.timer !== undefined)
|
|
164
|
+
clearTimeout(this.timer);
|
|
165
|
+
this.gain.gain.cancelScheduledValues(fade.start);
|
|
166
|
+
this.gain.gain.setValueAtTime(fade.volume, fade.start);
|
|
167
|
+
this.gain.gain.linearRampToValueAtTime(0, fade.start + fade.seconds);
|
|
168
|
+
this.timer = setTimeout(() => this.dispose(), fade.seconds * 1000);
|
|
169
|
+
}
|
|
170
|
+
dispose() {
|
|
171
|
+
if (this.disposed)
|
|
172
|
+
return;
|
|
173
|
+
this.disposed = true;
|
|
174
|
+
if (this.timer !== undefined)
|
|
175
|
+
clearTimeout(this.timer);
|
|
176
|
+
this.controller.abort();
|
|
177
|
+
this.fadeOut = undefined;
|
|
178
|
+
this.pause();
|
|
179
|
+
this.media.removeAttribute("src");
|
|
180
|
+
this.media.load();
|
|
181
|
+
this.source.disconnect();
|
|
182
|
+
this.gain.disconnect();
|
|
183
|
+
this.ended();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=music.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"music.js","sourceRoot":"","sources":["../../src/audio/music.ts"],"names":[],"mappings":"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"}
|
package/dist/audio/player.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type GameAssetBinding, type GameAssetBinding3D, type GameAudioMixerInput, type GameEvent, type GameEvent3D, type GameSnapshot } from "@nodetool-ai/protocol";
|
|
1
|
+
import { type GameAssetBinding, type GameAssetBinding3D, type GameAudioMixerInput, type GameEvent, type GameEvent3D, type GameSnapshot, type GameScene } from "@nodetool-ai/protocol";
|
|
2
2
|
import { type GameAudioMixerState } from "./mixer.js";
|
|
3
3
|
import { type GameAudioSpatialQuality, type GameAudioSpatialView } from "./spatial.js";
|
|
4
4
|
export interface GameAudioOptions {
|
|
5
5
|
readonly assets: Readonly<Record<string, GameAssetBinding | GameAssetBinding3D>>;
|
|
6
6
|
readonly tickRate: number;
|
|
7
|
+
/** Scene music settings are presentation configuration, separate from snapshots. */
|
|
8
|
+
readonly scenes?: ReadonlyArray<Pick<GameScene, "id" | "music">>;
|
|
7
9
|
readonly resolveAsset: (binding: GameAssetBinding | GameAssetBinding3D) => Promise<string | null>;
|
|
8
10
|
readonly status: (message: string) => void;
|
|
9
11
|
readonly context?: AudioContext;
|
|
@@ -17,6 +19,11 @@ export declare class GameAudioPlayer {
|
|
|
17
19
|
private options;
|
|
18
20
|
private readonly context;
|
|
19
21
|
private readonly mixer;
|
|
22
|
+
private readonly failedStreams;
|
|
23
|
+
private readonly streamed;
|
|
24
|
+
private readonly deferredDecodedFades;
|
|
25
|
+
private readonly deferredFades;
|
|
26
|
+
private readonly streamingFades;
|
|
20
27
|
private readonly buffers;
|
|
21
28
|
private readonly voices;
|
|
22
29
|
private readonly fading;
|
|
@@ -37,6 +44,8 @@ export declare class GameAudioPlayer {
|
|
|
37
44
|
private mixerSettings;
|
|
38
45
|
/** Applies edited mixer settings. Playing voices keep playing on their buses. */
|
|
39
46
|
updateMixer(settings: GameAudioMixerInput | undefined): void;
|
|
47
|
+
/** Updates scene presentation settings without replacing the audio context. */
|
|
48
|
+
updateScenes(scenes: GameAudioOptions["scenes"]): void;
|
|
40
49
|
/** Player-facing bus volume, for example from a settings screen. */
|
|
41
50
|
setBusVolume(busId: string, volume: number): void;
|
|
42
51
|
setBusMuted(busId: string, muted: boolean): void;
|
|
@@ -50,6 +59,10 @@ export declare class GameAudioPlayer {
|
|
|
50
59
|
resume(): void;
|
|
51
60
|
sync(snapshot: Pick<GameSnapshot, "tick" | "music" | "sceneId">): void;
|
|
52
61
|
private startDesiredMusic;
|
|
62
|
+
private streamingSettings;
|
|
63
|
+
private voiceCount;
|
|
64
|
+
private makeVoiceRoom;
|
|
65
|
+
private startStream;
|
|
53
66
|
/**
|
|
54
67
|
* Moves the listener and every spatial voice to a rendered frame. Players call it once per rendered frame with the view
|
|
55
68
|
* built from the interpolated frame, so positions follow what is on screen. Emitters missing from the view keep their last position.
|
|
@@ -60,6 +73,7 @@ export declare class GameAudioPlayer {
|
|
|
60
73
|
/** Plays audio events and lets other simulation events drive mixer snapshot transitions. */
|
|
61
74
|
handle(event: GameEvent | GameEvent3D): void;
|
|
62
75
|
private start;
|
|
76
|
+
private finishDeferredFades;
|
|
63
77
|
private stop;
|
|
64
78
|
reset(snapshot: Pick<GameSnapshot, "tick" | "music" | "sceneId">): void;
|
|
65
79
|
dispose(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/audio/player.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,gBAAgB,EAAE,KAAK,kBAAkB,EAAyB,KAAK,mBAAmB,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/audio/player.ts"],"names":[],"mappings":"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"}
|
package/dist/audio/player.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { gameAudioMixer } from "@nodetool-ai/protocol";
|
|
2
|
+
import { GameStreamingMusicVoice } from "./music.js";
|
|
2
3
|
import { GameAudioMixer } from "./mixer.js";
|
|
3
4
|
import { createGameAudioPanner, gameAudioDopplerRate, gameAudioVelocity, placeGameAudioListener, placeGameAudioPanner, setGameAudioDopplerRate } from "./spatial.js";
|
|
4
5
|
import { playBuiltinGameVoice, stopGameVoice } from "./voices.js";
|
|
@@ -8,6 +9,11 @@ export class GameAudioPlayer {
|
|
|
8
9
|
options;
|
|
9
10
|
context;
|
|
10
11
|
mixer;
|
|
12
|
+
failedStreams = new Set();
|
|
13
|
+
streamed = new Map();
|
|
14
|
+
deferredDecodedFades = new Map();
|
|
15
|
+
deferredFades = new Map();
|
|
16
|
+
streamingFades = new Map();
|
|
11
17
|
buffers = new Map();
|
|
12
18
|
voices = new Map();
|
|
13
19
|
fading = new Set();
|
|
@@ -43,6 +49,21 @@ export class GameAudioPlayer {
|
|
|
43
49
|
return;
|
|
44
50
|
this.mixer.update(this.mixerSettings(settings));
|
|
45
51
|
}
|
|
52
|
+
/** Updates scene presentation settings without replacing the audio context. */
|
|
53
|
+
updateScenes(scenes) {
|
|
54
|
+
if (this.disposed)
|
|
55
|
+
return;
|
|
56
|
+
const before = this.streamingSettings();
|
|
57
|
+
this.options = { ...this.options, scenes };
|
|
58
|
+
for (const scene of scenes ?? []) {
|
|
59
|
+
if (scene.music?.streaming)
|
|
60
|
+
this.buffers.delete(scene.music.assetId);
|
|
61
|
+
}
|
|
62
|
+
if (JSON.stringify(before) !== JSON.stringify(this.streamingSettings()) && this.desiredMusic) {
|
|
63
|
+
this.stop(this.desiredMusic.voiceId, 0);
|
|
64
|
+
this.startDesiredMusic();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
46
67
|
/** Player-facing bus volume, for example from a settings screen. */
|
|
47
68
|
setBusVolume(busId, volume) {
|
|
48
69
|
if (!this.disposed)
|
|
@@ -103,13 +124,24 @@ export class GameAudioPlayer {
|
|
|
103
124
|
this.fading.delete(voice);
|
|
104
125
|
}
|
|
105
126
|
}
|
|
127
|
+
for (const [id, stream] of this.streamed) {
|
|
128
|
+
if (changed.has(stream.assetId))
|
|
129
|
+
this.stop(id, 0);
|
|
130
|
+
}
|
|
131
|
+
for (const [voice, assetId] of this.streamingFades) {
|
|
132
|
+
if (changed.has(assetId))
|
|
133
|
+
voice.dispose();
|
|
134
|
+
}
|
|
135
|
+
if (this.desiredMusic && changed.has(this.desiredMusic.assetId))
|
|
136
|
+
this.failedStreams.delete(this.desiredMusic.voiceId);
|
|
106
137
|
this.options = { ...this.options, assets };
|
|
107
138
|
this.preload();
|
|
108
139
|
this.startDesiredMusic();
|
|
109
140
|
}
|
|
110
141
|
preload() {
|
|
142
|
+
const streamedSlots = new Set((this.options.scenes ?? []).flatMap((scene) => scene.music?.streaming ? [scene.music.assetId] : []));
|
|
111
143
|
for (const [id, binding] of Object.entries(this.options.assets)) {
|
|
112
|
-
if (binding.mediaKind === "audio" && !binding.assetId.startsWith("builtin:"))
|
|
144
|
+
if (binding.mediaKind === "audio" && !binding.assetId.startsWith("builtin:") && !streamedSlots.has(id))
|
|
113
145
|
void this.buffer(id);
|
|
114
146
|
}
|
|
115
147
|
}
|
|
@@ -117,6 +149,7 @@ export class GameAudioPlayer {
|
|
|
117
149
|
if (this.disposed || this.paused)
|
|
118
150
|
return;
|
|
119
151
|
try {
|
|
152
|
+
this.failedStreams.clear();
|
|
120
153
|
await this.context.resume();
|
|
121
154
|
this.startDesiredMusic();
|
|
122
155
|
}
|
|
@@ -128,11 +161,22 @@ export class GameAudioPlayer {
|
|
|
128
161
|
if (this.disposed || this.paused)
|
|
129
162
|
return;
|
|
130
163
|
this.paused = true;
|
|
164
|
+
for (const stream of this.streamed.values())
|
|
165
|
+
stream.voice.pause();
|
|
166
|
+
for (const voice of this.streamingFades.keys())
|
|
167
|
+
voice.pause();
|
|
131
168
|
void this.context.suspend().catch(() => this.options.status("Audio could not pause"));
|
|
132
169
|
}
|
|
133
170
|
resume() {
|
|
134
171
|
this.paused = false;
|
|
135
|
-
void this.unlock()
|
|
172
|
+
void this.unlock().then(() => {
|
|
173
|
+
if (!this.paused && !this.disposed && this.context.state === "running") {
|
|
174
|
+
for (const stream of this.streamed.values())
|
|
175
|
+
stream.voice.resume();
|
|
176
|
+
for (const voice of this.streamingFades.keys())
|
|
177
|
+
voice.resume();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
136
180
|
}
|
|
137
181
|
sync(snapshot) {
|
|
138
182
|
if (this.disposed)
|
|
@@ -154,23 +198,115 @@ export class GameAudioPlayer {
|
|
|
154
198
|
if (this.desiredMusic?.voiceId !== next?.voiceId || this.desiredMusic?.startTick !== next?.startTick ||
|
|
155
199
|
this.desiredMusic?.assetId !== next?.assetId || this.desiredMusic?.volume !== next?.volume ||
|
|
156
200
|
this.desiredMusic?.fadeInTicks !== next?.fadeInTicks || this.desiredMusic?.fadeOutTicks !== next?.fadeOutTicks) {
|
|
157
|
-
if (this.desiredMusic)
|
|
158
|
-
this.
|
|
201
|
+
if (this.desiredMusic) {
|
|
202
|
+
const outgoing = this.streamed.get(this.desiredMusic.voiceId);
|
|
203
|
+
const decoded = this.voices.get(this.desiredMusic.voiceId);
|
|
204
|
+
const incomingStream = next && this.options.scenes?.find(scene => scene.id === snapshot.sceneId)?.music?.streaming;
|
|
205
|
+
if (incomingStream && (outgoing || decoded)) {
|
|
206
|
+
if (decoded) {
|
|
207
|
+
this.voices.delete(decoded.id);
|
|
208
|
+
this.fading.add(decoded);
|
|
209
|
+
this.deferredDecodedFades.set(decoded, this.desiredMusic.fadeOutTicks);
|
|
210
|
+
}
|
|
211
|
+
this.pending.delete(this.desiredMusic.voiceId);
|
|
212
|
+
if (outgoing) {
|
|
213
|
+
this.streamed.delete(this.desiredMusic.voiceId);
|
|
214
|
+
this.failedStreams.delete(this.desiredMusic.voiceId);
|
|
215
|
+
this.streamingFades.set(outgoing.voice, outgoing.assetId);
|
|
216
|
+
this.deferredFades.set(outgoing.voice, this.desiredMusic.fadeOutTicks);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.stop(this.desiredMusic.voiceId, this.desiredMusic.fadeOutTicks);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!next) {
|
|
224
|
+
this.finishDeferredFades();
|
|
225
|
+
}
|
|
159
226
|
this.desiredMusic = next;
|
|
160
227
|
}
|
|
161
228
|
this.startDesiredMusic();
|
|
162
229
|
}
|
|
163
230
|
startDesiredMusic() {
|
|
164
231
|
const music = this.desiredMusic;
|
|
165
|
-
if (!music || !this.options.assets[music.assetId] || this.paused || this.context.state !== "running" || this.voices.has(music.voiceId) || this.pending.has(music.voiceId))
|
|
232
|
+
if (!music || !this.options.assets[music.assetId] || this.paused || this.context.state !== "running" || this.voices.has(music.voiceId) || this.streamed.has(music.voiceId) || this.failedStreams.has(music.voiceId) || this.pending.has(music.voiceId))
|
|
166
233
|
return;
|
|
234
|
+
const streaming = this.streamingSettings() !== undefined;
|
|
167
235
|
const token = Symbol();
|
|
168
236
|
this.pending.set(music.voiceId, { token, assetId: music.assetId });
|
|
169
237
|
void this.start(music.voiceId, music.assetId, true, music.volume, music.fadeInTicks, music.fadeOutTicks, token, music.startTick)
|
|
170
|
-
.catch((error) =>
|
|
238
|
+
.catch((error) => {
|
|
239
|
+
if (this.pending.get(music.voiceId)?.token !== token)
|
|
240
|
+
return;
|
|
241
|
+
if (streaming)
|
|
242
|
+
this.failedStreams.add(music.voiceId);
|
|
243
|
+
this.options.status(`Music could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
244
|
+
})
|
|
171
245
|
.finally(() => { if (this.pending.get(music.voiceId)?.token === token)
|
|
172
246
|
this.pending.delete(music.voiceId); });
|
|
173
247
|
}
|
|
248
|
+
streamingSettings() {
|
|
249
|
+
const music = this.options.scenes?.find((scene) => scene.id === this.sceneId)?.music;
|
|
250
|
+
return music?.assetId === this.desiredMusic?.assetId ? music?.streaming : undefined;
|
|
251
|
+
}
|
|
252
|
+
voiceCount() {
|
|
253
|
+
return this.voices.size + this.fading.size + this.streamed.size + this.streamingFades.size;
|
|
254
|
+
}
|
|
255
|
+
makeVoiceRoom() {
|
|
256
|
+
if (this.voiceCount() < MAX_VOICES)
|
|
257
|
+
return true;
|
|
258
|
+
const stream = this.streamingFades.keys().next().value;
|
|
259
|
+
if (stream)
|
|
260
|
+
stream.dispose();
|
|
261
|
+
else {
|
|
262
|
+
const fade = this.fading.values().next().value;
|
|
263
|
+
if (fade) {
|
|
264
|
+
fade.source.stop();
|
|
265
|
+
this.fading.delete(fade);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
const effect = [...this.voices.values()].find((voice) => !voice.loop);
|
|
269
|
+
if (!effect)
|
|
270
|
+
return false;
|
|
271
|
+
this.stop(effect.id, 0);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return this.voiceCount() < MAX_VOICES;
|
|
275
|
+
}
|
|
276
|
+
async startStream(id, assetId, volume, fadeInTicks, logicalStartTick, token, settings) {
|
|
277
|
+
const generation = this.generation;
|
|
278
|
+
const binding = this.options.assets[assetId];
|
|
279
|
+
if (!binding)
|
|
280
|
+
return;
|
|
281
|
+
const url = await this.options.resolveAsset(binding);
|
|
282
|
+
if (this.disposed || this.paused || generation !== this.generation || this.context.state !== "running" ||
|
|
283
|
+
this.pending.get(id)?.token !== token || this.streamed.has(id))
|
|
284
|
+
return;
|
|
285
|
+
if (!url)
|
|
286
|
+
throw new Error("asset URL is unavailable");
|
|
287
|
+
if (!this.makeVoiceRoom())
|
|
288
|
+
return;
|
|
289
|
+
const bus = this.mixer.busFor(assetId, true);
|
|
290
|
+
const voice = new GameStreamingMusicVoice(this.context, url, this.mixer.input(bus), settings, volume, fadeInTicks / this.options.tickRate, () => Math.max(0, this.tick - logicalStartTick) / this.options.tickRate, (message) => {
|
|
291
|
+
if (this.streamed.get(id)?.voice === voice) {
|
|
292
|
+
this.failedStreams.add(id);
|
|
293
|
+
}
|
|
294
|
+
this.options.status(message);
|
|
295
|
+
}, () => {
|
|
296
|
+
this.mixer.voiceEnded(bus);
|
|
297
|
+
if (this.streamed.get(id)?.voice === voice)
|
|
298
|
+
this.streamed.delete(id);
|
|
299
|
+
this.streamingFades.delete(voice);
|
|
300
|
+
this.deferredFades.delete(voice);
|
|
301
|
+
}, () => {
|
|
302
|
+
if (this.streamed.get(id)?.voice === voice) {
|
|
303
|
+
this.finishDeferredFades();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
this.streamed.set(id, { voice, assetId });
|
|
307
|
+
this.mixer.voiceStarted(bus);
|
|
308
|
+
voice.load();
|
|
309
|
+
}
|
|
174
310
|
/**
|
|
175
311
|
* Moves the listener and every spatial voice to a rendered frame. Players call it once per rendered frame with the view
|
|
176
312
|
* built from the interpolated frame, so positions follow what is on screen. Emitters missing from the view keep their last position.
|
|
@@ -251,23 +387,17 @@ export class GameAudioPlayer {
|
|
|
251
387
|
}
|
|
252
388
|
return;
|
|
253
389
|
}
|
|
390
|
+
const streaming = logicalStartTick === undefined ? undefined : this.streamingSettings();
|
|
391
|
+
if (streaming && logicalStartTick !== undefined) {
|
|
392
|
+
await this.startStream(id, assetId, volume, fadeInTicks, logicalStartTick, token, streaming);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
254
395
|
const buffer = await this.buffer(assetId);
|
|
255
396
|
if (!buffer || this.disposed || this.paused || this.context.state !== "running" || generation !== this.generation ||
|
|
256
397
|
this.pending.get(id)?.token !== token || this.voices.has(id))
|
|
257
398
|
return;
|
|
258
|
-
if (this.
|
|
259
|
-
|
|
260
|
-
if (fading) {
|
|
261
|
-
fading.source.stop();
|
|
262
|
-
this.fading.delete(fading);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (this.voices.size + this.fading.size >= MAX_VOICES) {
|
|
266
|
-
const oldestEffect = [...this.voices.values()].find((voice) => !voice.loop);
|
|
267
|
-
if (!oldestEffect)
|
|
268
|
-
return;
|
|
269
|
-
this.stop(oldestEffect.id, 0);
|
|
270
|
-
}
|
|
399
|
+
if (!this.makeVoiceRoom())
|
|
400
|
+
return;
|
|
271
401
|
const source = this.context.createBufferSource();
|
|
272
402
|
const gain = this.context.createGain();
|
|
273
403
|
source.buffer = buffer;
|
|
@@ -291,15 +421,39 @@ export class GameAudioPlayer {
|
|
|
291
421
|
if (this.voices.get(id) === voice)
|
|
292
422
|
this.voices.delete(id);
|
|
293
423
|
this.fading.delete(voice);
|
|
424
|
+
this.deferredDecodedFades.delete(voice);
|
|
294
425
|
source.disconnect();
|
|
295
426
|
gain.disconnect();
|
|
296
427
|
output.release();
|
|
297
428
|
};
|
|
298
429
|
const offset = logicalStartTick === undefined ? 0 : Math.max(0, this.tick - logicalStartTick) / this.options.tickRate;
|
|
299
430
|
source.start(0, loop && buffer.duration > 0 ? offset % buffer.duration : 0);
|
|
431
|
+
if (logicalStartTick !== undefined) {
|
|
432
|
+
this.finishDeferredFades();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
finishDeferredFades() {
|
|
436
|
+
const outgoing = [...this.deferredFades];
|
|
437
|
+
this.deferredFades.clear();
|
|
438
|
+
for (const [voice, ticks] of outgoing) {
|
|
439
|
+
voice.stop(ticks / this.options.tickRate);
|
|
440
|
+
}
|
|
441
|
+
for (const [voice, ticks] of this.deferredDecodedFades) {
|
|
442
|
+
if (this.fading.has(voice)) {
|
|
443
|
+
stopGameVoice(voice, this.context, ticks, this.options.tickRate, this.fading);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
this.deferredDecodedFades.clear();
|
|
300
447
|
}
|
|
301
448
|
stop(id, fadeTicks) {
|
|
302
449
|
this.pending.delete(id);
|
|
450
|
+
this.failedStreams.delete(id);
|
|
451
|
+
const stream = this.streamed.get(id);
|
|
452
|
+
if (stream) {
|
|
453
|
+
this.streamed.delete(id);
|
|
454
|
+
this.streamingFades.set(stream.voice, stream.assetId);
|
|
455
|
+
stream.voice.stop(fadeTicks / this.options.tickRate);
|
|
456
|
+
}
|
|
303
457
|
const voice = this.voices.get(id);
|
|
304
458
|
if (!voice)
|
|
305
459
|
return;
|
|
@@ -309,11 +463,17 @@ export class GameAudioPlayer {
|
|
|
309
463
|
reset(snapshot) {
|
|
310
464
|
this.generation += 1;
|
|
311
465
|
this.pending.clear();
|
|
466
|
+
this.failedStreams.clear();
|
|
467
|
+
for (const id of this.streamed.keys())
|
|
468
|
+
this.stop(id, 0);
|
|
469
|
+
for (const voice of this.streamingFades.keys())
|
|
470
|
+
voice.dispose();
|
|
312
471
|
for (const id of this.voices.keys())
|
|
313
472
|
this.stop(id, 0);
|
|
314
473
|
for (const voice of this.fading)
|
|
315
474
|
voice.source.stop();
|
|
316
475
|
this.fading.clear();
|
|
476
|
+
this.deferredDecodedFades.clear();
|
|
317
477
|
this.desiredMusic = null;
|
|
318
478
|
this.mixer.reset(snapshot.sceneId);
|
|
319
479
|
this.sync(snapshot);
|
|
@@ -324,11 +484,17 @@ export class GameAudioPlayer {
|
|
|
324
484
|
this.disposed = true;
|
|
325
485
|
this.generation += 1;
|
|
326
486
|
this.pending.clear();
|
|
487
|
+
this.failedStreams.clear();
|
|
488
|
+
for (const id of this.streamed.keys())
|
|
489
|
+
this.stop(id, 0);
|
|
490
|
+
for (const voice of this.streamingFades.keys())
|
|
491
|
+
voice.dispose();
|
|
327
492
|
for (const id of this.voices.keys())
|
|
328
493
|
this.stop(id, 0);
|
|
329
494
|
for (const voice of this.fading)
|
|
330
495
|
voice.source.stop();
|
|
331
496
|
this.fading.clear();
|
|
497
|
+
this.deferredDecodedFades.clear();
|
|
332
498
|
this.buffers.clear();
|
|
333
499
|
this.mixer.dispose();
|
|
334
500
|
void this.context.close();
|