@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,90 @@
1
+ // ─── Song Loader ────────────────────────────────────────────────────────────
2
+ //
3
+ // Loads SongEntry objects from JSON files at runtime.
4
+ // Two directories: builtin (ships with package) + user (~/.pianoai/songs/).
5
+ // No compilation needed to add songs — just drop a .json file.
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ import { readdirSync, readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
8
+ import { join, basename } from "node:path";
9
+ import { validateSong, registerSong, clearRegistry } from "./registry.js";
10
+ /**
11
+ * Load all .json song files from a directory.
12
+ * Validates each file and returns valid SongEntry objects.
13
+ * Skips (with warning) any files that fail validation.
14
+ */
15
+ export function loadSongsFromDir(dir) {
16
+ if (!existsSync(dir))
17
+ return [];
18
+ const files = readdirSync(dir).filter(f => f.endsWith(".json"));
19
+ const songs = [];
20
+ for (const file of files) {
21
+ try {
22
+ const song = loadSongFile(join(dir, file));
23
+ songs.push(song);
24
+ }
25
+ catch (err) {
26
+ console.error(` SKIP ${file}: ${err instanceof Error ? err.message : String(err)}`);
27
+ }
28
+ }
29
+ return songs;
30
+ }
31
+ /**
32
+ * Load a single song from a JSON file.
33
+ * Validates the parsed object and returns a typed SongEntry.
34
+ */
35
+ export function loadSongFile(filePath) {
36
+ const raw = JSON.parse(readFileSync(filePath, "utf8"));
37
+ const errors = validateSong(raw);
38
+ if (errors.length > 0) {
39
+ throw new Error(`Invalid song in ${basename(filePath)}:\n - ${errors.join("\n - ")}`);
40
+ }
41
+ return raw;
42
+ }
43
+ /**
44
+ * Save a SongEntry as a JSON file in the given directory.
45
+ * Creates the directory if it doesn't exist.
46
+ * Returns the full path to the saved file.
47
+ */
48
+ export function saveSong(song, dir) {
49
+ if (!existsSync(dir)) {
50
+ mkdirSync(dir, { recursive: true });
51
+ }
52
+ const filePath = join(dir, `${song.id}.json`);
53
+ writeFileSync(filePath, JSON.stringify(song, null, 2) + "\n", "utf8");
54
+ return filePath;
55
+ }
56
+ /**
57
+ * Initialize the song registry from builtin and optional user directories.
58
+ * Clears any existing registry entries first.
59
+ *
60
+ * @param builtinDir - Path to the builtin songs directory (ships with package)
61
+ * @param userDir - Optional path to user songs directory (default: ~/.pianoai/songs/)
62
+ */
63
+ export function initializeRegistry(builtinDir, userDir) {
64
+ clearRegistry();
65
+ // Load builtin songs
66
+ const builtinSongs = loadSongsFromDir(builtinDir);
67
+ for (const song of builtinSongs) {
68
+ try {
69
+ registerSong(song);
70
+ }
71
+ catch (err) {
72
+ console.error(` SKIP builtin ${song.id}: ${err instanceof Error ? err.message : String(err)}`);
73
+ }
74
+ }
75
+ // Load user songs (if directory exists)
76
+ if (userDir) {
77
+ const userSongs = loadSongsFromDir(userDir);
78
+ for (const song of userSongs) {
79
+ try {
80
+ registerSong(song);
81
+ }
82
+ catch (err) {
83
+ console.error(` SKIP user ${song.id}: ${err instanceof Error ? err.message : String(err)}`);
84
+ }
85
+ }
86
+ }
87
+ const total = builtinSongs.length + (userDir ? loadSongsFromDir(userDir).length : 0);
88
+ console.error(`Song registry initialized: ${builtinSongs.length} builtin songs loaded`);
89
+ }
90
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/songs/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { ResolvedNote } from "./types.js";
2
+ export declare const DEFAULT_SPLIT_POINT = 60;
3
+ export declare const DEFAULT_CHORD_TOLERANCE = 10;
4
+ /** Result of splitting notes into two hands. */
5
+ export interface HandSplit {
6
+ rightHand: ResolvedNote[];
7
+ leftHand: ResolvedNote[];
8
+ }
9
+ /**
10
+ * Separate notes into right hand and left hand based on split point.
11
+ */
12
+ export declare function separateHands(notes: ResolvedNote[], splitPoint?: number): HandSplit;
13
+ /**
14
+ * Group simultaneous notes (within a tick tolerance window) into chords.
15
+ */
16
+ export declare function groupIntoChords(notes: ResolvedNote[], tolerance?: number): ResolvedNote[][];
17
+ /**
18
+ * Detect if a group of notes forms a chord (2+ simultaneous notes).
19
+ */
20
+ export declare function isChord(group: ResolvedNote[]): boolean;
21
+ /**
22
+ * Convert a MIDI note number to scientific pitch notation.
23
+ * 60 → "C4", 69 → "A4", 48 → "C3"
24
+ */
25
+ export declare function midiNoteToScientific(noteNumber: number): string;
26
+ /**
27
+ * Convert ticks duration to a note duration suffix.
28
+ */
29
+ export declare function ticksToDuration(ticks: number, ticksPerBeat: number): string;
30
+ /**
31
+ * Format a single note as scientific notation with duration suffix.
32
+ */
33
+ export declare function formatNote(note: ResolvedNote, ticksPerBeat: number): string;
34
+ /**
35
+ * Format a chord group to the string representation.
36
+ * Single note: "C4:q"
37
+ * Chord: "C4 E4 G4:q" (sorted low to high, duration from longest note)
38
+ */
39
+ export declare function chordToString(chord: ResolvedNote[], ticksPerBeat: number): string;
40
+ /**
41
+ * Format a hand's notes into a single notation string.
42
+ * Groups into chords, formats each, joins with spaces.
43
+ * Returns "R:w" (whole rest) if the hand has no notes.
44
+ */
45
+ export declare function formatHand(notes: ResolvedNote[], ticksPerBeat: number, chordTolerance?: number): string;
46
+ //# sourceMappingURL=hands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hands.d.ts","sourceRoot":"","sources":["../../../src/songs/midi/hands.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAM1C,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,YAAY,EAAE,EACrB,UAAU,GAAE,MAA4B,GACvC,SAAS,CAaX;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EAAE,EACrB,SAAS,GAAE,MAAgC,GAC1C,YAAY,EAAE,EAAE,CAgBlB;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,OAAO,CAEtD;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAoB3E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAUjF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EAAE,EACrB,YAAY,EAAE,MAAM,EACpB,cAAc,GAAE,MAAgC,GAC/C,MAAM,CAIR"}
@@ -0,0 +1,134 @@
1
+ // ─── Hand Separation & Chord Detection ───────────────────────────────────────
2
+ //
3
+ // Splits notes into right hand / left hand by a configurable split point,
4
+ // groups simultaneous notes into chords, and formats them as notation strings.
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // ─── Constants ───────────────────────────────────────────────────────────────
7
+ export const DEFAULT_SPLIT_POINT = 60; // Middle C (C4)
8
+ export const DEFAULT_CHORD_TOLERANCE = 10; // ticks
9
+ const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
10
+ /**
11
+ * Separate notes into right hand and left hand based on split point.
12
+ */
13
+ export function separateHands(notes, splitPoint = DEFAULT_SPLIT_POINT) {
14
+ const rightHand = [];
15
+ const leftHand = [];
16
+ for (const note of notes) {
17
+ if (note.noteNumber >= splitPoint) {
18
+ rightHand.push(note);
19
+ }
20
+ else {
21
+ leftHand.push(note);
22
+ }
23
+ }
24
+ return { rightHand, leftHand };
25
+ }
26
+ /**
27
+ * Group simultaneous notes (within a tick tolerance window) into chords.
28
+ */
29
+ export function groupIntoChords(notes, tolerance = DEFAULT_CHORD_TOLERANCE) {
30
+ if (notes.length === 0)
31
+ return [];
32
+ const groups = [];
33
+ let current = [notes[0]];
34
+ for (let i = 1; i < notes.length; i++) {
35
+ if (notes[i].startTick - current[0].startTick <= tolerance) {
36
+ current.push(notes[i]);
37
+ }
38
+ else {
39
+ groups.push(current);
40
+ current = [notes[i]];
41
+ }
42
+ }
43
+ groups.push(current);
44
+ return groups;
45
+ }
46
+ /**
47
+ * Detect if a group of notes forms a chord (2+ simultaneous notes).
48
+ */
49
+ export function isChord(group) {
50
+ return group.length > 1;
51
+ }
52
+ // ─── Note Formatting ─────────────────────────────────────────────────────────
53
+ /**
54
+ * Convert a MIDI note number to scientific pitch notation.
55
+ * 60 → "C4", 69 → "A4", 48 → "C3"
56
+ */
57
+ export function midiNoteToScientific(noteNumber) {
58
+ const octave = Math.floor(noteNumber / 12) - 1;
59
+ const name = NOTE_NAMES[noteNumber % 12];
60
+ return `${name}${octave}`;
61
+ }
62
+ /**
63
+ * Convert ticks duration to a note duration suffix.
64
+ */
65
+ export function ticksToDuration(ticks, ticksPerBeat) {
66
+ const ratio = ticks / ticksPerBeat;
67
+ if (Math.abs(ratio - 4) < 0.15)
68
+ return "w";
69
+ if (Math.abs(ratio - 3) < 0.15)
70
+ return "h.";
71
+ if (Math.abs(ratio - 2) < 0.15)
72
+ return "h";
73
+ if (Math.abs(ratio - 1.5) < 0.15)
74
+ return "q.";
75
+ if (Math.abs(ratio - 4 / 3) < 0.1)
76
+ return "ht";
77
+ if (Math.abs(ratio - 1) < 0.15)
78
+ return "q";
79
+ if (Math.abs(ratio - 0.75) < 0.05)
80
+ return "e.";
81
+ if (Math.abs(ratio - 2 / 3) < 0.08)
82
+ return "qt";
83
+ if (Math.abs(ratio - 0.5) < 0.1)
84
+ return "e";
85
+ if (Math.abs(ratio - 1 / 3) < 0.06)
86
+ return "et";
87
+ if (Math.abs(ratio - 0.25) < 0.06)
88
+ return "s";
89
+ if (ratio >= 3)
90
+ return "w";
91
+ if (ratio >= 1.5)
92
+ return "h";
93
+ if (ratio >= 0.75)
94
+ return "q";
95
+ if (ratio >= 0.375)
96
+ return "e";
97
+ return "s";
98
+ }
99
+ /**
100
+ * Format a single note as scientific notation with duration suffix.
101
+ */
102
+ export function formatNote(note, ticksPerBeat) {
103
+ const name = midiNoteToScientific(note.noteNumber);
104
+ const dur = ticksToDuration(note.durationTicks, ticksPerBeat);
105
+ return `${name}:${dur}`;
106
+ }
107
+ /**
108
+ * Format a chord group to the string representation.
109
+ * Single note: "C4:q"
110
+ * Chord: "C4 E4 G4:q" (sorted low to high, duration from longest note)
111
+ */
112
+ export function chordToString(chord, ticksPerBeat) {
113
+ if (chord.length === 1)
114
+ return formatNote(chord[0], ticksPerBeat);
115
+ const maxDur = Math.max(...chord.map(n => n.durationTicks));
116
+ const dur = ticksToDuration(maxDur, ticksPerBeat);
117
+ const noteNames = [...chord]
118
+ .sort((a, b) => a.noteNumber - b.noteNumber)
119
+ .map(n => midiNoteToScientific(n.noteNumber))
120
+ .join(" ");
121
+ return `${noteNames}:${dur}`;
122
+ }
123
+ /**
124
+ * Format a hand's notes into a single notation string.
125
+ * Groups into chords, formats each, joins with spaces.
126
+ * Returns "R:w" (whole rest) if the hand has no notes.
127
+ */
128
+ export function formatHand(notes, ticksPerBeat, chordTolerance = DEFAULT_CHORD_TOLERANCE) {
129
+ if (notes.length === 0)
130
+ return "R:w";
131
+ const chords = groupIntoChords(notes, chordTolerance);
132
+ return chords.map(c => chordToString(c, ticksPerBeat)).join(" ");
133
+ }
134
+ //# sourceMappingURL=hands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hands.js","sourceRoot":"","sources":["../../../src/songs/midi/hands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { SongEntry } from "../types.js";
2
+ import type { SongConfig } from "../config/schema.js";
3
+ /**
4
+ * Convert a MIDI buffer + human-authored config into a complete SongEntry.
5
+ */
6
+ export declare function midiToSongEntry(midiBuffer: Uint8Array, config: SongConfig): SongEntry;
7
+ export { midiNoteToScientific } from "./hands.js";
8
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../src/songs/midi/ingest.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAqBtD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,UAAU,GACjB,SAAS,CAkDX;AAGD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,191 @@
1
+ // ─── MIDI → SongEntry Converter ──────────────────────────────────────────────
2
+ //
3
+ // Parses a standard MIDI file and merges it with a human-authored SongConfig
4
+ // to produce a complete SongEntry ready for the registry.
5
+ //
6
+ // The MIDI provides: notes, timing, duration, structure.
7
+ // The config provides: metadata, musical language, teaching notes, fingering.
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ import { parseMidi } from "midi-file";
10
+ import { ticksPerMeasure, computeTotalMeasures, sliceIntoMeasures, resolveTimeSignature, } from "./measures.js";
11
+ import { separateHands, formatHand, DEFAULT_SPLIT_POINT, } from "./hands.js";
12
+ // ─── Constants ───────────────────────────────────────────────────────────────
13
+ const DEFAULT_TEMPO = 120;
14
+ // ─── Public API ──────────────────────────────────────────────────────────────
15
+ /**
16
+ * Convert a MIDI buffer + human-authored config into a complete SongEntry.
17
+ */
18
+ export function midiToSongEntry(midiBuffer, config) {
19
+ const midi = parseMidi(midiBuffer);
20
+ const tpb = midi.header.ticksPerBeat ?? 480;
21
+ const splitPoint = config.splitPoint ?? DEFAULT_SPLIT_POINT;
22
+ // 1. Collect tempo + time signature events across all tracks
23
+ const tempoEvents = extractTempoEvents(midi);
24
+ const timeSigEvents = extractTimeSigEvents(midi);
25
+ // 2. Resolve all notes to absolute ticks + durations
26
+ const notes = resolveNotes(midi);
27
+ // 3. Determine effective tempo and time signature
28
+ const effectiveTempo = config.tempo ?? tempoFromEvents(tempoEvents);
29
+ const effectiveTimeSig = resolveTimeSignature(timeSigEvents, config.timeSignature);
30
+ // 4. Compute ticks per measure
31
+ const tpm = ticksPerMeasure(tpb, effectiveTimeSig.numerator, effectiveTimeSig.denominator);
32
+ // 5. Determine total measures
33
+ const totalMeasures = computeTotalMeasures(notes, tpm);
34
+ // 6. Slice notes into measure buckets
35
+ const buckets = sliceIntoMeasures(notes, totalMeasures, tpm);
36
+ // 7. Build measure objects with hand separation + formatting
37
+ const measures = buildMeasures(buckets, tpb, splitPoint, config);
38
+ // 8. Compute duration in seconds
39
+ const lastNoteTick = notes.length > 0
40
+ ? Math.max(...notes.map(n => n.startTick + n.durationTicks))
41
+ : 0;
42
+ const durationSeconds = ticksToSeconds(lastNoteTick, tempoEvents, tpb);
43
+ return {
44
+ id: config.id,
45
+ title: config.title,
46
+ genre: config.genre,
47
+ composer: config.composer,
48
+ arranger: config.arranger,
49
+ difficulty: config.difficulty,
50
+ key: config.key,
51
+ tempo: effectiveTempo,
52
+ timeSignature: `${effectiveTimeSig.numerator}/${effectiveTimeSig.denominator}`,
53
+ durationSeconds: Math.round(durationSeconds),
54
+ musicalLanguage: config.musicalLanguage,
55
+ measures,
56
+ tags: config.tags,
57
+ source: config.source,
58
+ };
59
+ }
60
+ // Re-export for barrel convenience
61
+ export { midiNoteToScientific } from "./hands.js";
62
+ // ─── Internal: Extract Events ────────────────────────────────────────────────
63
+ function extractTempoEvents(midi) {
64
+ const events = [];
65
+ for (const track of midi.tracks) {
66
+ let tick = 0;
67
+ for (const event of track) {
68
+ tick += event.deltaTime;
69
+ if (event.type === "setTempo") {
70
+ events.push({ tick, microsecondsPerBeat: event.microsecondsPerBeat });
71
+ }
72
+ }
73
+ }
74
+ events.sort((a, b) => a.tick - b.tick);
75
+ return events;
76
+ }
77
+ function extractTimeSigEvents(midi) {
78
+ const events = [];
79
+ for (const track of midi.tracks) {
80
+ let tick = 0;
81
+ for (const event of track) {
82
+ tick += event.deltaTime;
83
+ if (event.type === "timeSignature") {
84
+ events.push({
85
+ tick,
86
+ numerator: event.numerator,
87
+ denominator: Math.pow(2, event.denominator),
88
+ });
89
+ }
90
+ }
91
+ }
92
+ events.sort((a, b) => a.tick - b.tick);
93
+ return events;
94
+ }
95
+ /** Get the initial tempo from MIDI events. */
96
+ function tempoFromEvents(events) {
97
+ if (events.length === 0)
98
+ return DEFAULT_TEMPO;
99
+ return Math.round(60_000_000 / events[0].microsecondsPerBeat);
100
+ }
101
+ // ─── Internal: Resolve Notes ─────────────────────────────────────────────────
102
+ /** Flatten all tracks into resolved notes with absolute tick positions. */
103
+ function resolveNotes(midi) {
104
+ const notes = [];
105
+ for (const track of midi.tracks) {
106
+ let tick = 0;
107
+ const pending = new Map();
108
+ for (const event of track) {
109
+ tick += event.deltaTime;
110
+ if (event.type === "noteOn" && event.velocity > 0) {
111
+ const key = `${event.channel}:${event.noteNumber}`;
112
+ pending.set(key, {
113
+ startTick: tick,
114
+ velocity: event.velocity,
115
+ channel: event.channel,
116
+ noteNumber: event.noteNumber,
117
+ });
118
+ }
119
+ else if (event.type === "noteOff" ||
120
+ (event.type === "noteOn" && event.velocity === 0)) {
121
+ const key = `${event.channel}:${event.noteNumber}`;
122
+ const start = pending.get(key);
123
+ if (start) {
124
+ notes.push({
125
+ noteNumber: start.noteNumber,
126
+ startTick: start.startTick,
127
+ durationTicks: tick - start.startTick,
128
+ velocity: start.velocity,
129
+ channel: start.channel,
130
+ });
131
+ pending.delete(key);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ notes.sort((a, b) => a.startTick - b.startTick || a.noteNumber - b.noteNumber);
137
+ return notes;
138
+ }
139
+ // ─── Internal: Build Measures ────────────────────────────────────────────────
140
+ /** Build Measure objects from measure buckets. */
141
+ function buildMeasures(buckets, ticksPerBeat, splitPoint, config) {
142
+ const overrides = new Map();
143
+ if (config.measureOverrides) {
144
+ for (const ov of config.measureOverrides) {
145
+ overrides.set(ov.measure, ov);
146
+ }
147
+ }
148
+ return buckets.map(bucket => {
149
+ const { rightHand, leftHand } = separateHands(bucket.notes, splitPoint);
150
+ const measure = {
151
+ number: bucket.number,
152
+ rightHand: formatHand(rightHand, ticksPerBeat),
153
+ leftHand: formatHand(leftHand, ticksPerBeat),
154
+ };
155
+ const ov = overrides.get(bucket.number);
156
+ if (ov?.fingering)
157
+ measure.fingering = ov.fingering;
158
+ if (ov?.teachingNote)
159
+ measure.teachingNote = ov.teachingNote;
160
+ if (ov?.dynamics)
161
+ measure.dynamics = ov.dynamics;
162
+ if (ov?.tempoOverride)
163
+ measure.tempoOverride = ov.tempoOverride;
164
+ return measure;
165
+ });
166
+ }
167
+ // ─── Internal: Tick-to-Time Conversion ───────────────────────────────────────
168
+ /** Convert a tick position to seconds, respecting tempo changes. */
169
+ function ticksToSeconds(targetTick, tempoEvents, ticksPerBeat) {
170
+ let seconds = 0;
171
+ let currentTick = 0;
172
+ let microsecondsPerBeat = tempoEvents.length > 0
173
+ ? tempoEvents[0].microsecondsPerBeat
174
+ : 500_000; // 120 BPM default
175
+ for (const event of tempoEvents) {
176
+ if (event.tick >= targetTick)
177
+ break;
178
+ if (event.tick > currentTick) {
179
+ const deltaTicks = event.tick - currentTick;
180
+ seconds += (deltaTicks / ticksPerBeat) * (microsecondsPerBeat / 1_000_000);
181
+ currentTick = event.tick;
182
+ }
183
+ microsecondsPerBeat = event.microsecondsPerBeat;
184
+ }
185
+ if (currentTick < targetTick) {
186
+ const deltaTicks = targetTick - currentTick;
187
+ seconds += (deltaTicks / ticksPerBeat) * (microsecondsPerBeat / 1_000_000);
188
+ }
189
+ return seconds;
190
+ }
191
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/songs/midi/ingest.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,6EAA6E;AAC7E,0DAA0D;AAC1D,EAAE;AACF,yDAAyD;AACzD,8EAA8E;AAC9E,gFAAgF;AAEhF,OAAO,EAAE,SAAS,EAAiB,MAAM,WAAW,CAAC;AAIrD,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACb,UAAU,EACV,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,gFAAgF;AAEhF,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B,gFAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,UAAsB,EACtB,MAAkB;IAElB,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,GAAG,CAAC;IAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAE5D,6DAA6D;IAC7D,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAEjD,qDAAqD;IACrD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAEjC,kDAAkD;IAClD,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,IAAI,eAAe,CAAC,WAAW,CAAC,CAAC;IACpE,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;IAEnF,+BAA+B;IAC/B,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,EAAE,gBAAgB,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAE3F,8BAA8B;IAC9B,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEvD,sCAAsC;IACtC,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAE7D,6DAA6D;IAC7D,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAEjE,iCAAiC;IACjC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;QACnC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC,CAAC;IACN,MAAM,eAAe,GAAG,cAAc,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;IAEvE,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,KAAK,EAAE,cAAc;QACrB,aAAa,EAAE,GAAG,gBAAgB,CAAC,SAAS,IAAI,gBAAgB,CAAC,WAAW,EAAE;QAC9E,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC;QAC5C,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,QAAQ;QACR,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;AACJ,CAAC;AAED,mCAAmC;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD,gFAAgF;AAEhF,SAAS,kBAAkB,CAAC,IAAc;IACxC,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;YACxB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,CAAC,mBAAmB,EAAE,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAc;IAC1C,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;YACxB,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI;oBACJ,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;iBAC5C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8CAA8C;AAC9C,SAAS,eAAe,CAAC,MAA0B;IACjD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC;IAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC;AAChE,CAAC;AAED,gFAAgF;AAEhF,2EAA2E;AAC3E,SAAS,YAAY,CAAC,IAAc;IAClC,MAAM,KAAK,GAAmB,EAAE,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwF,CAAC;QAEhH,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;YAExB,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;gBAClD,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACnD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;oBACf,SAAS,EAAE,IAAI;oBACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,UAAU,EAAE,KAAK,CAAC,UAAU;iBAC7B,CAAC,CAAC;YACL,CAAC;iBAAM,IACL,KAAK,CAAC,IAAI,KAAK,SAAS;gBACxB,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,CAAC,EACjD,CAAC;gBACD,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC/B,IAAI,KAAK,EAAE,CAAC;oBACV,KAAK,CAAC,IAAI,CAAC;wBACT,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,aAAa,EAAE,IAAI,GAAG,KAAK,CAAC,SAAS;wBACrC,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;qBACvB,CAAC,CAAC;oBACH,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;IAC/E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,gFAAgF;AAEhF,kDAAkD;AAClD,SAAS,aAAa,CACpB,OAAwB,EACxB,YAAoB,EACpB,UAAkB,EAClB,MAAkB;IAElB,MAAM,SAAS,GAAG,IAAI,GAAG,EAA+D,CAAC;IACzF,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC5B,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACzC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1B,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAExE,MAAM,OAAO,GAAY;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,YAAY,CAAC;YAC9C,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,YAAY,CAAC;SAC7C,CAAC;QAEF,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,EAAE,EAAE,SAAS;YAAE,OAAO,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC;QACpD,IAAI,EAAE,EAAE,YAAY;YAAE,OAAO,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;QAC7D,IAAI,EAAE,EAAE,QAAQ;YAAE,OAAO,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;QACjD,IAAI,EAAE,EAAE,aAAa;YAAE,OAAO,CAAC,aAAa,GAAG,EAAE,CAAC,aAAa,CAAC;QAEhE,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gFAAgF;AAEhF,oEAAoE;AACpE,SAAS,cAAc,CACrB,UAAkB,EAClB,WAA+B,EAC/B,YAAoB;IAEpB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,mBAAmB,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC;QAC9C,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,mBAAmB;QACpC,CAAC,CAAC,OAAO,CAAC,CAAC,kBAAkB;IAE/B,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,IAAI,UAAU;YAAE,MAAM;QAEpC,IAAI,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;YAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC;YAC5C,OAAO,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;YAC3E,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,CAAC;QACD,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC;IAClD,CAAC;IAED,IAAI,WAAW,GAAG,UAAU,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC;QAC5C,OAAO,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { ResolvedNote, TimeSigEvent } from "./types.js";
2
+ /** A bucket of notes belonging to a single measure. */
3
+ export interface MeasureBucket {
4
+ /** 1-based measure number. */
5
+ number: number;
6
+ /** Absolute tick where this measure starts. */
7
+ startTick: number;
8
+ /** Absolute tick where this measure ends. */
9
+ endTick: number;
10
+ /** Notes whose startTick falls within this measure. */
11
+ notes: ResolvedNote[];
12
+ }
13
+ /**
14
+ * Compute ticks per measure for a given time signature.
15
+ */
16
+ export declare function ticksPerMeasure(ticksPerBeat: number, numerator: number, denominator: number): number;
17
+ /**
18
+ * Determine total number of measures needed to contain all notes.
19
+ */
20
+ export declare function computeTotalMeasures(notes: ResolvedNote[], tpm: number): number;
21
+ /**
22
+ * Slice notes into measure buckets.
23
+ */
24
+ export declare function sliceIntoMeasures(notes: ResolvedNote[], totalMeasures: number, tpm: number): MeasureBucket[];
25
+ /**
26
+ * Parse a time signature string like "4/4" or "3/4".
27
+ * Returns { numerator, denominator } or defaults to 4/4 on invalid input.
28
+ */
29
+ export declare function parseTimeSignature(timeSig?: string): {
30
+ numerator: number;
31
+ denominator: number;
32
+ };
33
+ /**
34
+ * Get effective time signature from MIDI events or config string.
35
+ * Config string takes priority over MIDI events.
36
+ */
37
+ export declare function resolveTimeSignature(events: TimeSigEvent[], configTimeSig?: string): {
38
+ numerator: number;
39
+ denominator: number;
40
+ };
41
+ //# sourceMappingURL=measures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measures.d.ts","sourceRoot":"","sources":["../../../src/songs/midi/measures.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI7D,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EAAE,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EAAE,EACrB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,GACV,aAAa,EAAE,CAgBjB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAO5C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EAAE,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAM5C"}
@@ -0,0 +1,64 @@
1
+ // ─── Measure Slicing ─────────────────────────────────────────────────────────
2
+ //
3
+ // Slices a flat array of resolved notes into measure buckets based on
4
+ // time signature and ticks-per-beat.
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // ─── Public API ──────────────────────────────────────────────────────────────
7
+ /**
8
+ * Compute ticks per measure for a given time signature.
9
+ */
10
+ export function ticksPerMeasure(ticksPerBeat, numerator, denominator) {
11
+ return ticksPerBeat * numerator * (4 / denominator);
12
+ }
13
+ /**
14
+ * Determine total number of measures needed to contain all notes.
15
+ */
16
+ export function computeTotalMeasures(notes, tpm) {
17
+ if (notes.length === 0)
18
+ return 1;
19
+ const lastNoteTick = Math.max(...notes.map(n => n.startTick + n.durationTicks));
20
+ return Math.max(1, Math.ceil(lastNoteTick / tpm));
21
+ }
22
+ /**
23
+ * Slice notes into measure buckets.
24
+ */
25
+ export function sliceIntoMeasures(notes, totalMeasures, tpm) {
26
+ const buckets = [];
27
+ for (let m = 0; m < totalMeasures; m++) {
28
+ const startTick = m * tpm;
29
+ const endTick = (m + 1) * tpm;
30
+ buckets.push({
31
+ number: m + 1,
32
+ startTick,
33
+ endTick,
34
+ notes: notes.filter(n => n.startTick >= startTick && n.startTick < endTick),
35
+ });
36
+ }
37
+ return buckets;
38
+ }
39
+ /**
40
+ * Parse a time signature string like "4/4" or "3/4".
41
+ * Returns { numerator, denominator } or defaults to 4/4 on invalid input.
42
+ */
43
+ export function parseTimeSignature(timeSig) {
44
+ if (!timeSig)
45
+ return { numerator: 4, denominator: 4 };
46
+ const parts = timeSig.split("/").map(Number);
47
+ if (parts.length === 2 && parts[0] > 0 && parts[1] > 0) {
48
+ return { numerator: parts[0], denominator: parts[1] };
49
+ }
50
+ return { numerator: 4, denominator: 4 };
51
+ }
52
+ /**
53
+ * Get effective time signature from MIDI events or config string.
54
+ * Config string takes priority over MIDI events.
55
+ */
56
+ export function resolveTimeSignature(events, configTimeSig) {
57
+ if (configTimeSig)
58
+ return parseTimeSignature(configTimeSig);
59
+ if (events.length > 0) {
60
+ return { numerator: events[0].numerator, denominator: events[0].denominator };
61
+ }
62
+ return { numerator: 4, denominator: 4 };
63
+ }
64
+ //# sourceMappingURL=measures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measures.js","sourceRoot":"","sources":["../../../src/songs/midi/measures.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,sEAAsE;AACtE,qCAAqC;AACrC,gFAAgF;AAkBhF,gFAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,SAAiB,EACjB,WAAmB;IAEnB,OAAO,YAAY,GAAG,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAqB,EACrB,GAAW;IAEX,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAqB,EACrB,aAAqB,EACrB,GAAW;IAEX,MAAM,OAAO,GAAoB,EAAE,CAAC;IAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1B,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAE9B,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,CAAC,GAAG,CAAC;YACb,SAAS;YACT,OAAO;YACP,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC;SAC5E,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAgB;IAEhB,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IACtD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAsB,EACtB,aAAsB;IAEtB,IAAI,aAAa;QAAE,OAAO,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAC5D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,25 @@
1
+ /** A resolved note with absolute timing from MIDI. */
2
+ export interface ResolvedNote {
3
+ /** MIDI note number 0-127. */
4
+ noteNumber: number;
5
+ /** Start time in ticks from the beginning. */
6
+ startTick: number;
7
+ /** Duration in ticks. */
8
+ durationTicks: number;
9
+ /** Velocity 0-127. */
10
+ velocity: number;
11
+ /** MIDI channel. */
12
+ channel: number;
13
+ }
14
+ /** A tempo change event with absolute tick position. */
15
+ export interface IngestTempoEvent {
16
+ tick: number;
17
+ microsecondsPerBeat: number;
18
+ }
19
+ /** A time signature event with absolute tick position. */
20
+ export interface TimeSigEvent {
21
+ tick: number;
22
+ numerator: number;
23
+ denominator: number;
24
+ }
25
+ //# sourceMappingURL=types.d.ts.map