@rydr/game-sdk 3.11.0 → 3.13.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 (81) hide show
  1. package/README.md +9 -1
  2. package/dist/client/PlatformClient.d.ts +17 -4
  3. package/dist/client/PlatformClient.d.ts.map +1 -1
  4. package/dist/client/PlatformClient.js +1 -1
  5. package/dist/client/PlatformClient.js.map +1 -1
  6. package/dist/conversations/voice-samples.d.ts.map +1 -1
  7. package/dist/conversations/voice-samples.js +183 -2
  8. package/dist/conversations/voice-samples.js.map +1 -1
  9. package/dist/conversations/voices.d.ts.map +1 -1
  10. package/dist/conversations/voices.js +5 -3
  11. package/dist/conversations/voices.js.map +1 -1
  12. package/dist/host/PlatformHost.d.ts +4 -2
  13. package/dist/host/PlatformHost.d.ts.map +1 -1
  14. package/dist/host/PlatformHost.js +1 -1
  15. package/dist/host/PlatformHost.js.map +1 -1
  16. package/dist/protocol/boards.d.ts +4 -0
  17. package/dist/protocol/boards.d.ts.map +1 -1
  18. package/dist/protocol/boards.js.map +1 -1
  19. package/dist/protocol/conversations.d.ts +11 -0
  20. package/dist/protocol/conversations.d.ts.map +1 -1
  21. package/dist/protocol/conversations.js +14 -0
  22. package/dist/protocol/conversations.js.map +1 -1
  23. package/dist/protocol/messages.d.ts +7 -1
  24. package/dist/protocol/messages.d.ts.map +1 -1
  25. package/dist/protocol/version.d.ts +2 -2
  26. package/dist/protocol/version.d.ts.map +1 -1
  27. package/dist/protocol/version.js +7 -2
  28. package/dist/protocol/version.js.map +1 -1
  29. package/dist/sound-gallery/index.d.ts +41 -0
  30. package/dist/sound-gallery/index.d.ts.map +1 -0
  31. package/dist/sound-gallery/index.js +1264 -0
  32. package/dist/sound-gallery/index.js.map +1 -0
  33. package/dist/soundboard-editor/index.d.ts +44 -0
  34. package/dist/soundboard-editor/index.d.ts.map +1 -0
  35. package/dist/soundboard-editor/index.js +253 -0
  36. package/dist/soundboard-editor/index.js.map +1 -0
  37. package/dist/sounds/audio-utils.d.ts +16 -0
  38. package/dist/sounds/audio-utils.d.ts.map +1 -0
  39. package/dist/sounds/audio-utils.js +150 -0
  40. package/dist/sounds/audio-utils.js.map +1 -0
  41. package/dist/sounds/index.d.ts +25 -0
  42. package/dist/sounds/index.d.ts.map +1 -0
  43. package/dist/sounds/index.js +22 -0
  44. package/dist/sounds/index.js.map +1 -0
  45. package/dist/sounds/sound-bank.d.ts +73 -0
  46. package/dist/sounds/sound-bank.d.ts.map +1 -0
  47. package/dist/sounds/sound-bank.js +321 -0
  48. package/dist/sounds/sound-bank.js.map +1 -0
  49. package/dist/sounds/soundboard.d.ts +121 -0
  50. package/dist/sounds/soundboard.d.ts.map +1 -0
  51. package/dist/sounds/soundboard.js +154 -0
  52. package/dist/sounds/soundboard.js.map +1 -0
  53. package/dist/sounds/types.d.ts +88 -0
  54. package/dist/sounds/types.d.ts.map +1 -0
  55. package/dist/sounds/types.js +8 -0
  56. package/dist/sounds/types.js.map +1 -0
  57. package/dist/ui/index.d.ts +2 -1
  58. package/dist/ui/index.d.ts.map +1 -1
  59. package/dist/ui/index.js +2 -1
  60. package/dist/ui/index.js.map +1 -1
  61. package/dist/ui/power-race.d.ts +2 -2
  62. package/dist/ui/power-race.d.ts.map +1 -1
  63. package/dist/ui/power-race.js +8 -72
  64. package/dist/ui/power-race.js.map +1 -1
  65. package/dist/ui/showcase/index.d.ts.map +1 -1
  66. package/dist/ui/showcase/index.js +42 -2
  67. package/dist/ui/showcase/index.js.map +1 -1
  68. package/dist/ui/solo-labeled-diamond.d.ts +69 -0
  69. package/dist/ui/solo-labeled-diamond.d.ts.map +1 -0
  70. package/dist/ui/solo-labeled-diamond.js +168 -0
  71. package/dist/ui/solo-labeled-diamond.js.map +1 -0
  72. package/dist/ui/styles.js +11 -2
  73. package/dist/ui/styles.js.map +1 -1
  74. package/dist/voice-gallery/index.d.ts.map +1 -1
  75. package/dist/voice-gallery/index.js +132 -12
  76. package/dist/voice-gallery/index.js.map +1 -1
  77. package/dist/voiceover-editor/index.d.ts +6 -0
  78. package/dist/voiceover-editor/index.d.ts.map +1 -1
  79. package/dist/voiceover-editor/index.js +222 -14
  80. package/dist/voiceover-editor/index.js.map +1 -1
  81. package/package.json +16 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@rydr/game-sdk/sounds` — the game-facing sound-library runtime.
3
+ *
4
+ * A game loads the published library once and plays events by their stable key, through a small
5
+ * built-in mixer (per-category buses + a master limiter):
6
+ *
7
+ * ```ts
8
+ * import { createSoundBank } from "@rydr/game-sdk/sounds";
9
+ * const sounds = createSoundBank({ manifestUrl: LIBRARY_URL });
10
+ * await sounds.load();
11
+ * sounds.play("coin");
12
+ * sounds.setBusVolume("music", -6);
13
+ * ```
14
+ *
15
+ * The library is authored in the platform's admin Sound Gallery and published to R2 as
16
+ * `sounds/library.json`; adding/cropping a sound needs no game redeploy. See the sound-library plan.
17
+ */
18
+ export { createSoundBank } from "./sound-bank.js";
19
+ export type { SoundBank, SoundBankOptions, PlayOptions, PlayHandle } from "./sound-bank.js";
20
+ export { createSoundboard, STANDARD_CUES, STANDARD_BINDINGS, DEFAULT_SOUND_LIBRARY_URL, SOUNDBOARD_COLLECTION, SOUNDBOARD_BINDINGS_ID, } from "./soundboard.js";
21
+ export type { Soundboard, SoundboardOptions, SoundboardDoc, CueDef, CueCatalog, CueBindings, } from "./soundboard.js";
22
+ export { decodeAudio, computePeaks, peakDb, cropBuffer, encodeWav, encodeMp3 } from "./audio-utils.js";
23
+ export { SOUND_CATEGORIES } from "./types.js";
24
+ export type { SoundAsset, SoundClip, SoundCategory, SoundVariation, SoundLoudness, SoundLicense, SoundFolder, SoundLibraryManifest, } from "./types.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sounds/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,MAAM,EACN,UAAU,EACV,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACZ,WAAW,EACX,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `@rydr/game-sdk/sounds` — the game-facing sound-library runtime.
3
+ *
4
+ * A game loads the published library once and plays events by their stable key, through a small
5
+ * built-in mixer (per-category buses + a master limiter):
6
+ *
7
+ * ```ts
8
+ * import { createSoundBank } from "@rydr/game-sdk/sounds";
9
+ * const sounds = createSoundBank({ manifestUrl: LIBRARY_URL });
10
+ * await sounds.load();
11
+ * sounds.play("coin");
12
+ * sounds.setBusVolume("music", -6);
13
+ * ```
14
+ *
15
+ * The library is authored in the platform's admin Sound Gallery and published to R2 as
16
+ * `sounds/library.json`; adding/cropping a sound needs no game redeploy. See the sound-library plan.
17
+ */
18
+ export { createSoundBank } from "./sound-bank.js";
19
+ export { createSoundboard, STANDARD_CUES, STANDARD_BINDINGS, DEFAULT_SOUND_LIBRARY_URL, SOUNDBOARD_COLLECTION, SOUNDBOARD_BINDINGS_ID, } from "./soundboard.js";
20
+ export { decodeAudio, computePeaks, peakDb, cropBuffer, encodeWav, encodeMp3 } from "./audio-utils.js";
21
+ export { SOUND_CATEGORIES } from "./types.js";
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sounds/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `SoundBank` — the game-facing runtime. A game loads the published library once and plays events by
3
+ * their stable key:
4
+ *
5
+ * ```ts
6
+ * const sounds = createSoundBank({ manifestUrl: LIBRARY_URL });
7
+ * await sounds.load();
8
+ * sounds.play("coin");
9
+ * sounds.setBusVolume("music", -6);
10
+ * ```
11
+ *
12
+ * The signal graph is a real (small) mixer: every event routes through its **category bus** gain,
13
+ * then a **master** gain, then a **limiter** (so nothing clips), then the destination. That makes the
14
+ * P2/P3 features additive — variation just jitters the per-voice `detune`/gain that adaptive audio
15
+ * will later drive continuously, and ducking is just a bus-gain ramp.
16
+ *
17
+ * P1 plays a single clip per event with the mixer + limiter wired; the variation knobs
18
+ * (pitch/volume jitter, cooldown, voice-cap, clip pools) are honoured here already but most catalog
19
+ * rows carry one clip and no `variation` until P2.
20
+ */
21
+ import type { SoundAsset, SoundCategory, SoundLibraryManifest } from "./types.js";
22
+ export interface SoundBankOptions {
23
+ /** Public URL of the published `sounds/library.json`. Fetched by {@link SoundBank.load}. */
24
+ manifestUrl?: string;
25
+ /** Provide the manifest directly (tests / preview) instead of fetching one. */
26
+ manifest?: SoundLibraryManifest;
27
+ /** Reuse an existing AudioContext; otherwise one is created lazily on first use. */
28
+ context?: AudioContext;
29
+ /** Insert a master limiter (DynamicsCompressor) so summed sounds never clip. Default true. */
30
+ limiter?: boolean;
31
+ }
32
+ export interface PlayOptions {
33
+ /** One-shot gain offset for this play, in dB (on top of the event's gainDb + jitter). */
34
+ gainDb?: number;
35
+ /** One-shot detune for this play, in cents (on top of the event's pitch jitter). */
36
+ detuneCents?: number;
37
+ /** Loop the clip (mainly for music/ambience). */
38
+ loop?: boolean;
39
+ /** Fires once when a NON-looping clip reaches its natural end. Not called on manual stop().
40
+ * Wired on the stream path (music/ambience) only — the hook a rotation advances on. */
41
+ onEnded?: () => void;
42
+ }
43
+ /** A live sound; `stop()` ends it early. */
44
+ export interface PlayHandle {
45
+ readonly key: string;
46
+ stop(): void;
47
+ }
48
+ export interface SoundBank {
49
+ /** Fetch/adopt the manifest and index it. Safe to call once. */
50
+ load(): Promise<void>;
51
+ /** Warm the decoded-buffer cache for these keys (or every non-stream event if omitted). */
52
+ preload(keys?: string[]): Promise<void>;
53
+ /** Play an event by key. Returns null if unknown, muted by cooldown, or audio is unavailable. */
54
+ play(key: string, opts?: PlayOptions): PlayHandle | null;
55
+ /** Stop every live voice of an event. */
56
+ stop(key: string): void;
57
+ /** Set a category bus level, in dB (0 = unity). */
58
+ setBusVolume(category: SoundCategory, db: number): void;
59
+ /** Set the master level, in dB. */
60
+ setMasterVolume(db: number): void;
61
+ /** Ramp one bus down by `db` over `ms` (e.g. duck "music" while "voice" speaks). */
62
+ duck(category: SoundCategory, db: number, ms?: number): void;
63
+ /** Undo a {@link duck} on a bus. */
64
+ unduck(category: SoundCategory, ms?: number): void;
65
+ /** All known event keys. */
66
+ keys(): string[];
67
+ /** The raw catalog (for a game that wants to browse/preview). */
68
+ assets(): SoundAsset[];
69
+ /** Tear down: stop all voices and close the context if this bank created it. */
70
+ dispose(): void;
71
+ }
72
+ export declare function createSoundBank(opts?: SoundBankOptions): SoundBank;
73
+ //# sourceMappingURL=sound-bank.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sound-bank.d.ts","sourceRoot":"","sources":["../../src/sounds/sound-bank.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EAEb,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,gBAAgB;IAC/B,4FAA4F;IAC5F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,oFAAoF;IACpF,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;4FACwF;IACxF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,IAAI,IAAI,IAAI,CAAC;CACd;AA0CD,MAAM,WAAW,SAAS;IACxB,gEAAgE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,2FAA2F;IAC3F,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,iGAAiG;IACjG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;IACzD,yCAAyC;IACzC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mDAAmD;IACnD,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,mCAAmC;IACnC,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,oFAAoF;IACpF,IAAI,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7D,oCAAoC;IACpC,MAAM,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,4BAA4B;IAC5B,IAAI,IAAI,MAAM,EAAE,CAAC;IACjB,iEAAiE;IACjE,MAAM,IAAI,UAAU,EAAE,CAAC;IACvB,gFAAgF;IAChF,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,IAAI,GAAE,gBAAqB,GAAG,SAAS,CA+QtE"}
@@ -0,0 +1,321 @@
1
+ import { SOUND_CATEGORIES } from "./types.js";
2
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
3
+ const dbToGain = (db) => Math.pow(10, db / 20);
4
+ /** Uniform in [-mag, +mag]. Deterministic-free; variation only needs perceptual jitter. */
5
+ const jitter = (mag) => (mag > 0 ? (Math.random() * 2 - 1) * mag : 0);
6
+ /**
7
+ * A shuffle-bag: deals every item once (shuffled) before repeating, and never repeats the last item
8
+ * across a reshuffle — so an N-clip event cycles through all N with no immediate repeat. Beats
9
+ * `Math.random()` picking, which is what makes repeated SFX sound repetitive.
10
+ */
11
+ class Bag {
12
+ size;
13
+ order = [];
14
+ last = -1;
15
+ constructor(size) {
16
+ this.size = size;
17
+ }
18
+ next() {
19
+ if (this.size <= 1)
20
+ return 0;
21
+ if (this.order.length === 0) {
22
+ this.order = Array.from({ length: this.size }, (_, i) => i);
23
+ for (let i = this.order.length - 1; i > 0; i--) {
24
+ const j = Math.floor(Math.random() * (i + 1));
25
+ const a = this.order[i];
26
+ this.order[i] = this.order[j];
27
+ this.order[j] = a;
28
+ }
29
+ if (this.order[0] === this.last && this.order.length > 1) {
30
+ const a = this.order[0];
31
+ this.order[0] = this.order[1];
32
+ this.order[1] = a;
33
+ }
34
+ }
35
+ const idx = this.order.pop();
36
+ this.last = idx;
37
+ return idx;
38
+ }
39
+ }
40
+ export function createSoundBank(opts = {}) {
41
+ const ownsContext = !opts.context;
42
+ let ctx = opts.context ?? null;
43
+ let master = null;
44
+ const buses = new Map();
45
+ const busTargetDb = new Map(); // pre-duck level, so unduck returns here
46
+ const events = new Map();
47
+ const bags = new Map();
48
+ const lastPlayedAt = new Map();
49
+ const live = new Map();
50
+ const bufferCache = new Map();
51
+ let assetList = [];
52
+ function ensureGraph() {
53
+ if (ctx && master)
54
+ return ctx;
55
+ const AC = typeof window !== "undefined"
56
+ ? window.AudioContext ?? window.webkitAudioContext
57
+ : undefined;
58
+ if (!ctx) {
59
+ if (!AC)
60
+ return null;
61
+ ctx = new AC();
62
+ }
63
+ master = ctx.createGain();
64
+ let tail = master;
65
+ if (opts.limiter !== false) {
66
+ const lim = ctx.createDynamicsCompressor();
67
+ lim.threshold.value = -3;
68
+ lim.knee.value = 0;
69
+ lim.ratio.value = 20;
70
+ lim.attack.value = 0.002;
71
+ lim.release.value = 0.15;
72
+ master.connect(lim);
73
+ tail = lim;
74
+ }
75
+ tail.connect(ctx.destination);
76
+ for (const cat of SOUND_CATEGORIES) {
77
+ const g = ctx.createGain();
78
+ g.connect(master);
79
+ buses.set(cat, g);
80
+ busTargetDb.set(cat, 0);
81
+ }
82
+ return ctx;
83
+ }
84
+ function busFor(cat) {
85
+ ensureGraph();
86
+ return buses.get(cat) ?? null;
87
+ }
88
+ async function decode(url) {
89
+ const cached = bufferCache.get(url);
90
+ if (cached)
91
+ return cached;
92
+ const p = (async () => {
93
+ const c = ensureGraph();
94
+ if (!c)
95
+ return null;
96
+ try {
97
+ const res = await fetch(url);
98
+ if (!res.ok)
99
+ return null;
100
+ const bytes = await res.arrayBuffer();
101
+ return await c.decodeAudioData(bytes);
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ })();
107
+ bufferCache.set(url, p);
108
+ return p;
109
+ }
110
+ function trackVoice(key, voice, maxVoices) {
111
+ const arr = live.get(key) ?? [];
112
+ arr.push(voice);
113
+ if (maxVoices && arr.length > maxVoices) {
114
+ const stolen = arr.shift();
115
+ try {
116
+ stolen?.stop();
117
+ }
118
+ catch {
119
+ /* already ended */
120
+ }
121
+ }
122
+ live.set(key, arr);
123
+ }
124
+ function untrackVoice(key, voice) {
125
+ const arr = live.get(key);
126
+ if (!arr)
127
+ return;
128
+ const i = arr.indexOf(voice);
129
+ if (i >= 0)
130
+ arr.splice(i, 1);
131
+ }
132
+ function pickClip(asset) {
133
+ if (asset.clips.length === 0)
134
+ return null;
135
+ if (asset.clips.length === 1)
136
+ return asset.clips[0] ?? null;
137
+ let bag = bags.get(asset.key);
138
+ if (!bag) {
139
+ bag = new Bag(asset.clips.length);
140
+ bags.set(asset.key, bag);
141
+ }
142
+ return asset.clips[bag.next()] ?? null;
143
+ }
144
+ function playStream(asset, clip, bus, c, o) {
145
+ if (typeof Audio === "undefined")
146
+ return null;
147
+ const media = new Audio();
148
+ media.crossOrigin = "anonymous";
149
+ media.src = clip.url;
150
+ media.loop = o.loop ?? (asset.category === "music" || asset.category === "ambience");
151
+ const src = c.createMediaElementSource(media);
152
+ const g = c.createGain();
153
+ g.gain.value = dbToGain((asset.gainDb ?? 0) + (o.gainDb ?? 0) + jitter(asset.variation?.volumeDb ?? 0));
154
+ src.connect(g).connect(bus);
155
+ void media.play().catch(() => undefined);
156
+ const voice = {
157
+ stop: () => {
158
+ media.pause();
159
+ try {
160
+ src.disconnect();
161
+ }
162
+ catch {
163
+ /* noop */
164
+ }
165
+ untrackVoice(asset.key, voice);
166
+ },
167
+ };
168
+ // "ended" fires only on natural completion of a non-looping clip — manual stop() calls
169
+ // media.pause(), which does not fire it. So this cleanly surfaces the end-of-track hook a
170
+ // rotation advances on (see PlayOptions.onEnded).
171
+ media.addEventListener("ended", () => {
172
+ voice.stop();
173
+ o.onEnded?.();
174
+ });
175
+ trackVoice(asset.key, voice, asset.variation?.maxVoices);
176
+ return { key: asset.key, stop: voice.stop };
177
+ }
178
+ return {
179
+ async load() {
180
+ let manifest = opts.manifest ?? null;
181
+ if (!manifest && opts.manifestUrl) {
182
+ try {
183
+ const res = await fetch(opts.manifestUrl, { cache: "no-cache" });
184
+ if (res.ok)
185
+ manifest = (await res.json());
186
+ }
187
+ catch {
188
+ manifest = null;
189
+ }
190
+ }
191
+ assetList = manifest?.sounds ?? [];
192
+ events.clear();
193
+ for (const a of assetList)
194
+ events.set(a.key, a);
195
+ },
196
+ async preload(keys) {
197
+ const targets = (keys ? keys.map((k) => events.get(k)) : assetList).filter((a) => !!a && !a.stream);
198
+ await Promise.all(targets.flatMap((a) => a.clips.map((cl) => decode(cl.url))));
199
+ },
200
+ play(key, o = {}) {
201
+ const asset = events.get(key);
202
+ if (!asset)
203
+ return null;
204
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
205
+ const cd = asset.variation?.cooldownMs ?? 0;
206
+ if (cd > 0 && now - (lastPlayedAt.get(key) ?? -Infinity) < cd)
207
+ return null;
208
+ lastPlayedAt.set(key, now);
209
+ const c = ensureGraph();
210
+ if (!c)
211
+ return null;
212
+ if (c.state === "suspended")
213
+ void c.resume().catch(() => undefined);
214
+ const bus = busFor(asset.category);
215
+ if (!bus)
216
+ return null;
217
+ const clip = pickClip(asset);
218
+ if (!clip)
219
+ return null;
220
+ if (asset.stream)
221
+ return playStream(asset, clip, bus, c, o);
222
+ // Decode-to-memory path (SFX): fire-and-forget; the buffer is cached after first play.
223
+ const handle = { stopped: false, voice: null };
224
+ void decode(clip.url).then((buf) => {
225
+ if (!buf || handle.stopped)
226
+ return;
227
+ const src = c.createBufferSource();
228
+ src.buffer = buf;
229
+ src.loop = o.loop ?? false;
230
+ src.detune.value = jitter(asset.variation?.pitchCents ?? 0) + (o.detuneCents ?? 0);
231
+ const g = c.createGain();
232
+ g.gain.value = dbToGain((asset.gainDb ?? 0) + (o.gainDb ?? 0) + jitter(asset.variation?.volumeDb ?? 0));
233
+ src.connect(g).connect(bus);
234
+ const voice = {
235
+ stop: () => {
236
+ try {
237
+ src.stop();
238
+ }
239
+ catch {
240
+ /* already stopped */
241
+ }
242
+ untrackVoice(asset.key, voice);
243
+ },
244
+ };
245
+ // TODO(onEnded): buffer path — "ended" here also fires on manual src.stop(), so wiring
246
+ // o.onEnded here would need a `stopped` guard. Beds stream, so this path never needs it.
247
+ src.addEventListener("ended", () => untrackVoice(asset.key, voice));
248
+ trackVoice(asset.key, voice, asset.variation?.maxVoices);
249
+ handle.voice = voice;
250
+ src.start();
251
+ });
252
+ return {
253
+ key,
254
+ stop: () => {
255
+ handle.stopped = true;
256
+ handle.voice?.stop();
257
+ },
258
+ };
259
+ },
260
+ stop(key) {
261
+ const arr = live.get(key);
262
+ if (!arr)
263
+ return;
264
+ for (const v of [...arr]) {
265
+ try {
266
+ v.stop();
267
+ }
268
+ catch {
269
+ /* noop */
270
+ }
271
+ }
272
+ live.set(key, []);
273
+ },
274
+ setBusVolume(category, db) {
275
+ busTargetDb.set(category, db);
276
+ const g = busFor(category);
277
+ if (g && ctx)
278
+ g.gain.setTargetAtTime(dbToGain(db), ctx.currentTime, 0.01);
279
+ },
280
+ setMasterVolume(db) {
281
+ ensureGraph();
282
+ if (master && ctx)
283
+ master.gain.setTargetAtTime(dbToGain(clamp(db, -60, 6)), ctx.currentTime, 0.01);
284
+ },
285
+ duck(category, db, ms = 120) {
286
+ const g = busFor(category);
287
+ if (!g || !ctx)
288
+ return;
289
+ const target = (busTargetDb.get(category) ?? 0) - Math.abs(db);
290
+ g.gain.setTargetAtTime(dbToGain(target), ctx.currentTime, Math.max(0.005, ms / 1000 / 3));
291
+ },
292
+ unduck(category, ms = 250) {
293
+ const g = busFor(category);
294
+ if (!g || !ctx)
295
+ return;
296
+ g.gain.setTargetAtTime(dbToGain(busTargetDb.get(category) ?? 0), ctx.currentTime, Math.max(0.005, ms / 1000 / 3));
297
+ },
298
+ keys: () => [...events.keys()],
299
+ assets: () => assetList.slice(),
300
+ dispose() {
301
+ for (const key of live.keys()) {
302
+ for (const v of live.get(key) ?? []) {
303
+ try {
304
+ v.stop();
305
+ }
306
+ catch {
307
+ /* noop */
308
+ }
309
+ }
310
+ }
311
+ live.clear();
312
+ bufferCache.clear();
313
+ if (ownsContext && ctx)
314
+ void ctx.close().catch(() => undefined);
315
+ ctx = null;
316
+ master = null;
317
+ buses.clear();
318
+ },
319
+ };
320
+ }
321
+ //# sourceMappingURL=sound-bank.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sound-bank.js","sourceRoot":"","sources":["../../src/sounds/sound-bank.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * `Soundboard` — the game-facing **cue layer** over {@link SoundBank}.
3
+ *
4
+ * A `SoundBank` plays by raw library key (`bank.play("anime_sword_slash_fatality")`). That couples
5
+ * every game to the catalog's exact keys and re-implements the same glue (bus routing, cooldown,
6
+ * volume/mute, preload) in each game. A `Soundboard` adds the missing middle: a small set of stable,
7
+ * **semantic cues** (`"nav.move"`, `"record.pb"`, …) that a game fires by intent, decoupled from
8
+ * which sound is bound to them — the same code-owns-structure / data-owns-binding split as
9
+ * `defineConversation` + the voice-over editor.
10
+ *
11
+ * The SDK ships {@link STANDARD_CUES} (+ {@link STANDARD_BINDINGS} to reserved `sys.*` platform
12
+ * keys) so every game's menus/records sound consistent out of the box; a game overrides any cue's
13
+ * binding (or adds its own cues) and the rest keep the platform defaults:
14
+ *
15
+ * ```ts
16
+ * import { createSoundboard, STANDARD_CUES } from "@rydr/game-sdk/sounds";
17
+ * const sounds = createSoundboard({
18
+ * context, // share the game's AudioContext
19
+ * cues: { "run.fail": { category: "sfx" } }, // game-specific cue (merged over STANDARD_CUES)
20
+ * bindings: { "record.wr": "chorale_spell_heal_holy" }, // override one default binding
21
+ * });
22
+ * await sounds.load(); // fetch the published library + preload every bound cue
23
+ * sounds.cue("nav.move"); // fail-silent: unknown/unbound/unpublished key just no-ops
24
+ * sounds.setUiVolume(-6); // player setting → the "ui" bus
25
+ * ```
26
+ *
27
+ * Fail-silent by construction: a cue with no binding, an unpublished key, or an unavailable
28
+ * AudioContext is a no-op — so a game can declare and fire cues before their sounds exist in the
29
+ * gallery, and ship sounds later with no redeploy.
30
+ */
31
+ import { type SoundBank, type PlayHandle, type PlayOptions } from "./sound-bank.js";
32
+ import type { SoundCategory, SoundLibraryManifest } from "./types.js";
33
+ /** The published, platform-wide sound library (R2 `sounds/library.json`). Same host as the TTS cache.
34
+ * A game may pass its own `manifestUrl`, but every game shares this one library by default. */
35
+ export declare const DEFAULT_SOUND_LIBRARY_URL = "https://pub-3e9a1541a2e9454596e0f4148c48adef.r2.dev/sounds/library.json";
36
+ /**
37
+ * A semantic sound event. The `category` picks the mixer bus (so UI cues duck/mute independently of
38
+ * gameplay) and groups the cue for preload; `cooldownMs` throttles retriggers at the cue level (so a
39
+ * fast menu scroll can't machine-gun `nav.move` regardless of the bound asset's own variation).
40
+ */
41
+ export interface CueDef {
42
+ /** Mixer bus + intent. Author the bound sound under the same category in the gallery. */
43
+ category: SoundCategory;
44
+ /** Drop retriggers faster than this many ms. Enforced per-cue, on top of the asset's own cooldown. */
45
+ cooldownMs?: number;
46
+ /** Static per-play trim in dB applied to every fire of this cue (on top of the asset's gainDb). */
47
+ gainDb?: number;
48
+ /** Human note — what the cue means / when it fires (for a future binding editor). */
49
+ description?: string;
50
+ }
51
+ /** A named set of cues. */
52
+ export type CueCatalog = Record<string, CueDef>;
53
+ /** Maps a cue id → a library sound key. A missing entry (or unpublished key) makes the cue silent. */
54
+ export type CueBindings = Record<string, string>;
55
+ /**
56
+ * The standard cues every RYDR game shares, so menus and record moments feel the same platform-wide.
57
+ * A game inherits these and overrides only what it wants. Navigation cues are `ui` (subtle,
58
+ * cooldown-gated); the fanfares are one-shots with a longer cooldown so they can't stack.
59
+ */
60
+ export declare const STANDARD_CUES: CueCatalog;
61
+ /**
62
+ * Default bindings for {@link STANDARD_CUES}, pointing at reserved `sys.*` keys the platform sound
63
+ * gallery is expected to publish. Until those keys exist the standard cues are silent (fail-silent),
64
+ * and a game can bind them to its own catalog keys in the meantime.
65
+ */
66
+ export declare const STANDARD_BINDINGS: CueBindings;
67
+ /**
68
+ * Where a game persists its cue→key bindings: a single `shared` gamedata doc (admin-written,
69
+ * public-read — like conversations/songs), edited in the soundboard editor and read at runtime to
70
+ * override the code defaults. The platform namespaces gamedata by game slug, so one collection + one
71
+ * doc id per game is enough. The doc's `data` is a {@link SoundboardDoc}.
72
+ */
73
+ export declare const SOUNDBOARD_COLLECTION = "soundboard";
74
+ export declare const SOUNDBOARD_BINDINGS_ID = "bindings";
75
+ /** Shape of the persisted bindings doc's `data`. */
76
+ export interface SoundboardDoc {
77
+ bindings: CueBindings;
78
+ }
79
+ export interface SoundboardOptions {
80
+ /** Published library URL. Defaults to {@link DEFAULT_SOUND_LIBRARY_URL}. */
81
+ manifestUrl?: string;
82
+ /** Provide the manifest directly (tests/preview) instead of fetching. */
83
+ manifest?: SoundLibraryManifest;
84
+ /** Reuse the game's AudioContext (so cues share its unlock gesture + clock). */
85
+ context?: AudioContext;
86
+ /** Master limiter, forwarded to the {@link SoundBank}. Default true. */
87
+ limiter?: boolean;
88
+ /** Game cues, merged over {@link STANDARD_CUES} (a same-id entry overrides). */
89
+ cues?: CueCatalog;
90
+ /** Game bindings, merged over {@link STANDARD_BINDINGS} (a same-id entry overrides). */
91
+ bindings?: CueBindings;
92
+ }
93
+ export interface Soundboard {
94
+ /** Fetch/adopt the library and warm the decoded-buffer cache for every bound cue. */
95
+ load(): Promise<void>;
96
+ /** Fire a cue by id. No-op (returns null) if unbound, cooling down, unpublished, or audio is off. */
97
+ cue(id: string, opts?: PlayOptions): PlayHandle | null;
98
+ /** Stop every live voice of a cue. */
99
+ stop(id: string): void;
100
+ /** Master level in dB (0 = unity). Mutes everything at the bottom of the graph. */
101
+ setMasterVolume(db: number): void;
102
+ /** UI-bus level in dB — the volume of `ui` cues, independent of gameplay `sfx`/`music`. */
103
+ setUiVolume(db: number): void;
104
+ /** Mute/unmute everything (master → -60 dB / back to the last set master level). */
105
+ mute(on: boolean): void;
106
+ /** Bind a cue to a library key live; `null`/`""` clears it (cue goes silent). Re-warms the cache. */
107
+ setBinding(cueId: string, key: string | null): void;
108
+ /** Merge a batch of bindings live — e.g. store overrides loaded after {@link load}, or an editor
109
+ * preview. A `null`/`""` value clears that cue. Re-warms the buffer cache. */
110
+ setBindings(map: CueBindings): void;
111
+ /** The merged cue catalog. */
112
+ readonly catalog: CueCatalog;
113
+ /** The merged cue→key bindings (live: reflects {@link setBinding}/{@link setBindings}). */
114
+ readonly bindings: CueBindings;
115
+ /** The underlying bank, for raw play-by-key / bus control. */
116
+ readonly bank: SoundBank;
117
+ /** Tear down the bank. */
118
+ dispose(): void;
119
+ }
120
+ export declare function createSoundboard(opts?: SoundboardOptions): Soundboard;
121
+ //# sourceMappingURL=soundboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soundboard.d.ts","sourceRoot":"","sources":["../../src/sounds/soundboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACrG,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEtE;gGACgG;AAChG,eAAO,MAAM,yBAAyB,4EACqC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,WAAW,MAAM;IACrB,yFAAyF;IACzF,QAAQ,EAAE,aAAa,CAAC;IACxB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2BAA2B;AAC3B,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAChD,sGAAsG;AACtG,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,UAS3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAS/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAClD,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gFAAgF;IAChF,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,qFAAqF;IACrF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,qGAAqG;IACrG,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;IACvD,sCAAsC;IACtC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mFAAmF;IACnF,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oFAAoF;IACpF,IAAI,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,qGAAqG;IACrG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACpD;mFAC+E;IAC/E,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,8BAA8B;IAC9B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,0BAA0B;IAC1B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,iBAAsB,GAAG,UAAU,CAmFzE"}