@mcptoolshop/ai_jam_session 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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.es.md +212 -0
  3. package/README.fr.md +212 -0
  4. package/README.hi.md +212 -0
  5. package/README.it.md +212 -0
  6. package/README.ja.md +214 -0
  7. package/README.md +214 -0
  8. package/README.pt-BR.md +212 -0
  9. package/dist/audio-engine.d.ts +9 -0
  10. package/dist/audio-engine.d.ts.map +1 -0
  11. package/dist/audio-engine.js +422 -0
  12. package/dist/audio-engine.js.map +1 -0
  13. package/dist/cli.d.ts +3 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +551 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/index.d.ts +32 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +41 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/mcp-server.d.ts +3 -0
  22. package/dist/mcp-server.d.ts.map +1 -0
  23. package/dist/mcp-server.js +903 -0
  24. package/dist/mcp-server.js.map +1 -0
  25. package/dist/midi/parser.d.ts +16 -0
  26. package/dist/midi/parser.d.ts.map +1 -0
  27. package/dist/midi/parser.js +192 -0
  28. package/dist/midi/parser.js.map +1 -0
  29. package/dist/midi/types.d.ts +44 -0
  30. package/dist/midi/types.d.ts.map +1 -0
  31. package/dist/midi/types.js +8 -0
  32. package/dist/midi/types.js.map +1 -0
  33. package/dist/note-parser.d.ts +105 -0
  34. package/dist/note-parser.d.ts.map +1 -0
  35. package/dist/note-parser.js +278 -0
  36. package/dist/note-parser.js.map +1 -0
  37. package/dist/playback/controls.d.ts +124 -0
  38. package/dist/playback/controls.d.ts.map +1 -0
  39. package/dist/playback/controls.js +252 -0
  40. package/dist/playback/controls.js.map +1 -0
  41. package/dist/playback/midi-engine.d.ts +68 -0
  42. package/dist/playback/midi-engine.d.ts.map +1 -0
  43. package/dist/playback/midi-engine.js +227 -0
  44. package/dist/playback/midi-engine.js.map +1 -0
  45. package/dist/playback/position.d.ts +95 -0
  46. package/dist/playback/position.d.ts.map +1 -0
  47. package/dist/playback/position.js +223 -0
  48. package/dist/playback/position.js.map +1 -0
  49. package/dist/playback/timing.d.ts +31 -0
  50. package/dist/playback/timing.d.ts.map +1 -0
  51. package/dist/playback/timing.js +57 -0
  52. package/dist/playback/timing.js.map +1 -0
  53. package/dist/sample-engine.d.ts +17 -0
  54. package/dist/sample-engine.d.ts.map +1 -0
  55. package/dist/sample-engine.js +428 -0
  56. package/dist/sample-engine.js.map +1 -0
  57. package/dist/schemas.d.ts +40 -0
  58. package/dist/schemas.d.ts.map +1 -0
  59. package/dist/schemas.js +42 -0
  60. package/dist/schemas.js.map +1 -0
  61. package/dist/session.d.ts +106 -0
  62. package/dist/session.d.ts.map +1 -0
  63. package/dist/session.js +361 -0
  64. package/dist/session.js.map +1 -0
  65. package/dist/sfz-parser.d.ts +36 -0
  66. package/dist/sfz-parser.d.ts.map +1 -0
  67. package/dist/sfz-parser.js +95 -0
  68. package/dist/sfz-parser.js.map +1 -0
  69. package/dist/smoke.d.ts +2 -0
  70. package/dist/smoke.d.ts.map +1 -0
  71. package/dist/smoke.js +512 -0
  72. package/dist/smoke.js.map +1 -0
  73. package/dist/songs/config/loader.d.ts +14 -0
  74. package/dist/songs/config/loader.d.ts.map +1 -0
  75. package/dist/songs/config/loader.js +53 -0
  76. package/dist/songs/config/loader.js.map +1 -0
  77. package/dist/songs/config/schema.d.ts +70 -0
  78. package/dist/songs/config/schema.d.ts.map +1 -0
  79. package/dist/songs/config/schema.js +53 -0
  80. package/dist/songs/config/schema.js.map +1 -0
  81. package/dist/songs/index.d.ts +16 -0
  82. package/dist/songs/index.d.ts.map +1 -0
  83. package/dist/songs/index.js +20 -0
  84. package/dist/songs/index.js.map +1 -0
  85. package/dist/songs/jam.d.ts +48 -0
  86. package/dist/songs/jam.d.ts.map +1 -0
  87. package/dist/songs/jam.js +324 -0
  88. package/dist/songs/jam.js.map +1 -0
  89. package/dist/songs/loader.d.ts +27 -0
  90. package/dist/songs/loader.d.ts.map +1 -0
  91. package/dist/songs/loader.js +90 -0
  92. package/dist/songs/loader.js.map +1 -0
  93. package/dist/songs/midi/hands.d.ts +46 -0
  94. package/dist/songs/midi/hands.d.ts.map +1 -0
  95. package/dist/songs/midi/hands.js +134 -0
  96. package/dist/songs/midi/hands.js.map +1 -0
  97. package/dist/songs/midi/ingest.d.ts +8 -0
  98. package/dist/songs/midi/ingest.d.ts.map +1 -0
  99. package/dist/songs/midi/ingest.js +191 -0
  100. package/dist/songs/midi/ingest.js.map +1 -0
  101. package/dist/songs/midi/measures.d.ts +41 -0
  102. package/dist/songs/midi/measures.d.ts.map +1 -0
  103. package/dist/songs/midi/measures.js +64 -0
  104. package/dist/songs/midi/measures.js.map +1 -0
  105. package/dist/songs/midi/types.d.ts +25 -0
  106. package/dist/songs/midi/types.d.ts.map +1 -0
  107. package/dist/songs/midi/types.js +8 -0
  108. package/dist/songs/midi/types.js.map +1 -0
  109. package/dist/songs/registry.d.ts +37 -0
  110. package/dist/songs/registry.d.ts.map +1 -0
  111. package/dist/songs/registry.js +197 -0
  112. package/dist/songs/registry.js.map +1 -0
  113. package/dist/songs/types.d.ts +99 -0
  114. package/dist/songs/types.d.ts.map +1 -0
  115. package/dist/songs/types.js +27 -0
  116. package/dist/songs/types.js.map +1 -0
  117. package/dist/teaching/live-midi-feedback.d.ts +36 -0
  118. package/dist/teaching/live-midi-feedback.d.ts.map +1 -0
  119. package/dist/teaching/live-midi-feedback.js +259 -0
  120. package/dist/teaching/live-midi-feedback.js.map +1 -0
  121. package/dist/teaching/midi-feedback.d.ts +33 -0
  122. package/dist/teaching/midi-feedback.d.ts.map +1 -0
  123. package/dist/teaching/midi-feedback.js +208 -0
  124. package/dist/teaching/midi-feedback.js.map +1 -0
  125. package/dist/teaching/sing-on-midi.d.ts +77 -0
  126. package/dist/teaching/sing-on-midi.d.ts.map +1 -0
  127. package/dist/teaching/sing-on-midi.js +186 -0
  128. package/dist/teaching/sing-on-midi.js.map +1 -0
  129. package/dist/teaching.d.ts +148 -0
  130. package/dist/teaching.d.ts.map +1 -0
  131. package/dist/teaching.js +453 -0
  132. package/dist/teaching.js.map +1 -0
  133. package/dist/test-sound.d.ts +3 -0
  134. package/dist/test-sound.d.ts.map +1 -0
  135. package/dist/test-sound.js +41 -0
  136. package/dist/test-sound.js.map +1 -0
  137. package/dist/types.d.ts +229 -0
  138. package/dist/types.d.ts.map +1 -0
  139. package/dist/types.js +22 -0
  140. package/dist/types.js.map +1 -0
  141. package/dist/vmpk.d.ts +23 -0
  142. package/dist/vmpk.d.ts.map +1 -0
  143. package/dist/vmpk.js +236 -0
  144. package/dist/vmpk.js.map +1 -0
  145. package/logo.png +0 -0
  146. package/package.json +70 -0
  147. package/songs/builtin/a-change-is-gonna-come.json +95 -0
  148. package/songs/builtin/a-thousand-years.json +93 -0
  149. package/songs/builtin/aint-no-sunshine.json +98 -0
  150. package/songs/builtin/all-blues.json +92 -0
  151. package/songs/builtin/autumn-leaves.json +100 -0
  152. package/songs/builtin/baba-oriley.json +91 -0
  153. package/songs/builtin/bach-invention-no1.json +70 -0
  154. package/songs/builtin/bach-prelude-c-major-bwv846.json +1282 -0
  155. package/songs/builtin/basic-12-bar-blues.json +119 -0
  156. package/songs/builtin/beethoven-waldstein-mvt1.json +7766 -0
  157. package/songs/builtin/bennie-and-the-jets.json +92 -0
  158. package/songs/builtin/besame-mucho.json +93 -0
  159. package/songs/builtin/black-orpheus.json +92 -0
  160. package/songs/builtin/blue-bossa.json +94 -0
  161. package/songs/builtin/blues-in-g.json +92 -0
  162. package/songs/builtin/bohemian-rhapsody-intro.json +94 -0
  163. package/songs/builtin/boogie-woogie-basics.json +93 -0
  164. package/songs/builtin/bossa-nova-basic.json +95 -0
  165. package/songs/builtin/chopin-nocturne-op9-no2.json +70 -0
  166. package/songs/builtin/cinema-paradiso.json +94 -0
  167. package/songs/builtin/clair-de-lune.json +11511 -0
  168. package/songs/builtin/clocks.json +91 -0
  169. package/songs/builtin/crystal-stream.json +70 -0
  170. package/songs/builtin/desafinado.json +93 -0
  171. package/songs/builtin/dont-stop-believin.json +91 -0
  172. package/songs/builtin/dream-on.json +100 -0
  173. package/songs/builtin/easy-winners.json +70 -0
  174. package/songs/builtin/el-condor-pasa.json +93 -0
  175. package/songs/builtin/elite-syncopations.json +70 -0
  176. package/songs/builtin/evening-calm.json +70 -0
  177. package/songs/builtin/everyday-blues.json +93 -0
  178. package/songs/builtin/fly-me-to-the-moon.json +91 -0
  179. package/songs/builtin/forrest-gump-suite.json +93 -0
  180. package/songs/builtin/fur-elise.json +20094 -0
  181. package/songs/builtin/georgia-on-my-mind.json +93 -0
  182. package/songs/builtin/girl-from-ipanema.json +92 -0
  183. package/songs/builtin/gladiolus-rag.json +70 -0
  184. package/songs/builtin/great-balls-of-fire.json +92 -0
  185. package/songs/builtin/guantanamera.json +92 -0
  186. package/songs/builtin/hallelujah.json +92 -0
  187. package/songs/builtin/hedwigs-theme.json +93 -0
  188. package/songs/builtin/hotel-california.json +92 -0
  189. package/songs/builtin/imagine.json +92 -0
  190. package/songs/builtin/just-the-two-of-us.json +92 -0
  191. package/songs/builtin/la-bamba.json +92 -0
  192. package/songs/builtin/layla-piano-coda.json +93 -0
  193. package/songs/builtin/lean-on-me.json +91 -0
  194. package/songs/builtin/let-it-be.json +101 -0
  195. package/songs/builtin/lets-stay-together.json +93 -0
  196. package/songs/builtin/magnetic-rag.json +70 -0
  197. package/songs/builtin/maple-leaf-rag.json +99 -0
  198. package/songs/builtin/mia-and-sebastians-theme.json +93 -0
  199. package/songs/builtin/minor-blues-in-a.json +94 -0
  200. package/songs/builtin/misty.json +94 -0
  201. package/songs/builtin/moon-river.json +93 -0
  202. package/songs/builtin/moonlight-sonata-mvt1.json +101 -0
  203. package/songs/builtin/morning-light.json +70 -0
  204. package/songs/builtin/mountain-dawn.json +70 -0
  205. package/songs/builtin/mozart-k545-mvt1.json +2956 -0
  206. package/songs/builtin/my-girl.json +92 -0
  207. package/songs/builtin/night-train.json +92 -0
  208. package/songs/builtin/november-rain.json +93 -0
  209. package/songs/builtin/ocean-waves.json +70 -0
  210. package/songs/builtin/over-the-rainbow.json +93 -0
  211. package/songs/builtin/oye-como-va.json +93 -0
  212. package/songs/builtin/peacherine-rag.json +70 -0
  213. package/songs/builtin/piano-man.json +92 -0
  214. package/songs/builtin/pineapple-rag.json +70 -0
  215. package/songs/builtin/pink-panther.json +94 -0
  216. package/songs/builtin/ragtime-dance.json +70 -0
  217. package/songs/builtin/river-flows-in-you.json +102 -0
  218. package/songs/builtin/rocket-man.json +92 -0
  219. package/songs/builtin/satie-gymnopedie-no1.json +70 -0
  220. package/songs/builtin/satin-doll.json +93 -0
  221. package/songs/builtin/schindlers-list.json +96 -0
  222. package/songs/builtin/schumann-traumerei.json +70 -0
  223. package/songs/builtin/sitting-on-the-dock.json +91 -0
  224. package/songs/builtin/slow-blues-in-bb.json +98 -0
  225. package/songs/builtin/snowfall.json +70 -0
  226. package/songs/builtin/so-what.json +92 -0
  227. package/songs/builtin/solace.json +70 -0
  228. package/songs/builtin/someone-like-you.json +92 -0
  229. package/songs/builtin/spirited-away.json +94 -0
  230. package/songs/builtin/st-louis-blues.json +93 -0
  231. package/songs/builtin/stairway-intro.json +93 -0
  232. package/songs/builtin/starlight-waltz.json +70 -0
  233. package/songs/builtin/stay-with-me.json +93 -0
  234. package/songs/builtin/stormy-monday.json +94 -0
  235. package/songs/builtin/superstition.json +93 -0
  236. package/songs/builtin/sweet-home-chicago.json +93 -0
  237. package/songs/builtin/take-five.json +92 -0
  238. package/songs/builtin/take-the-a-train.json +93 -0
  239. package/songs/builtin/the-entertainer.json +98 -0
  240. package/songs/builtin/the-godfather-waltz.json +93 -0
  241. package/songs/builtin/thrill-is-gone.json +94 -0
  242. package/songs/builtin/twilight-garden.json +70 -0
  243. package/songs/builtin/watermark.json +70 -0
  244. package/songs/builtin/wave.json +93 -0
  245. package/songs/builtin/whats-going-on.json +93 -0
  246. package/songs/builtin/yesterday.json +92 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controls.js","sourceRoot":"","sources":["../../src/playback/controls.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { ParsedMidi } from "../midi/types.js";
2
+ import type { VmpkConnector, ProgressCallback } from "../types.js";
3
+ /** Options for MIDI playback. */
4
+ export interface MidiPlaybackOptions {
5
+ /** Speed multiplier (0.1–4.0). Default: 1.0. */
6
+ speed?: number;
7
+ /** Progress callback. */
8
+ onProgress?: ProgressCallback;
9
+ /** AbortSignal for external cancellation. */
10
+ signal?: AbortSignal;
11
+ }
12
+ /** Playback state. */
13
+ export type MidiPlaybackState = "idle" | "playing" | "paused" | "stopped" | "finished";
14
+ /**
15
+ * MIDI Playback Engine.
16
+ *
17
+ * Takes a ParsedMidi (from the parser) and plays it through a VmpkConnector.
18
+ * Events are scheduled sequentially — sleep between events, fire noteOn/noteOff.
19
+ * Active notes are tracked for clean pause/stop (all-notes-off).
20
+ */
21
+ export declare class MidiPlaybackEngine {
22
+ private readonly connector;
23
+ private readonly midi;
24
+ private _state;
25
+ private _speed;
26
+ private _eventIndex;
27
+ private _playbackTime;
28
+ private _wallStartTime;
29
+ private _pausedAtTime;
30
+ private _activeNotes;
31
+ private _abortController;
32
+ private _resolveWait;
33
+ constructor(connector: VmpkConnector, midi: ParsedMidi);
34
+ /** Current playback state. */
35
+ get state(): MidiPlaybackState;
36
+ /** Current speed multiplier. */
37
+ get speed(): number;
38
+ /** Total duration in seconds (at speed 1.0). */
39
+ get durationSeconds(): number;
40
+ /** Current playback position in seconds (logical, at speed 1.0). */
41
+ get positionSeconds(): number;
42
+ /** Number of events played so far. */
43
+ get eventsPlayed(): number;
44
+ /** Total number of note events. */
45
+ get totalEvents(): number;
46
+ /**
47
+ * Play the MIDI file from the current position.
48
+ * Resolves when playback finishes or is stopped.
49
+ */
50
+ play(options?: MidiPlaybackOptions): Promise<void>;
51
+ /** Pause playback. Can be resumed with play(). */
52
+ pause(): void;
53
+ /** Resume after pause. */
54
+ resume(options?: MidiPlaybackOptions): Promise<void>;
55
+ /** Stop playback and reset to beginning. */
56
+ stop(): void;
57
+ /** Change speed during playback. Takes effect on next event. */
58
+ setSpeed(speed: number): void;
59
+ /** Reset to beginning without stopping state. */
60
+ reset(): void;
61
+ /** Send all-notes-off to silence everything. */
62
+ private silenceAll;
63
+ /** Schedule a noteOff after a delay. */
64
+ private scheduleNoteOff;
65
+ /** Sleep that can be interrupted by AbortSignal. */
66
+ private sleepInterruptible;
67
+ }
68
+ //# sourceMappingURL=midi-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-engine.d.ts","sourceRoot":"","sources":["../../src/playback/midi-engine.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,kBAAkB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAoB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAErF,iCAAiC;AACjC,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,sBAAsB;AACtB,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAY3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAZvB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,YAAY,CAA6B;gBAG9B,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,UAAU;IAGnC,8BAA8B;IAC9B,IAAI,KAAK,IAAI,iBAAiB,CAE7B;IAED,gCAAgC;IAChC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,gDAAgD;IAChD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,oEAAoE;IACpE,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,sCAAsC;IACtC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,mCAAmC;IACnC,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;OAGG;IACG,IAAI,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwF5D,kDAAkD;IAClD,KAAK,IAAI,IAAI;IAQb,0BAA0B;IACpB,MAAM,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9D,4CAA4C;IAC5C,IAAI,IAAI,IAAI;IAQZ,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAO7B,iDAAiD;IACjD,KAAK,IAAI,IAAI;IASb,gDAAgD;IAChD,OAAO,CAAC,UAAU;IASlB,wCAAwC;IACxC,OAAO,CAAC,eAAe;IAWvB,oDAAoD;IACpD,OAAO,CAAC,kBAAkB;CA8B3B"}
@@ -0,0 +1,227 @@
1
+ // ─── MIDI Playback Engine ────────────────────────────────────────────────────
2
+ //
3
+ // Plays parsed MIDI file events through any VmpkConnector (audio engine or MIDI).
4
+ // Deterministic scheduling: walks the sorted event list, sleeps between events,
5
+ // fires noteOn/noteOff at the right times. Supports speed control, pause/resume,
6
+ // and abort via AbortSignal.
7
+ //
8
+ // This is the engine that makes `pianoai play song.mid` actually work.
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ /**
11
+ * MIDI Playback Engine.
12
+ *
13
+ * Takes a ParsedMidi (from the parser) and plays it through a VmpkConnector.
14
+ * Events are scheduled sequentially — sleep between events, fire noteOn/noteOff.
15
+ * Active notes are tracked for clean pause/stop (all-notes-off).
16
+ */
17
+ export class MidiPlaybackEngine {
18
+ connector;
19
+ midi;
20
+ _state = "idle";
21
+ _speed = 1.0;
22
+ _eventIndex = 0;
23
+ _playbackTime = 0; // seconds into the piece (logical time)
24
+ _wallStartTime = 0;
25
+ _pausedAtTime = 0;
26
+ _activeNotes = new Set(); // "channel-note" keys
27
+ _abortController = null;
28
+ _resolveWait = null;
29
+ constructor(connector, midi) {
30
+ this.connector = connector;
31
+ this.midi = midi;
32
+ }
33
+ /** Current playback state. */
34
+ get state() {
35
+ return this._state;
36
+ }
37
+ /** Current speed multiplier. */
38
+ get speed() {
39
+ return this._speed;
40
+ }
41
+ /** Total duration in seconds (at speed 1.0). */
42
+ get durationSeconds() {
43
+ return this.midi.durationSeconds;
44
+ }
45
+ /** Current playback position in seconds (logical, at speed 1.0). */
46
+ get positionSeconds() {
47
+ return this._playbackTime;
48
+ }
49
+ /** Number of events played so far. */
50
+ get eventsPlayed() {
51
+ return this._eventIndex;
52
+ }
53
+ /** Total number of note events. */
54
+ get totalEvents() {
55
+ return this.midi.events.length;
56
+ }
57
+ /**
58
+ * Play the MIDI file from the current position.
59
+ * Resolves when playback finishes or is stopped.
60
+ */
61
+ async play(options = {}) {
62
+ if (this._state === "playing")
63
+ return;
64
+ this._speed = options.speed ?? this._speed;
65
+ this._state = "playing";
66
+ this._abortController = new AbortController();
67
+ this._wallStartTime = Date.now();
68
+ const signal = options.signal;
69
+ const onProgress = options.onProgress;
70
+ const internalSignal = this._abortController.signal;
71
+ // Build a schedule: for each event, calculate when noteOn and noteOff fire
72
+ const events = this.midi.events;
73
+ const totalDuration = this.midi.durationSeconds;
74
+ try {
75
+ while (this._eventIndex < events.length) {
76
+ if (internalSignal.aborted || signal?.aborted) {
77
+ this._state = "stopped";
78
+ this.silenceAll();
79
+ return;
80
+ }
81
+ const event = events[this._eventIndex];
82
+ const targetTime = event.time; // seconds at speed 1.0
83
+ // Sleep until this event's time
84
+ if (targetTime > this._playbackTime) {
85
+ const waitSeconds = (targetTime - this._playbackTime) / this._speed;
86
+ await this.sleepInterruptible(waitSeconds * 1000, internalSignal);
87
+ if (internalSignal.aborted || signal?.aborted) {
88
+ this._state = "stopped";
89
+ this.silenceAll();
90
+ return;
91
+ }
92
+ }
93
+ this._playbackTime = targetTime;
94
+ // Fire noteOn
95
+ const key = `${event.channel}-${event.note}`;
96
+ this.connector.noteOn(event.note, event.velocity, event.channel);
97
+ this._activeNotes.add(key);
98
+ // Schedule noteOff after duration
99
+ const offDelay = (event.duration / this._speed) * 1000;
100
+ this.scheduleNoteOff(event, offDelay, key);
101
+ this._eventIndex++;
102
+ // Progress
103
+ if (onProgress && totalDuration > 0) {
104
+ const ratio = this._playbackTime / totalDuration;
105
+ onProgress({
106
+ currentMeasure: this._eventIndex,
107
+ totalMeasures: events.length,
108
+ ratio: Math.min(1, ratio),
109
+ percent: `${Math.round(ratio * 100)}%`,
110
+ elapsedMs: Date.now() - this._wallStartTime,
111
+ });
112
+ }
113
+ }
114
+ // Wait for the last notes to finish
115
+ if (events.length > 0) {
116
+ const lastEvent = events[events.length - 1];
117
+ const remainingMs = (lastEvent.duration / this._speed) * 1000;
118
+ if (remainingMs > 0) {
119
+ await this.sleepInterruptible(remainingMs, internalSignal);
120
+ }
121
+ }
122
+ if (!internalSignal.aborted && !signal?.aborted) {
123
+ this._state = "finished";
124
+ }
125
+ }
126
+ catch (err) {
127
+ if (internalSignal.aborted || signal?.aborted) {
128
+ this._state = "stopped";
129
+ }
130
+ else {
131
+ throw err;
132
+ }
133
+ }
134
+ finally {
135
+ this.silenceAll();
136
+ }
137
+ }
138
+ /** Pause playback. Can be resumed with play(). */
139
+ pause() {
140
+ if (this._state !== "playing")
141
+ return;
142
+ this._state = "paused";
143
+ this._pausedAtTime = this._playbackTime;
144
+ this._abortController?.abort();
145
+ this.silenceAll();
146
+ }
147
+ /** Resume after pause. */
148
+ async resume(options = {}) {
149
+ if (this._state !== "paused")
150
+ return;
151
+ this._playbackTime = this._pausedAtTime;
152
+ await this.play(options);
153
+ }
154
+ /** Stop playback and reset to beginning. */
155
+ stop() {
156
+ this._state = "stopped";
157
+ this._abortController?.abort();
158
+ this.silenceAll();
159
+ this._eventIndex = 0;
160
+ this._playbackTime = 0;
161
+ }
162
+ /** Change speed during playback. Takes effect on next event. */
163
+ setSpeed(speed) {
164
+ if (speed <= 0 || speed > 4) {
165
+ throw new Error(`Speed must be between 0 (exclusive) and 4: got ${speed}`);
166
+ }
167
+ this._speed = speed;
168
+ }
169
+ /** Reset to beginning without stopping state. */
170
+ reset() {
171
+ this._eventIndex = 0;
172
+ this._playbackTime = 0;
173
+ this._state = "idle";
174
+ this._activeNotes.clear();
175
+ }
176
+ // ─── Internal ─────────────────────────────────────────────────────────────
177
+ /** Send all-notes-off to silence everything. */
178
+ silenceAll() {
179
+ try {
180
+ this.connector.allNotesOff();
181
+ }
182
+ catch {
183
+ /* connector may already be disconnected */
184
+ }
185
+ this._activeNotes.clear();
186
+ }
187
+ /** Schedule a noteOff after a delay. */
188
+ scheduleNoteOff(event, delayMs, key) {
189
+ setTimeout(() => {
190
+ try {
191
+ this.connector.noteOff(event.note, event.channel);
192
+ }
193
+ catch {
194
+ /* ok */
195
+ }
196
+ this._activeNotes.delete(key);
197
+ }, delayMs);
198
+ }
199
+ /** Sleep that can be interrupted by AbortSignal. */
200
+ sleepInterruptible(ms, signal) {
201
+ if (ms <= 0)
202
+ return Promise.resolve();
203
+ return new Promise((resolve) => {
204
+ if (signal.aborted) {
205
+ resolve();
206
+ return;
207
+ }
208
+ const timer = setTimeout(() => {
209
+ signal.removeEventListener("abort", onAbort);
210
+ this._resolveWait = null;
211
+ resolve();
212
+ }, ms);
213
+ const onAbort = () => {
214
+ clearTimeout(timer);
215
+ this._resolveWait = null;
216
+ resolve();
217
+ };
218
+ this._resolveWait = () => {
219
+ clearTimeout(timer);
220
+ signal.removeEventListener("abort", onAbort);
221
+ resolve();
222
+ };
223
+ signal.addEventListener("abort", onAbort, { once: true });
224
+ });
225
+ }
226
+ }
227
+ //# sourceMappingURL=midi-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi-engine.js","sourceRoot":"","sources":["../../src/playback/midi-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import type { ParsedMidi, MidiNoteEvent } from "../midi/types.js";
2
+ /** Snapshot of playback position at a given moment. */
3
+ export interface PositionSnapshot {
4
+ /** Absolute time in seconds (at speed 1.0). */
5
+ timeSec: number;
6
+ /** Current beat number (0-based, fractional). */
7
+ beat: number;
8
+ /** Estimated measure/bar number (1-based). */
9
+ measure: number;
10
+ /** Beat within the current measure (1-based, fractional). */
11
+ beatInMeasure: number;
12
+ /** Current tempo in BPM. */
13
+ bpm: number;
14
+ /** Event index of the nearest event at or before this position. */
15
+ nearestEventIndex: number;
16
+ /** Ratio through the total duration (0–1). */
17
+ ratio: number;
18
+ }
19
+ /** Position change callback. */
20
+ export type PositionCallback = (snapshot: PositionSnapshot) => void;
21
+ /**
22
+ * Playback position tracker for MIDI files.
23
+ *
24
+ * Given a ParsedMidi, provides:
25
+ * - `snapshotAt(timeSec)` — position info at any time
26
+ * - `eventIndexForTime(timeSec)` — find the event index at a given time
27
+ * - `timeForMeasure(measure)` — seek target for a given measure
28
+ * - `seekEventIndex(timeSec)` — find the event index to resume from
29
+ * - `measures` — estimated total measure count
30
+ *
31
+ * Assumes 4/4 time signature (most MIDI files). Tempo changes are respected.
32
+ */
33
+ export declare class PositionTracker {
34
+ readonly midi: ParsedMidi;
35
+ /** Beats per measure (default 4 for 4/4). */
36
+ readonly beatsPerMeasure: number;
37
+ /** Pre-computed beat positions for each tempo segment. */
38
+ private tempoSegments;
39
+ /** Total duration in beats. */
40
+ readonly totalBeats: number;
41
+ /** Estimated total measure count. */
42
+ readonly totalMeasures: number;
43
+ constructor(midi: ParsedMidi, beatsPerMeasure?: number);
44
+ /**
45
+ * Get a position snapshot at a given time (seconds, at speed 1.0).
46
+ */
47
+ snapshotAt(timeSec: number): PositionSnapshot;
48
+ /**
49
+ * Find the event index of the first event at or after the given time.
50
+ * Returns events.length if past the end.
51
+ */
52
+ eventIndexForTime(timeSec: number): number;
53
+ /**
54
+ * Get the time (seconds) at the start of a given measure (1-based).
55
+ * Useful for seek operations.
56
+ */
57
+ timeForMeasure(measure: number): number;
58
+ /**
59
+ * Get the event index to resume playback from after seeking to a time.
60
+ * Same as eventIndexForTime but named clearly for seek use.
61
+ */
62
+ seekEventIndex(timeSec: number): number;
63
+ /**
64
+ * Get the BPM at a given time.
65
+ */
66
+ bpmAt(timeSec: number): number;
67
+ /**
68
+ * Get all events within a measure (1-based).
69
+ */
70
+ eventsInMeasure(measure: number): MidiNoteEvent[];
71
+ /**
72
+ * Get a summary of what's happening at a given measure for teaching purposes.
73
+ */
74
+ measureSummary(measure: number): {
75
+ measure: number;
76
+ events: MidiNoteEvent[];
77
+ noteCount: number;
78
+ avgVelocity: number;
79
+ minNote: number;
80
+ maxNote: number;
81
+ hasChord: boolean;
82
+ bpm: number;
83
+ };
84
+ /** Build tempo segments from the MIDI's tempo changes. */
85
+ private buildTempoSegments;
86
+ /** Convert absolute time (seconds) to beat number. */
87
+ private timeToBeat;
88
+ /** Convert beat number to absolute time (seconds). */
89
+ private beatToTime;
90
+ }
91
+ /**
92
+ * Create a PositionTracker for a parsed MIDI file.
93
+ */
94
+ export declare function createPositionTracker(midi: ParsedMidi, beatsPerMeasure?: number): PositionTracker;
95
+ //# sourceMappingURL=position.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../src/playback/position.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAI9E,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gCAAgC;AAChC,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAIpE;;;;;;;;;;;GAWG;AACH,qBAAa,eAAe;aAmBR,IAAI,EAAE,UAAU;IAlBlC,6CAA6C;IAC7C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,0DAA0D;IAC1D,OAAO,CAAC,aAAa,CAKlB;IAEH,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,qCAAqC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBAGb,IAAI,EAAE,UAAU,EAChC,eAAe,GAAE,MAAU;IAQ7B;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB;IAsB7C;;;OAGG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAgB1C;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAKvC;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIvC;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAY9B;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE;IAMjD;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;QAC/B,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,aAAa,EAAE,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,OAAO,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;KACb;IAyCD,0DAA0D;IAC1D,OAAO,CAAC,kBAAkB;IA0C1B,sDAAsD;IACtD,OAAO,CAAC,UAAU;IAelB,sDAAsD;IACtD,OAAO,CAAC,UAAU;CAcnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,eAAe,GAAE,MAAU,GAC1B,eAAe,CAEjB"}
@@ -0,0 +1,223 @@
1
+ // ─── Playback Position Tracker ──────────────────────────────────────────────
2
+ //
3
+ // Tracks position within a MIDI file during playback: current time, beat,
4
+ // estimated measure number, tempo, and provides seek-to-time support.
5
+ //
6
+ // MIDI files don't inherently have "measures" — we estimate them from the
7
+ // time signature (assumed 4/4) and tempo. This gives a good-enough bar
8
+ // number for display and teaching hook triggers.
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ // ─── Position Tracker ───────────────────────────────────────────────────────
11
+ /**
12
+ * Playback position tracker for MIDI files.
13
+ *
14
+ * Given a ParsedMidi, provides:
15
+ * - `snapshotAt(timeSec)` — position info at any time
16
+ * - `eventIndexForTime(timeSec)` — find the event index at a given time
17
+ * - `timeForMeasure(measure)` — seek target for a given measure
18
+ * - `seekEventIndex(timeSec)` — find the event index to resume from
19
+ * - `measures` — estimated total measure count
20
+ *
21
+ * Assumes 4/4 time signature (most MIDI files). Tempo changes are respected.
22
+ */
23
+ export class PositionTracker {
24
+ midi;
25
+ /** Beats per measure (default 4 for 4/4). */
26
+ beatsPerMeasure;
27
+ /** Pre-computed beat positions for each tempo segment. */
28
+ tempoSegments;
29
+ /** Total duration in beats. */
30
+ totalBeats;
31
+ /** Estimated total measure count. */
32
+ totalMeasures;
33
+ constructor(midi, beatsPerMeasure = 4) {
34
+ this.midi = midi;
35
+ this.beatsPerMeasure = beatsPerMeasure;
36
+ this.tempoSegments = this.buildTempoSegments();
37
+ this.totalBeats = this.timeToBeat(midi.durationSeconds);
38
+ this.totalMeasures = Math.ceil(this.totalBeats / beatsPerMeasure);
39
+ }
40
+ /**
41
+ * Get a position snapshot at a given time (seconds, at speed 1.0).
42
+ */
43
+ snapshotAt(timeSec) {
44
+ const clampedTime = Math.max(0, Math.min(timeSec, this.midi.durationSeconds));
45
+ const beat = this.timeToBeat(clampedTime);
46
+ const measure = Math.floor(beat / this.beatsPerMeasure) + 1;
47
+ const beatInMeasure = (beat % this.beatsPerMeasure) + 1;
48
+ const bpm = this.bpmAt(clampedTime);
49
+ const nearestEventIndex = this.eventIndexForTime(clampedTime);
50
+ const ratio = this.midi.durationSeconds > 0
51
+ ? clampedTime / this.midi.durationSeconds
52
+ : 0;
53
+ return {
54
+ timeSec: clampedTime,
55
+ beat,
56
+ measure,
57
+ beatInMeasure,
58
+ bpm,
59
+ nearestEventIndex,
60
+ ratio,
61
+ };
62
+ }
63
+ /**
64
+ * Find the event index of the first event at or after the given time.
65
+ * Returns events.length if past the end.
66
+ */
67
+ eventIndexForTime(timeSec) {
68
+ const events = this.midi.events;
69
+ // Binary search for efficiency
70
+ let lo = 0;
71
+ let hi = events.length;
72
+ while (lo < hi) {
73
+ const mid = (lo + hi) >>> 1;
74
+ if (events[mid].time < timeSec) {
75
+ lo = mid + 1;
76
+ }
77
+ else {
78
+ hi = mid;
79
+ }
80
+ }
81
+ return lo;
82
+ }
83
+ /**
84
+ * Get the time (seconds) at the start of a given measure (1-based).
85
+ * Useful for seek operations.
86
+ */
87
+ timeForMeasure(measure) {
88
+ const targetBeat = (measure - 1) * this.beatsPerMeasure;
89
+ return this.beatToTime(targetBeat);
90
+ }
91
+ /**
92
+ * Get the event index to resume playback from after seeking to a time.
93
+ * Same as eventIndexForTime but named clearly for seek use.
94
+ */
95
+ seekEventIndex(timeSec) {
96
+ return this.eventIndexForTime(timeSec);
97
+ }
98
+ /**
99
+ * Get the BPM at a given time.
100
+ */
101
+ bpmAt(timeSec) {
102
+ let bpm = this.midi.bpm;
103
+ for (const seg of this.tempoSegments) {
104
+ if (seg.startTimeSec <= timeSec) {
105
+ bpm = seg.bpm;
106
+ }
107
+ else {
108
+ break;
109
+ }
110
+ }
111
+ return bpm;
112
+ }
113
+ /**
114
+ * Get all events within a measure (1-based).
115
+ */
116
+ eventsInMeasure(measure) {
117
+ const startTime = this.timeForMeasure(measure);
118
+ const endTime = this.timeForMeasure(measure + 1);
119
+ return this.midi.events.filter((e) => e.time >= startTime && e.time < endTime);
120
+ }
121
+ /**
122
+ * Get a summary of what's happening at a given measure for teaching purposes.
123
+ */
124
+ measureSummary(measure) {
125
+ const events = this.eventsInMeasure(measure);
126
+ if (events.length === 0) {
127
+ return {
128
+ measure,
129
+ events,
130
+ noteCount: 0,
131
+ avgVelocity: 0,
132
+ minNote: 0,
133
+ maxNote: 0,
134
+ hasChord: false,
135
+ bpm: this.bpmAt(this.timeForMeasure(measure)),
136
+ };
137
+ }
138
+ const velocities = events.map((e) => e.velocity);
139
+ const notes = events.map((e) => e.note);
140
+ // Detect chords: multiple notes within 10ms of each other
141
+ let hasChord = false;
142
+ for (let i = 1; i < events.length; i++) {
143
+ if (events[i].time - events[i - 1].time < 0.01) {
144
+ hasChord = true;
145
+ break;
146
+ }
147
+ }
148
+ return {
149
+ measure,
150
+ events,
151
+ noteCount: events.length,
152
+ avgVelocity: Math.round(velocities.reduce((a, b) => a + b, 0) / velocities.length),
153
+ minNote: Math.min(...notes),
154
+ maxNote: Math.max(...notes),
155
+ hasChord,
156
+ bpm: this.bpmAt(events[0].time),
157
+ };
158
+ }
159
+ // ─── Internal ─────────────────────────────────────────────────────────────
160
+ /** Build tempo segments from the MIDI's tempo changes. */
161
+ buildTempoSegments() {
162
+ const changes = this.midi.tempoChanges;
163
+ if (changes.length === 0) {
164
+ const spb = 60 / this.midi.bpm;
165
+ return [{ startTimeSec: 0, startBeat: 0, bpm: this.midi.bpm, secondsPerBeat: spb }];
166
+ }
167
+ const segments = [];
168
+ let accumulatedBeats = 0;
169
+ for (let i = 0; i < changes.length; i++) {
170
+ const change = changes[i];
171
+ const spb = 60 / change.bpm;
172
+ if (i > 0) {
173
+ const prevSeg = segments[i - 1];
174
+ const timeDelta = change.time - prevSeg.startTimeSec;
175
+ accumulatedBeats = prevSeg.startBeat + timeDelta / prevSeg.secondsPerBeat;
176
+ }
177
+ segments.push({
178
+ startTimeSec: change.time,
179
+ startBeat: accumulatedBeats,
180
+ bpm: change.bpm,
181
+ secondsPerBeat: spb,
182
+ });
183
+ }
184
+ return segments;
185
+ }
186
+ /** Convert absolute time (seconds) to beat number. */
187
+ timeToBeat(timeSec) {
188
+ // Find the active tempo segment
189
+ let seg = this.tempoSegments[0];
190
+ for (const s of this.tempoSegments) {
191
+ if (s.startTimeSec <= timeSec) {
192
+ seg = s;
193
+ }
194
+ else {
195
+ break;
196
+ }
197
+ }
198
+ const timeSinceSegStart = timeSec - seg.startTimeSec;
199
+ return seg.startBeat + timeSinceSegStart / seg.secondsPerBeat;
200
+ }
201
+ /** Convert beat number to absolute time (seconds). */
202
+ beatToTime(beat) {
203
+ // Find the active tempo segment for this beat
204
+ let seg = this.tempoSegments[0];
205
+ for (const s of this.tempoSegments) {
206
+ if (s.startBeat <= beat) {
207
+ seg = s;
208
+ }
209
+ else {
210
+ break;
211
+ }
212
+ }
213
+ const beatsSinceSegStart = beat - seg.startBeat;
214
+ return seg.startTimeSec + beatsSinceSegStart * seg.secondsPerBeat;
215
+ }
216
+ }
217
+ /**
218
+ * Create a PositionTracker for a parsed MIDI file.
219
+ */
220
+ export function createPositionTracker(midi, beatsPerMeasure = 4) {
221
+ return new PositionTracker(midi, beatsPerMeasure);
222
+ }
223
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","sourceRoot":"","sources":["../../src/playback/position.ts"],"names":[],"mappings":"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"}