@zkmake/sound-scape 0.1.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.
@@ -0,0 +1,255 @@
1
+ import { n as createWarnOnce, r as sources, t as createDucker } from "./adapter-DHuOy8V8.js";
2
+ //#region src/web-audio/index.ts
3
+ /**
4
+ * @zkmake/sound-scape/web-audio: a `SoundscapeAdapter` on the Web Audio API, no dependencies.
5
+ * Graph: each voice is source → gain → stereo pan → bus gain (level, mute) → duck gain → out.
6
+ * Gain above 1 works. The context is created on first use, never at import, and resumes on the
7
+ * first pointer or key press (browsers start it suspended).
8
+ */
9
+ /** Gain ramps start from where the param is now, so a fade that interrupts a fade doesn't jump. */
10
+ const rampTo = (param, target, now, ms) => {
11
+ param.cancelScheduledValues(now);
12
+ param.setValueAtTime(param.value, now);
13
+ param.linearRampToValueAtTime(target, now + Math.max(ms, 8) / 1e3);
14
+ };
15
+ const MIME = {
16
+ aac: "audio/mp4",
17
+ flac: "audio/flac",
18
+ m4a: "audio/mp4",
19
+ mp3: "audio/mpeg",
20
+ mp4: "audio/mp4",
21
+ oga: "audio/ogg",
22
+ ogg: "audio/ogg",
23
+ opus: "audio/ogg; codecs=\"opus\"",
24
+ wav: "audio/wav",
25
+ webm: "audio/webm"
26
+ };
27
+ /** Sources the browser says it may play, in the order given; unknown extensions are kept. */
28
+ const playable = (urls) => {
29
+ if (typeof document === "undefined") return urls;
30
+ const probe = document.createElement("audio");
31
+ return urls.filter((url) => {
32
+ const extension = /\.([a-z0-9]+)(?:[?#]|$)/i.exec(url)?.[1]?.toLowerCase();
33
+ const mime = extension ? MIME[extension] : void 0;
34
+ return mime === void 0 || probe.canPlayType(mime) !== "";
35
+ });
36
+ };
37
+ const createWebAudioBus = (samples, opts = {}) => {
38
+ const warn = createWarnOnce("sound-scape/web-audio");
39
+ const buffers = /* @__PURE__ */ new Map();
40
+ const decoded = /* @__PURE__ */ new Map();
41
+ const voices = /* @__PURE__ */ new Map();
42
+ let nextId = 1;
43
+ let level = opts.level ?? 1;
44
+ let muted = opts.muted ?? false;
45
+ let graph = null;
46
+ let detachUnlock = null;
47
+ const attachUnlock = (context) => {
48
+ const events = [
49
+ "pointerdown",
50
+ "keydown",
51
+ "touchend"
52
+ ];
53
+ const resume = () => {
54
+ if (context.state !== "running" && context.state !== "closed") context.resume();
55
+ };
56
+ const onState = () => {
57
+ if (context.state === "running" || context.state === "closed") events.forEach((event) => globalThis.removeEventListener(event, resume, true));
58
+ else events.forEach((event) => globalThis.addEventListener(event, resume, true));
59
+ };
60
+ context.addEventListener("statechange", onState);
61
+ onState();
62
+ return () => {
63
+ context.removeEventListener("statechange", onState);
64
+ events.forEach((event) => globalThis.removeEventListener(event, resume, true));
65
+ };
66
+ };
67
+ const ensure = () => {
68
+ if (graph) return graph;
69
+ const context = opts.context ?? new AudioContext();
70
+ const bus = context.createGain();
71
+ const duck = context.createGain();
72
+ bus.gain.value = muted ? 0 : level;
73
+ bus.connect(duck);
74
+ duck.connect(opts.destination ?? context.destination);
75
+ graph = {
76
+ context,
77
+ bus,
78
+ duck
79
+ };
80
+ if ((opts.autoUnlock ?? true) && typeof globalThis.addEventListener === "function") detachUnlock = attachUnlock(context);
81
+ return graph;
82
+ };
83
+ const decode = async (context, sample, urls) => {
84
+ for (const url of playable(urls)) try {
85
+ const response = await fetch(url);
86
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
87
+ return await context.decodeAudioData(await response.arrayBuffer());
88
+ } catch (error) {
89
+ warn(`${sample} (${url})`, `failed to load (${String(error)})`);
90
+ }
91
+ warn(sample, "no source played; it stays silent");
92
+ return null;
93
+ };
94
+ const bufferFor = (sample) => {
95
+ const known = buffers.get(sample);
96
+ if (known) return known;
97
+ const source = samples[sample];
98
+ if (source === void 0) {
99
+ warn(sample, "not in the sample map; the fire is skipped");
100
+ return null;
101
+ }
102
+ const pending = decode(ensure().context, sample, sources(source)).then((buffer) => {
103
+ decoded.set(sample, buffer);
104
+ return buffer;
105
+ });
106
+ buffers.set(sample, pending);
107
+ return pending;
108
+ };
109
+ const startVoice = (id, voice, buffer, { loop, rate, fadeInMs }) => {
110
+ const { context } = ensure();
111
+ const source = context.createBufferSource();
112
+ const now = context.currentTime;
113
+ source.buffer = buffer;
114
+ source.loop = loop;
115
+ source.playbackRate.value = rate;
116
+ source.connect(voice.gain);
117
+ voice.source = source;
118
+ source.onended = () => {
119
+ voice.gain.disconnect();
120
+ voice.pan.disconnect();
121
+ if (voices.get(id) === voice) {
122
+ voices.delete(id);
123
+ voice.onEnd?.();
124
+ }
125
+ };
126
+ if (fadeInMs > 0) {
127
+ voice.gain.gain.setValueAtTime(0, now);
128
+ voice.gain.gain.linearRampToValueAtTime(voice.base, now + fadeInMs / 1e3);
129
+ } else voice.gain.gain.value = voice.base;
130
+ source.start(now);
131
+ };
132
+ const makeVoice = (base, pan, onEnd) => {
133
+ const { context, bus } = ensure();
134
+ const gain = context.createGain();
135
+ const panner = context.createStereoPanner();
136
+ panner.pan.value = Math.max(-1, Math.min(1, pan));
137
+ gain.connect(panner);
138
+ panner.connect(bus);
139
+ return {
140
+ source: null,
141
+ gain,
142
+ pan: panner,
143
+ base,
144
+ onEnd
145
+ };
146
+ };
147
+ const ducker = createDucker(opts.duck, (gain, fadeMs) => {
148
+ const { context, duck } = ensure();
149
+ rampTo(duck.gain, gain, context.currentTime, fadeMs);
150
+ });
151
+ const stop = (id, fadeOutMs) => {
152
+ const voice = voices.get(id);
153
+ if (!voice) return;
154
+ voices.delete(id);
155
+ const { context } = ensure();
156
+ const now = context.currentTime;
157
+ if (!voice.source) {
158
+ voice.gain.disconnect();
159
+ voice.pan.disconnect();
160
+ return;
161
+ }
162
+ rampTo(voice.gain.gain, 0, now, fadeOutMs);
163
+ voice.source.stop(now + Math.max(fadeOutMs, 8) / 1e3 + .02);
164
+ };
165
+ return {
166
+ get context() {
167
+ return ensure().context;
168
+ },
169
+ playBed(sample, { volume, fadeInMs }) {
170
+ const pending = bufferFor(sample);
171
+ if (!pending) return null;
172
+ const id = nextId++;
173
+ const voice = makeVoice(volume, 0, null);
174
+ voices.set(id, voice);
175
+ pending.then((buffer) => {
176
+ if (buffer && voices.get(id) === voice) startVoice(id, voice, buffer, {
177
+ loop: true,
178
+ rate: 1,
179
+ fadeInMs
180
+ });
181
+ });
182
+ return id;
183
+ },
184
+ playEmitter(sample, { volume, rate, pan, onEnd }) {
185
+ if (muted || !bufferFor(sample)) return null;
186
+ const buffer = decoded.get(sample);
187
+ if (!buffer) return null;
188
+ const id = nextId++;
189
+ const voice = makeVoice(volume, pan, onEnd);
190
+ voices.set(id, voice);
191
+ startVoice(id, voice, buffer, {
192
+ loop: false,
193
+ rate,
194
+ fadeInMs: 0
195
+ });
196
+ return id;
197
+ },
198
+ stop,
199
+ setVolume(id, volume, fadeMs) {
200
+ const voice = voices.get(id);
201
+ if (voice) {
202
+ voice.base = volume;
203
+ rampTo(voice.gain.gain, volume, ensure().context.currentTime, fadeMs);
204
+ }
205
+ },
206
+ async load(loadOpts = {}) {
207
+ const names = Object.keys(samples);
208
+ let settled = 0;
209
+ await Promise.all(names.map(async (name) => {
210
+ await bufferFor(name);
211
+ settled += 1;
212
+ loadOpts.onProgress?.(settled, names.length);
213
+ }));
214
+ },
215
+ duck: ducker.duck,
216
+ get ducked() {
217
+ return ducker.ducked;
218
+ },
219
+ setLevel(next, fadeMs = 0) {
220
+ level = next;
221
+ if (graph && !muted) rampTo(graph.bus.gain, level, graph.context.currentTime, fadeMs);
222
+ },
223
+ setMuted(next) {
224
+ muted = next;
225
+ if (graph) rampTo(graph.bus.gain, muted ? 0 : level, graph.context.currentTime, 30);
226
+ },
227
+ stopAll(fadeOutMs = 0) {
228
+ for (const id of Array.from(voices.keys())) stop(id, fadeOutMs);
229
+ },
230
+ get voices() {
231
+ return voices.size;
232
+ },
233
+ dispose() {
234
+ for (const voice of voices.values()) {
235
+ voice.onEnd = null;
236
+ voice.source?.stop();
237
+ }
238
+ voices.clear();
239
+ ducker.clear();
240
+ detachUnlock?.();
241
+ detachUnlock = null;
242
+ if (graph) {
243
+ graph.duck.disconnect();
244
+ if (!opts.context) graph.context.close();
245
+ }
246
+ graph = null;
247
+ buffers.clear();
248
+ decoded.clear();
249
+ }
250
+ };
251
+ };
252
+ //#endregion
253
+ export { createWebAudioBus };
254
+
255
+ //# sourceMappingURL=web-audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-audio.js","names":[],"sources":["../src/web-audio/index.ts"],"sourcesContent":["/**\n * @zkmake/sound-scape/web-audio: a `SoundscapeAdapter` on the Web Audio API, no dependencies.\n * Graph: each voice is source → gain → stereo pan → bus gain (level, mute) → duck gain → out.\n * Gain above 1 works. The context is created on first use, never at import, and resumes on the\n * first pointer or key press (browsers start it suspended).\n */\nimport {\n createDucker,\n createWarnOnce,\n type DuckOptions,\n type LoadOptions,\n type SampleSource,\n sources,\n type SoundscapeAdapter,\n} from \"../core/adapter.ts\";\n\ntype WebAudioBusOptions = {\n /** Share the game's context. Default: one made on first use, closed on `dispose`. */\n context?: AudioContext;\n /** Where the bus ends up. Default: the context's destination. */\n destination?: AudioNode;\n /** Bus level. Default 1; above 1 is allowed. */\n level?: number;\n muted?: boolean;\n duck?: DuckOptions;\n /** Resume the context on the first pointer or key press, and again after an interruption. Default true. */\n autoUnlock?: boolean;\n};\n\ntype Voice = {\n source: AudioBufferSourceNode | null;\n gain: GainNode;\n pan: StereoPannerNode;\n base: number;\n onEnd: (() => void) | null;\n};\n\n/** Gain ramps start from where the param is now, so a fade that interrupts a fade doesn't jump. */\nconst rampTo = (param: AudioParam, target: number, now: number, ms: number) => {\n param.cancelScheduledValues(now);\n param.setValueAtTime(param.value, now);\n // Never an instant step: 8 ms is below hearing a fade, above hearing a click.\n param.linearRampToValueAtTime(target, now + Math.max(ms, 8) / 1000);\n};\n\nconst MIME: Record<string, string> = {\n aac: \"audio/mp4\",\n flac: \"audio/flac\",\n m4a: \"audio/mp4\",\n mp3: \"audio/mpeg\",\n mp4: \"audio/mp4\",\n oga: \"audio/ogg\",\n ogg: \"audio/ogg\",\n opus: 'audio/ogg; codecs=\"opus\"',\n wav: \"audio/wav\",\n webm: \"audio/webm\",\n};\n\n/** Sources the browser says it may play, in the order given; unknown extensions are kept. */\nconst playable = (urls: readonly string[]) => {\n if (typeof document === \"undefined\") {\n return urls;\n }\n\n const probe = document.createElement(\"audio\");\n\n return urls.filter((url) => {\n const extension = /\\.([a-z0-9]+)(?:[?#]|$)/i.exec(url)?.[1]?.toLowerCase();\n const mime = extension ? MIME[extension] : undefined;\n\n return mime === undefined || probe.canPlayType(mime) !== \"\";\n });\n};\n\nconst createWebAudioBus = <Sample extends string>(\n samples: Record<Sample, SampleSource>,\n opts: WebAudioBusOptions = {},\n): SoundscapeAdapter<number> & {\n /** The context, made on first access. */\n readonly context: AudioContext;\n} => {\n const warn = createWarnOnce(\"sound-scape/web-audio\");\n const buffers = new Map<string, Promise<AudioBuffer | null>>();\n const decoded = new Map<string, AudioBuffer | null>();\n const voices = new Map<number, Voice>();\n let nextId = 1;\n let level = opts.level ?? 1;\n let muted = opts.muted ?? false;\n let graph: { context: AudioContext; bus: GainNode; duck: GainNode } | null = null;\n let detachUnlock: (() => void) | null = null;\n\n const attachUnlock = (context: AudioContext) => {\n const events = [\"pointerdown\", \"keydown\", \"touchend\"] as const;\n const resume = () => {\n if (context.state !== \"running\" && context.state !== \"closed\") {\n void context.resume();\n }\n };\n const onState = () => {\n // Safari reports \"interrupted\" after a call or Siri; the next gesture brings it back.\n if (context.state === \"running\" || context.state === \"closed\") {\n events.forEach((event) => globalThis.removeEventListener(event, resume, true));\n } else {\n events.forEach((event) => globalThis.addEventListener(event, resume, true));\n }\n };\n\n context.addEventListener(\"statechange\", onState);\n onState();\n\n return () => {\n context.removeEventListener(\"statechange\", onState);\n events.forEach((event) => globalThis.removeEventListener(event, resume, true));\n };\n };\n\n const ensure = () => {\n if (graph) {\n return graph;\n }\n\n const context = opts.context ?? new AudioContext();\n const bus = context.createGain();\n const duck = context.createGain();\n\n bus.gain.value = muted ? 0 : level;\n bus.connect(duck);\n duck.connect(opts.destination ?? context.destination);\n graph = { context, bus, duck };\n\n if ((opts.autoUnlock ?? true) && typeof globalThis.addEventListener === \"function\") {\n detachUnlock = attachUnlock(context);\n }\n\n return graph;\n };\n\n const decode = async (context: AudioContext, sample: string, urls: readonly string[]) => {\n for (const url of playable(urls)) {\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n return await context.decodeAudioData(await response.arrayBuffer());\n } catch (error) {\n warn(`${sample} (${url})`, `failed to load (${String(error)})`);\n }\n }\n\n warn(sample, \"no source played; it stays silent\");\n\n return null;\n };\n\n const bufferFor = (sample: string) => {\n const known = buffers.get(sample);\n\n if (known) {\n return known;\n }\n\n const source = (samples as Record<string, SampleSource | undefined>)[sample];\n\n if (source === undefined) {\n warn(sample, \"not in the sample map; the fire is skipped\");\n\n return null;\n }\n\n const pending = decode(ensure().context, sample, sources(source)).then((buffer) => {\n decoded.set(sample, buffer);\n\n return buffer;\n });\n\n buffers.set(sample, pending);\n\n return pending;\n };\n\n const startVoice = (\n id: number,\n voice: Voice,\n buffer: AudioBuffer,\n { loop, rate, fadeInMs }: { loop: boolean; rate: number; fadeInMs: number },\n ) => {\n const { context } = ensure();\n const source = context.createBufferSource();\n const now = context.currentTime;\n\n source.buffer = buffer;\n source.loop = loop;\n source.playbackRate.value = rate;\n source.connect(voice.gain);\n voice.source = source;\n source.onended = () => {\n voice.gain.disconnect();\n voice.pan.disconnect();\n\n if (voices.get(id) === voice) {\n voices.delete(id);\n voice.onEnd?.();\n }\n };\n\n if (fadeInMs > 0) {\n voice.gain.gain.setValueAtTime(0, now);\n voice.gain.gain.linearRampToValueAtTime(voice.base, now + fadeInMs / 1000);\n } else {\n voice.gain.gain.value = voice.base;\n }\n\n source.start(now);\n };\n\n const makeVoice = (base: number, pan: number, onEnd: (() => void) | null): Voice => {\n const { context, bus } = ensure();\n const gain = context.createGain();\n const panner = context.createStereoPanner();\n\n panner.pan.value = Math.max(-1, Math.min(1, pan));\n gain.connect(panner);\n panner.connect(bus);\n\n return { source: null, gain, pan: panner, base, onEnd };\n };\n\n const ducker = createDucker(opts.duck, (gain, fadeMs) => {\n const { context, duck } = ensure();\n\n rampTo(duck.gain, gain, context.currentTime, fadeMs);\n });\n\n const stop = (id: number, fadeOutMs: number) => {\n const voice = voices.get(id);\n\n if (!voice) {\n return;\n }\n\n voices.delete(id);\n\n const { context } = ensure();\n const now = context.currentTime;\n\n if (!voice.source) {\n // A bed still decoding: it never starts.\n voice.gain.disconnect();\n voice.pan.disconnect();\n\n return;\n }\n\n rampTo(voice.gain.gain, 0, now, fadeOutMs);\n voice.source.stop(now + Math.max(fadeOutMs, 8) / 1000 + 0.02);\n };\n\n return {\n get context() {\n return ensure().context;\n },\n\n playBed(sample, { volume, fadeInMs }) {\n const pending = bufferFor(sample);\n\n if (!pending) {\n return null;\n }\n\n const id = nextId++;\n const voice = makeVoice(volume, 0, null);\n\n voices.set(id, voice);\n // Beds may start before their sample has loaded: they come in when it arrives.\n void pending.then((buffer) => {\n if (buffer && voices.get(id) === voice) {\n startVoice(id, voice, buffer, { loop: true, rate: 1, fadeInMs });\n }\n });\n\n return id;\n },\n\n playEmitter(sample, { volume, rate, pan, onEnd }) {\n if (muted || !bufferFor(sample)) {\n return null;\n }\n\n // Emitters only fire once loaded: a late bird is worse than a skipped one.\n const buffer = decoded.get(sample);\n\n if (!buffer) {\n return null;\n }\n\n const id = nextId++;\n const voice = makeVoice(volume, pan, onEnd);\n\n voices.set(id, voice);\n startVoice(id, voice, buffer, { loop: false, rate, fadeInMs: 0 });\n\n return id;\n },\n\n stop,\n\n setVolume(id, volume, fadeMs) {\n const voice = voices.get(id);\n\n if (voice) {\n voice.base = volume;\n rampTo(voice.gain.gain, volume, ensure().context.currentTime, fadeMs);\n }\n },\n\n async load(loadOpts: LoadOptions = {}) {\n const names = Object.keys(samples);\n let settled = 0;\n\n await Promise.all(\n names.map(async (name) => {\n await bufferFor(name);\n settled += 1;\n loadOpts.onProgress?.(settled, names.length);\n }),\n );\n },\n\n duck: ducker.duck,\n\n get ducked() {\n return ducker.ducked;\n },\n\n setLevel(next, fadeMs = 0) {\n level = next;\n\n if (graph && !muted) {\n rampTo(graph.bus.gain, level, graph.context.currentTime, fadeMs);\n }\n },\n\n setMuted(next) {\n muted = next;\n\n if (graph) {\n rampTo(graph.bus.gain, muted ? 0 : level, graph.context.currentTime, 30);\n }\n },\n\n stopAll(fadeOutMs = 0) {\n for (const id of Array.from(voices.keys())) {\n stop(id, fadeOutMs);\n }\n },\n\n get voices() {\n return voices.size;\n },\n\n dispose() {\n for (const voice of voices.values()) {\n voice.onEnd = null;\n voice.source?.stop();\n }\n\n voices.clear();\n ducker.clear();\n detachUnlock?.();\n detachUnlock = null;\n\n if (graph) {\n graph.duck.disconnect();\n\n if (!opts.context) {\n void graph.context.close();\n }\n }\n\n graph = null;\n buffers.clear();\n decoded.clear();\n },\n };\n};\n\nexport { createWebAudioBus };\nexport type { WebAudioBusOptions };\nexport type { DuckOptions, LoadOptions, SampleSource, SoundscapeAdapter } from \"../core/adapter.ts\";\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@zkmake/sound-scape",
3
+ "version": "0.1.0",
4
+ "description": "Soundscapes for web games: a looping bed under emitters that fire on random timers, with no-repeat picks, tight random ranges, a voice cap and ducking. Howler or Web Audio, vanilla or React.",
5
+ "keywords": [
6
+ "ambience",
7
+ "audio",
8
+ "game-audio",
9
+ "howler",
10
+ "r3f",
11
+ "react",
12
+ "soundscape",
13
+ "web-audio"
14
+ ],
15
+ "homepage": "https://zkmake.github.io/sound-kit/sound-scape/",
16
+ "bugs": "https://github.com/zkmake/sound-kit/issues",
17
+ "license": "MIT",
18
+ "author": "Zubin Khavarian",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/zkmake/sound-kit.git",
22
+ "directory": "packages/sound-scape"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "type": "module",
30
+ "sideEffects": false,
31
+ "exports": {
32
+ ".": "./dist/index.js",
33
+ "./howler": "./dist/howler.js",
34
+ "./react": "./dist/react.js",
35
+ "./web-audio": "./dist/web-audio.js",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "ci:test": "vitest run",
44
+ "ci:typecheck": "tsc",
45
+ "pack:check": "npm pack --dry-run",
46
+ "test": "vitest watch",
47
+ "typecheck": "tsc"
48
+ },
49
+ "devDependencies": {
50
+ "@arethetypeswrong/core": "catalog:",
51
+ "@types/howler": "catalog:",
52
+ "@types/react": "catalog:",
53
+ "@types/react-dom": "catalog:",
54
+ "howler": "catalog:",
55
+ "publint": "catalog:",
56
+ "react": "catalog:",
57
+ "react-dom": "catalog:",
58
+ "tsdown": "catalog:",
59
+ "typescript": "catalog:",
60
+ "vitest": "catalog:"
61
+ },
62
+ "peerDependencies": {
63
+ "howler": ">=2.2",
64
+ "react": ">=18"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "howler": {
68
+ "optional": true
69
+ },
70
+ "react": {
71
+ "optional": true
72
+ }
73
+ },
74
+ "engines": {
75
+ "node": ">=20.19"
76
+ }
77
+ }