waa-play 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/adapters.cjs +28 -0
  4. package/dist/adapters.cjs.map +1 -0
  5. package/dist/adapters.d.cts +42 -0
  6. package/dist/adapters.d.ts +42 -0
  7. package/dist/adapters.js +3 -0
  8. package/dist/adapters.js.map +1 -0
  9. package/dist/buffer.cjs +24 -0
  10. package/dist/buffer.cjs.map +1 -0
  11. package/dist/buffer.d.cts +34 -0
  12. package/dist/buffer.d.ts +34 -0
  13. package/dist/buffer.js +3 -0
  14. package/dist/buffer.js.map +1 -0
  15. package/dist/chunk-2DL7CAEP.js +69 -0
  16. package/dist/chunk-2DL7CAEP.js.map +1 -0
  17. package/dist/chunk-37CPPRLV.js +24 -0
  18. package/dist/chunk-37CPPRLV.js.map +1 -0
  19. package/dist/chunk-4LNVRSTM.cjs +72 -0
  20. package/dist/chunk-4LNVRSTM.cjs.map +1 -0
  21. package/dist/chunk-5J7S6QV3.cjs +44 -0
  22. package/dist/chunk-5J7S6QV3.cjs.map +1 -0
  23. package/dist/chunk-6UTN73HG.cjs +29 -0
  24. package/dist/chunk-6UTN73HG.cjs.map +1 -0
  25. package/dist/chunk-AGP2IRC6.js +63 -0
  26. package/dist/chunk-AGP2IRC6.js.map +1 -0
  27. package/dist/chunk-C2ASIYN5.js +67 -0
  28. package/dist/chunk-C2ASIYN5.js.map +1 -0
  29. package/dist/chunk-CJJC6ASU.js +73 -0
  30. package/dist/chunk-CJJC6ASU.js.map +1 -0
  31. package/dist/chunk-CPAT75WD.cjs +60 -0
  32. package/dist/chunk-CPAT75WD.cjs.map +1 -0
  33. package/dist/chunk-CRODJ4KS.js +71 -0
  34. package/dist/chunk-CRODJ4KS.js.map +1 -0
  35. package/dist/chunk-D5CD5KQZ.cjs +72 -0
  36. package/dist/chunk-D5CD5KQZ.cjs.map +1 -0
  37. package/dist/chunk-GYH2JSCY.js +42 -0
  38. package/dist/chunk-GYH2JSCY.js.map +1 -0
  39. package/dist/chunk-HTGOHC73.cjs +69 -0
  40. package/dist/chunk-HTGOHC73.cjs.map +1 -0
  41. package/dist/chunk-LETS7FKB.js +33 -0
  42. package/dist/chunk-LETS7FKB.js.map +1 -0
  43. package/dist/chunk-M5PDY5EZ.cjs +84 -0
  44. package/dist/chunk-M5PDY5EZ.cjs.map +1 -0
  45. package/dist/chunk-PZE6HTZR.cjs +358 -0
  46. package/dist/chunk-PZE6HTZR.cjs.map +1 -0
  47. package/dist/chunk-QFFQQMU4.cjs +75 -0
  48. package/dist/chunk-QFFQQMU4.cjs.map +1 -0
  49. package/dist/chunk-QWNV2BZ5.cjs +37 -0
  50. package/dist/chunk-QWNV2BZ5.cjs.map +1 -0
  51. package/dist/chunk-RWJ4EWJT.js +356 -0
  52. package/dist/chunk-RWJ4EWJT.js.map +1 -0
  53. package/dist/chunk-T74FBKTY.js +55 -0
  54. package/dist/chunk-T74FBKTY.js.map +1 -0
  55. package/dist/chunk-TULV7V5M.cjs +1710 -0
  56. package/dist/chunk-TULV7V5M.cjs.map +1 -0
  57. package/dist/chunk-V2QX5K42.js +1708 -0
  58. package/dist/chunk-V2QX5K42.js.map +1 -0
  59. package/dist/context.cjs +24 -0
  60. package/dist/context.cjs.map +1 -0
  61. package/dist/context.d.cts +27 -0
  62. package/dist/context.d.ts +27 -0
  63. package/dist/context.js +3 -0
  64. package/dist/context.js.map +1 -0
  65. package/dist/emitter.cjs +12 -0
  66. package/dist/emitter.cjs.map +1 -0
  67. package/dist/emitter.d.cts +24 -0
  68. package/dist/emitter.d.ts +24 -0
  69. package/dist/emitter.js +3 -0
  70. package/dist/emitter.js.map +1 -0
  71. package/dist/engine-5JK2FCNL.cjs +13 -0
  72. package/dist/engine-5JK2FCNL.cjs.map +1 -0
  73. package/dist/engine-M2U4LE3F.js +4 -0
  74. package/dist/engine-M2U4LE3F.js.map +1 -0
  75. package/dist/fade.cjs +24 -0
  76. package/dist/fade.cjs.map +1 -0
  77. package/dist/fade.d.cts +21 -0
  78. package/dist/fade.d.ts +21 -0
  79. package/dist/fade.js +3 -0
  80. package/dist/fade.js.map +1 -0
  81. package/dist/index.cjs +165 -0
  82. package/dist/index.cjs.map +1 -0
  83. package/dist/index.d.cts +11 -0
  84. package/dist/index.d.ts +11 -0
  85. package/dist/index.js +12 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/nodes.cjs +48 -0
  88. package/dist/nodes.cjs.map +1 -0
  89. package/dist/nodes.d.cts +61 -0
  90. package/dist/nodes.d.ts +61 -0
  91. package/dist/nodes.js +3 -0
  92. package/dist/nodes.js.map +1 -0
  93. package/dist/play.cjs +13 -0
  94. package/dist/play.cjs.map +1 -0
  95. package/dist/play.d.cts +19 -0
  96. package/dist/play.d.ts +19 -0
  97. package/dist/play.js +4 -0
  98. package/dist/play.js.map +1 -0
  99. package/dist/scheduler.cjs +16 -0
  100. package/dist/scheduler.cjs.map +1 -0
  101. package/dist/scheduler.d.cts +39 -0
  102. package/dist/scheduler.d.ts +39 -0
  103. package/dist/scheduler.js +3 -0
  104. package/dist/scheduler.js.map +1 -0
  105. package/dist/stretcher.cjs +13 -0
  106. package/dist/stretcher.cjs.map +1 -0
  107. package/dist/stretcher.d.cts +171 -0
  108. package/dist/stretcher.d.ts +171 -0
  109. package/dist/stretcher.js +4 -0
  110. package/dist/stretcher.js.map +1 -0
  111. package/dist/synth.cjs +20 -0
  112. package/dist/synth.cjs.map +1 -0
  113. package/dist/synth.d.cts +15 -0
  114. package/dist/synth.d.ts +15 -0
  115. package/dist/synth.js +3 -0
  116. package/dist/synth.js.map +1 -0
  117. package/dist/types-DUrbEbPl.d.cts +177 -0
  118. package/dist/types-DUrbEbPl.d.ts +177 -0
  119. package/dist/waveform.cjs +20 -0
  120. package/dist/waveform.cjs.map +1 -0
  121. package/dist/waveform.d.cts +22 -0
  122. package/dist/waveform.d.ts +22 -0
  123. package/dist/waveform.js +3 -0
  124. package/dist/waveform.js.map +1 -0
  125. package/package.json +123 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emitter.ts"],"names":[],"mappings":";;;AAmCO,SAAS,aAAA,GAEK;AACnB,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAA8C;AAEpE,EAAA,SAAS,OAA+B,KAAA,EAAU;AAChD,IAAA,IAAI,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,GAAA,uBAAU,GAAA,EAAI;AACd,MAAA,SAAA,CAAU,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,EAAA,CACE,OACA,OAAA,EACY;AACZ,MAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,MAAA,GAAA,CAAI,IAAI,OAAgC,CAAA;AACxC,MAAA,OAAO,MAAM;AACX,QAAA,GAAA,CAAI,OAAO,OAAgC,CAAA;AAAA,MAC7C,CAAA;AAAA,IACF,CAAA;AAAA,IAEA,GAAA,CACE,OACA,OAAA,EACM;AACN,MAAA,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,MAAA,CAAO,OAAgC,CAAA;AAAA,IAC/D,CAAA;AAAA,IAEA,IAAA,CAA6B,OAAU,IAAA,EAAuB;AAC5D,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC/B,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,KAAA,MAAW,WAAW,GAAA,EAAK;AACzB,QAAA,OAAA,CAAQ,IAAa,CAAA;AAAA,MACvB;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,KAAA,EAA4B;AAChC,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,SAAA,CAAU,OAAO,KAAK,CAAA;AAAA,MACxB,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,KAAA,EAAM;AAAA,MAClB;AAAA,IACF;AAAA,GACF;AACF","file":"chunk-5J7S6QV3.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M4: Lightweight type-safe event emitter\n// ---------------------------------------------------------------------------\n\n/** A minimal, type-safe event emitter. */\nexport interface Emitter<Events extends Record<string, any> = Record<string, unknown>> {\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): () => void;\n\n /** Unsubscribe a handler from an event. */\n off<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): void;\n\n /** Emit an event with data. */\n emit<K extends keyof Events>(event: K, data: Events[K]): void;\n\n /** Remove all listeners (optionally for a specific event). */\n clear(event?: keyof Events): void;\n}\n\n/**\n * Create a lightweight, type-safe event emitter.\n *\n * ```ts\n * const emitter = createEmitter<{ tick: number; done: void }>();\n * const unsub = emitter.on(\"tick\", (n) => console.log(n));\n * emitter.emit(\"tick\", 42);\n * unsub();\n * ```\n */\nexport function createEmitter<\n Events extends Record<string, any> = Record<string, unknown>,\n>(): Emitter<Events> {\n const listeners = new Map<keyof Events, Set<(data: never) => void>>();\n\n function getSet<K extends keyof Events>(event: K) {\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n return set;\n }\n\n return {\n on<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): () => void {\n const set = getSet(event);\n set.add(handler as (data: never) => void);\n return () => {\n set.delete(handler as (data: never) => void);\n };\n },\n\n off<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): void {\n listeners.get(event)?.delete(handler as (data: never) => void);\n },\n\n emit<K extends keyof Events>(event: K, data: Events[K]): void {\n const set = listeners.get(event);\n if (!set) return;\n for (const handler of set) {\n handler(data as never);\n }\n },\n\n clear(event?: keyof Events): void {\n if (event !== undefined) {\n listeners.delete(event);\n } else {\n listeners.clear();\n }\n },\n };\n}\n"]}
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // src/context.ts
4
+ function createContext(options) {
5
+ return new AudioContext({
6
+ sampleRate: options?.sampleRate,
7
+ latencyHint: options?.latencyHint
8
+ });
9
+ }
10
+ async function resumeContext(ctx) {
11
+ if (ctx.state === "suspended") {
12
+ await ctx.resume();
13
+ }
14
+ }
15
+ async function ensureRunning(ctx) {
16
+ if (ctx.state !== "running") {
17
+ await ctx.resume();
18
+ }
19
+ }
20
+ function now(ctx) {
21
+ return ctx.currentTime;
22
+ }
23
+
24
+ exports.createContext = createContext;
25
+ exports.ensureRunning = ensureRunning;
26
+ exports.now = now;
27
+ exports.resumeContext = resumeContext;
28
+ //# sourceMappingURL=chunk-6UTN73HG.cjs.map
29
+ //# sourceMappingURL=chunk-6UTN73HG.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts"],"names":[],"mappings":";;;AAaO,SAAS,cAAc,OAAA,EAA8C;AAC1E,EAAA,OAAO,IAAI,YAAA,CAAa;AAAA,IACtB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,aAAa,OAAA,EAAS;AAAA,GACvB,CAAA;AACH;AAQA,eAAsB,cAAc,GAAA,EAAkC;AACpE,EAAA,IAAI,GAAA,CAAI,UAAU,WAAA,EAAa;AAC7B,IAAA,MAAM,IAAI,MAAA,EAAO;AAAA,EACnB;AACF;AAKA,eAAsB,cAAc,GAAA,EAAkC;AACpE,EAAA,IAAI,GAAA,CAAI,UAAU,SAAA,EAAW;AAC3B,IAAA,MAAM,IAAI,MAAA,EAAO;AAAA,EACnB;AACF;AAKO,SAAS,IAAI,GAAA,EAA2B;AAC7C,EAAA,OAAO,GAAA,CAAI,WAAA;AACb","file":"chunk-6UTN73HG.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M1: AudioContext utilities\n// ---------------------------------------------------------------------------\n\nimport type { CreateContextOptions } from \"./types.js\";\n\n/**\n * Create an `AudioContext` with optional configuration.\n *\n * This is the only place in the library where `new AudioContext()` is called.\n * Users are encouraged to create their own context and pass it to other\n * functions — this helper exists purely for convenience.\n */\nexport function createContext(options?: CreateContextOptions): AudioContext {\n return new AudioContext({\n sampleRate: options?.sampleRate,\n latencyHint: options?.latencyHint,\n });\n}\n\n/**\n * Resume a suspended AudioContext (e.g. blocked by autoplay policy).\n *\n * Should be called inside a user-interaction event handler (click, keydown…)\n * if the context is in the `\"suspended\"` state.\n */\nexport async function resumeContext(ctx: AudioContext): Promise<void> {\n if (ctx.state === \"suspended\") {\n await ctx.resume();\n }\n}\n\n/**\n * Ensure the context is in the `\"running\"` state, resuming if necessary.\n */\nexport async function ensureRunning(ctx: AudioContext): Promise<void> {\n if (ctx.state !== \"running\") {\n await ctx.resume();\n }\n}\n\n/**\n * Shorthand for `ctx.currentTime`.\n */\nexport function now(ctx: AudioContext): number {\n return ctx.currentTime;\n}\n"]}
@@ -0,0 +1,63 @@
1
+ // src/adapters.ts
2
+ function getSnapshot(playback) {
3
+ const base = {
4
+ state: playback.getState(),
5
+ position: playback.getCurrentTime(),
6
+ duration: playback.getDuration(),
7
+ progress: playback.getProgress()
8
+ };
9
+ const getter = playback["_getStretcherSnapshot"];
10
+ if (typeof getter === "function") {
11
+ const stretcher = getter();
12
+ if (stretcher) {
13
+ base.stretcher = stretcher;
14
+ }
15
+ }
16
+ return base;
17
+ }
18
+ function subscribeSnapshot(playback, callback) {
19
+ const unsubs = [];
20
+ unsubs.push(playback.on("statechange", callback));
21
+ unsubs.push(playback.on("timeupdate", callback));
22
+ unsubs.push(playback.on("seek", callback));
23
+ unsubs.push(playback.on("ended", callback));
24
+ return () => {
25
+ for (const unsub of unsubs) unsub();
26
+ };
27
+ }
28
+ function onFrame(playback, callback) {
29
+ let rafId = null;
30
+ function tick() {
31
+ callback(getSnapshot(playback));
32
+ rafId = requestAnimationFrame(tick);
33
+ }
34
+ rafId = requestAnimationFrame(tick);
35
+ return () => {
36
+ if (rafId !== null) {
37
+ cancelAnimationFrame(rafId);
38
+ rafId = null;
39
+ }
40
+ };
41
+ }
42
+ function whenEnded(playback) {
43
+ return new Promise((resolve) => {
44
+ const unsub = playback.on("ended", () => {
45
+ unsub();
46
+ resolve();
47
+ });
48
+ });
49
+ }
50
+ function whenPosition(playback, position) {
51
+ return new Promise((resolve) => {
52
+ const unsub = playback.on("timeupdate", ({ position: current }) => {
53
+ if (current >= position) {
54
+ unsub();
55
+ resolve();
56
+ }
57
+ });
58
+ });
59
+ }
60
+
61
+ export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition };
62
+ //# sourceMappingURL=chunk-AGP2IRC6.js.map
63
+ //# sourceMappingURL=chunk-AGP2IRC6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters.ts"],"names":[],"mappings":";AAUO,SAAS,YAAY,QAAA,EAAsC;AAChE,EAAA,MAAM,IAAA,GAAyB;AAAA,IAC7B,KAAA,EAAO,SAAS,QAAA,EAAS;AAAA,IACzB,QAAA,EAAU,SAAS,cAAA,EAAe;AAAA,IAClC,QAAA,EAAU,SAAS,WAAA,EAAY;AAAA,IAC/B,QAAA,EAAU,SAAS,WAAA;AAAY,GACjC;AAGA,EAAA,MAAM,MAAA,GAAU,SAAgD,uBAAuB,CAAA;AACvF,EAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,IAAA,MAAM,YAAa,MAAA,EAA+C;AAClE,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAiBO,SAAS,iBAAA,CACd,UACA,QAAA,EACY;AACZ,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,aAAA,EAAe,QAAQ,CAAC,CAAA;AAChD,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,YAAA,EAAc,QAAQ,CAAC,CAAA;AAC/C,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,MAAA,EAAQ,QAAQ,CAAC,CAAA;AACzC,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,QAAQ,CAAC,CAAA;AAE1C,EAAA,OAAO,MAAM;AACX,IAAA,KAAA,MAAW,KAAA,IAAS,QAAQ,KAAA,EAAM;AAAA,EACpC,CAAA;AACF;AAQO,SAAS,OAAA,CACd,UACA,QAAA,EACY;AACZ,EAAA,IAAI,KAAA,GAAuB,IAAA;AAE3B,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,QAAA,CAAS,WAAA,CAAY,QAAQ,CAAC,CAAA;AAC9B,IAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAAA,EACpC;AAEA,EAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAElC,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,oBAAA,CAAqB,KAAK,CAAA;AAC1B,MAAA,KAAA,GAAQ,IAAA;AAAA,IACV;AAAA,EACF,CAAA;AACF;AAMO,SAAS,UAAU,QAAA,EAAmC;AAC3D,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,MAAM;AACvC,MAAA,KAAA,EAAM;AACN,MAAA,OAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAMO,SAAS,YAAA,CACd,UACA,QAAA,EACe;AACf,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,IAAA,MAAM,KAAA,GAAQ,SAAS,EAAA,CAAG,YAAA,EAAc,CAAC,EAAE,QAAA,EAAU,SAAQ,KAAM;AACjE,MAAA,IAAI,WAAW,QAAA,EAAU;AACvB,QAAA,KAAA,EAAM;AACN,QAAA,OAAA,EAAQ;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH","file":"chunk-AGP2IRC6.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M10: Framework adapters\n// ---------------------------------------------------------------------------\n\nimport type { Playback, PlaybackSnapshot } from \"./types.js\";\n\n/**\n * Get an immutable snapshot of the current playback state.\n * Designed for use with React's `useSyncExternalStore` or similar patterns.\n */\nexport function getSnapshot(playback: Playback): PlaybackSnapshot {\n const base: PlaybackSnapshot = {\n state: playback.getState(),\n position: playback.getCurrentTime(),\n duration: playback.getDuration(),\n progress: playback.getProgress(),\n };\n\n // Include stretcher snapshot if available (no static import required)\n const getter = (playback as unknown as Record<string, unknown>)[\"_getStretcherSnapshot\"];\n if (typeof getter === \"function\") {\n const stretcher = (getter as () => PlaybackSnapshot[\"stretcher\"])();\n if (stretcher) {\n base.stretcher = stretcher;\n }\n }\n\n return base;\n}\n\n/**\n * Subscribe to playback state changes, calling `callback` with a fresh\n * snapshot whenever the state updates.\n *\n * Returns an unsubscribe function. Works as the `subscribe` parameter for\n * React's `useSyncExternalStore`.\n *\n * ```ts\n * // React example:\n * const snap = useSyncExternalStore(\n * (cb) => subscribeSnapshot(playback, cb),\n * () => getSnapshot(playback),\n * );\n * ```\n */\nexport function subscribeSnapshot(\n playback: Playback,\n callback: () => void,\n): () => void {\n const unsubs: Array<() => void> = [];\n\n unsubs.push(playback.on(\"statechange\", callback));\n unsubs.push(playback.on(\"timeupdate\", callback));\n unsubs.push(playback.on(\"seek\", callback));\n unsubs.push(playback.on(\"ended\", callback));\n\n return () => {\n for (const unsub of unsubs) unsub();\n };\n}\n\n/**\n * Call `callback` on every animation frame with the current playback snapshot.\n * Useful for smooth UI animations (waveform cursors, progress bars, etc.).\n *\n * Returns a `stop` function that cancels the loop.\n */\nexport function onFrame(\n playback: Playback,\n callback: (snapshot: PlaybackSnapshot) => void,\n): () => void {\n let rafId: number | null = null;\n\n function tick() {\n callback(getSnapshot(playback));\n rafId = requestAnimationFrame(tick);\n }\n\n rafId = requestAnimationFrame(tick);\n\n return () => {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n };\n}\n\n/**\n * Return a `Promise` that resolves when the playback reaches the `\"stopped\"`\n * state via the `ended` event (natural end, not manual stop).\n */\nexport function whenEnded(playback: Playback): Promise<void> {\n return new Promise<void>((resolve) => {\n const unsub = playback.on(\"ended\", () => {\n unsub();\n resolve();\n });\n });\n}\n\n/**\n * Return a `Promise` that resolves when the playback position reaches or\n * exceeds `position` seconds.\n */\nexport function whenPosition(\n playback: Playback,\n position: number,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n const unsub = playback.on(\"timeupdate\", ({ position: current }) => {\n if (current >= position) {\n unsub();\n resolve();\n }\n });\n });\n}\n"]}
@@ -0,0 +1,67 @@
1
+ // src/fade.ts
2
+ var EXP_MIN = 1e-4;
3
+ function applyRamp(node, from, to, duration, curve) {
4
+ const param = node.gain;
5
+ const now = node.context.currentTime;
6
+ param.cancelScheduledValues(now);
7
+ param.setValueAtTime(from, now);
8
+ switch (curve) {
9
+ case "exponential":
10
+ param.exponentialRampToValueAtTime(
11
+ Math.max(to, EXP_MIN),
12
+ now + duration
13
+ );
14
+ break;
15
+ case "equal-power": {
16
+ const steps = Math.max(Math.ceil(duration * 100), 2);
17
+ const values = new Float32Array(steps);
18
+ for (let i = 0; i < steps; i++) {
19
+ const t = i / (steps - 1);
20
+ const gain = from + (to - from) * Math.sin(t * Math.PI / 2);
21
+ values[i] = gain;
22
+ }
23
+ param.setValueCurveAtTime(values, now, duration);
24
+ break;
25
+ }
26
+ case "linear":
27
+ default:
28
+ param.linearRampToValueAtTime(to, now + duration);
29
+ break;
30
+ }
31
+ }
32
+ function fadeIn(gain, target, options) {
33
+ const { duration = 1, curve = "linear" } = options ?? {};
34
+ applyRamp(gain, 0, target, duration, curve);
35
+ }
36
+ function fadeOut(gain, options) {
37
+ const { duration = 1, curve = "linear" } = options ?? {};
38
+ applyRamp(gain, gain.gain.value, 0, duration, curve);
39
+ }
40
+ function crossfade(gainA, gainB, options) {
41
+ const { duration = 1, curve = "linear" } = options ?? {};
42
+ applyRamp(gainA, gainA.gain.value, 0, duration, curve);
43
+ applyRamp(gainB, 0, gainA.gain.value || 1, duration, curve);
44
+ }
45
+ function autoFade(playback, gain, options) {
46
+ const {
47
+ fadeIn: fadeInDuration = 0,
48
+ fadeOut: fadeOutDuration = 0,
49
+ curve = "linear"
50
+ } = options ?? {};
51
+ const duration = playback.getDuration();
52
+ let fadeOutScheduled = false;
53
+ if (fadeInDuration > 0) {
54
+ applyRamp(gain, 0, 1, fadeInDuration, curve);
55
+ }
56
+ const unsub = playback.on("timeupdate", ({ position }) => {
57
+ if (fadeOutDuration > 0 && !fadeOutScheduled && position >= duration - fadeOutDuration) {
58
+ fadeOutScheduled = true;
59
+ applyRamp(gain, gain.gain.value, 0, fadeOutDuration, curve);
60
+ }
61
+ });
62
+ return unsub;
63
+ }
64
+
65
+ export { autoFade, crossfade, fadeIn, fadeOut };
66
+ //# sourceMappingURL=chunk-C2ASIYN5.js.map
67
+ //# sourceMappingURL=chunk-C2ASIYN5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fade.ts"],"names":[],"mappings":"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","file":"chunk-C2ASIYN5.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M7: Fade utilities\n// ---------------------------------------------------------------------------\n\nimport type {\n AutoFadeOptions,\n CrossfadeOptions,\n FadeCurve,\n FadeOptions,\n Playback,\n} from \"./types.js\";\n\n/** Minimum value used for exponential ramps (cannot ramp to/from 0). */\nconst EXP_MIN = 0.0001;\n\nfunction applyRamp(\n node: GainNode,\n from: number,\n to: number,\n duration: number,\n curve: FadeCurve,\n): void {\n const param = node.gain;\n const now = node.context.currentTime;\n param.cancelScheduledValues(now);\n param.setValueAtTime(from, now);\n\n switch (curve) {\n case \"exponential\":\n param.exponentialRampToValueAtTime(\n Math.max(to, EXP_MIN),\n now + duration,\n );\n break;\n case \"equal-power\": {\n // Approximate equal-power with a setValueCurveAtTime.\n const steps = Math.max(Math.ceil(duration * 100), 2);\n const values = new Float32Array(steps);\n for (let i = 0; i < steps; i++) {\n const t = i / (steps - 1);\n const gain = from + (to - from) * Math.sin((t * Math.PI) / 2);\n values[i] = gain;\n }\n param.setValueCurveAtTime(values, now, duration);\n break;\n }\n case \"linear\":\n default:\n param.linearRampToValueAtTime(to, now + duration);\n break;\n }\n}\n\n/**\n * Fade a `GainNode` in from 0 to `target`.\n */\nexport function fadeIn(\n gain: GainNode,\n target: number,\n options?: FadeOptions,\n): void {\n const { duration = 1, curve = \"linear\" } = options ?? {};\n applyRamp(gain, 0, target, duration, curve);\n}\n\n/**\n * Fade a `GainNode` out to 0.\n */\nexport function fadeOut(gain: GainNode, options?: FadeOptions): void {\n const { duration = 1, curve = \"linear\" } = options ?? {};\n applyRamp(gain, gain.gain.value, 0, duration, curve);\n}\n\n/**\n * Crossfade between two `GainNode`s.\n */\nexport function crossfade(\n gainA: GainNode,\n gainB: GainNode,\n options?: CrossfadeOptions,\n): void {\n const { duration = 1, curve = \"linear\" } = options ?? {};\n applyRamp(gainA, gainA.gain.value, 0, duration, curve);\n applyRamp(gainB, 0, gainA.gain.value || 1, duration, curve);\n}\n\n/**\n * Automatically apply fade-in at start and/or fade-out near end of a Playback.\n * Returns a cleanup function.\n */\nexport function autoFade(\n playback: Playback,\n gain: GainNode,\n options?: AutoFadeOptions,\n): () => void {\n const {\n fadeIn: fadeInDuration = 0,\n fadeOut: fadeOutDuration = 0,\n curve = \"linear\",\n } = options ?? {};\n\n const duration = playback.getDuration();\n let fadeOutScheduled = false;\n\n // Apply fade-in immediately if playing from the start.\n if (fadeInDuration > 0) {\n applyRamp(gain, 0, 1, fadeInDuration, curve);\n }\n\n const unsub = playback.on(\"timeupdate\", ({ position }) => {\n if (\n fadeOutDuration > 0 &&\n !fadeOutScheduled &&\n position >= duration - fadeOutDuration\n ) {\n fadeOutScheduled = true;\n applyRamp(gain, gain.gain.value, 0, fadeOutDuration, curve);\n }\n });\n\n return unsub;\n}\n"]}
@@ -0,0 +1,73 @@
1
+ // src/nodes.ts
2
+ function createGain(ctx, initialValue) {
3
+ const gain = ctx.createGain();
4
+ if (initialValue !== void 0) {
5
+ gain.gain.value = initialValue;
6
+ }
7
+ return gain;
8
+ }
9
+ function rampGain(gain, target, duration) {
10
+ const now = gain.context.currentTime;
11
+ gain.gain.cancelScheduledValues(now);
12
+ gain.gain.setValueAtTime(gain.gain.value, now);
13
+ gain.gain.linearRampToValueAtTime(target, now + duration);
14
+ }
15
+ function createAnalyser(ctx, options) {
16
+ const analyser = ctx.createAnalyser();
17
+ if (options?.fftSize !== void 0) analyser.fftSize = options.fftSize;
18
+ if (options?.smoothingTimeConstant !== void 0) {
19
+ analyser.smoothingTimeConstant = options.smoothingTimeConstant;
20
+ }
21
+ return analyser;
22
+ }
23
+ function getFrequencyData(analyser) {
24
+ const data = new Float32Array(analyser.frequencyBinCount);
25
+ analyser.getFloatFrequencyData(data);
26
+ return data;
27
+ }
28
+ function getFrequencyDataByte(analyser) {
29
+ const data = new Uint8Array(analyser.frequencyBinCount);
30
+ analyser.getByteFrequencyData(data);
31
+ return data;
32
+ }
33
+ function createFilter(ctx, options) {
34
+ const filter = ctx.createBiquadFilter();
35
+ if (options?.type !== void 0) filter.type = options.type;
36
+ if (options?.frequency !== void 0) filter.frequency.value = options.frequency;
37
+ if (options?.Q !== void 0) filter.Q.value = options.Q;
38
+ if (options?.gain !== void 0) filter.gain.value = options.gain;
39
+ return filter;
40
+ }
41
+ function createPanner(ctx, pan) {
42
+ const panner = ctx.createStereoPanner();
43
+ if (pan !== void 0) {
44
+ panner.pan.value = pan;
45
+ }
46
+ return panner;
47
+ }
48
+ function createCompressor(ctx, options) {
49
+ const comp = ctx.createDynamicsCompressor();
50
+ if (options?.threshold !== void 0) comp.threshold.value = options.threshold;
51
+ if (options?.knee !== void 0) comp.knee.value = options.knee;
52
+ if (options?.ratio !== void 0) comp.ratio.value = options.ratio;
53
+ if (options?.attack !== void 0) comp.attack.value = options.attack;
54
+ if (options?.release !== void 0) comp.release.value = options.release;
55
+ return comp;
56
+ }
57
+ function chain(...nodes) {
58
+ for (let i = 0; i < nodes.length - 1; i++) {
59
+ nodes[i].connect(nodes[i + 1]);
60
+ }
61
+ }
62
+ function disconnectChain(...nodes) {
63
+ for (const node of nodes) {
64
+ try {
65
+ node.disconnect();
66
+ } catch {
67
+ }
68
+ }
69
+ }
70
+
71
+ export { chain, createAnalyser, createCompressor, createFilter, createGain, createPanner, disconnectChain, getFrequencyData, getFrequencyDataByte, rampGain };
72
+ //# sourceMappingURL=chunk-CJJC6ASU.js.map
73
+ //# sourceMappingURL=chunk-CJJC6ASU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nodes.ts"],"names":[],"mappings":"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","file":"chunk-CJJC6ASU.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M5: Audio node factory & graph utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Create a `GainNode` with an optional initial value.\n */\nexport function createGain(\n ctx: AudioContext,\n initialValue?: number,\n): GainNode {\n const gain = ctx.createGain();\n if (initialValue !== undefined) {\n gain.gain.value = initialValue;\n }\n return gain;\n}\n\n/**\n * Smoothly ramp a `GainNode` to a target value over `duration` seconds using\n * `linearRampToValueAtTime`. This avoids audible clicks when changing volume.\n */\nexport function rampGain(\n gain: GainNode,\n target: number,\n duration: number,\n): void {\n const now = gain.context.currentTime;\n gain.gain.cancelScheduledValues(now);\n gain.gain.setValueAtTime(gain.gain.value, now);\n gain.gain.linearRampToValueAtTime(target, now + duration);\n}\n\n/**\n * Create an `AnalyserNode`.\n */\nexport function createAnalyser(\n ctx: AudioContext,\n options?: { fftSize?: number; smoothingTimeConstant?: number },\n): AnalyserNode {\n const analyser = ctx.createAnalyser();\n if (options?.fftSize !== undefined) analyser.fftSize = options.fftSize;\n if (options?.smoothingTimeConstant !== undefined) {\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n }\n return analyser;\n}\n\n/**\n * Get the current frequency data from an `AnalyserNode` as `Float32Array`.\n */\nexport function getFrequencyData(analyser: AnalyserNode): Float32Array {\n const data = new Float32Array(analyser.frequencyBinCount);\n analyser.getFloatFrequencyData(data);\n return data;\n}\n\n/**\n * Get the current frequency data from an `AnalyserNode` as `Uint8Array`.\n */\nexport function getFrequencyDataByte(analyser: AnalyserNode): Uint8Array {\n const data = new Uint8Array(analyser.frequencyBinCount);\n analyser.getByteFrequencyData(data);\n return data;\n}\n\n/**\n * Create a `BiquadFilterNode`.\n */\nexport function createFilter(\n ctx: AudioContext,\n options?: {\n type?: BiquadFilterType;\n frequency?: number;\n Q?: number;\n gain?: number;\n },\n): BiquadFilterNode {\n const filter = ctx.createBiquadFilter();\n if (options?.type !== undefined) filter.type = options.type;\n if (options?.frequency !== undefined) filter.frequency.value = options.frequency;\n if (options?.Q !== undefined) filter.Q.value = options.Q;\n if (options?.gain !== undefined) filter.gain.value = options.gain;\n return filter;\n}\n\n/**\n * Create a `StereoPannerNode`.\n */\nexport function createPanner(\n ctx: AudioContext,\n pan?: number,\n): StereoPannerNode {\n const panner = ctx.createStereoPanner();\n if (pan !== undefined) {\n panner.pan.value = pan;\n }\n return panner;\n}\n\n/**\n * Create a `DynamicsCompressorNode`.\n */\nexport function createCompressor(\n ctx: AudioContext,\n options?: {\n threshold?: number;\n knee?: number;\n ratio?: number;\n attack?: number;\n release?: number;\n },\n): DynamicsCompressorNode {\n const comp = ctx.createDynamicsCompressor();\n if (options?.threshold !== undefined) comp.threshold.value = options.threshold;\n if (options?.knee !== undefined) comp.knee.value = options.knee;\n if (options?.ratio !== undefined) comp.ratio.value = options.ratio;\n if (options?.attack !== undefined) comp.attack.value = options.attack;\n if (options?.release !== undefined) comp.release.value = options.release;\n return comp;\n}\n\n/**\n * Connect a series of `AudioNode`s in order (serial chain).\n *\n * ```ts\n * chain(source, gain, analyser, ctx.destination);\n * ```\n */\nexport function chain(...nodes: AudioNode[]): void {\n for (let i = 0; i < nodes.length - 1; i++) {\n nodes[i]!.connect(nodes[i + 1]!);\n }\n}\n\n/**\n * Disconnect a series of `AudioNode`s that were previously chained.\n */\nexport function disconnectChain(...nodes: AudioNode[]): void {\n for (const node of nodes) {\n try {\n node.disconnect();\n } catch {\n // Already disconnected — safe to ignore.\n }\n }\n}\n"]}
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ // src/buffer.ts
4
+ async function loadBuffer(ctx, url, options) {
5
+ const response = await fetch(url);
6
+ if (!response.ok) {
7
+ throw new Error(`Failed to fetch audio: ${response.status} ${response.statusText}`);
8
+ }
9
+ if (options?.onProgress && response.body && response.headers.get("content-length")) {
10
+ const total = Number(response.headers.get("content-length"));
11
+ const reader = response.body.getReader();
12
+ const chunks = [];
13
+ let received = 0;
14
+ for (; ; ) {
15
+ const { done, value } = await reader.read();
16
+ if (done) break;
17
+ chunks.push(value);
18
+ received += value.byteLength;
19
+ options.onProgress(total > 0 ? received / total : 0);
20
+ }
21
+ const merged = new Uint8Array(received);
22
+ let offset = 0;
23
+ for (const chunk of chunks) {
24
+ merged.set(chunk, offset);
25
+ offset += chunk.byteLength;
26
+ }
27
+ return ctx.decodeAudioData(merged.buffer);
28
+ }
29
+ const arrayBuffer = await response.arrayBuffer();
30
+ return ctx.decodeAudioData(arrayBuffer);
31
+ }
32
+ async function loadBufferFromBlob(ctx, blob) {
33
+ const arrayBuffer = await blob.arrayBuffer();
34
+ return ctx.decodeAudioData(arrayBuffer);
35
+ }
36
+ async function loadBuffers(ctx, map) {
37
+ const entries = Object.entries(map);
38
+ const results = await Promise.all(
39
+ entries.map(async ([key, url]) => {
40
+ const buffer = await loadBuffer(ctx, url);
41
+ return [key, buffer];
42
+ })
43
+ );
44
+ return new Map(results);
45
+ }
46
+ function getBufferInfo(buffer) {
47
+ return {
48
+ duration: buffer.duration,
49
+ numberOfChannels: buffer.numberOfChannels,
50
+ sampleRate: buffer.sampleRate,
51
+ length: buffer.length
52
+ };
53
+ }
54
+
55
+ exports.getBufferInfo = getBufferInfo;
56
+ exports.loadBuffer = loadBuffer;
57
+ exports.loadBufferFromBlob = loadBufferFromBlob;
58
+ exports.loadBuffers = loadBuffers;
59
+ //# sourceMappingURL=chunk-CPAT75WD.cjs.map
60
+ //# sourceMappingURL=chunk-CPAT75WD.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/buffer.ts"],"names":[],"mappings":";;;AAeA,eAAsB,UAAA,CACpB,GAAA,EACA,GAAA,EACA,OAAA,EACsB;AACtB,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,MAAM,CAAA,uBAAA,EAA0B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,EACpF;AAEA,EAAA,IAAI,OAAA,EAAS,cAAc,QAAA,CAAS,IAAA,IAAQ,SAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,EAAG;AAClF,IAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAC,CAAA;AAC3D,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,IAAA,MAAM,SAAuB,EAAC;AAC9B,IAAA,IAAI,QAAA,GAAW,CAAA;AAEf,IAAA,WAAS;AACP,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AACV,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AACjB,MAAA,QAAA,IAAY,KAAA,CAAM,UAAA;AAClB,MAAA,OAAA,CAAQ,UAAA,CAAW,KAAA,GAAQ,CAAA,GAAI,QAAA,GAAW,QAAQ,CAAC,CAAA;AAAA,IACrD;AAEA,IAAA,MAAM,MAAA,GAAS,IAAI,UAAA,CAAW,QAAQ,CAAA;AACtC,IAAA,IAAI,MAAA,GAAS,CAAA;AACb,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,MAAA,CAAO,GAAA,CAAI,OAAO,MAAM,CAAA;AACxB,MAAA,MAAA,IAAU,KAAA,CAAM,UAAA;AAAA,IAClB;AAEA,IAAA,OAAO,GAAA,CAAI,eAAA,CAAgB,MAAA,CAAO,MAAM,CAAA;AAAA,EAC1C;AAEA,EAAA,MAAM,WAAA,GAAc,MAAM,QAAA,CAAS,WAAA,EAAY;AAC/C,EAAA,OAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AACxC;AAKA,eAAsB,kBAAA,CACpB,KACA,IAAA,EACsB;AACtB,EAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,WAAA,EAAY;AAC3C,EAAA,OAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AACxC;AAaA,eAAsB,WAAA,CACpB,KACA,GAAA,EACmC;AACnC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AAClC,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,GAAA;AAAA,IAC5B,QAAQ,GAAA,CAAI,OAAO,CAAC,GAAA,EAAK,GAAG,CAAA,KAAM;AAChC,MAAA,MAAM,MAAA,GAAS,MAAM,UAAA,CAAW,GAAA,EAAK,GAAG,CAAA;AACxC,MAAA,OAAO,CAAC,KAAK,MAAM,CAAA;AAAA,IACrB,CAAC;AAAA,GACH;AACA,EAAA,OAAO,IAAI,IAAI,OAAO,CAAA;AACxB;AAKO,SAAS,cAAc,MAAA,EAAiC;AAC7D,EAAA,OAAO;AAAA,IACL,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,kBAAkB,MAAA,CAAO,gBAAA;AAAA,IACzB,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,QAAQ,MAAA,CAAO;AAAA,GACjB;AACF","file":"chunk-CPAT75WD.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M2: Audio buffer loading utilities\n// ---------------------------------------------------------------------------\n\nimport type { BufferInfo, LoadBufferOptions } from \"./types.js\";\n\n/**\n * Fetch an audio file from a URL and decode it into an `AudioBuffer`.\n *\n * ```ts\n * const buffer = await loadBuffer(ctx, \"/audio/track.mp3\", {\n * onProgress: (p) => console.log(`${(p * 100).toFixed(0)}%`),\n * });\n * ```\n */\nexport async function loadBuffer(\n ctx: AudioContext,\n url: string,\n options?: LoadBufferOptions,\n): Promise<AudioBuffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch audio: ${response.status} ${response.statusText}`);\n }\n\n if (options?.onProgress && response.body && response.headers.get(\"content-length\")) {\n const total = Number(response.headers.get(\"content-length\"));\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let received = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n received += value.byteLength;\n options.onProgress(total > 0 ? received / total : 0);\n }\n\n const merged = new Uint8Array(received);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return ctx.decodeAudioData(merged.buffer);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return ctx.decodeAudioData(arrayBuffer);\n}\n\n/**\n * Decode an `AudioBuffer` from a `Blob` or `File`.\n */\nexport async function loadBufferFromBlob(\n ctx: AudioContext,\n blob: Blob,\n): Promise<AudioBuffer> {\n const arrayBuffer = await blob.arrayBuffer();\n return ctx.decodeAudioData(arrayBuffer);\n}\n\n/**\n * Load multiple audio files in parallel.\n *\n * ```ts\n * const buffers = await loadBuffers(ctx, {\n * kick: \"/samples/kick.wav\",\n * snare: \"/samples/snare.wav\",\n * });\n * buffers.get(\"kick\"); // AudioBuffer\n * ```\n */\nexport async function loadBuffers(\n ctx: AudioContext,\n map: Record<string, string>,\n): Promise<Map<string, AudioBuffer>> {\n const entries = Object.entries(map);\n const results = await Promise.all(\n entries.map(async ([key, url]) => {\n const buffer = await loadBuffer(ctx, url);\n return [key, buffer] as const;\n }),\n );\n return new Map(results);\n}\n\n/**\n * Return metadata about an `AudioBuffer`.\n */\nexport function getBufferInfo(buffer: AudioBuffer): BufferInfo {\n return {\n duration: buffer.duration,\n numberOfChannels: buffer.numberOfChannels,\n sampleRate: buffer.sampleRate,\n length: buffer.length,\n };\n}\n"]}
@@ -0,0 +1,71 @@
1
+ // src/waveform.ts
2
+ function extractPeaks(buffer, options) {
3
+ const { resolution = 200, channel = 0 } = options ?? {};
4
+ const data = buffer.getChannelData(channel);
5
+ const blockSize = Math.floor(data.length / resolution);
6
+ const peaks = [];
7
+ for (let i = 0; i < resolution; i++) {
8
+ const start = i * blockSize;
9
+ const end = Math.min(start + blockSize, data.length);
10
+ let max = 0;
11
+ for (let j = start; j < end; j++) {
12
+ const abs = Math.abs(data[j]);
13
+ if (abs > max) max = abs;
14
+ }
15
+ peaks.push(max);
16
+ }
17
+ return peaks;
18
+ }
19
+ function extractPeakPairs(buffer, options) {
20
+ const { resolution = 200, channel = 0 } = options ?? {};
21
+ const data = buffer.getChannelData(channel);
22
+ const blockSize = Math.floor(data.length / resolution);
23
+ const pairs = [];
24
+ for (let i = 0; i < resolution; i++) {
25
+ const start = i * blockSize;
26
+ const end = Math.min(start + blockSize, data.length);
27
+ let min = 0;
28
+ let max = 0;
29
+ for (let j = start; j < end; j++) {
30
+ const sample = data[j];
31
+ if (sample < min) min = sample;
32
+ if (sample > max) max = sample;
33
+ }
34
+ pairs.push({ min, max });
35
+ }
36
+ return pairs;
37
+ }
38
+ function extractRMS(buffer, options) {
39
+ const { resolution = 200, channel = 0 } = options ?? {};
40
+ if (channel === -1) {
41
+ const allChannels = [];
42
+ for (let ch = 0; ch < buffer.numberOfChannels; ch++) {
43
+ allChannels.push(extractRMS(buffer, { resolution, channel: ch }));
44
+ }
45
+ return allChannels[0].map((_, i) => {
46
+ let sum = 0;
47
+ for (const ch of allChannels) {
48
+ sum += ch[i];
49
+ }
50
+ return sum / allChannels.length;
51
+ });
52
+ }
53
+ const data = buffer.getChannelData(channel);
54
+ const blockSize = Math.floor(data.length / resolution);
55
+ const rms = [];
56
+ for (let i = 0; i < resolution; i++) {
57
+ const start = i * blockSize;
58
+ const end = Math.min(start + blockSize, data.length);
59
+ let sumSq = 0;
60
+ for (let j = start; j < end; j++) {
61
+ const s = data[j];
62
+ sumSq += s * s;
63
+ }
64
+ rms.push(Math.sqrt(sumSq / (end - start)));
65
+ }
66
+ return rms;
67
+ }
68
+
69
+ export { extractPeakPairs, extractPeaks, extractRMS };
70
+ //# sourceMappingURL=chunk-CRODJ4KS.js.map
71
+ //# sourceMappingURL=chunk-CRODJ4KS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/waveform.ts"],"names":[],"mappings":"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","file":"chunk-CRODJ4KS.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M6: Waveform data extraction\n// ---------------------------------------------------------------------------\n\nimport type { ExtractPeaksOptions, PeakPair } from \"./types.js\";\n\n/**\n * Extract normalised peak amplitude values from an `AudioBuffer`.\n * Returns an array of numbers in the range `[0, 1]`.\n */\nexport function extractPeaks(\n buffer: AudioBuffer,\n options?: ExtractPeaksOptions,\n): number[] {\n const { resolution = 200, channel = 0 } = options ?? {};\n const data = buffer.getChannelData(channel);\n const blockSize = Math.floor(data.length / resolution);\n const peaks: number[] = [];\n\n for (let i = 0; i < resolution; i++) {\n const start = i * blockSize;\n const end = Math.min(start + blockSize, data.length);\n let max = 0;\n for (let j = start; j < end; j++) {\n const abs = Math.abs(data[j]!);\n if (abs > max) max = abs;\n }\n peaks.push(max);\n }\n\n return peaks;\n}\n\n/**\n * Extract min/max peak pairs for detailed waveform rendering.\n */\nexport function extractPeakPairs(\n buffer: AudioBuffer,\n options?: ExtractPeaksOptions,\n): PeakPair[] {\n const { resolution = 200, channel = 0 } = options ?? {};\n const data = buffer.getChannelData(channel);\n const blockSize = Math.floor(data.length / resolution);\n const pairs: PeakPair[] = [];\n\n for (let i = 0; i < resolution; i++) {\n const start = i * blockSize;\n const end = Math.min(start + blockSize, data.length);\n let min = 0;\n let max = 0;\n for (let j = start; j < end; j++) {\n const sample = data[j]!;\n if (sample < min) min = sample;\n if (sample > max) max = sample;\n }\n pairs.push({ min, max });\n }\n\n return pairs;\n}\n\n/**\n * Extract RMS (root mean square) values representing perceived loudness.\n * Returns values in the range `[0, 1]`.\n *\n * When `channel` is set to `-1`, all channels are averaged.\n */\nexport function extractRMS(\n buffer: AudioBuffer,\n options?: ExtractPeaksOptions & { channel?: number },\n): number[] {\n const { resolution = 200, channel = 0 } = options ?? {};\n\n if (channel === -1) {\n // Average across all channels.\n const allChannels: number[][] = [];\n for (let ch = 0; ch < buffer.numberOfChannels; ch++) {\n allChannels.push(extractRMS(buffer, { resolution, channel: ch }));\n }\n return allChannels[0]!.map((_, i) => {\n let sum = 0;\n for (const ch of allChannels) {\n sum += ch[i]!;\n }\n return sum / allChannels.length;\n });\n }\n\n const data = buffer.getChannelData(channel);\n const blockSize = Math.floor(data.length / resolution);\n const rms: number[] = [];\n\n for (let i = 0; i < resolution; i++) {\n const start = i * blockSize;\n const end = Math.min(start + blockSize, data.length);\n let sumSq = 0;\n for (let j = start; j < end; j++) {\n const s = data[j]!;\n sumSq += s * s;\n }\n rms.push(Math.sqrt(sumSq / (end - start)));\n }\n\n return rms;\n}\n"]}
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ // src/scheduler.ts
4
+ function createScheduler(ctx, options) {
5
+ const { lookahead = 0.1, interval = 25 } = options ?? {};
6
+ const events = [];
7
+ let timerId = null;
8
+ function tick() {
9
+ const horizon = ctx.currentTime + lookahead;
10
+ for (let i = events.length - 1; i >= 0; i--) {
11
+ const evt = events[i];
12
+ if (evt.time <= horizon) {
13
+ evt.callback(evt.time);
14
+ events.splice(i, 1);
15
+ }
16
+ }
17
+ }
18
+ return {
19
+ schedule(id, time, callback) {
20
+ events.push({ id, time, callback });
21
+ },
22
+ cancel(id) {
23
+ const idx = events.findIndex((e) => e.id === id);
24
+ if (idx !== -1) events.splice(idx, 1);
25
+ },
26
+ start() {
27
+ if (timerId !== null) return;
28
+ timerId = setInterval(tick, interval);
29
+ },
30
+ stop() {
31
+ if (timerId !== null) {
32
+ clearInterval(timerId);
33
+ timerId = null;
34
+ }
35
+ },
36
+ dispose() {
37
+ this.stop();
38
+ events.length = 0;
39
+ }
40
+ };
41
+ }
42
+ function createClock(ctx, options) {
43
+ let bpm = options?.bpm ?? 120;
44
+ const startTime = ctx.currentTime;
45
+ function secondsPerBeat() {
46
+ return 60 / bpm;
47
+ }
48
+ return {
49
+ beatToTime(beat) {
50
+ return startTime + beat * secondsPerBeat();
51
+ },
52
+ getCurrentBeat() {
53
+ return (ctx.currentTime - startTime) / secondsPerBeat();
54
+ },
55
+ getNextBeatTime() {
56
+ const current = this.getCurrentBeat();
57
+ const next = Math.ceil(current);
58
+ return this.beatToTime(next === current ? next + 1 : next);
59
+ },
60
+ setBpm(value) {
61
+ bpm = value;
62
+ },
63
+ getBpm() {
64
+ return bpm;
65
+ }
66
+ };
67
+ }
68
+
69
+ exports.createClock = createClock;
70
+ exports.createScheduler = createScheduler;
71
+ //# sourceMappingURL=chunk-D5CD5KQZ.cjs.map
72
+ //# sourceMappingURL=chunk-D5CD5KQZ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scheduler.ts"],"names":[],"mappings":";;;AA6BO,SAAS,eAAA,CACd,KACA,OAAA,EACW;AACX,EAAA,MAAM,EAAE,SAAA,GAAY,GAAA,EAAK,WAAW,EAAA,EAAG,GAAI,WAAW,EAAC;AAEvD,EAAA,MAAM,SAA2B,EAAC;AAClC,EAAA,IAAI,OAAA,GAAiD,IAAA;AAErD,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,MAAM,OAAA,GAAU,IAAI,WAAA,GAAc,SAAA;AAClC,IAAA,KAAA,IAAS,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC3C,MAAA,MAAM,GAAA,GAAM,OAAO,CAAC,CAAA;AACpB,MAAA,IAAI,GAAA,CAAI,QAAQ,OAAA,EAAS;AACvB,QAAA,GAAA,CAAI,QAAA,CAAS,IAAI,IAAI,CAAA;AACrB,QAAA,MAAA,CAAO,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA,CAAS,EAAA,EAAI,IAAA,EAAM,QAAA,EAAU;AAC3B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,EAAA,EAAI,IAAA,EAAM,UAAU,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,OAAO,EAAA,EAAI;AACT,MAAA,MAAM,MAAM,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAC/C,MAAA,IAAI,GAAA,KAAQ,EAAA,EAAI,MAAA,CAAO,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,IAAI,YAAY,IAAA,EAAM;AACtB,MAAA,OAAA,GAAU,WAAA,CAAY,MAAM,QAAQ,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,IAAA,GAAO;AACL,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,aAAA,CAAc,OAAO,CAAA;AACrB,QAAA,OAAA,GAAU,IAAA;AAAA,MACZ;AAAA,IACF,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,IAAA,CAAK,IAAA,EAAK;AACV,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAAA,IAClB;AAAA,GACF;AACF;AAsBO,SAAS,WAAA,CACd,KACA,OAAA,EACO;AACP,EAAA,IAAI,GAAA,GAAM,SAAS,GAAA,IAAO,GAAA;AAC1B,EAAA,MAAM,YAAY,GAAA,CAAI,WAAA;AAEtB,EAAA,SAAS,cAAA,GAAiB;AACxB,IAAA,OAAO,EAAA,GAAK,GAAA;AAAA,EACd;AAEA,EAAA,OAAO;AAAA,IACL,WAAW,IAAA,EAAsB;AAC/B,MAAA,OAAO,SAAA,GAAY,OAAO,cAAA,EAAe;AAAA,IAC3C,CAAA;AAAA,IACA,cAAA,GAAyB;AACvB,MAAA,OAAA,CAAQ,GAAA,CAAI,WAAA,GAAc,SAAA,IAAa,cAAA,EAAe;AAAA,IACxD,CAAA;AAAA,IACA,eAAA,GAA0B;AACxB,MAAA,MAAM,OAAA,GAAU,KAAK,cAAA,EAAe;AACpC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAC9B,MAAA,OAAO,KAAK,UAAA,CAAW,IAAA,KAAS,OAAA,GAAU,IAAA,GAAO,IAAI,IAAI,CAAA;AAAA,IAC3D,CAAA;AAAA,IACA,OAAO,KAAA,EAAe;AACpB,MAAA,GAAA,GAAM,KAAA;AAAA,IACR,CAAA;AAAA,IACA,MAAA,GAAiB;AACf,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACF","file":"chunk-D5CD5KQZ.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M8: Scheduler & Clock\n// ---------------------------------------------------------------------------\n\nimport type { ClockOptions, ScheduledEvent, SchedulerOptions } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Scheduler\n// ---------------------------------------------------------------------------\n\nexport interface Scheduler {\n /** Schedule a callback at a specific AudioContext time. */\n schedule(id: string, time: number, callback: (time: number) => void): void;\n /** Remove a scheduled event by id. */\n cancel(id: string): void;\n /** Start the scheduler loop. */\n start(): void;\n /** Stop the scheduler loop. */\n stop(): void;\n /** Dispose of all resources. */\n dispose(): void;\n}\n\n/**\n * Create a lookahead-based event scheduler.\n *\n * Uses `setInterval` to check upcoming events and fire callbacks\n * slightly before their scheduled time, enabling sample-accurate timing.\n */\nexport function createScheduler(\n ctx: AudioContext,\n options?: SchedulerOptions,\n): Scheduler {\n const { lookahead = 0.1, interval = 25 } = options ?? {};\n\n const events: ScheduledEvent[] = [];\n let timerId: ReturnType<typeof setInterval> | null = null;\n\n function tick() {\n const horizon = ctx.currentTime + lookahead;\n for (let i = events.length - 1; i >= 0; i--) {\n const evt = events[i]!;\n if (evt.time <= horizon) {\n evt.callback(evt.time);\n events.splice(i, 1);\n }\n }\n }\n\n return {\n schedule(id, time, callback) {\n events.push({ id, time, callback });\n },\n cancel(id) {\n const idx = events.findIndex((e) => e.id === id);\n if (idx !== -1) events.splice(idx, 1);\n },\n start() {\n if (timerId !== null) return;\n timerId = setInterval(tick, interval);\n },\n stop() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n },\n dispose() {\n this.stop();\n events.length = 0;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Clock\n// ---------------------------------------------------------------------------\n\nexport interface Clock {\n /** Convert a beat number to AudioContext time (seconds). */\n beatToTime(beat: number): number;\n /** Get the current beat based on AudioContext time. */\n getCurrentBeat(): number;\n /** Get the AudioContext time of the next beat boundary. */\n getNextBeatTime(): number;\n /** Update BPM. */\n setBpm(bpm: number): void;\n /** Get current BPM. */\n getBpm(): number;\n}\n\n/**\n * Create a BPM-based clock tied to an `AudioContext`.\n */\nexport function createClock(\n ctx: AudioContext,\n options?: ClockOptions,\n): Clock {\n let bpm = options?.bpm ?? 120;\n const startTime = ctx.currentTime;\n\n function secondsPerBeat() {\n return 60 / bpm;\n }\n\n return {\n beatToTime(beat: number): number {\n return startTime + beat * secondsPerBeat();\n },\n getCurrentBeat(): number {\n return (ctx.currentTime - startTime) / secondsPerBeat();\n },\n getNextBeatTime(): number {\n const current = this.getCurrentBeat();\n const next = Math.ceil(current);\n return this.beatToTime(next === current ? next + 1 : next);\n },\n setBpm(value: number) {\n bpm = value;\n },\n getBpm(): number {\n return bpm;\n },\n };\n}\n"]}
@@ -0,0 +1,42 @@
1
+ // src/emitter.ts
2
+ function createEmitter() {
3
+ const listeners = /* @__PURE__ */ new Map();
4
+ function getSet(event) {
5
+ let set = listeners.get(event);
6
+ if (!set) {
7
+ set = /* @__PURE__ */ new Set();
8
+ listeners.set(event, set);
9
+ }
10
+ return set;
11
+ }
12
+ return {
13
+ on(event, handler) {
14
+ const set = getSet(event);
15
+ set.add(handler);
16
+ return () => {
17
+ set.delete(handler);
18
+ };
19
+ },
20
+ off(event, handler) {
21
+ listeners.get(event)?.delete(handler);
22
+ },
23
+ emit(event, data) {
24
+ const set = listeners.get(event);
25
+ if (!set) return;
26
+ for (const handler of set) {
27
+ handler(data);
28
+ }
29
+ },
30
+ clear(event) {
31
+ if (event !== void 0) {
32
+ listeners.delete(event);
33
+ } else {
34
+ listeners.clear();
35
+ }
36
+ }
37
+ };
38
+ }
39
+
40
+ export { createEmitter };
41
+ //# sourceMappingURL=chunk-GYH2JSCY.js.map
42
+ //# sourceMappingURL=chunk-GYH2JSCY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emitter.ts"],"names":[],"mappings":";AAmCO,SAAS,aAAA,GAEK;AACnB,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAA8C;AAEpE,EAAA,SAAS,OAA+B,KAAA,EAAU;AAChD,IAAA,IAAI,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,GAAA,uBAAU,GAAA,EAAI;AACd,MAAA,SAAA,CAAU,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,EAAA,CACE,OACA,OAAA,EACY;AACZ,MAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,MAAA,GAAA,CAAI,IAAI,OAAgC,CAAA;AACxC,MAAA,OAAO,MAAM;AACX,QAAA,GAAA,CAAI,OAAO,OAAgC,CAAA;AAAA,MAC7C,CAAA;AAAA,IACF,CAAA;AAAA,IAEA,GAAA,CACE,OACA,OAAA,EACM;AACN,MAAA,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,MAAA,CAAO,OAAgC,CAAA;AAAA,IAC/D,CAAA;AAAA,IAEA,IAAA,CAA6B,OAAU,IAAA,EAAuB;AAC5D,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC/B,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,KAAA,MAAW,WAAW,GAAA,EAAK;AACzB,QAAA,OAAA,CAAQ,IAAa,CAAA;AAAA,MACvB;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,KAAA,EAA4B;AAChC,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,SAAA,CAAU,OAAO,KAAK,CAAA;AAAA,MACxB,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,KAAA,EAAM;AAAA,MAClB;AAAA,IACF;AAAA,GACF;AACF","file":"chunk-GYH2JSCY.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M4: Lightweight type-safe event emitter\n// ---------------------------------------------------------------------------\n\n/** A minimal, type-safe event emitter. */\nexport interface Emitter<Events extends Record<string, any> = Record<string, unknown>> {\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): () => void;\n\n /** Unsubscribe a handler from an event. */\n off<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): void;\n\n /** Emit an event with data. */\n emit<K extends keyof Events>(event: K, data: Events[K]): void;\n\n /** Remove all listeners (optionally for a specific event). */\n clear(event?: keyof Events): void;\n}\n\n/**\n * Create a lightweight, type-safe event emitter.\n *\n * ```ts\n * const emitter = createEmitter<{ tick: number; done: void }>();\n * const unsub = emitter.on(\"tick\", (n) => console.log(n));\n * emitter.emit(\"tick\", 42);\n * unsub();\n * ```\n */\nexport function createEmitter<\n Events extends Record<string, any> = Record<string, unknown>,\n>(): Emitter<Events> {\n const listeners = new Map<keyof Events, Set<(data: never) => void>>();\n\n function getSet<K extends keyof Events>(event: K) {\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n return set;\n }\n\n return {\n on<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): () => void {\n const set = getSet(event);\n set.add(handler as (data: never) => void);\n return () => {\n set.delete(handler as (data: never) => void);\n };\n },\n\n off<K extends keyof Events>(\n event: K,\n handler: (data: Events[K]) => void,\n ): void {\n listeners.get(event)?.delete(handler as (data: never) => void);\n },\n\n emit<K extends keyof Events>(event: K, data: Events[K]): void {\n const set = listeners.get(event);\n if (!set) return;\n for (const handler of set) {\n handler(data as never);\n }\n },\n\n clear(event?: keyof Events): void {\n if (event !== undefined) {\n listeners.delete(event);\n } else {\n listeners.clear();\n }\n },\n };\n}\n"]}