@weasel-js/audio 1.4.4 → 1.5.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.
- package/README.md +11 -6
- package/dist/index.d.ts +35 -11
- package/dist/index.js +130 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,13 +7,18 @@ Playback is lookahead-scheduled on the engine's own timer rather than triggered
|
|
|
7
7
|
from an animation frame: `AudioContext.currentTime` is driven by the audio
|
|
8
8
|
hardware, ticks independently of `requestAnimationFrame`, and cannot be paused
|
|
9
9
|
or time-scaled. Triggering a sound *on* a frame inherits frame jitter, which is
|
|
10
|
-
audible.
|
|
10
|
+
audible. A `play()` whose `when` is already inside the window does not wait for
|
|
11
|
+
the next pass: it is booked at the end of the task that called it.
|
|
11
12
|
|
|
12
|
-
A hidden tab
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
A hidden tab clamps main-thread `setTimeout` to at least a second, far too late
|
|
14
|
+
for a 100 ms lookahead, so the engine wakes each pass from a timer inside a
|
|
15
|
+
dedicated Worker instead (`createTickTimer`). The worker is built from an inline
|
|
16
|
+
`blob:` URL, so no bundler setup is needed. Where none can be made — no `Worker`
|
|
17
|
+
global, or a Content-Security-Policy without `worker-src blob:` — it falls back
|
|
18
|
+
to `setTimeout`, and a hidden tab books late again.
|
|
19
|
+
|
|
20
|
+
A suspended `AudioContext` is a separate matter: its clock stops, and on resume
|
|
21
|
+
the engine drops what came due meanwhile rather than firing it all at once.
|
|
17
22
|
|
|
18
23
|
```ts
|
|
19
24
|
const engine = createAudioEngine();
|
package/dist/index.d.ts
CHANGED
|
@@ -128,10 +128,10 @@ interface VoicePool {
|
|
|
128
128
|
* Slot bookkeeping for concurrent voices, with a steal policy for when the
|
|
129
129
|
* limit is reached.
|
|
130
130
|
*
|
|
131
|
-
* This is pure accounting — it owns no audio nodes. The
|
|
132
|
-
* `GainNode`/`StereoPannerNode`
|
|
133
|
-
* per play, because a source node is single-use by
|
|
134
|
-
*
|
|
131
|
+
* This is pure accounting — it owns no audio nodes. The engine reuses its
|
|
132
|
+
* `GainNode`/`StereoPannerNode` chains separately, and mints a fresh
|
|
133
|
+
* `AudioBufferSourceNode` per play, because a source node is single-use by
|
|
134
|
+
* specification and cannot be restarted once stopped.
|
|
135
135
|
*/
|
|
136
136
|
declare function createVoicePool(opts: VoicePoolOptions): VoicePool;
|
|
137
137
|
|
|
@@ -183,6 +183,9 @@ interface AudioEngineOptions {
|
|
|
183
183
|
/** Default 'oldest'. */
|
|
184
184
|
steal?: StealPolicy;
|
|
185
185
|
fetchFn?: typeof fetch;
|
|
186
|
+
/** One-shot timer that wakes each scheduler pass. Default: `createTickTimer()`,
|
|
187
|
+
* a Worker-backed timer that survives a hidden tab. Injecting either half
|
|
188
|
+
* replaces the default, and the missing half falls back to `setTimeout`'s. */
|
|
186
189
|
setTimer?: (cb: () => void, ms: number) => unknown;
|
|
187
190
|
clearTimer?: (handle: unknown) => void;
|
|
188
191
|
}
|
|
@@ -238,9 +241,11 @@ interface Scheduler {
|
|
|
238
241
|
start(): void;
|
|
239
242
|
/** Stop the timer. The queue survives; `clear()` empties it. */
|
|
240
243
|
stop(): void;
|
|
241
|
-
/** Book `fire` for engine time `when
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
+
/** Book `fire` for engine time `when`, receiving `when` so it can hand the
|
|
245
|
+
* true time to `source.start()` rather than "now". It runs on the first
|
|
246
|
+
* pass whose lookahead window reaches it — or, when the window already
|
|
247
|
+
* covers it and the scheduler is running, at the end of the current task,
|
|
248
|
+
* in time order with everything else then due. */
|
|
244
249
|
schedule(when: number, fire: (when: number) => void, key?: string): void;
|
|
245
250
|
/** Drop queued events with this key. A pass takes its whole batch out of the
|
|
246
251
|
* queue before firing any of it, so a callback cannot cancel a sibling that
|
|
@@ -253,13 +258,32 @@ interface Scheduler {
|
|
|
253
258
|
}
|
|
254
259
|
/**
|
|
255
260
|
* Lookahead scheduler. Each pass fires everything due within `lookahead` ms,
|
|
256
|
-
* in time order, handing each callback its own scheduled time.
|
|
261
|
+
* in time order, handing each callback its own scheduled time. An event
|
|
262
|
+
* scheduled inside that window does not wait up to `interval` for the next
|
|
263
|
+
* pass: the end of the current task fires what is due then.
|
|
257
264
|
*
|
|
258
265
|
* It runs on its own timer rather than on an animation frame, which stops
|
|
259
266
|
* entirely when nothing is animating. The timer is one-shot: the pass re-arms
|
|
260
|
-
* it at the end, so passes cannot overlap. A
|
|
261
|
-
* second, which the lookahead cannot cover
|
|
267
|
+
* it at the end, so passes cannot overlap. A main-thread `setTimeout` is clamped
|
|
268
|
+
* to a second in a hidden tab, which the lookahead cannot cover; the engine
|
|
269
|
+
* passes `createTickTimer` instead.
|
|
262
270
|
*/
|
|
263
271
|
declare function createScheduler(opts: SchedulerOptions): Scheduler;
|
|
264
272
|
|
|
265
|
-
|
|
273
|
+
interface TickTimer {
|
|
274
|
+
/** One-shot, as `createScheduler` expects. */
|
|
275
|
+
setTimer(cb: () => void, ms: number): unknown;
|
|
276
|
+
clearTimer(handle: unknown): void;
|
|
277
|
+
/** Terminate the worker and drop every pending timer. */
|
|
278
|
+
dispose(): void;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* A timer for the scheduler's pass that is not held to the main thread's
|
|
282
|
+
* hidden-tab clamp: each delay runs inside a dedicated Worker, which posts back
|
|
283
|
+
* when it elapses. Falls back to `setTimeout` when no worker can be made — no
|
|
284
|
+
* `Worker` global, a constructor that throws, or a worker that fails to load,
|
|
285
|
+
* which is how a CSP refusal can arrive.
|
|
286
|
+
*/
|
|
287
|
+
declare function createTickTimer(): TickTimer;
|
|
288
|
+
|
|
289
|
+
export { type Acquisition, type AnalyserTap, type AnalyserTapOptions, type AudioEngine, type AudioEngineOptions, type BusGraph, type BusHandle, type PlayOptions, type Scheduler, type SchedulerOptions, type SoundCache, type SoundHandle, type SpatialOptions, type StealPolicy, type TickTimer, type Vec2, type VoiceHandle, type VoicePool, type VoicePoolOptions, type VoiceRecord, createAnalyserTap, createAudioEngine, createBusGraph, createScheduler, createSoundCache, createTickTimer, createVoicePool, spatialize };
|
package/dist/index.js
CHANGED
|
@@ -144,7 +144,8 @@ function createScheduler(opts) {
|
|
|
144
144
|
let queue = [];
|
|
145
145
|
let running = false;
|
|
146
146
|
let handle = null;
|
|
147
|
-
|
|
147
|
+
let flushQueued = false;
|
|
148
|
+
const fireDue = () => {
|
|
148
149
|
const horizon = opts.now() + lookahead;
|
|
149
150
|
const due = [];
|
|
150
151
|
const rest = [];
|
|
@@ -158,8 +159,15 @@ function createScheduler(opts) {
|
|
|
158
159
|
console.error("@weasel-js/audio scheduler: callback threw", err);
|
|
159
160
|
}
|
|
160
161
|
}
|
|
162
|
+
};
|
|
163
|
+
const pass = () => {
|
|
164
|
+
fireDue();
|
|
161
165
|
if (running) handle = opts.setTimer(pass, interval);
|
|
162
166
|
};
|
|
167
|
+
const flush = () => {
|
|
168
|
+
flushQueued = false;
|
|
169
|
+
if (running) fireDue();
|
|
170
|
+
};
|
|
163
171
|
return {
|
|
164
172
|
start() {
|
|
165
173
|
if (running) return;
|
|
@@ -174,6 +182,10 @@ function createScheduler(opts) {
|
|
|
174
182
|
},
|
|
175
183
|
schedule(when, fire, key) {
|
|
176
184
|
queue.push({ when, fire, key });
|
|
185
|
+
if (running && !flushQueued && when <= opts.now() + lookahead) {
|
|
186
|
+
flushQueued = true;
|
|
187
|
+
queueMicrotask(flush);
|
|
188
|
+
}
|
|
177
189
|
},
|
|
178
190
|
cancelKey(key) {
|
|
179
191
|
queue = queue.filter((e) => e.key !== key);
|
|
@@ -256,6 +268,86 @@ function spatialize(source, listener, opts = {}) {
|
|
|
256
268
|
return { gain, pan };
|
|
257
269
|
}
|
|
258
270
|
|
|
271
|
+
// src/tickTimer.ts
|
|
272
|
+
var WORKER_SOURCE = `const timers = new Map();
|
|
273
|
+
self.onmessage = (e) => {
|
|
274
|
+
const id = e.data.id;
|
|
275
|
+
clearTimeout(timers.get(id));
|
|
276
|
+
timers.delete(id);
|
|
277
|
+
if (e.data.ms === undefined) return;
|
|
278
|
+
timers.set(id, setTimeout(() => {
|
|
279
|
+
timers.delete(id);
|
|
280
|
+
self.postMessage(id);
|
|
281
|
+
}, e.data.ms));
|
|
282
|
+
};
|
|
283
|
+
`;
|
|
284
|
+
function spawn() {
|
|
285
|
+
if (typeof Worker !== "function" || typeof Blob !== "function" || typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
let url;
|
|
289
|
+
try {
|
|
290
|
+
url = URL.createObjectURL(new Blob([WORKER_SOURCE], { type: "text/javascript" }));
|
|
291
|
+
return { worker: new Worker(url), url };
|
|
292
|
+
} catch {
|
|
293
|
+
if (url !== void 0) URL.revokeObjectURL(url);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function createTickTimer() {
|
|
298
|
+
let nextId = 1;
|
|
299
|
+
const pending = /* @__PURE__ */ new Map();
|
|
300
|
+
let spawned = spawn();
|
|
301
|
+
const fire = (id) => {
|
|
302
|
+
const p = pending.get(id);
|
|
303
|
+
if (!p) return;
|
|
304
|
+
pending.delete(id);
|
|
305
|
+
p.cb();
|
|
306
|
+
};
|
|
307
|
+
const arm = (id, p) => {
|
|
308
|
+
p.fallback = setTimeout(() => fire(id), p.ms);
|
|
309
|
+
};
|
|
310
|
+
const release = () => {
|
|
311
|
+
if (!spawned) return;
|
|
312
|
+
spawned.worker.terminate();
|
|
313
|
+
URL.revokeObjectURL(spawned.url);
|
|
314
|
+
spawned = null;
|
|
315
|
+
};
|
|
316
|
+
if (spawned) {
|
|
317
|
+
spawned.worker.addEventListener("message", (e) => fire(e.data));
|
|
318
|
+
spawned.worker.addEventListener("error", () => {
|
|
319
|
+
if (!spawned) return;
|
|
320
|
+
release();
|
|
321
|
+
for (const [id, p] of pending) arm(id, p);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
setTimer(cb, ms) {
|
|
326
|
+
const id = nextId++;
|
|
327
|
+
const p = { cb, ms };
|
|
328
|
+
pending.set(id, p);
|
|
329
|
+
if (spawned) spawned.worker.postMessage({ id, ms });
|
|
330
|
+
else arm(id, p);
|
|
331
|
+
return id;
|
|
332
|
+
},
|
|
333
|
+
clearTimer(handle) {
|
|
334
|
+
const id = handle;
|
|
335
|
+
const p = pending.get(id);
|
|
336
|
+
if (!p) return;
|
|
337
|
+
pending.delete(id);
|
|
338
|
+
if (p.fallback !== void 0) clearTimeout(p.fallback);
|
|
339
|
+
else spawned?.worker.postMessage({ id });
|
|
340
|
+
},
|
|
341
|
+
dispose() {
|
|
342
|
+
for (const p of pending.values()) {
|
|
343
|
+
if (p.fallback !== void 0) clearTimeout(p.fallback);
|
|
344
|
+
}
|
|
345
|
+
pending.clear();
|
|
346
|
+
release();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
259
351
|
// src/voicePool.ts
|
|
260
352
|
function createVoicePool(opts) {
|
|
261
353
|
if (!(opts.limit >= 1)) {
|
|
@@ -328,13 +420,14 @@ function createAudioEngine(opts = {}) {
|
|
|
328
420
|
return s;
|
|
329
421
|
};
|
|
330
422
|
const now = () => ctx.currentTime * 1e3;
|
|
423
|
+
const tickTimer = opts.setTimer === void 0 && opts.clearTimer === void 0 ? createTickTimer() : null;
|
|
331
424
|
const scheduler = createScheduler({
|
|
332
425
|
now,
|
|
333
426
|
// One-shot, not repeating: the scheduler re-arms at the end of every pass,
|
|
334
427
|
// so an interval would leave the previous one running and double the live
|
|
335
428
|
// timer count per tick.
|
|
336
|
-
setTimer: opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms)),
|
|
337
|
-
clearTimer: opts.clearTimer ?? ((h) => clearTimeout(h)),
|
|
429
|
+
setTimer: tickTimer?.setTimer ?? opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms)),
|
|
430
|
+
clearTimer: tickTimer?.clearTimer ?? opts.clearTimer ?? ((h) => clearTimeout(h)),
|
|
338
431
|
lookahead: opts.lookahead,
|
|
339
432
|
interval: opts.tickInterval
|
|
340
433
|
});
|
|
@@ -347,6 +440,19 @@ function createAudioEngine(opts = {}) {
|
|
|
347
440
|
let staleFloor = -Infinity;
|
|
348
441
|
const live = /* @__PURE__ */ new Map();
|
|
349
442
|
const taps = /* @__PURE__ */ new Set();
|
|
443
|
+
const idleChains = [];
|
|
444
|
+
const idleChainLimit = (opts.voiceLimit ?? 32) * busNames.length;
|
|
445
|
+
const takeChain = (bus, gain, pan) => {
|
|
446
|
+
let chain = idleChains.pop();
|
|
447
|
+
if (!chain) {
|
|
448
|
+
chain = { gain: ctx.createGain(), panner: ctx.createStereoPanner() };
|
|
449
|
+
chain.panner.connect(chain.gain);
|
|
450
|
+
}
|
|
451
|
+
writeParam(ctx, chain.gain.gain, gain);
|
|
452
|
+
chain.panner.pan.value = pan;
|
|
453
|
+
chain.gain.connect(bus);
|
|
454
|
+
return chain;
|
|
455
|
+
};
|
|
350
456
|
let warnedLocked = false;
|
|
351
457
|
const warnLocked = () => {
|
|
352
458
|
if (warnedLocked) return;
|
|
@@ -399,11 +505,11 @@ function createAudioEngine(opts = {}) {
|
|
|
399
505
|
voice.slots.pool.setGain(voice.slot, voice.token, voice.baseGain * voice.spatialGain);
|
|
400
506
|
};
|
|
401
507
|
const applySpatial = (voice) => {
|
|
402
|
-
if (!voice.position) return;
|
|
508
|
+
if (!voice.position || !voice.chain) return;
|
|
403
509
|
const s = spatialize(voice.position, listener, spatialOpts);
|
|
404
510
|
voice.spatialGain = s.gain;
|
|
405
|
-
voice.
|
|
406
|
-
writeParam(ctx, voice.
|
|
511
|
+
voice.chain.panner.pan.value = s.pan;
|
|
512
|
+
writeParam(ctx, voice.chain.gain.gain, voice.baseGain * s.gain);
|
|
407
513
|
poolGain(voice);
|
|
408
514
|
};
|
|
409
515
|
const teardown = (voice) => {
|
|
@@ -418,8 +524,11 @@ function createAudioEngine(opts = {}) {
|
|
|
418
524
|
voice.source.disconnect();
|
|
419
525
|
}
|
|
420
526
|
voice.source = null;
|
|
421
|
-
voice.
|
|
422
|
-
|
|
527
|
+
if (voice.chain) {
|
|
528
|
+
voice.chain.gain.disconnect();
|
|
529
|
+
if (idleChains.length < idleChainLimit) idleChains.push(voice.chain);
|
|
530
|
+
voice.chain = null;
|
|
531
|
+
}
|
|
423
532
|
live.delete(voice.id);
|
|
424
533
|
if (voice.slot !== null) {
|
|
425
534
|
voice.slots.byToken.delete(voice.token);
|
|
@@ -466,15 +575,10 @@ function createAudioEngine(opts = {}) {
|
|
|
466
575
|
}
|
|
467
576
|
const busName = playOpts.bus ?? busNames[0];
|
|
468
577
|
const slots = slotsFor(busName);
|
|
469
|
-
const
|
|
578
|
+
const bookedAt = now();
|
|
579
|
+
const when = playOpts.when ?? bookedAt;
|
|
470
580
|
const baseGain = playOpts.gain ?? 1;
|
|
471
581
|
const spatial = playOpts.position ? spatialize(playOpts.position, listener, spatialOpts) : { gain: 1, pan: playOpts.pan ?? 0 };
|
|
472
|
-
const gainNode = ctx.createGain();
|
|
473
|
-
const panNode = ctx.createStereoPanner();
|
|
474
|
-
gainNode.gain.value = baseGain * spatial.gain;
|
|
475
|
-
panNode.pan.value = spatial.pan;
|
|
476
|
-
panNode.connect(gainNode);
|
|
477
|
-
gainNode.connect(graph.node(busName));
|
|
478
582
|
const voice = {
|
|
479
583
|
id,
|
|
480
584
|
slots,
|
|
@@ -482,8 +586,7 @@ function createAudioEngine(opts = {}) {
|
|
|
482
586
|
token: 0,
|
|
483
587
|
key: playOpts.cancelKey,
|
|
484
588
|
source: null,
|
|
485
|
-
|
|
486
|
-
panNode,
|
|
589
|
+
chain: takeChain(graph.node(busName), baseGain * spatial.gain, spatial.pan),
|
|
487
590
|
baseGain,
|
|
488
591
|
spatialGain: spatial.gain,
|
|
489
592
|
rate: playOpts.rate ?? 1,
|
|
@@ -495,7 +598,7 @@ function createAudioEngine(opts = {}) {
|
|
|
495
598
|
live.set(id, voice);
|
|
496
599
|
scheduler.schedule(when, (scheduledWhen) => {
|
|
497
600
|
if (voice.cancelled) return;
|
|
498
|
-
if (scheduledWhen < staleFloor) {
|
|
601
|
+
if (bookedAt < staleFloor && scheduledWhen < staleFloor) {
|
|
499
602
|
teardown(voice);
|
|
500
603
|
return;
|
|
501
604
|
}
|
|
@@ -521,7 +624,7 @@ function createAudioEngine(opts = {}) {
|
|
|
521
624
|
source.loop = playOpts.loop ?? false;
|
|
522
625
|
source.playbackRate.value = voice.rate;
|
|
523
626
|
source.detune.value = voice.detune;
|
|
524
|
-
source.connect(
|
|
627
|
+
source.connect(voice.chain.panner);
|
|
525
628
|
source.onended = () => {
|
|
526
629
|
teardown(voice);
|
|
527
630
|
playOpts.onDone?.();
|
|
@@ -532,9 +635,9 @@ function createAudioEngine(opts = {}) {
|
|
|
532
635
|
return {
|
|
533
636
|
id,
|
|
534
637
|
stop(fadeMs) {
|
|
535
|
-
if (fadeMs && fadeMs > 0 && voice.source && !voice.cancelled) {
|
|
638
|
+
if (fadeMs && fadeMs > 0 && voice.source && voice.chain && !voice.cancelled) {
|
|
536
639
|
voice.cancelled = true;
|
|
537
|
-
writeParam(ctx,
|
|
640
|
+
writeParam(ctx, voice.chain.gain.gain, 0, fadeMs);
|
|
538
641
|
try {
|
|
539
642
|
voice.source.stop(ctx.currentTime + fadeMs / 1e3);
|
|
540
643
|
} catch {
|
|
@@ -545,7 +648,8 @@ function createAudioEngine(opts = {}) {
|
|
|
545
648
|
},
|
|
546
649
|
setGain(value, rampMs) {
|
|
547
650
|
voice.baseGain = value;
|
|
548
|
-
|
|
651
|
+
if (!voice.chain) return;
|
|
652
|
+
writeParam(ctx, voice.chain.gain.gain, value * voice.spatialGain, rampMs);
|
|
549
653
|
poolGain(voice);
|
|
550
654
|
},
|
|
551
655
|
setRate(value) {
|
|
@@ -558,7 +662,7 @@ function createAudioEngine(opts = {}) {
|
|
|
558
662
|
},
|
|
559
663
|
setPan(value) {
|
|
560
664
|
voice.position = void 0;
|
|
561
|
-
|
|
665
|
+
if (voice.chain) voice.chain.panner.pan.value = value;
|
|
562
666
|
},
|
|
563
667
|
setPosition(p) {
|
|
564
668
|
voice.position = p;
|
|
@@ -604,8 +708,10 @@ function createAudioEngine(opts = {}) {
|
|
|
604
708
|
if (disposed) return;
|
|
605
709
|
disposed = true;
|
|
606
710
|
engine.stopAll();
|
|
711
|
+
idleChains.length = 0;
|
|
607
712
|
scheduler.stop();
|
|
608
713
|
scheduler.clear();
|
|
714
|
+
tickTimer?.dispose();
|
|
609
715
|
for (const tap of [...taps]) tap.dispose();
|
|
610
716
|
taps.clear();
|
|
611
717
|
disarmGestures();
|
|
@@ -617,6 +723,6 @@ function createAudioEngine(opts = {}) {
|
|
|
617
723
|
return engine;
|
|
618
724
|
}
|
|
619
725
|
|
|
620
|
-
export { createAnalyserTap, createAudioEngine, createBusGraph, createScheduler, createSoundCache, createVoicePool, spatialize };
|
|
726
|
+
export { createAnalyserTap, createAudioEngine, createBusGraph, createScheduler, createSoundCache, createTickTimer, createVoicePool, spatialize };
|
|
621
727
|
//# sourceMappingURL=index.js.map
|
|
622
728
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyser.ts","../src/param.ts","../src/buses.ts","../src/scheduler.ts","../src/soundCache.ts","../src/spatialize.ts","../src/voicePool.ts","../src/createAudioEngine.ts"],"names":["slot"],"mappings":"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","file":"index.js","sourcesContent":["export interface AnalyserTapOptions {\n /** Power of two, 32..32768. Default 2048. */\n fftSize?: number;\n}\n\nexport interface AnalyserTap {\n /** The underlying node, exposed for disposal assertions and advanced wiring. */\n node: AnalyserNode;\n /** Frequency data, sized `node.frequencyBinCount` when no array is given. */\n frequencies(out?: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;\n /** Time-domain data, sized `node.fftSize` when no array is given — twice the\n * frequency bin count, which is the whole window rather than half of it. */\n waveform(out?: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;\n /** RMS amplitude over the whole time-domain window, 0..1. */\n level(): number;\n /** `n` averaged frequency bands normalized to 0..1 — the ergonomic form for\n * driving a shader uniform, a vertex color, or a pose. `out`, when given,\n * must be exactly `n` long. */\n bands(n: number, out?: Float32Array<ArrayBuffer>): Float32Array<ArrayBuffer>;\n /** Detach from the tapped node. Idempotent. */\n dispose(): void;\n}\n\nexport function createAnalyserTap(\n ctx: AudioContext,\n source: AudioNode,\n opts: AnalyserTapOptions = {},\n): AnalyserTap {\n const node = ctx.createAnalyser();\n node.fftSize = opts.fftSize ?? 2048;\n source.connect(node);\n let attached = true;\n\n // `node` is public surface, so `fftSize` can change under the tap. Time-domain\n // data is sized by `fftSize`; frequency data by half of it.\n let freqScratch = new Uint8Array(0);\n let timeScratch = new Uint8Array(0);\n const freqBuf = (): Uint8Array<ArrayBuffer> => {\n if (freqScratch.length !== node.frequencyBinCount) {\n freqScratch = new Uint8Array(node.frequencyBinCount);\n }\n return freqScratch;\n };\n const timeBuf = (): Uint8Array<ArrayBuffer> => {\n if (timeScratch.length !== node.fftSize) timeScratch = new Uint8Array(node.fftSize);\n return timeScratch;\n };\n\n return {\n node,\n frequencies(out) {\n const target = out ?? new Uint8Array(node.frequencyBinCount);\n node.getByteFrequencyData(target);\n return target;\n },\n waveform(out) {\n const target = out ?? new Uint8Array(node.fftSize);\n node.getByteTimeDomainData(target);\n return target;\n },\n level() {\n const buf = timeBuf();\n node.getByteTimeDomainData(buf);\n let sum = 0;\n for (let i = 0; i < buf.length; i += 1) {\n // Time-domain bytes center on 128; subtract to get a signed sample.\n const v = (buf[i] - 128) / 128;\n sum += v * v;\n }\n return Math.sqrt(sum / buf.length);\n },\n bands(n, out) {\n if (!Number.isInteger(n) || n < 1) {\n throw new Error('@weasel-js/audio: bands(n) requires a positive integer');\n }\n // Unlike the byte readers, a short `out` here would return fewer bands\n // than asked for with nothing to distinguish it from a correct result.\n if (out && out.length !== n) {\n throw new Error(\n `@weasel-js/audio: bands(${n}, out) requires an out array of length ${n}, got ${out.length}`,\n );\n }\n const buf = freqBuf();\n node.getByteFrequencyData(buf);\n const target = out ?? new Float32Array(n);\n const per = buf.length / n;\n for (let b = 0; b < n; b += 1) {\n const lo = Math.floor(b * per);\n const hi = Math.max(lo + 1, Math.floor((b + 1) * per));\n let sum = 0;\n for (let i = lo; i < hi; i += 1) sum += buf[i];\n target[b] = sum / (hi - lo) / 255;\n }\n return target;\n },\n // `node.disconnect()` would cut the analyser's outgoing edges, and it has\n // none: the edge that keeps it alive is the source's.\n dispose() {\n if (!attached) return;\n attached = false;\n source.disconnect(node);\n },\n };\n}\n","/**\n * Every gain write in the package goes through here.\n *\n * Two traps, both silent: a bare `param.value = x` sits *behind* any scheduled\n * ramp, which keeps running and wins; and a ramp with no anchor interpolates\n * from the previous scheduled endpoint rather than from the value the param\n * holds now, so a second ramp jumps before it moves.\n */\nexport function writeParam(\n ctx: BaseAudioContext,\n param: AudioParam,\n value: number,\n rampMs?: number,\n): void {\n const now = ctx.currentTime;\n param.cancelScheduledValues(now);\n if (rampMs && rampMs > 0) {\n param.setValueAtTime(param.value, now);\n param.linearRampToValueAtTime(value, now + rampMs / 1000);\n } else {\n param.setValueAtTime(value, now);\n }\n}\n","import { writeParam } from './param';\n\nexport interface BusHandle {\n setGain(value: number, rampMs?: number): void;\n mute(on: boolean): void;\n solo(on: boolean): void;\n /** The bus's own gain, as last set — not what a mute or solo is imposing. */\n gain(): number;\n /** The mute flag as set, independent of any solo. */\n muted(): boolean;\n /** The solo flag as set; whether it silences anything is a graph-wide answer. */\n soloed(): boolean;\n /** Whether the bus is being heard: unmuted, and soloed if any bus is soloed.\n * Not derivable from `muted()` and `soloed()` alone. */\n audible(): boolean;\n}\n\nexport interface BusGraph {\n master: GainNode;\n node(name: string): GainNode;\n bus(name: string): BusHandle;\n names(): string[];\n}\n\ninterface BusState { node: GainNode; gain: number; muted: boolean; soloed: boolean }\n\n/**\n * The mix graph: one `GainNode` per named bus, all routed to a master that\n * routes to the destination.\n *\n * Gain, mute and solo are three inputs to one output value rather than three\n * things that write the node directly — otherwise unmuting restores the wrong\n * value whenever a solo changed it in between. All three write through\n * `writeParam`, so `rampMs` survives instead of being overwritten by the\n * recomputation that follows it.\n */\nexport function createBusGraph(ctx: AudioContext, names: string[]): BusGraph {\n const master = ctx.createGain();\n master.connect(ctx.destination);\n\n const states = new Map<string, BusState>();\n for (const name of names) {\n const node = ctx.createGain();\n node.connect(master);\n states.set(name, { node, gain: 1, muted: false, soloed: false });\n }\n\n const anySoloed = (): boolean => {\n for (const s of states.values()) if (s.soloed) return true;\n return false;\n };\n\n const audibleUnder = (s: BusState, soloing: boolean): boolean =>\n !s.muted && (!soloing || s.soloed);\n\n // `ramp` names the one bus whose own value changed, so a solo does not slew\n // every other bus at whatever rate that bus was last set with.\n const apply = (ramp?: { bus: BusState; ms: number }): void => {\n const soloing = anySoloed();\n for (const s of states.values()) {\n const value = audibleUnder(s, soloing) ? s.gain : 0;\n writeParam(ctx, s.node.gain, value, ramp?.bus === s ? ramp.ms : undefined);\n }\n };\n\n const get = (name: string): BusState => {\n const s = states.get(name);\n if (!s) throw new Error(`@weasel-js/audio: unknown bus \"${name}\"`);\n return s;\n };\n\n return {\n master,\n node: (name) => get(name).node,\n names: () => [...states.keys()],\n bus(name) {\n const s = get(name);\n return {\n setGain(value, rampMs) {\n s.gain = value;\n apply(rampMs && rampMs > 0 ? { bus: s, ms: rampMs } : undefined);\n },\n mute(on) { s.muted = on; apply(); },\n solo(on) { s.soloed = on; apply(); },\n gain: () => s.gain,\n muted: () => s.muted,\n soloed: () => s.soloed,\n audible: () => audibleUnder(s, anySoloed()),\n };\n },\n };\n}\n","export interface SchedulerOptions {\n /** Engine time in ms. Backed by `AudioContext.currentTime * 1000` in production. */\n now: () => number;\n /** One-shot: it fires once and the pass re-arms it. `setInterval` here would\n * leave every previous timer running. */\n setTimer: (cb: () => void, ms: number) => unknown;\n clearTimer: (handle: unknown) => void;\n /** How far ahead to book events, in ms. Default 100. */\n lookahead?: number;\n /** Time between passes, in ms. Default 25. */\n interval?: number;\n}\n\ninterface Entry {\n when: number;\n fire: (when: number) => void;\n key?: string;\n}\n\nexport interface Scheduler {\n start(): void;\n /** Stop the timer. The queue survives; `clear()` empties it. */\n stop(): void;\n /** Book `fire` for engine time `when`. It runs on the first pass whose\n * lookahead window reaches it, receiving `when` so it can hand the true\n * time to `source.start()` rather than \"now\". */\n schedule(when: number, fire: (when: number) => void, key?: string): void;\n /** Drop queued events with this key. A pass takes its whole batch out of the\n * queue before firing any of it, so a callback cannot cancel a sibling that\n * came due alongside it. */\n cancelKey(key: string): void;\n /** Drop every queued event. Without this, a stop/start cycle fires\n * everything that came due meanwhile in one burst on the first pass. */\n clear(): void;\n pending(): number;\n}\n\n/**\n * Lookahead scheduler. Each pass fires everything due within `lookahead` ms,\n * in time order, handing each callback its own scheduled time.\n *\n * It runs on its own timer rather than on an animation frame, which stops\n * entirely when nothing is animating. The timer is one-shot: the pass re-arms\n * it at the end, so passes cannot overlap. A hidden tab clamps it to at least a\n * second, which the lookahead cannot cover — see the README.\n */\nexport function createScheduler(opts: SchedulerOptions): Scheduler {\n const lookahead = opts.lookahead ?? 100;\n const interval = opts.interval ?? 25;\n let queue: Entry[] = [];\n let running = false;\n let handle: unknown = null;\n\n const pass = (): void => {\n const horizon = opts.now() + lookahead;\n const due: Entry[] = [];\n const rest: Entry[] = [];\n for (const entry of queue) (entry.when <= horizon ? due : rest).push(entry);\n queue = rest;\n due.sort((a, b) => a.when - b.when);\n for (const entry of due) {\n try {\n entry.fire(entry.when);\n } catch (err) {\n console.error('@weasel-js/audio scheduler: callback threw', err);\n }\n }\n if (running) handle = opts.setTimer(pass, interval);\n };\n\n return {\n start() {\n if (running) return;\n running = true;\n handle = opts.setTimer(pass, interval);\n },\n stop() {\n if (!running) return;\n running = false;\n opts.clearTimer(handle);\n handle = null;\n },\n schedule(when, fire, key) {\n queue.push({ when, fire, key });\n },\n cancelKey(key) {\n queue = queue.filter((e) => e.key !== key);\n },\n clear() {\n queue = [];\n },\n pending: () => queue.length,\n };\n}\n","/** Opaque reference to a decoded sound. Mirrors `TextureHandle` in core. */\nexport interface SoundHandle { readonly id: string }\n\nexport interface SoundCache {\n load(url: string): Promise<SoundHandle>;\n loadAll(urls: Record<string, string>): Promise<Record<string, SoundHandle>>;\n decode(bytes: ArrayBuffer): Promise<SoundHandle>;\n /** Take ownership of a buffer the caller already has — a procedural synth,\n * an OfflineAudioContext render, a recording. Synchronous; nothing to decode. */\n register(buffer: AudioBuffer): SoundHandle;\n buffer(handle: SoundHandle): AudioBuffer | null;\n}\n\nexport function createSoundCache(\n ctx: AudioContext,\n fetchFn: typeof fetch = fetch,\n): SoundCache {\n let counter = 0;\n const buffers = new Map<string, AudioBuffer>();\n const byUrl = new Map<string, SoundHandle>();\n // In-flight loads, so two concurrent `load` calls for one url share a fetch.\n const inflight = new Map<string, Promise<SoundHandle>>();\n\n const store = (buffer: AudioBuffer): SoundHandle => {\n const id = `snd_${++counter}`;\n buffers.set(id, buffer);\n return { id };\n };\n\n const cache: SoundCache = {\n async load(url) {\n const existing = byUrl.get(url);\n if (existing) return existing;\n const pending = inflight.get(url);\n if (pending) return pending;\n\n const task = (async () => {\n const res = await fetchFn(url);\n if (!res.ok) throw new Error(`@weasel-js/audio: failed to load ${url} (${res.status})`);\n const bytes = await res.arrayBuffer();\n const buffer = await ctx.decodeAudioData(bytes);\n const handle = store(buffer);\n byUrl.set(url, handle);\n return handle;\n })();\n\n inflight.set(url, task);\n try {\n return await task;\n } finally {\n // Always clear: a failed load must not be cached, or a retry can never\n // succeed for the lifetime of the page.\n inflight.delete(url);\n }\n },\n async loadAll(urls) {\n const names = Object.keys(urls);\n const handles = await Promise.all(names.map((n) => cache.load(urls[n])));\n return Object.fromEntries(names.map((n, i) => [n, handles[i]]));\n },\n async decode(bytes) {\n return store(await ctx.decodeAudioData(bytes));\n },\n register: (buffer) => store(buffer),\n buffer: (handle) => buffers.get(handle.id) ?? null,\n };\n return cache;\n}\n","export interface Vec2 { x: number; y: number }\n\nexport interface SpatialOptions {\n /** Distance within which gain stays at 1. Default 1. The inverse model needs\n * this above 0: at 0 it cliffs gain from 1 to 0 at any nonzero distance. */\n refDistance?: number;\n /** Distance at which linear rolloff reaches 0. Ignored by inverse. Default\n * 10000. At or below `refDistance` it gives the same 1-to-0 cliff, there. */\n maxDistance?: number;\n /** Default 'inverse' — the natural-sounding one. */\n rolloff?: 'inverse' | 'linear';\n /** How sharply gain falls. Default 1. */\n rolloffFactor?: number;\n /** Horizontal distance mapping to full left/right. Default 500. */\n panWidth?: number;\n}\n\n/**\n * Map a source position to a gain and a stereo pan, relative to the listener.\n *\n * Pure — no Web Audio, no state. This is the whole spatial model.\n */\nexport function spatialize(\n source: Vec2,\n listener: Vec2,\n opts: SpatialOptions = {},\n): { gain: number; pan: number } {\n const refDistance = opts.refDistance ?? 1;\n const maxDistance = opts.maxDistance ?? 10000;\n const rolloff = opts.rolloff ?? 'inverse';\n const rolloffFactor = opts.rolloffFactor ?? 1;\n const panWidth = opts.panWidth ?? 500;\n\n const dx = source.x - listener.x;\n const dy = source.y - listener.y;\n const distance = Math.hypot(dx, dy);\n\n let gain: number;\n if (distance <= refDistance) {\n gain = 1;\n } else if (rolloff === 'linear') {\n const span = maxDistance - refDistance;\n gain = span <= 0 ? 0 : 1 - rolloffFactor * ((distance - refDistance) / span);\n } else {\n gain = refDistance / (refDistance + rolloffFactor * (distance - refDistance));\n }\n gain = Math.min(1, Math.max(0, gain));\n\n const pan = panWidth <= 0 ? 0 : Math.min(1, Math.max(-1, dx / panWidth));\n\n return { gain, pan };\n}\n","export type StealPolicy = 'oldest' | 'quietest';\n\nexport interface VoicePoolOptions {\n /** Maximum concurrent voices, at least 1. Beyond this, `acquire` steals. */\n limit: number;\n /** Default 'oldest'. */\n steal?: StealPolicy;\n}\n\nexport interface VoiceRecord {\n /** Engine time the voice started, in ms. */\n startedAt: number;\n /** Current gain, consulted by the 'quietest' steal policy. */\n gain: number;\n}\n\nexport interface Acquisition {\n slot: number;\n /** Identifies this voice, not its slot: a stolen slot is reissued at once,\n * so `release` and `setGain` take the token and ignore a stale one. */\n token: number;\n /** Token of the voice evicted to make room, or null. The caller is\n * responsible for actually stopping that voice's nodes. */\n stolen: number | null;\n}\n\nexport interface VoicePool {\n acquire(record: VoiceRecord): Acquisition;\n release(slot: number, token: number): void;\n setGain(slot: number, token: number, gain: number): void;\n active(): number;\n}\n\n/**\n * Slot bookkeeping for concurrent voices, with a steal policy for when the\n * limit is reached.\n *\n * This is pure accounting — it owns no audio nodes. The caller builds the\n * `GainNode`/`StereoPannerNode` chain and mints a fresh `AudioBufferSourceNode`\n * per play, because a source node is single-use by specification and cannot be\n * restarted once stopped. Nothing is pooled but the slot numbers.\n */\nexport function createVoicePool(opts: VoicePoolOptions): VoicePool {\n if (!(opts.limit >= 1)) {\n throw new RangeError(`@weasel-js/audio: voice pool limit must be at least 1, got ${opts.limit}`);\n }\n const steal = opts.steal ?? 'oldest';\n const live = new Map<number, VoiceRecord & { token: number }>();\n const free: number[] = [];\n let nextSlot = 0;\n let nextToken = 1;\n\n const victim = (): number => {\n let worst = -1;\n let worstScore = Infinity;\n for (const [slot, rec] of live) {\n const score = steal === 'quietest' ? rec.gain : rec.startedAt;\n if (score < worstScore) { worstScore = score; worst = slot; }\n }\n return worst;\n };\n\n return {\n acquire(record) {\n const token = nextToken++;\n if (live.size < opts.limit) {\n const slot = free.length > 0 ? free.pop()! : nextSlot++;\n live.set(slot, { ...record, token });\n return { slot, token, stolen: null };\n }\n const slot = victim();\n const stolen = live.get(slot)!.token;\n // Re-inserting moves the slot to the back of the iteration order, so\n // voices tied on score take turns being the victim.\n live.delete(slot);\n live.set(slot, { ...record, token });\n return { slot, token, stolen };\n },\n release(slot, token) {\n if (live.get(slot)?.token !== token) return;\n live.delete(slot);\n free.push(slot);\n },\n setGain(slot, token, gain) {\n const rec = live.get(slot);\n if (rec?.token === token) rec.gain = gain;\n },\n active: () => live.size,\n };\n}\n","import { createAnalyserTap, type AnalyserTap, type AnalyserTapOptions } from './analyser';\nimport { createBusGraph, type BusHandle } from './buses';\nimport { writeParam } from './param';\nimport { createScheduler } from './scheduler';\nimport { createSoundCache, type SoundHandle } from './soundCache';\nimport { spatialize, type SpatialOptions, type Vec2 } from './spatialize';\nimport type { AudioEngineOptions, PlayOptions, VoiceHandle } from './types';\nimport { createVoicePool, type VoicePool } from './voicePool';\n\nexport interface AudioEngine {\n /** The engine's `AudioContext` — the one passed as `options.context`, or the\n * one the engine created. Use it for `createBuffer`, for analysis, or for a\n * node graph of your own alongside the engine's.\n *\n * `dispose()` closes a context the engine created, and every call on a\n * closed context throws; a context you passed in is left open, because it is\n * yours to close. The reference stays valid either way — read `state()`\n * rather than assuming. */\n readonly context: AudioContext;\n state(): AudioContextState;\n unlock(): Promise<void>;\n /** Engine time in ms. */\n now(): number;\n load(url: string): Promise<SoundHandle>;\n loadAll(urls: Record<string, string>): Promise<Record<string, SoundHandle>>;\n decode(bytes: ArrayBuffer): Promise<SoundHandle>;\n /** Play a buffer the consumer already holds — a synth, an offline render, a\n * recording. `load` and `decode` both assume encoded bytes. */\n register(buffer: AudioBuffer): SoundHandle;\n play(sound: SoundHandle, opts?: PlayOptions): VoiceHandle;\n stopKey(key: string): void;\n stopAll(): void;\n bus(name: string): BusHandle;\n /** The configured bus names, in order. The first is `play()`'s default. */\n busNames(): string[];\n /** Voices currently sounding, on one bus or on all of them. A voice booked\n * for a future `when` is not counted until it starts. */\n activeVoices(bus?: string): number;\n analyser(busName?: string, opts?: AnalyserTapOptions): AnalyserTap;\n setListener(p: Vec2, opts?: SpatialOptions): void;\n dispose(): void;\n}\n\n/** A voice pool and its token index. Tokens restart at 1 in every pool, so the\n * index has to be per pool — keyed by bus name, a shared pool would look a\n * steal victim up under the wrong name and silently never tear it down. */\ninterface Slots {\n pool: VoicePool;\n byToken: Map<number, LiveVoice>;\n}\n\ninterface LiveVoice {\n id: number;\n slots: Slots;\n /** Null until the scheduler fires it: a voice booked seconds out must not\n * hold a slot, and must not be an eviction candidate, while silent. */\n slot: number | null;\n token: number;\n key?: string;\n source: AudioBufferSourceNode | null;\n gainNode: GainNode;\n panNode: StereoPannerNode;\n baseGain: number;\n spatialGain: number;\n rate: number;\n detune: number;\n /** Kept so `setListener` can re-spatialize a voice that is already playing. */\n position?: Vec2;\n playing: boolean;\n cancelled: boolean;\n}\n\nexport function createAudioEngine(opts: AudioEngineOptions = {}): AudioEngine {\n const ctx = opts.context ?? new AudioContext();\n // A context the engine made is a context the engine closes; a consumer's is\n // theirs to keep.\n const ownsContext = opts.context === undefined;\n const busNames = opts.buses ?? ['sfx', 'music', 'ui'];\n if (busNames.length === 0) {\n throw new Error('@weasel-js/audio: an engine needs at least one bus');\n }\n const graph = createBusGraph(ctx, busNames);\n const sounds = createSoundCache(ctx, opts.fetchFn);\n // One pool PER BUS, not one global pool: the spec's limit is per-bus, and a\n // shared pool lets a burst of one-shots on `sfx` steal the music bed.\n const slotsByBus = new Map<string, Slots>();\n for (const name of busNames) {\n slotsByBus.set(name, {\n pool: createVoicePool({ limit: opts.voiceLimit ?? 32, steal: opts.steal }),\n byToken: new Map(),\n });\n }\n const slotsFor = (bus: string): Slots => {\n const s = slotsByBus.get(bus);\n if (!s) throw new Error(`@weasel-js/audio: unknown bus \"${bus}\"`);\n return s;\n };\n\n const now = (): number => ctx.currentTime * 1000;\n const scheduler = createScheduler({\n now,\n // One-shot, not repeating: the scheduler re-arms at the end of every pass,\n // so an interval would leave the previous one running and double the live\n // timer count per tick.\n setTimer: opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms)),\n clearTimer: opts.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>)),\n lookahead: opts.lookahead,\n interval: opts.tickInterval,\n });\n scheduler.start();\n\n let listener: Vec2 = { x: 0, y: 0 };\n let spatialOpts: SpatialOptions = {};\n let nextVoiceId = 1;\n let disposed = false;\n let suspendedSince = false;\n /** Engine time before which a queued entry is stale — set on resume, so a\n * suspension does not end in one pass that fires its whole backlog. */\n let staleFloor = -Infinity;\n const live = new Map<number, LiveVoice>();\n const taps = new Set<AnalyserTap>();\n\n let warnedLocked = false;\n const warnLocked = (): void => {\n if (warnedLocked) return;\n warnedLocked = true;\n console.warn(\n '@weasel-js/audio: play() before the AudioContext was unlocked — the voice was dropped. ' +\n 'Browsers require a user gesture; call engine.unlock() from one, or wait for the ' +\n 'automatic gesture listener.',\n );\n };\n\n let warnedDisposed = false;\n const warnDisposed = (): void => {\n if (warnedDisposed) return;\n warnedDisposed = true;\n console.warn('@weasel-js/audio: play() after dispose() — the voice was dropped.');\n };\n\n let warnedUnknownSound = false;\n const warnUnknownSound = (id: string): void => {\n if (warnedUnknownSound) return;\n warnedUnknownSound = true;\n console.warn(\n `@weasel-js/audio: play() was handed sound \"${id}\", which this engine never loaded — ` +\n 'the voice was dropped. A handle belongs to the engine that produced it.',\n );\n };\n\n // Resume on the first user gesture, then stop listening.\n const gestures = ['pointerdown', 'keydown', 'touchstart'] as const;\n const onGesture = (): void => { void engine.unlock(); };\n const armGestures = (): void => {\n if (typeof window === 'undefined') return;\n for (const g of gestures) window.addEventListener(g, onGesture, { once: true, passive: true });\n };\n const disarmGestures = (): void => {\n if (typeof window === 'undefined') return;\n for (const g of gestures) window.removeEventListener(g, onGesture);\n };\n armGestures();\n\n // A browser suspends the context when the tab goes away, and the one-shot\n // gesture listeners are long consumed by then. Without this the engine is\n // permanently silent afterwards, with no diagnostic — the drop warning is a\n // once-per-engine latch.\n const onStateChange = (): void => {\n if (disposed) return;\n if (ctx.state === 'running') {\n if (suspendedSince) staleFloor = now();\n suspendedSince = false;\n return;\n }\n suspendedSince = true;\n warnedLocked = false;\n armGestures();\n };\n ctx.addEventListener('statechange', onStateChange);\n\n const poolGain = (voice: LiveVoice): void => {\n if (voice.slot === null) return;\n voice.slots.pool.setGain(voice.slot, voice.token, voice.baseGain * voice.spatialGain);\n };\n\n const applySpatial = (voice: LiveVoice): void => {\n if (!voice.position) return;\n const s = spatialize(voice.position, listener, spatialOpts);\n voice.spatialGain = s.gain;\n voice.panNode.pan.value = s.pan;\n writeParam(ctx, voice.gainNode.gain, voice.baseGain * s.gain);\n poolGain(voice);\n };\n\n const teardown = (voice: LiveVoice): void => {\n // First, and never at a call site: teardown is what makes a voice dead, and\n // a queued callback that outlives it must find it cancelled. The steal path\n // has no other place to set this.\n voice.cancelled = true;\n if (!voice.playing && voice.source === null) return;\n voice.playing = false;\n if (voice.source) {\n try { voice.source.stop(); } catch { /* already stopped */ }\n voice.source.disconnect();\n }\n voice.source = null;\n // Nothing pools the chain, so it goes when the voice does.\n voice.panNode.disconnect();\n voice.gainNode.disconnect();\n live.delete(voice.id);\n if (voice.slot !== null) {\n voice.slots.byToken.delete(voice.token);\n // Safe on the steal path too: the pool has already reissued this slot\n // under a new token, and it ignores a release carrying the old one.\n voice.slots.pool.release(voice.slot, voice.token);\n }\n };\n\n const dropped = (id: number): VoiceHandle => ({\n id,\n stop: () => {}, setGain: () => {}, setRate: () => {}, setDetune: () => {},\n setPan: () => {}, setPosition: () => {}, isPlaying: () => false,\n });\n\n const engine: AudioEngine = {\n context: ctx,\n state: () => ctx.state,\n async unlock() {\n if (disposed) return;\n if (ctx.state !== 'running') await ctx.resume();\n },\n now,\n load: sounds.load,\n loadAll: sounds.loadAll,\n decode: sounds.decode,\n register: sounds.register,\n\n play(sound, playOpts = {}) {\n const id = nextVoiceId++;\n\n if (disposed) {\n warnDisposed();\n return dropped(id);\n }\n if (ctx.state !== 'running') {\n warnLocked();\n return dropped(id);\n }\n\n const busName = playOpts.bus ?? busNames[0];\n const slots = slotsFor(busName);\n const when = playOpts.when ?? now();\n const baseGain = playOpts.gain ?? 1;\n\n const spatial = playOpts.position\n ? spatialize(playOpts.position, listener, spatialOpts)\n : { gain: 1, pan: playOpts.pan ?? 0 };\n\n const gainNode = ctx.createGain();\n const panNode = ctx.createStereoPanner();\n gainNode.gain.value = baseGain * spatial.gain;\n panNode.pan.value = spatial.pan;\n panNode.connect(gainNode);\n gainNode.connect(graph.node(busName));\n\n const voice: LiveVoice = {\n id, slots, slot: null, token: 0, key: playOpts.cancelKey,\n source: null, gainNode, panNode,\n baseGain, spatialGain: spatial.gain,\n rate: playOpts.rate ?? 1, detune: playOpts.detune ?? 0,\n position: playOpts.position,\n playing: true, cancelled: false,\n };\n live.set(id, voice);\n\n scheduler.schedule(when, (scheduledWhen) => {\n if (voice.cancelled) return;\n // Came due while the context was suspended: starting it now would play\n // a backlog at once, which is what dropping a locked play avoids.\n if (scheduledWhen < staleFloor) {\n teardown(voice);\n return;\n }\n const buffer = sounds.buffer(sound);\n if (!buffer) {\n warnUnknownSound(sound.id);\n teardown(voice);\n return;\n }\n\n // The slot is taken here, not at play() time. A voice booked a bar out\n // would otherwise hold one while silent — and, its `startedAt` being in\n // the future, be the last thing the 'oldest' policy evicts, so booking\n // a bar of events would evict everything currently audible.\n const acquired = slots.pool.acquire({\n startedAt: scheduledWhen,\n gain: voice.baseGain * voice.spatialGain,\n });\n voice.slot = acquired.slot;\n voice.token = acquired.token;\n slots.byToken.set(acquired.token, voice);\n if (acquired.stolen !== null) {\n const victim = slots.byToken.get(acquired.stolen);\n if (victim) teardown(victim);\n }\n\n // A fresh source per play: AudioBufferSourceNode is single-use by\n // specification and cannot be restarted once stopped.\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.loop = playOpts.loop ?? false;\n source.playbackRate.value = voice.rate;\n source.detune.value = voice.detune;\n source.connect(panNode);\n source.onended = () => { teardown(voice); playOpts.onDone?.(); };\n voice.source = source;\n source.start(scheduledWhen / 1000);\n }, playOpts.cancelKey);\n\n return {\n id,\n stop(fadeMs) {\n if (fadeMs && fadeMs > 0 && voice.source && !voice.cancelled) {\n // Cancelled but not torn down: the fade is what ends this voice, and\n // stopping the source now would cut the ramp scheduled a line above\n // it — which is the click `fadeMs` exists to avoid. `onended` does\n // the bookkeeping when the fade reaches the end.\n voice.cancelled = true;\n writeParam(ctx, gainNode.gain, 0, fadeMs);\n try { voice.source.stop(ctx.currentTime + fadeMs / 1000); } catch { /* ended */ }\n return;\n }\n teardown(voice);\n },\n setGain(value, rampMs) {\n voice.baseGain = value;\n writeParam(ctx, gainNode.gain, value * voice.spatialGain, rampMs);\n poolGain(voice);\n },\n setRate(value) {\n voice.rate = value;\n if (voice.source) voice.source.playbackRate.value = value;\n },\n setDetune(cents) {\n voice.detune = cents;\n if (voice.source) voice.source.detune.value = cents;\n },\n setPan(value) {\n voice.position = undefined;\n panNode.pan.value = value;\n },\n setPosition(p) {\n voice.position = p;\n applySpatial(voice);\n },\n isPlaying: () => voice.playing,\n };\n },\n\n stopKey(key) {\n scheduler.cancelKey(key);\n for (const voice of [...live.values()]) {\n if (voice.key === key) teardown(voice);\n }\n },\n stopAll() {\n for (const voice of [...live.values()]) teardown(voice);\n },\n bus: graph.bus,\n busNames: graph.names,\n activeVoices(bus) {\n if (bus !== undefined) return slotsFor(bus).pool.active();\n let total = 0;\n for (const s of slotsByBus.values()) total += s.pool.active();\n return total;\n },\n analyser(busName, tapOpts) {\n // Every tap is one more live FFT on the graph. They are tracked so\n // `dispose()` detaches them; a consumer calling this per frame still\n // wants to keep one tap rather than mint one.\n const tap = createAnalyserTap(ctx, busName ? graph.node(busName) : graph.master, tapOpts);\n taps.add(tap);\n return {\n ...tap,\n dispose() {\n taps.delete(tap);\n tap.dispose();\n },\n };\n },\n setListener(p, o) {\n listener = p;\n if (o) spatialOpts = o;\n for (const voice of live.values()) applySpatial(voice);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n engine.stopAll();\n scheduler.stop();\n scheduler.clear();\n for (const tap of [...taps]) tap.dispose();\n taps.clear();\n disarmGestures();\n ctx.removeEventListener('statechange', onStateChange);\n graph.master.disconnect();\n if (ownsContext) void ctx.close().catch(() => undefined);\n },\n };\n\n return engine;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyser.ts","../src/param.ts","../src/buses.ts","../src/scheduler.ts","../src/soundCache.ts","../src/spatialize.ts","../src/tickTimer.ts","../src/voicePool.ts","../src/createAudioEngine.ts"],"names":["slot"],"mappings":"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","file":"index.js","sourcesContent":["export interface AnalyserTapOptions {\n /** Power of two, 32..32768. Default 2048. */\n fftSize?: number;\n}\n\nexport interface AnalyserTap {\n /** The underlying node, exposed for disposal assertions and advanced wiring. */\n node: AnalyserNode;\n /** Frequency data, sized `node.frequencyBinCount` when no array is given. */\n frequencies(out?: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;\n /** Time-domain data, sized `node.fftSize` when no array is given — twice the\n * frequency bin count, which is the whole window rather than half of it. */\n waveform(out?: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;\n /** RMS amplitude over the whole time-domain window, 0..1. */\n level(): number;\n /** `n` averaged frequency bands normalized to 0..1 — the ergonomic form for\n * driving a shader uniform, a vertex color, or a pose. `out`, when given,\n * must be exactly `n` long. */\n bands(n: number, out?: Float32Array<ArrayBuffer>): Float32Array<ArrayBuffer>;\n /** Detach from the tapped node. Idempotent. */\n dispose(): void;\n}\n\nexport function createAnalyserTap(\n ctx: AudioContext,\n source: AudioNode,\n opts: AnalyserTapOptions = {},\n): AnalyserTap {\n const node = ctx.createAnalyser();\n node.fftSize = opts.fftSize ?? 2048;\n source.connect(node);\n let attached = true;\n\n // `node` is public surface, so `fftSize` can change under the tap. Time-domain\n // data is sized by `fftSize`; frequency data by half of it.\n let freqScratch = new Uint8Array(0);\n let timeScratch = new Uint8Array(0);\n const freqBuf = (): Uint8Array<ArrayBuffer> => {\n if (freqScratch.length !== node.frequencyBinCount) {\n freqScratch = new Uint8Array(node.frequencyBinCount);\n }\n return freqScratch;\n };\n const timeBuf = (): Uint8Array<ArrayBuffer> => {\n if (timeScratch.length !== node.fftSize) timeScratch = new Uint8Array(node.fftSize);\n return timeScratch;\n };\n\n return {\n node,\n frequencies(out) {\n const target = out ?? new Uint8Array(node.frequencyBinCount);\n node.getByteFrequencyData(target);\n return target;\n },\n waveform(out) {\n const target = out ?? new Uint8Array(node.fftSize);\n node.getByteTimeDomainData(target);\n return target;\n },\n level() {\n const buf = timeBuf();\n node.getByteTimeDomainData(buf);\n let sum = 0;\n for (let i = 0; i < buf.length; i += 1) {\n // Time-domain bytes center on 128; subtract to get a signed sample.\n const v = (buf[i] - 128) / 128;\n sum += v * v;\n }\n return Math.sqrt(sum / buf.length);\n },\n bands(n, out) {\n if (!Number.isInteger(n) || n < 1) {\n throw new Error('@weasel-js/audio: bands(n) requires a positive integer');\n }\n // Unlike the byte readers, a short `out` here would return fewer bands\n // than asked for with nothing to distinguish it from a correct result.\n if (out && out.length !== n) {\n throw new Error(\n `@weasel-js/audio: bands(${n}, out) requires an out array of length ${n}, got ${out.length}`,\n );\n }\n const buf = freqBuf();\n node.getByteFrequencyData(buf);\n const target = out ?? new Float32Array(n);\n const per = buf.length / n;\n for (let b = 0; b < n; b += 1) {\n const lo = Math.floor(b * per);\n const hi = Math.max(lo + 1, Math.floor((b + 1) * per));\n let sum = 0;\n for (let i = lo; i < hi; i += 1) sum += buf[i];\n target[b] = sum / (hi - lo) / 255;\n }\n return target;\n },\n // `node.disconnect()` would cut the analyser's outgoing edges, and it has\n // none: the edge that keeps it alive is the source's.\n dispose() {\n if (!attached) return;\n attached = false;\n source.disconnect(node);\n },\n };\n}\n","/**\n * Every gain write in the package goes through here.\n *\n * Two traps, both silent: a bare `param.value = x` sits *behind* any scheduled\n * ramp, which keeps running and wins; and a ramp with no anchor interpolates\n * from the previous scheduled endpoint rather than from the value the param\n * holds now, so a second ramp jumps before it moves.\n */\nexport function writeParam(\n ctx: BaseAudioContext,\n param: AudioParam,\n value: number,\n rampMs?: number,\n): void {\n const now = ctx.currentTime;\n param.cancelScheduledValues(now);\n if (rampMs && rampMs > 0) {\n param.setValueAtTime(param.value, now);\n param.linearRampToValueAtTime(value, now + rampMs / 1000);\n } else {\n param.setValueAtTime(value, now);\n }\n}\n","import { writeParam } from './param';\n\nexport interface BusHandle {\n setGain(value: number, rampMs?: number): void;\n mute(on: boolean): void;\n solo(on: boolean): void;\n /** The bus's own gain, as last set — not what a mute or solo is imposing. */\n gain(): number;\n /** The mute flag as set, independent of any solo. */\n muted(): boolean;\n /** The solo flag as set; whether it silences anything is a graph-wide answer. */\n soloed(): boolean;\n /** Whether the bus is being heard: unmuted, and soloed if any bus is soloed.\n * Not derivable from `muted()` and `soloed()` alone. */\n audible(): boolean;\n}\n\nexport interface BusGraph {\n master: GainNode;\n node(name: string): GainNode;\n bus(name: string): BusHandle;\n names(): string[];\n}\n\ninterface BusState { node: GainNode; gain: number; muted: boolean; soloed: boolean }\n\n/**\n * The mix graph: one `GainNode` per named bus, all routed to a master that\n * routes to the destination.\n *\n * Gain, mute and solo are three inputs to one output value rather than three\n * things that write the node directly — otherwise unmuting restores the wrong\n * value whenever a solo changed it in between. All three write through\n * `writeParam`, so `rampMs` survives instead of being overwritten by the\n * recomputation that follows it.\n */\nexport function createBusGraph(ctx: AudioContext, names: string[]): BusGraph {\n const master = ctx.createGain();\n master.connect(ctx.destination);\n\n const states = new Map<string, BusState>();\n for (const name of names) {\n const node = ctx.createGain();\n node.connect(master);\n states.set(name, { node, gain: 1, muted: false, soloed: false });\n }\n\n const anySoloed = (): boolean => {\n for (const s of states.values()) if (s.soloed) return true;\n return false;\n };\n\n const audibleUnder = (s: BusState, soloing: boolean): boolean =>\n !s.muted && (!soloing || s.soloed);\n\n // `ramp` names the one bus whose own value changed, so a solo does not slew\n // every other bus at whatever rate that bus was last set with.\n const apply = (ramp?: { bus: BusState; ms: number }): void => {\n const soloing = anySoloed();\n for (const s of states.values()) {\n const value = audibleUnder(s, soloing) ? s.gain : 0;\n writeParam(ctx, s.node.gain, value, ramp?.bus === s ? ramp.ms : undefined);\n }\n };\n\n const get = (name: string): BusState => {\n const s = states.get(name);\n if (!s) throw new Error(`@weasel-js/audio: unknown bus \"${name}\"`);\n return s;\n };\n\n return {\n master,\n node: (name) => get(name).node,\n names: () => [...states.keys()],\n bus(name) {\n const s = get(name);\n return {\n setGain(value, rampMs) {\n s.gain = value;\n apply(rampMs && rampMs > 0 ? { bus: s, ms: rampMs } : undefined);\n },\n mute(on) { s.muted = on; apply(); },\n solo(on) { s.soloed = on; apply(); },\n gain: () => s.gain,\n muted: () => s.muted,\n soloed: () => s.soloed,\n audible: () => audibleUnder(s, anySoloed()),\n };\n },\n };\n}\n","export interface SchedulerOptions {\n /** Engine time in ms. Backed by `AudioContext.currentTime * 1000` in production. */\n now: () => number;\n /** One-shot: it fires once and the pass re-arms it. `setInterval` here would\n * leave every previous timer running. */\n setTimer: (cb: () => void, ms: number) => unknown;\n clearTimer: (handle: unknown) => void;\n /** How far ahead to book events, in ms. Default 100. */\n lookahead?: number;\n /** Time between passes, in ms. Default 25. */\n interval?: number;\n}\n\ninterface Entry {\n when: number;\n fire: (when: number) => void;\n key?: string;\n}\n\nexport interface Scheduler {\n start(): void;\n /** Stop the timer. The queue survives; `clear()` empties it. */\n stop(): void;\n /** Book `fire` for engine time `when`, receiving `when` so it can hand the\n * true time to `source.start()` rather than \"now\". It runs on the first\n * pass whose lookahead window reaches it — or, when the window already\n * covers it and the scheduler is running, at the end of the current task,\n * in time order with everything else then due. */\n schedule(when: number, fire: (when: number) => void, key?: string): void;\n /** Drop queued events with this key. A pass takes its whole batch out of the\n * queue before firing any of it, so a callback cannot cancel a sibling that\n * came due alongside it. */\n cancelKey(key: string): void;\n /** Drop every queued event. Without this, a stop/start cycle fires\n * everything that came due meanwhile in one burst on the first pass. */\n clear(): void;\n pending(): number;\n}\n\n/**\n * Lookahead scheduler. Each pass fires everything due within `lookahead` ms,\n * in time order, handing each callback its own scheduled time. An event\n * scheduled inside that window does not wait up to `interval` for the next\n * pass: the end of the current task fires what is due then.\n *\n * It runs on its own timer rather than on an animation frame, which stops\n * entirely when nothing is animating. The timer is one-shot: the pass re-arms\n * it at the end, so passes cannot overlap. A main-thread `setTimeout` is clamped\n * to a second in a hidden tab, which the lookahead cannot cover; the engine\n * passes `createTickTimer` instead.\n */\nexport function createScheduler(opts: SchedulerOptions): Scheduler {\n const lookahead = opts.lookahead ?? 100;\n const interval = opts.interval ?? 25;\n let queue: Entry[] = [];\n let running = false;\n let handle: unknown = null;\n\n let flushQueued = false;\n\n const fireDue = (): void => {\n const horizon = opts.now() + lookahead;\n const due: Entry[] = [];\n const rest: Entry[] = [];\n for (const entry of queue) (entry.when <= horizon ? due : rest).push(entry);\n queue = rest;\n due.sort((a, b) => a.when - b.when);\n for (const entry of due) {\n try {\n entry.fire(entry.when);\n } catch (err) {\n console.error('@weasel-js/audio scheduler: callback threw', err);\n }\n }\n };\n\n const pass = (): void => {\n fireDue();\n if (running) handle = opts.setTimer(pass, interval);\n };\n\n // A microtask rather than firing inside `schedule()`: everything booked in\n // one task is fired as one batch, sorted by time, exactly as a pass would.\n const flush = (): void => {\n flushQueued = false;\n if (running) fireDue();\n };\n\n return {\n start() {\n if (running) return;\n running = true;\n handle = opts.setTimer(pass, interval);\n },\n stop() {\n if (!running) return;\n running = false;\n opts.clearTimer(handle);\n handle = null;\n },\n schedule(when, fire, key) {\n queue.push({ when, fire, key });\n if (running && !flushQueued && when <= opts.now() + lookahead) {\n flushQueued = true;\n queueMicrotask(flush);\n }\n },\n cancelKey(key) {\n queue = queue.filter((e) => e.key !== key);\n },\n clear() {\n queue = [];\n },\n pending: () => queue.length,\n };\n}\n","/** Opaque reference to a decoded sound. Mirrors `TextureHandle` in core. */\nexport interface SoundHandle { readonly id: string }\n\nexport interface SoundCache {\n load(url: string): Promise<SoundHandle>;\n loadAll(urls: Record<string, string>): Promise<Record<string, SoundHandle>>;\n decode(bytes: ArrayBuffer): Promise<SoundHandle>;\n /** Take ownership of a buffer the caller already has — a procedural synth,\n * an OfflineAudioContext render, a recording. Synchronous; nothing to decode. */\n register(buffer: AudioBuffer): SoundHandle;\n buffer(handle: SoundHandle): AudioBuffer | null;\n}\n\nexport function createSoundCache(\n ctx: AudioContext,\n fetchFn: typeof fetch = fetch,\n): SoundCache {\n let counter = 0;\n const buffers = new Map<string, AudioBuffer>();\n const byUrl = new Map<string, SoundHandle>();\n // In-flight loads, so two concurrent `load` calls for one url share a fetch.\n const inflight = new Map<string, Promise<SoundHandle>>();\n\n const store = (buffer: AudioBuffer): SoundHandle => {\n const id = `snd_${++counter}`;\n buffers.set(id, buffer);\n return { id };\n };\n\n const cache: SoundCache = {\n async load(url) {\n const existing = byUrl.get(url);\n if (existing) return existing;\n const pending = inflight.get(url);\n if (pending) return pending;\n\n const task = (async () => {\n const res = await fetchFn(url);\n if (!res.ok) throw new Error(`@weasel-js/audio: failed to load ${url} (${res.status})`);\n const bytes = await res.arrayBuffer();\n const buffer = await ctx.decodeAudioData(bytes);\n const handle = store(buffer);\n byUrl.set(url, handle);\n return handle;\n })();\n\n inflight.set(url, task);\n try {\n return await task;\n } finally {\n // Always clear: a failed load must not be cached, or a retry can never\n // succeed for the lifetime of the page.\n inflight.delete(url);\n }\n },\n async loadAll(urls) {\n const names = Object.keys(urls);\n const handles = await Promise.all(names.map((n) => cache.load(urls[n])));\n return Object.fromEntries(names.map((n, i) => [n, handles[i]]));\n },\n async decode(bytes) {\n return store(await ctx.decodeAudioData(bytes));\n },\n register: (buffer) => store(buffer),\n buffer: (handle) => buffers.get(handle.id) ?? null,\n };\n return cache;\n}\n","export interface Vec2 { x: number; y: number }\n\nexport interface SpatialOptions {\n /** Distance within which gain stays at 1. Default 1. The inverse model needs\n * this above 0: at 0 it cliffs gain from 1 to 0 at any nonzero distance. */\n refDistance?: number;\n /** Distance at which linear rolloff reaches 0. Ignored by inverse. Default\n * 10000. At or below `refDistance` it gives the same 1-to-0 cliff, there. */\n maxDistance?: number;\n /** Default 'inverse' — the natural-sounding one. */\n rolloff?: 'inverse' | 'linear';\n /** How sharply gain falls. Default 1. */\n rolloffFactor?: number;\n /** Horizontal distance mapping to full left/right. Default 500. */\n panWidth?: number;\n}\n\n/**\n * Map a source position to a gain and a stereo pan, relative to the listener.\n *\n * Pure — no Web Audio, no state. This is the whole spatial model.\n */\nexport function spatialize(\n source: Vec2,\n listener: Vec2,\n opts: SpatialOptions = {},\n): { gain: number; pan: number } {\n const refDistance = opts.refDistance ?? 1;\n const maxDistance = opts.maxDistance ?? 10000;\n const rolloff = opts.rolloff ?? 'inverse';\n const rolloffFactor = opts.rolloffFactor ?? 1;\n const panWidth = opts.panWidth ?? 500;\n\n const dx = source.x - listener.x;\n const dy = source.y - listener.y;\n const distance = Math.hypot(dx, dy);\n\n let gain: number;\n if (distance <= refDistance) {\n gain = 1;\n } else if (rolloff === 'linear') {\n const span = maxDistance - refDistance;\n gain = span <= 0 ? 0 : 1 - rolloffFactor * ((distance - refDistance) / span);\n } else {\n gain = refDistance / (refDistance + rolloffFactor * (distance - refDistance));\n }\n gain = Math.min(1, Math.max(0, gain));\n\n const pan = panWidth <= 0 ? 0 : Math.min(1, Math.max(-1, dx / panWidth));\n\n return { gain, pan };\n}\n","/** Runs in the worker. A message with `ms` books timer `id`; one without clears\n * it. Each timer posts its id back once, when it elapses. */\nconst WORKER_SOURCE = `const timers = new Map();\nself.onmessage = (e) => {\n const id = e.data.id;\n clearTimeout(timers.get(id));\n timers.delete(id);\n if (e.data.ms === undefined) return;\n timers.set(id, setTimeout(() => {\n timers.delete(id);\n self.postMessage(id);\n }, e.data.ms));\n};\n`;\n\nexport interface TickTimer {\n /** One-shot, as `createScheduler` expects. */\n setTimer(cb: () => void, ms: number): unknown;\n clearTimer(handle: unknown): void;\n /** Terminate the worker and drop every pending timer. */\n dispose(): void;\n}\n\ninterface Pending {\n cb: () => void;\n ms: number;\n fallback?: ReturnType<typeof setTimeout>;\n}\n\nfunction spawn(): { worker: Worker; url: string } | null {\n if (\n typeof Worker !== 'function' ||\n typeof Blob !== 'function' ||\n typeof URL === 'undefined' ||\n typeof URL.createObjectURL !== 'function'\n ) {\n return null;\n }\n let url: string | undefined;\n try {\n url = URL.createObjectURL(new Blob([WORKER_SOURCE], { type: 'text/javascript' }));\n return { worker: new Worker(url), url };\n } catch {\n if (url !== undefined) URL.revokeObjectURL(url);\n return null;\n }\n}\n\n/**\n * A timer for the scheduler's pass that is not held to the main thread's\n * hidden-tab clamp: each delay runs inside a dedicated Worker, which posts back\n * when it elapses. Falls back to `setTimeout` when no worker can be made — no\n * `Worker` global, a constructor that throws, or a worker that fails to load,\n * which is how a CSP refusal can arrive.\n */\nexport function createTickTimer(): TickTimer {\n let nextId = 1;\n const pending = new Map<number, Pending>();\n let spawned = spawn();\n\n const fire = (id: number): void => {\n const p = pending.get(id);\n if (!p) return;\n pending.delete(id);\n p.cb();\n };\n const arm = (id: number, p: Pending): void => {\n p.fallback = setTimeout(() => fire(id), p.ms);\n };\n const release = (): void => {\n if (!spawned) return;\n spawned.worker.terminate();\n URL.revokeObjectURL(spawned.url);\n spawned = null;\n };\n\n if (spawned) {\n spawned.worker.addEventListener('message', (e: MessageEvent) => fire(e.data as number));\n spawned.worker.addEventListener('error', () => {\n if (!spawned) return;\n release();\n // Anything posted to a worker that never loaded is lost with it.\n for (const [id, p] of pending) arm(id, p);\n });\n }\n\n return {\n setTimer(cb, ms) {\n const id = nextId++;\n const p: Pending = { cb, ms };\n pending.set(id, p);\n if (spawned) spawned.worker.postMessage({ id, ms });\n else arm(id, p);\n return id;\n },\n clearTimer(handle) {\n const id = handle as number;\n const p = pending.get(id);\n if (!p) return;\n pending.delete(id);\n if (p.fallback !== undefined) clearTimeout(p.fallback);\n else spawned?.worker.postMessage({ id });\n },\n dispose() {\n for (const p of pending.values()) {\n if (p.fallback !== undefined) clearTimeout(p.fallback);\n }\n pending.clear();\n release();\n },\n };\n}\n","export type StealPolicy = 'oldest' | 'quietest';\n\nexport interface VoicePoolOptions {\n /** Maximum concurrent voices, at least 1. Beyond this, `acquire` steals. */\n limit: number;\n /** Default 'oldest'. */\n steal?: StealPolicy;\n}\n\nexport interface VoiceRecord {\n /** Engine time the voice started, in ms. */\n startedAt: number;\n /** Current gain, consulted by the 'quietest' steal policy. */\n gain: number;\n}\n\nexport interface Acquisition {\n slot: number;\n /** Identifies this voice, not its slot: a stolen slot is reissued at once,\n * so `release` and `setGain` take the token and ignore a stale one. */\n token: number;\n /** Token of the voice evicted to make room, or null. The caller is\n * responsible for actually stopping that voice's nodes. */\n stolen: number | null;\n}\n\nexport interface VoicePool {\n acquire(record: VoiceRecord): Acquisition;\n release(slot: number, token: number): void;\n setGain(slot: number, token: number, gain: number): void;\n active(): number;\n}\n\n/**\n * Slot bookkeeping for concurrent voices, with a steal policy for when the\n * limit is reached.\n *\n * This is pure accounting — it owns no audio nodes. The engine reuses its\n * `GainNode`/`StereoPannerNode` chains separately, and mints a fresh\n * `AudioBufferSourceNode` per play, because a source node is single-use by\n * specification and cannot be restarted once stopped.\n */\nexport function createVoicePool(opts: VoicePoolOptions): VoicePool {\n if (!(opts.limit >= 1)) {\n throw new RangeError(`@weasel-js/audio: voice pool limit must be at least 1, got ${opts.limit}`);\n }\n const steal = opts.steal ?? 'oldest';\n const live = new Map<number, VoiceRecord & { token: number }>();\n const free: number[] = [];\n let nextSlot = 0;\n let nextToken = 1;\n\n const victim = (): number => {\n let worst = -1;\n let worstScore = Infinity;\n for (const [slot, rec] of live) {\n const score = steal === 'quietest' ? rec.gain : rec.startedAt;\n if (score < worstScore) { worstScore = score; worst = slot; }\n }\n return worst;\n };\n\n return {\n acquire(record) {\n const token = nextToken++;\n if (live.size < opts.limit) {\n const slot = free.length > 0 ? free.pop()! : nextSlot++;\n live.set(slot, { ...record, token });\n return { slot, token, stolen: null };\n }\n const slot = victim();\n const stolen = live.get(slot)!.token;\n // Re-inserting moves the slot to the back of the iteration order, so\n // voices tied on score take turns being the victim.\n live.delete(slot);\n live.set(slot, { ...record, token });\n return { slot, token, stolen };\n },\n release(slot, token) {\n if (live.get(slot)?.token !== token) return;\n live.delete(slot);\n free.push(slot);\n },\n setGain(slot, token, gain) {\n const rec = live.get(slot);\n if (rec?.token === token) rec.gain = gain;\n },\n active: () => live.size,\n };\n}\n","import { createAnalyserTap, type AnalyserTap, type AnalyserTapOptions } from './analyser';\nimport { createBusGraph, type BusHandle } from './buses';\nimport { writeParam } from './param';\nimport { createScheduler } from './scheduler';\nimport { createSoundCache, type SoundHandle } from './soundCache';\nimport { spatialize, type SpatialOptions, type Vec2 } from './spatialize';\nimport { createTickTimer } from './tickTimer';\nimport type { AudioEngineOptions, PlayOptions, VoiceHandle } from './types';\nimport { createVoicePool, type VoicePool } from './voicePool';\n\nexport interface AudioEngine {\n /** The engine's `AudioContext` — the one passed as `options.context`, or the\n * one the engine created. Use it for `createBuffer`, for analysis, or for a\n * node graph of your own alongside the engine's.\n *\n * `dispose()` closes a context the engine created, and every call on a\n * closed context throws; a context you passed in is left open, because it is\n * yours to close. The reference stays valid either way — read `state()`\n * rather than assuming. */\n readonly context: AudioContext;\n state(): AudioContextState;\n unlock(): Promise<void>;\n /** Engine time in ms. */\n now(): number;\n load(url: string): Promise<SoundHandle>;\n loadAll(urls: Record<string, string>): Promise<Record<string, SoundHandle>>;\n decode(bytes: ArrayBuffer): Promise<SoundHandle>;\n /** Play a buffer the consumer already holds — a synth, an offline render, a\n * recording. `load` and `decode` both assume encoded bytes. */\n register(buffer: AudioBuffer): SoundHandle;\n play(sound: SoundHandle, opts?: PlayOptions): VoiceHandle;\n stopKey(key: string): void;\n stopAll(): void;\n bus(name: string): BusHandle;\n /** The configured bus names, in order. The first is `play()`'s default. */\n busNames(): string[];\n /** Voices currently sounding, on one bus or on all of them. A voice booked\n * for a future `when` is not counted until it starts. */\n activeVoices(bus?: string): number;\n analyser(busName?: string, opts?: AnalyserTapOptions): AnalyserTap;\n setListener(p: Vec2, opts?: SpatialOptions): void;\n dispose(): void;\n}\n\n/** A voice pool and its token index. Tokens restart at 1 in every pool, so the\n * index has to be per pool — keyed by bus name, a shared pool would look a\n * steal victim up under the wrong name and silently never tear it down. */\ninterface Slots {\n pool: VoicePool;\n byToken: Map<number, LiveVoice>;\n}\n\ninterface Chain {\n gain: GainNode;\n panner: StereoPannerNode;\n}\n\ninterface LiveVoice {\n id: number;\n slots: Slots;\n /** Null until the scheduler fires it: a voice booked seconds out must not\n * hold a slot, and must not be an eviction candidate, while silent. */\n slot: number | null;\n token: number;\n key?: string;\n source: AudioBufferSourceNode | null;\n /** Null once torn down, when the chain goes back to be reused: a handle\n * that outlives its voice must not write to another voice's nodes. */\n chain: Chain | null;\n baseGain: number;\n spatialGain: number;\n rate: number;\n detune: number;\n /** Kept so `setListener` can re-spatialize a voice that is already playing. */\n position?: Vec2;\n playing: boolean;\n cancelled: boolean;\n}\n\nexport function createAudioEngine(opts: AudioEngineOptions = {}): AudioEngine {\n const ctx = opts.context ?? new AudioContext();\n // A context the engine made is a context the engine closes; a consumer's is\n // theirs to keep.\n const ownsContext = opts.context === undefined;\n const busNames = opts.buses ?? ['sfx', 'music', 'ui'];\n if (busNames.length === 0) {\n throw new Error('@weasel-js/audio: an engine needs at least one bus');\n }\n const graph = createBusGraph(ctx, busNames);\n const sounds = createSoundCache(ctx, opts.fetchFn);\n // One pool PER BUS, not one global pool: the spec's limit is per-bus, and a\n // shared pool lets a burst of one-shots on `sfx` steal the music bed.\n const slotsByBus = new Map<string, Slots>();\n for (const name of busNames) {\n slotsByBus.set(name, {\n pool: createVoicePool({ limit: opts.voiceLimit ?? 32, steal: opts.steal }),\n byToken: new Map(),\n });\n }\n const slotsFor = (bus: string): Slots => {\n const s = slotsByBus.get(bus);\n if (!s) throw new Error(`@weasel-js/audio: unknown bus \"${bus}\"`);\n return s;\n };\n\n const now = (): number => ctx.currentTime * 1000;\n const tickTimer = opts.setTimer === undefined && opts.clearTimer === undefined\n ? createTickTimer()\n : null;\n const scheduler = createScheduler({\n now,\n // One-shot, not repeating: the scheduler re-arms at the end of every pass,\n // so an interval would leave the previous one running and double the live\n // timer count per tick.\n setTimer: tickTimer?.setTimer ?? opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms)),\n clearTimer: tickTimer?.clearTimer\n ?? opts.clearTimer\n ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>)),\n lookahead: opts.lookahead,\n interval: opts.tickInterval,\n });\n scheduler.start();\n\n let listener: Vec2 = { x: 0, y: 0 };\n let spatialOpts: SpatialOptions = {};\n let nextVoiceId = 1;\n let disposed = false;\n let suspendedSince = false;\n /** Engine time before which a queued entry is stale — set on resume, so a\n * suspension does not end in one pass that fires its whole backlog. */\n let staleFloor = -Infinity;\n const live = new Map<number, LiveVoice>();\n const taps = new Set<AnalyserTap>();\n\n // An idle chain is kept off its bus: still wired to the destination, it costs\n // the audio thread every render quantum whether or not anything sounds.\n const idleChains: Chain[] = [];\n const idleChainLimit = (opts.voiceLimit ?? 32) * busNames.length;\n const takeChain = (bus: AudioNode, gain: number, pan: number): Chain => {\n let chain = idleChains.pop();\n if (!chain) {\n chain = { gain: ctx.createGain(), panner: ctx.createStereoPanner() };\n chain.panner.connect(chain.gain);\n }\n // Not a bare `.value`: a reused gain can still hold the last voice's fade.\n writeParam(ctx, chain.gain.gain, gain);\n chain.panner.pan.value = pan;\n chain.gain.connect(bus);\n return chain;\n };\n\n let warnedLocked = false;\n const warnLocked = (): void => {\n if (warnedLocked) return;\n warnedLocked = true;\n console.warn(\n '@weasel-js/audio: play() before the AudioContext was unlocked — the voice was dropped. ' +\n 'Browsers require a user gesture; call engine.unlock() from one, or wait for the ' +\n 'automatic gesture listener.',\n );\n };\n\n let warnedDisposed = false;\n const warnDisposed = (): void => {\n if (warnedDisposed) return;\n warnedDisposed = true;\n console.warn('@weasel-js/audio: play() after dispose() — the voice was dropped.');\n };\n\n let warnedUnknownSound = false;\n const warnUnknownSound = (id: string): void => {\n if (warnedUnknownSound) return;\n warnedUnknownSound = true;\n console.warn(\n `@weasel-js/audio: play() was handed sound \"${id}\", which this engine never loaded — ` +\n 'the voice was dropped. A handle belongs to the engine that produced it.',\n );\n };\n\n // Resume on the first user gesture, then stop listening.\n const gestures = ['pointerdown', 'keydown', 'touchstart'] as const;\n const onGesture = (): void => { void engine.unlock(); };\n const armGestures = (): void => {\n if (typeof window === 'undefined') return;\n for (const g of gestures) window.addEventListener(g, onGesture, { once: true, passive: true });\n };\n const disarmGestures = (): void => {\n if (typeof window === 'undefined') return;\n for (const g of gestures) window.removeEventListener(g, onGesture);\n };\n armGestures();\n\n // A browser suspends the context when the tab goes away, and the one-shot\n // gesture listeners are long consumed by then. Without this the engine is\n // permanently silent afterwards, with no diagnostic — the drop warning is a\n // once-per-engine latch.\n const onStateChange = (): void => {\n if (disposed) return;\n if (ctx.state === 'running') {\n if (suspendedSince) staleFloor = now();\n suspendedSince = false;\n return;\n }\n suspendedSince = true;\n warnedLocked = false;\n armGestures();\n };\n ctx.addEventListener('statechange', onStateChange);\n\n const poolGain = (voice: LiveVoice): void => {\n if (voice.slot === null) return;\n voice.slots.pool.setGain(voice.slot, voice.token, voice.baseGain * voice.spatialGain);\n };\n\n const applySpatial = (voice: LiveVoice): void => {\n if (!voice.position || !voice.chain) return;\n const s = spatialize(voice.position, listener, spatialOpts);\n voice.spatialGain = s.gain;\n voice.chain.panner.pan.value = s.pan;\n writeParam(ctx, voice.chain.gain.gain, voice.baseGain * s.gain);\n poolGain(voice);\n };\n\n const teardown = (voice: LiveVoice): void => {\n // First, and never at a call site: teardown is what makes a voice dead, and\n // a queued callback that outlives it must find it cancelled. The steal path\n // has no other place to set this.\n voice.cancelled = true;\n if (!voice.playing && voice.source === null) return;\n voice.playing = false;\n if (voice.source) {\n try { voice.source.stop(); } catch { /* already stopped */ }\n voice.source.disconnect();\n }\n voice.source = null;\n if (voice.chain) {\n voice.chain.gain.disconnect();\n if (idleChains.length < idleChainLimit) idleChains.push(voice.chain);\n voice.chain = null;\n }\n live.delete(voice.id);\n if (voice.slot !== null) {\n voice.slots.byToken.delete(voice.token);\n // Safe on the steal path too: the pool has already reissued this slot\n // under a new token, and it ignores a release carrying the old one.\n voice.slots.pool.release(voice.slot, voice.token);\n }\n };\n\n const dropped = (id: number): VoiceHandle => ({\n id,\n stop: () => {}, setGain: () => {}, setRate: () => {}, setDetune: () => {},\n setPan: () => {}, setPosition: () => {}, isPlaying: () => false,\n });\n\n const engine: AudioEngine = {\n context: ctx,\n state: () => ctx.state,\n async unlock() {\n if (disposed) return;\n if (ctx.state !== 'running') await ctx.resume();\n },\n now,\n load: sounds.load,\n loadAll: sounds.loadAll,\n decode: sounds.decode,\n register: sounds.register,\n\n play(sound, playOpts = {}) {\n const id = nextVoiceId++;\n\n if (disposed) {\n warnDisposed();\n return dropped(id);\n }\n if (ctx.state !== 'running') {\n warnLocked();\n return dropped(id);\n }\n\n const busName = playOpts.bus ?? busNames[0];\n const slots = slotsFor(busName);\n const bookedAt = now();\n const when = playOpts.when ?? bookedAt;\n const baseGain = playOpts.gain ?? 1;\n\n const spatial = playOpts.position\n ? spatialize(playOpts.position, listener, spatialOpts)\n : { gain: 1, pan: playOpts.pan ?? 0 };\n\n const voice: LiveVoice = {\n id, slots, slot: null, token: 0, key: playOpts.cancelKey,\n source: null,\n chain: takeChain(graph.node(busName), baseGain * spatial.gain, spatial.pan),\n baseGain, spatialGain: spatial.gain,\n rate: playOpts.rate ?? 1, detune: playOpts.detune ?? 0,\n position: playOpts.position,\n playing: true, cancelled: false,\n };\n live.set(id, voice);\n\n scheduler.schedule(when, (scheduledWhen) => {\n if (voice.cancelled) return;\n // Came due while the context was suspended: starting it now would play\n // a backlog at once, which is what dropping a locked play avoids. A voice\n // played since the resume is no backlog, however early its `when`.\n if (bookedAt < staleFloor && scheduledWhen < staleFloor) {\n teardown(voice);\n return;\n }\n const buffer = sounds.buffer(sound);\n if (!buffer) {\n warnUnknownSound(sound.id);\n teardown(voice);\n return;\n }\n\n // The slot is taken here, not at play() time. A voice booked a bar out\n // would otherwise hold one while silent — and, its `startedAt` being in\n // the future, be the last thing the 'oldest' policy evicts, so booking\n // a bar of events would evict everything currently audible.\n const acquired = slots.pool.acquire({\n startedAt: scheduledWhen,\n gain: voice.baseGain * voice.spatialGain,\n });\n voice.slot = acquired.slot;\n voice.token = acquired.token;\n slots.byToken.set(acquired.token, voice);\n if (acquired.stolen !== null) {\n const victim = slots.byToken.get(acquired.stolen);\n if (victim) teardown(victim);\n }\n\n // A fresh source per play: AudioBufferSourceNode is single-use by\n // specification and cannot be restarted once stopped.\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.loop = playOpts.loop ?? false;\n source.playbackRate.value = voice.rate;\n source.detune.value = voice.detune;\n // Still live past the check above, so the voice still holds its chain.\n source.connect(voice.chain!.panner);\n source.onended = () => { teardown(voice); playOpts.onDone?.(); };\n voice.source = source;\n source.start(scheduledWhen / 1000);\n }, playOpts.cancelKey);\n\n return {\n id,\n stop(fadeMs) {\n if (fadeMs && fadeMs > 0 && voice.source && voice.chain && !voice.cancelled) {\n // Cancelled but not torn down: the fade is what ends this voice, and\n // stopping the source now would cut the ramp scheduled a line above\n // it — which is the click `fadeMs` exists to avoid. `onended` does\n // the bookkeeping when the fade reaches the end.\n voice.cancelled = true;\n writeParam(ctx, voice.chain.gain.gain, 0, fadeMs);\n try { voice.source.stop(ctx.currentTime + fadeMs / 1000); } catch { /* ended */ }\n return;\n }\n teardown(voice);\n },\n setGain(value, rampMs) {\n voice.baseGain = value;\n if (!voice.chain) return;\n writeParam(ctx, voice.chain.gain.gain, value * voice.spatialGain, rampMs);\n poolGain(voice);\n },\n setRate(value) {\n voice.rate = value;\n if (voice.source) voice.source.playbackRate.value = value;\n },\n setDetune(cents) {\n voice.detune = cents;\n if (voice.source) voice.source.detune.value = cents;\n },\n setPan(value) {\n voice.position = undefined;\n if (voice.chain) voice.chain.panner.pan.value = value;\n },\n setPosition(p) {\n voice.position = p;\n applySpatial(voice);\n },\n isPlaying: () => voice.playing,\n };\n },\n\n stopKey(key) {\n scheduler.cancelKey(key);\n for (const voice of [...live.values()]) {\n if (voice.key === key) teardown(voice);\n }\n },\n stopAll() {\n for (const voice of [...live.values()]) teardown(voice);\n },\n bus: graph.bus,\n busNames: graph.names,\n activeVoices(bus) {\n if (bus !== undefined) return slotsFor(bus).pool.active();\n let total = 0;\n for (const s of slotsByBus.values()) total += s.pool.active();\n return total;\n },\n analyser(busName, tapOpts) {\n // Every tap is one more live FFT on the graph. They are tracked so\n // `dispose()` detaches them; a consumer calling this per frame still\n // wants to keep one tap rather than mint one.\n const tap = createAnalyserTap(ctx, busName ? graph.node(busName) : graph.master, tapOpts);\n taps.add(tap);\n return {\n ...tap,\n dispose() {\n taps.delete(tap);\n tap.dispose();\n },\n };\n },\n setListener(p, o) {\n listener = p;\n if (o) spatialOpts = o;\n for (const voice of live.values()) applySpatial(voice);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n engine.stopAll();\n idleChains.length = 0;\n scheduler.stop();\n scheduler.clear();\n tickTimer?.dispose();\n for (const tap of [...taps]) tap.dispose();\n taps.clear();\n disarmGestures();\n ctx.removeEventListener('statechange', onStateChange);\n graph.master.disconnect();\n if (ownsContext) void ctx.close().catch(() => undefined);\n },\n };\n\n return engine;\n}\n"]}
|
package/package.json
CHANGED