@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 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 is the limit of that. Browsers clamp `setTimeout` to at least a
13
- second there, so the pass runs far too late for a 100 ms lookahead and anything
14
- booked while the tab is away arrives late. The clock keeps running, so ordering
15
- survives; on return the engine drops what came due meanwhile rather than firing
16
- it all at once. Moving the tick to a Worker would fix it and is not built.
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 caller builds the
132
- * `GainNode`/`StereoPannerNode` chain and mints a fresh `AudioBufferSourceNode`
133
- * per play, because a source node is single-use by specification and cannot be
134
- * restarted once stopped. Nothing is pooled but the slot numbers.
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`. It runs on the first pass whose
242
- * lookahead window reaches it, receiving `when` so it can hand the true
243
- * time to `source.start()` rather than "now". */
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 hidden tab clamps it to at least a
261
- * second, which the lookahead cannot cover — see the README.
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
- 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 Vec2, type VoiceHandle, type VoicePool, type VoicePoolOptions, type VoiceRecord, createAnalyserTap, createAudioEngine, createBusGraph, createScheduler, createSoundCache, createVoicePool, spatialize };
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
- const pass = () => {
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.panNode.pan.value = s.pan;
406
- writeParam(ctx, voice.gainNode.gain, voice.baseGain * s.gain);
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.panNode.disconnect();
422
- voice.gainNode.disconnect();
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 when = playOpts.when ?? now();
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
- gainNode,
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(panNode);
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, gainNode.gain, 0, fadeMs);
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
- writeParam(ctx, gainNode.gain, value * voice.spatialGain, rampMs);
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
- panNode.pan.value = value;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/audio",
3
- "version": "1.4.4",
3
+ "version": "1.5.0",
4
4
  "description": "Web Audio engine for 2D scenes: voices, buses, lookahead scheduling, spatialization and analysis. No weasel dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",