waa-play 0.1.1 → 0.2.1

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 (122) hide show
  1. package/README.md +40 -123
  2. package/dist/adapters.cjs +6 -6
  3. package/dist/adapters.d.cts +15 -3
  4. package/dist/adapters.d.ts +15 -3
  5. package/dist/adapters.js +1 -1
  6. package/dist/buffer.cjs +5 -5
  7. package/dist/buffer.d.cts +1 -1
  8. package/dist/buffer.d.ts +1 -1
  9. package/dist/buffer.js +1 -1
  10. package/dist/{chunk-T74FBKTY.js → chunk-2FGUFHZM.js} +2 -2
  11. package/dist/chunk-2FGUFHZM.js.map +1 -0
  12. package/dist/{chunk-CPAT75WD.cjs → chunk-3VTU5OX5.cjs} +2 -2
  13. package/dist/chunk-3VTU5OX5.cjs.map +1 -0
  14. package/dist/{chunk-2DL7CAEP.js → chunk-7JUVBZ6B.js} +2 -2
  15. package/dist/chunk-7JUVBZ6B.js.map +1 -0
  16. package/dist/{chunk-D5CD5KQZ.cjs → chunk-BRS7LZVH.cjs} +2 -2
  17. package/dist/chunk-BRS7LZVH.cjs.map +1 -0
  18. package/dist/{chunk-QWNV2BZ5.cjs → chunk-F6WXD3XW.cjs} +2 -2
  19. package/dist/chunk-F6WXD3XW.cjs.map +1 -0
  20. package/dist/{chunk-C2ASIYN5.js → chunk-FESPIMZM.js} +3 -7
  21. package/dist/chunk-FESPIMZM.js.map +1 -0
  22. package/dist/{chunk-GYH2JSCY.js → chunk-FY273Z3I.js} +2 -2
  23. package/dist/chunk-FY273Z3I.js.map +1 -0
  24. package/dist/{chunk-TULV7V5M.cjs → chunk-G37HMZEX.cjs} +1075 -982
  25. package/dist/chunk-G37HMZEX.cjs.map +1 -0
  26. package/dist/{chunk-V2QX5K42.js → chunk-GDBOHOGF.js} +1074 -982
  27. package/dist/chunk-GDBOHOGF.js.map +1 -0
  28. package/dist/{chunk-5J7S6QV3.cjs → chunk-HIF3UAF3.cjs} +2 -2
  29. package/dist/chunk-HIF3UAF3.cjs.map +1 -0
  30. package/dist/{chunk-CRODJ4KS.js → chunk-HTN52U23.js} +13 -6
  31. package/dist/chunk-HTN52U23.js.map +1 -0
  32. package/dist/{chunk-RWJ4EWJT.js → chunk-HYRDCTBO.js} +152 -116
  33. package/dist/chunk-HYRDCTBO.js.map +1 -0
  34. package/dist/chunk-JIHPQAEA.js +90 -0
  35. package/dist/chunk-JIHPQAEA.js.map +1 -0
  36. package/dist/chunk-KVKW7W66.cjs +148 -0
  37. package/dist/chunk-KVKW7W66.cjs.map +1 -0
  38. package/dist/{chunk-4LNVRSTM.cjs → chunk-OIY6I4TU.cjs} +3 -7
  39. package/dist/chunk-OIY6I4TU.cjs.map +1 -0
  40. package/dist/chunk-OZN5X4N6.cjs +96 -0
  41. package/dist/chunk-OZN5X4N6.cjs.map +1 -0
  42. package/dist/{chunk-CJJC6ASU.js → chunk-PL4J3NR7.js} +2 -2
  43. package/dist/chunk-PL4J3NR7.js.map +1 -0
  44. package/dist/chunk-QFJQU7TQ.js +146 -0
  45. package/dist/chunk-QFJQU7TQ.js.map +1 -0
  46. package/dist/{chunk-M5PDY5EZ.cjs → chunk-QGZGERGK.cjs} +2 -2
  47. package/dist/chunk-QGZGERGK.cjs.map +1 -0
  48. package/dist/{chunk-QFFQQMU4.cjs → chunk-VOSIA3GF.cjs} +13 -6
  49. package/dist/chunk-VOSIA3GF.cjs.map +1 -0
  50. package/dist/{chunk-PZE6HTZR.cjs → chunk-VY4UMZMJ.cjs} +154 -118
  51. package/dist/chunk-VY4UMZMJ.cjs.map +1 -0
  52. package/dist/{chunk-LETS7FKB.js → chunk-YFK7ETCF.js} +2 -2
  53. package/dist/chunk-YFK7ETCF.js.map +1 -0
  54. package/dist/context.d.cts +1 -1
  55. package/dist/context.d.ts +1 -1
  56. package/dist/emitter.cjs +2 -2
  57. package/dist/emitter.js +1 -1
  58. package/dist/engine-7DCOERRN.js +4 -0
  59. package/dist/{engine-M2U4LE3F.js.map → engine-7DCOERRN.js.map} +1 -1
  60. package/dist/engine-ALWPAIX6.cjs +17 -0
  61. package/dist/{engine-5JK2FCNL.cjs.map → engine-ALWPAIX6.cjs.map} +1 -1
  62. package/dist/fade.cjs +5 -5
  63. package/dist/fade.d.cts +1 -1
  64. package/dist/fade.d.ts +1 -1
  65. package/dist/fade.js +1 -1
  66. package/dist/index.cjs +47 -42
  67. package/dist/index.d.cts +7 -6
  68. package/dist/index.d.ts +7 -6
  69. package/dist/index.js +10 -9
  70. package/dist/nodes.cjs +11 -11
  71. package/dist/nodes.js +1 -1
  72. package/dist/play.cjs +3 -3
  73. package/dist/play.d.cts +1 -1
  74. package/dist/play.d.ts +1 -1
  75. package/dist/play.js +2 -2
  76. package/dist/player.cjs +22 -0
  77. package/dist/player.cjs.map +1 -0
  78. package/dist/player.d.cts +64 -0
  79. package/dist/player.d.ts +64 -0
  80. package/dist/player.js +13 -0
  81. package/dist/player.js.map +1 -0
  82. package/dist/scheduler.cjs +3 -3
  83. package/dist/scheduler.d.cts +1 -1
  84. package/dist/scheduler.d.ts +1 -1
  85. package/dist/scheduler.js +1 -1
  86. package/dist/stretcher.cjs +3 -3
  87. package/dist/stretcher.d.cts +5 -3
  88. package/dist/stretcher.d.ts +5 -3
  89. package/dist/stretcher.js +2 -2
  90. package/dist/synth.cjs +4 -4
  91. package/dist/synth.js +1 -1
  92. package/dist/{types-DUrbEbPl.d.cts → types-BYC6m7Q0.d.cts} +6 -6
  93. package/dist/{types-DUrbEbPl.d.ts → types-BYC6m7Q0.d.ts} +6 -6
  94. package/dist/waveform.cjs +4 -4
  95. package/dist/waveform.d.cts +1 -1
  96. package/dist/waveform.d.ts +1 -1
  97. package/dist/waveform.js +1 -1
  98. package/package.json +19 -7
  99. package/dist/chunk-2DL7CAEP.js.map +0 -1
  100. package/dist/chunk-4LNVRSTM.cjs.map +0 -1
  101. package/dist/chunk-5J7S6QV3.cjs.map +0 -1
  102. package/dist/chunk-AGP2IRC6.js +0 -63
  103. package/dist/chunk-AGP2IRC6.js.map +0 -1
  104. package/dist/chunk-C2ASIYN5.js.map +0 -1
  105. package/dist/chunk-CJJC6ASU.js.map +0 -1
  106. package/dist/chunk-CPAT75WD.cjs.map +0 -1
  107. package/dist/chunk-CRODJ4KS.js.map +0 -1
  108. package/dist/chunk-D5CD5KQZ.cjs.map +0 -1
  109. package/dist/chunk-GYH2JSCY.js.map +0 -1
  110. package/dist/chunk-HTGOHC73.cjs +0 -69
  111. package/dist/chunk-HTGOHC73.cjs.map +0 -1
  112. package/dist/chunk-LETS7FKB.js.map +0 -1
  113. package/dist/chunk-M5PDY5EZ.cjs.map +0 -1
  114. package/dist/chunk-PZE6HTZR.cjs.map +0 -1
  115. package/dist/chunk-QFFQQMU4.cjs.map +0 -1
  116. package/dist/chunk-QWNV2BZ5.cjs.map +0 -1
  117. package/dist/chunk-RWJ4EWJT.js.map +0 -1
  118. package/dist/chunk-T74FBKTY.js.map +0 -1
  119. package/dist/chunk-TULV7V5M.cjs.map +0 -1
  120. package/dist/chunk-V2QX5K42.js.map +0 -1
  121. package/dist/engine-5JK2FCNL.cjs +0 -13
  122. package/dist/engine-M2U4LE3F.js +0 -4
@@ -81,8 +81,9 @@ interface StretcherEvents {
81
81
  chunkready: {
82
82
  index: number;
83
83
  };
84
- complete: void;
85
- ended: void;
84
+ complete: undefined;
85
+ ended: undefined;
86
+ loop: undefined;
86
87
  error: {
87
88
  message: string;
88
89
  chunkIndex?: number;
@@ -97,6 +98,7 @@ interface StretcherEngine {
97
98
  seek(position: number): void;
98
99
  stop(): void;
99
100
  setTempo(tempo: number): void;
101
+ setLoop(loop: boolean): void;
100
102
  getCurrentPosition(): number;
101
103
  getStatus(): StretcherStatus;
102
104
  getSnapshot(): StretcherSnapshotExtension;
@@ -138,7 +140,7 @@ interface ConversionScheduler {
138
140
  _handleError(chunkIndex: number, error: string): void;
139
141
  }
140
142
  interface ChunkPlayer {
141
- playChunk(buffer: AudioBuffer, startTime: number, offsetInChunk?: number): void;
143
+ playChunk(buffer: AudioBuffer, startTime: number, offsetInChunk?: number, skipFadeIn?: boolean): void;
142
144
  scheduleNext(buffer: AudioBuffer, startTime: number): void;
143
145
  hasNextScheduled(): boolean;
144
146
  handleSeek(buffer: AudioBuffer, offsetInChunk: number): void;
package/dist/stretcher.js CHANGED
@@ -1,4 +1,4 @@
1
- export { createStretcherEngine } from './chunk-V2QX5K42.js';
2
- import './chunk-GYH2JSCY.js';
1
+ export { createStretcherEngine } from './chunk-GDBOHOGF.js';
2
+ import './chunk-FY273Z3I.js';
3
3
  //# sourceMappingURL=stretcher.js.map
4
4
  //# sourceMappingURL=stretcher.js.map
package/dist/synth.cjs CHANGED
@@ -1,20 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var chunkQWNV2BZ5_cjs = require('./chunk-QWNV2BZ5.cjs');
3
+ var chunkF6WXD3XW_cjs = require('./chunk-F6WXD3XW.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "createClickBuffer", {
8
8
  enumerable: true,
9
- get: function () { return chunkQWNV2BZ5_cjs.createClickBuffer; }
9
+ get: function () { return chunkF6WXD3XW_cjs.createClickBuffer; }
10
10
  });
11
11
  Object.defineProperty(exports, "createNoiseBuffer", {
12
12
  enumerable: true,
13
- get: function () { return chunkQWNV2BZ5_cjs.createNoiseBuffer; }
13
+ get: function () { return chunkF6WXD3XW_cjs.createNoiseBuffer; }
14
14
  });
15
15
  Object.defineProperty(exports, "createSineBuffer", {
16
16
  enumerable: true,
17
- get: function () { return chunkQWNV2BZ5_cjs.createSineBuffer; }
17
+ get: function () { return chunkF6WXD3XW_cjs.createSineBuffer; }
18
18
  });
19
19
  //# sourceMappingURL=synth.cjs.map
20
20
  //# sourceMappingURL=synth.cjs.map
package/dist/synth.js CHANGED
@@ -1,3 +1,3 @@
1
- export { createClickBuffer, createNoiseBuffer, createSineBuffer } from './chunk-LETS7FKB.js';
1
+ export { createClickBuffer, createNoiseBuffer, createSineBuffer } from './chunk-YFK7ETCF.js';
2
2
  //# sourceMappingURL=synth.js.map
3
3
  //# sourceMappingURL=synth.js.map
@@ -58,15 +58,15 @@ interface PlaybackSnapshot {
58
58
  }
59
59
  /** Event map emitted by a Playback instance. */
60
60
  interface PlaybackEventMap {
61
- play: void;
62
- pause: void;
63
- resume: void;
61
+ play: undefined;
62
+ pause: undefined;
63
+ resume: undefined;
64
64
  seek: {
65
65
  position: number;
66
66
  };
67
- stop: void;
68
- ended: void;
69
- loop: void;
67
+ stop: undefined;
68
+ ended: undefined;
69
+ loop: undefined;
70
70
  statechange: {
71
71
  state: PlaybackState;
72
72
  };
@@ -58,15 +58,15 @@ interface PlaybackSnapshot {
58
58
  }
59
59
  /** Event map emitted by a Playback instance. */
60
60
  interface PlaybackEventMap {
61
- play: void;
62
- pause: void;
63
- resume: void;
61
+ play: undefined;
62
+ pause: undefined;
63
+ resume: undefined;
64
64
  seek: {
65
65
  position: number;
66
66
  };
67
- stop: void;
68
- ended: void;
69
- loop: void;
67
+ stop: undefined;
68
+ ended: undefined;
69
+ loop: undefined;
70
70
  statechange: {
71
71
  state: PlaybackState;
72
72
  };
package/dist/waveform.cjs CHANGED
@@ -1,20 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var chunkQFFQQMU4_cjs = require('./chunk-QFFQQMU4.cjs');
3
+ var chunkVOSIA3GF_cjs = require('./chunk-VOSIA3GF.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "extractPeakPairs", {
8
8
  enumerable: true,
9
- get: function () { return chunkQFFQQMU4_cjs.extractPeakPairs; }
9
+ get: function () { return chunkVOSIA3GF_cjs.extractPeakPairs; }
10
10
  });
11
11
  Object.defineProperty(exports, "extractPeaks", {
12
12
  enumerable: true,
13
- get: function () { return chunkQFFQQMU4_cjs.extractPeaks; }
13
+ get: function () { return chunkVOSIA3GF_cjs.extractPeaks; }
14
14
  });
15
15
  Object.defineProperty(exports, "extractRMS", {
16
16
  enumerable: true,
17
- get: function () { return chunkQFFQQMU4_cjs.extractRMS; }
17
+ get: function () { return chunkVOSIA3GF_cjs.extractRMS; }
18
18
  });
19
19
  //# sourceMappingURL=waveform.cjs.map
20
20
  //# sourceMappingURL=waveform.cjs.map
@@ -1,4 +1,4 @@
1
- import { E as ExtractPeaksOptions, P as PeakPair } from './types-DUrbEbPl.cjs';
1
+ import { E as ExtractPeaksOptions, P as PeakPair } from './types-BYC6m7Q0.cjs';
2
2
 
3
3
  /**
4
4
  * Extract normalised peak amplitude values from an `AudioBuffer`.
@@ -1,4 +1,4 @@
1
- import { E as ExtractPeaksOptions, P as PeakPair } from './types-DUrbEbPl.js';
1
+ import { E as ExtractPeaksOptions, P as PeakPair } from './types-BYC6m7Q0.js';
2
2
 
3
3
  /**
4
4
  * Extract normalised peak amplitude values from an `AudioBuffer`.
package/dist/waveform.js CHANGED
@@ -1,3 +1,3 @@
1
- export { extractPeakPairs, extractPeaks, extractRMS } from './chunk-CRODJ4KS.js';
1
+ export { extractPeakPairs, extractPeaks, extractRMS } from './chunk-HTN52U23.js';
2
2
  //# sourceMappingURL=waveform.js.map
3
3
  //# sourceMappingURL=waveform.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "waa-play",
3
- "version": "0.1.1",
4
- "description": "Composable Web Audio API utilities. BYO AudioContext. Framework-agnostic. Sample-accurate. Pitch-preserving time-stretch.",
3
+ "version": "0.2.1",
4
+ "description": "Web Audio API modules with WSOLA time-stretching, chunk-based streaming, and waveform extraction",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -82,6 +82,12 @@
82
82
  "types": "./dist/stretcher.d.ts",
83
83
  "default": "./dist/stretcher.js"
84
84
  }
85
+ },
86
+ "./player": {
87
+ "import": {
88
+ "types": "./dist/player.d.ts",
89
+ "default": "./dist/player.js"
90
+ }
85
91
  }
86
92
  },
87
93
  "sideEffects": false,
@@ -92,19 +98,22 @@
92
98
  "build": "tsup",
93
99
  "dev": "tsup --watch",
94
100
  "test": "vitest run",
101
+ "test:unit": "vitest run --project=unit",
102
+ "test:browser": "vitest run --project=browser",
95
103
  "test:watch": "vitest",
96
104
  "typecheck": "tsc --noEmit",
97
- "lint": "tsc --noEmit",
105
+ "lint": "biome lint .",
106
+ "format": "biome format --write .",
107
+ "check": "biome check .",
98
108
  "prepublishOnly": "npm run build"
99
109
  },
100
110
  "keywords": [
101
111
  "web-audio",
102
112
  "audio",
113
+ "audio-player",
103
114
  "sound",
104
115
  "webaudio",
105
116
  "audiocontext",
106
- "composable",
107
- "tree-shakeable",
108
117
  "time-stretch",
109
118
  "wsola",
110
119
  "playback",
@@ -118,10 +127,13 @@
118
127
  },
119
128
  "homepage": "https://ivgtr.github.io/waa/",
120
129
  "bugs": "https://github.com/ivgtr/waa/issues",
121
- "dependencies": {},
122
130
  "devDependencies": {
131
+ "@biomejs/biome": "^2.3.14",
132
+ "@vitest/browser": "^4.0.18",
133
+ "@vitest/browser-playwright": "^4.0.18",
134
+ "playwright": "^1.58.2",
123
135
  "tsup": "^8.4.0",
124
136
  "typescript": "^5.7.0",
125
- "vitest": "^2.1.0"
137
+ "vitest": "^4.0.18"
126
138
  }
127
139
  }
@@ -1 +0,0 @@
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-2DL7CAEP.js","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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/fade.ts"],"names":[],"mappings":"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","file":"chunk-4LNVRSTM.cjs","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"]}
@@ -1 +0,0 @@
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"]}
@@ -1,63 +0,0 @@
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
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1,69 +0,0 @@
1
- 'use strict';
2
-
3
- // src/adapters.ts
4
- function getSnapshot(playback) {
5
- const base = {
6
- state: playback.getState(),
7
- position: playback.getCurrentTime(),
8
- duration: playback.getDuration(),
9
- progress: playback.getProgress()
10
- };
11
- const getter = playback["_getStretcherSnapshot"];
12
- if (typeof getter === "function") {
13
- const stretcher = getter();
14
- if (stretcher) {
15
- base.stretcher = stretcher;
16
- }
17
- }
18
- return base;
19
- }
20
- function subscribeSnapshot(playback, callback) {
21
- const unsubs = [];
22
- unsubs.push(playback.on("statechange", callback));
23
- unsubs.push(playback.on("timeupdate", callback));
24
- unsubs.push(playback.on("seek", callback));
25
- unsubs.push(playback.on("ended", callback));
26
- return () => {
27
- for (const unsub of unsubs) unsub();
28
- };
29
- }
30
- function onFrame(playback, callback) {
31
- let rafId = null;
32
- function tick() {
33
- callback(getSnapshot(playback));
34
- rafId = requestAnimationFrame(tick);
35
- }
36
- rafId = requestAnimationFrame(tick);
37
- return () => {
38
- if (rafId !== null) {
39
- cancelAnimationFrame(rafId);
40
- rafId = null;
41
- }
42
- };
43
- }
44
- function whenEnded(playback) {
45
- return new Promise((resolve) => {
46
- const unsub = playback.on("ended", () => {
47
- unsub();
48
- resolve();
49
- });
50
- });
51
- }
52
- function whenPosition(playback, position) {
53
- return new Promise((resolve) => {
54
- const unsub = playback.on("timeupdate", ({ position: current }) => {
55
- if (current >= position) {
56
- unsub();
57
- resolve();
58
- }
59
- });
60
- });
61
- }
62
-
63
- exports.getSnapshot = getSnapshot;
64
- exports.onFrame = onFrame;
65
- exports.subscribeSnapshot = subscribeSnapshot;
66
- exports.whenEnded = whenEnded;
67
- exports.whenPosition = whenPosition;
68
- //# sourceMappingURL=chunk-HTGOHC73.cjs.map
69
- //# sourceMappingURL=chunk-HTGOHC73.cjs.map
@@ -1 +0,0 @@
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-HTGOHC73.cjs","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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/synth.ts"],"names":[],"mappings":";AAQO,SAAS,gBAAA,CACd,GAAA,EACA,SAAA,EACA,QAAA,EACa;AACb,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,aAAa,QAAQ,CAAA;AAClD,EAAA,MAAM,SAAS,GAAA,CAAI,YAAA,CAAa,CAAA,EAAG,MAAA,EAAQ,IAAI,UAAU,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AAEpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAK,CAAA,GAAI,KAAK,EAAA,GAAK,SAAA,GAAY,CAAA,GAAK,GAAA,CAAI,UAAU,CAAA;AAAA,EACnE;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,iBAAA,CACd,KACA,QAAA,EACa;AACb,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,aAAa,QAAQ,CAAA;AAClD,EAAA,MAAM,SAAS,GAAA,CAAI,YAAA,CAAa,CAAA,EAAG,MAAA,EAAQ,IAAI,UAAU,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AAEpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,CAAA;AAAA,EAChC;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,iBAAA,CACd,GAAA,EACA,SAAA,EACA,QAAA,EACa;AACb,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,aAAa,QAAQ,CAAA;AAClD,EAAA,MAAM,SAAS,GAAA,CAAI,YAAA,CAAa,CAAA,EAAG,MAAA,EAAQ,IAAI,UAAU,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AAEpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAE/B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAK,EAAA,GAAK,IAAK,MAAM,CAAA;AAC3C,IAAA,IAAA,CAAK,CAAC,CAAA,GACJ,QAAA,GAAW,IAAA,CAAK,GAAA,CAAK,CAAA,GAAI,IAAA,CAAK,EAAA,GAAK,SAAA,GAAY,CAAA,GAAK,GAAA,CAAI,UAAU,CAAA;AAAA,EACtE;AAEA,EAAA,OAAO,MAAA;AACT","file":"chunk-LETS7FKB.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M9: Buffer synthesis\n// ---------------------------------------------------------------------------\n\n/**\n * Create an `AudioBuffer` containing a sine wave.\n * Useful for test tones and debugging.\n */\nexport function createSineBuffer(\n ctx: AudioContext,\n frequency: number,\n duration: number,\n): AudioBuffer {\n const length = Math.ceil(ctx.sampleRate * duration);\n const buffer = ctx.createBuffer(1, length, ctx.sampleRate);\n const data = buffer.getChannelData(0);\n\n for (let i = 0; i < length; i++) {\n data[i] = Math.sin((2 * Math.PI * frequency * i) / ctx.sampleRate);\n }\n\n return buffer;\n}\n\n/**\n * Create an `AudioBuffer` containing white noise.\n */\nexport function createNoiseBuffer(\n ctx: AudioContext,\n duration: number,\n): AudioBuffer {\n const length = Math.ceil(ctx.sampleRate * duration);\n const buffer = ctx.createBuffer(1, length, ctx.sampleRate);\n const data = buffer.getChannelData(0);\n\n for (let i = 0; i < length; i++) {\n data[i] = Math.random() * 2 - 1;\n }\n\n return buffer;\n}\n\n/**\n * Create an `AudioBuffer` containing a short click/impulse.\n */\nexport function createClickBuffer(\n ctx: AudioContext,\n frequency: number,\n duration: number,\n): AudioBuffer {\n const length = Math.ceil(ctx.sampleRate * duration);\n const buffer = ctx.createBuffer(1, length, ctx.sampleRate);\n const data = buffer.getChannelData(0);\n\n for (let i = 0; i < length; i++) {\n // Exponential decay envelope.\n const envelope = Math.exp((-5 * i) / length);\n data[i] =\n envelope * Math.sin((2 * Math.PI * frequency * i) / ctx.sampleRate);\n }\n\n return buffer;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/nodes.ts"],"names":[],"mappings":"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","file":"chunk-M5PDY5EZ.cjs","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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/play.ts"],"names":["createEmitter"],"mappings":"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","file":"chunk-PZE6HTZR.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M3: Playback engine\n// ---------------------------------------------------------------------------\n\nimport { createEmitter } from \"./emitter.js\";\nimport type {\n Playback,\n PlaybackEventMap,\n PlaybackState,\n PlayOptions,\n StretcherSnapshotExtension,\n} from \"./types.js\";\n\n/**\n * Play an `AudioBuffer` through an `AudioContext` and return a controllable\n * `Playback` handle.\n *\n * ```ts\n * const pb = play(ctx, buffer, { loop: true });\n * pb.on(\"timeupdate\", ({ position }) => console.log(position));\n * pb.pause();\n * pb.resume();\n * pb.seek(10);\n * pb.stop();\n * pb.dispose();\n * ```\n */\nexport function play(\n ctx: AudioContext,\n buffer: AudioBuffer,\n options?: PlayOptions,\n): Playback {\n const { preservePitch = true } = options ?? {};\n\n // ----- Pitch-preserving mode (WSOLA-based time-stretch) -----\n if (preservePitch) {\n return createStretchedPlayback(ctx, buffer, options ?? {});\n }\n\n const {\n offset: initialOffset = 0,\n loop = false,\n loopStart,\n loopEnd,\n playbackRate: initialRate = 1,\n through = [],\n destination = ctx.destination,\n timeupdateInterval = 50,\n } = options ?? {};\n\n const emitter = createEmitter<PlaybackEventMap>();\n const duration = buffer.duration;\n\n // ----- mutable internal state -----\n let state: PlaybackState = \"stopped\";\n let sourceNode: AudioBufferSourceNode | null = null;\n let startedAt = 0; // ctx.currentTime when playback last started/resumed\n let pausedAt = initialOffset; // position in the buffer (seconds)\n let currentRate = initialRate;\n let isLooping = loop;\n let timerId: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n\n // ----- helpers -----\n\n function createSource(): AudioBufferSourceNode {\n const src = ctx.createBufferSource();\n src.buffer = buffer;\n src.playbackRate.value = currentRate;\n src.loop = isLooping;\n if (loopStart !== undefined) src.loopStart = loopStart;\n if (loopEnd !== undefined) src.loopEnd = loopEnd;\n\n // Connect through the node chain (or directly to destination).\n if (through.length > 0) {\n src.connect(through[0]!);\n for (let i = 0; i < through.length - 1; i++) {\n through[i]!.connect(through[i + 1]!);\n }\n through[through.length - 1]!.connect(destination);\n } else {\n src.connect(destination);\n }\n\n src.onended = handleEnded;\n return src;\n }\n\n function startSource(positionInBuffer: number) {\n sourceNode = createSource();\n sourceNode.start(0, positionInBuffer);\n startedAt = ctx.currentTime - positionInBuffer / currentRate;\n }\n\n function stopSource() {\n if (sourceNode) {\n sourceNode.onended = null;\n try {\n sourceNode.stop();\n } catch {\n // Already stopped — safe to ignore.\n }\n sourceNode.disconnect();\n sourceNode = null;\n }\n }\n\n function handleEnded() {\n // If we manually stopped / paused, the handler was already removed.\n if (state !== \"playing\") return;\n if (isLooping) {\n emitter.emit(\"loop\", undefined as never);\n return;\n }\n setState(\"stopped\");\n pausedAt = 0;\n stopTimer();\n emitter.emit(\"ended\", undefined as never);\n }\n\n function setState(next: PlaybackState) {\n if (state === next) return;\n state = next;\n emitter.emit(\"statechange\", { state: next });\n }\n\n function startTimer() {\n if (timerId !== null) return;\n timerId = setInterval(() => {\n if (state !== \"playing\") return;\n emitter.emit(\"timeupdate\", {\n position: getCurrentTime(),\n duration,\n });\n }, timeupdateInterval);\n }\n\n function stopTimer() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n }\n\n // ----- public API -----\n\n function getCurrentTime(): number {\n if (state === \"playing\") {\n const elapsed = (ctx.currentTime - startedAt) * currentRate;\n if (isLooping) {\n const loopDur =\n (loopEnd ?? duration) - (loopStart ?? 0);\n return ((elapsed - (loopStart ?? 0)) % loopDur) + (loopStart ?? 0);\n }\n return Math.min(elapsed, duration);\n }\n if (state === \"paused\") return pausedAt;\n return 0;\n }\n\n function pause() {\n if (state !== \"playing\" || disposed) return;\n pausedAt = getCurrentTime();\n stopSource();\n stopTimer();\n setState(\"paused\");\n emitter.emit(\"pause\", undefined as never);\n }\n\n function resume() {\n if (state !== \"paused\" || disposed) return;\n startSource(pausedAt);\n setState(\"playing\");\n startTimer();\n emitter.emit(\"resume\", undefined as never);\n }\n\n function togglePlayPause() {\n if (state === \"playing\") pause();\n else if (state === \"paused\") resume();\n }\n\n function seek(position: number) {\n if (disposed) return;\n const clamped = Math.max(0, Math.min(position, duration));\n const wasPlaying = state === \"playing\";\n\n stopSource();\n stopTimer();\n\n pausedAt = clamped;\n\n if (wasPlaying) {\n startSource(clamped);\n startTimer();\n }\n\n emitter.emit(\"seek\", { position: clamped });\n }\n\n function stop() {\n if (state === \"stopped\" || disposed) return;\n stopSource();\n stopTimer();\n pausedAt = 0;\n setState(\"stopped\");\n emitter.emit(\"stop\", undefined as never);\n }\n\n function setPlaybackRate(rate: number) {\n const position = getCurrentTime();\n currentRate = rate;\n if (sourceNode) {\n sourceNode.playbackRate.value = rate;\n startedAt = ctx.currentTime - position / rate;\n }\n }\n\n function setLoop(value: boolean) {\n isLooping = value;\n if (sourceNode) {\n sourceNode.loop = value;\n }\n }\n\n function dispose() {\n if (disposed) return;\n disposed = true;\n stopSource();\n stopTimer();\n emitter.clear();\n }\n\n // ----- kick off initial playback -----\n\n startSource(initialOffset);\n setState(\"playing\");\n startTimer();\n emitter.emit(\"play\", undefined as never);\n\n return {\n getState: () => state,\n getCurrentTime,\n getDuration: () => duration,\n getProgress: () => (duration > 0 ? getCurrentTime() / duration : 0),\n pause,\n resume,\n togglePlayPause,\n seek,\n stop,\n setPlaybackRate,\n setLoop,\n on: emitter.on.bind(emitter) as Playback[\"on\"],\n off: emitter.off.bind(emitter) as Playback[\"off\"],\n dispose,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Stretched playback (preservePitch: true)\n// ---------------------------------------------------------------------------\n\nfunction createStretchedPlayback(\n ctx: AudioContext,\n buffer: AudioBuffer,\n options: PlayOptions,\n): Playback {\n const {\n offset: initialOffset = 0,\n playbackRate: initialRate = 1,\n through = [],\n destination = ctx.destination,\n timeupdateInterval = 50,\n } = options;\n\n const emitter = createEmitter<PlaybackEventMap>();\n const duration = buffer.duration;\n\n let state: PlaybackState = \"playing\";\n let engineInstance: import(\"./stretcher/types.js\").StretcherEngine | null =\n null;\n let timerId: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let currentRate = initialRate;\n let pendingSeek: number | null = null;\n\n // Emit initial play event\n emitter.emit(\"statechange\", { state: \"playing\" });\n emitter.emit(\"play\", undefined as never);\n\n // Fire-and-forget dynamic import of the stretcher engine\n import(\"./stretcher/engine.js\").then(({ createStretcherEngine }) => {\n if (disposed) return;\n\n engineInstance = createStretcherEngine(ctx, buffer, {\n tempo: currentRate,\n offset: initialOffset,\n through,\n destination,\n timeupdateInterval,\n });\n\n // Wire stretcher events to playback events\n engineInstance.on(\"buffering\", (data) => {\n if (disposed) return;\n emitter.emit(\"buffering\", data);\n });\n\n engineInstance.on(\"buffered\", (data) => {\n if (disposed) return;\n emitter.emit(\"buffered\", data);\n });\n\n engineInstance.on(\"ended\", () => {\n if (disposed) return;\n state = \"stopped\";\n stopTimer();\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"ended\", undefined as never);\n });\n\n engineInstance.on(\"error\", (data) => {\n if (disposed) return;\n if (data.fatal) {\n state = \"stopped\";\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"ended\", undefined as never);\n }\n });\n\n // Start engine and timeupdate timer\n engineInstance.start();\n startTimer();\n\n // Apply pending seek if any\n if (pendingSeek !== null) {\n engineInstance.seek(pendingSeek);\n pendingSeek = null;\n }\n\n // If we were paused before the engine loaded, pause it\n if (state === \"paused\") {\n engineInstance.pause();\n } else if (state === \"stopped\") {\n engineInstance.stop();\n }\n });\n\n function startTimer() {\n if (timerId !== null) return;\n timerId = setInterval(() => {\n if (state !== \"playing\" || disposed) return;\n emitter.emit(\"timeupdate\", {\n position: getCurrentTime(),\n duration,\n });\n }, timeupdateInterval);\n }\n\n function stopTimer() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n }\n\n function getCurrentTime(): number {\n if (pendingSeek !== null) {\n return pendingSeek;\n }\n if (engineInstance) {\n return engineInstance.getCurrentPosition();\n }\n return initialOffset;\n }\n\n function pause() {\n if (state !== \"playing\" || disposed) return;\n state = \"paused\";\n engineInstance?.pause();\n stopTimer();\n emitter.emit(\"statechange\", { state: \"paused\" });\n emitter.emit(\"pause\", undefined as never);\n }\n\n function resume() {\n if (state !== \"paused\" || disposed) return;\n state = \"playing\";\n engineInstance?.resume();\n startTimer();\n emitter.emit(\"statechange\", { state: \"playing\" });\n emitter.emit(\"resume\", undefined as never);\n }\n\n function togglePlayPause() {\n if (state === \"playing\") pause();\n else if (state === \"paused\") resume();\n }\n\n function seek(position: number) {\n if (disposed) return;\n const clamped = Math.max(0, Math.min(position, duration));\n if (engineInstance) {\n engineInstance.seek(clamped);\n } else {\n pendingSeek = clamped;\n }\n emitter.emit(\"seek\", { position: clamped });\n }\n\n function stop() {\n if (state === \"stopped\" || disposed) return;\n state = \"stopped\";\n engineInstance?.stop();\n stopTimer();\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"stop\", undefined as never);\n }\n\n function setPlaybackRate(rate: number) {\n currentRate = rate;\n if (engineInstance) {\n engineInstance.setTempo(rate);\n }\n }\n\n function setLoop(_value: boolean) {\n // Loop is not supported in stretcher mode\n }\n\n function dispose() {\n if (disposed) return;\n disposed = true;\n stopTimer();\n engineInstance?.dispose();\n emitter.clear();\n }\n\n function _getStretcherSnapshot(): StretcherSnapshotExtension | null {\n if (!engineInstance) return null;\n return engineInstance.getSnapshot();\n }\n\n const playback: Playback & {\n _getStretcherSnapshot: typeof _getStretcherSnapshot;\n } = {\n getState: () => state,\n getCurrentTime,\n getDuration: () => duration,\n getProgress: () => (duration > 0 ? getCurrentTime() / duration : 0),\n pause,\n resume,\n togglePlayPause,\n seek,\n stop,\n setPlaybackRate,\n setLoop,\n on: emitter.on.bind(emitter) as Playback[\"on\"],\n off: emitter.off.bind(emitter) as Playback[\"off\"],\n dispose,\n _getStretcherSnapshot,\n };\n\n return playback;\n}\n"]}