@sudobility/music_codecs 0.1.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 (81) hide show
  1. package/dist/index.d.ts +43 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +46 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/midi/analyze.d.ts +53 -0
  6. package/dist/midi/analyze.d.ts.map +1 -0
  7. package/dist/midi/analyze.js +58 -0
  8. package/dist/midi/analyze.js.map +1 -0
  9. package/dist/midi/export.d.ts +20 -0
  10. package/dist/midi/export.d.ts.map +1 -0
  11. package/dist/midi/export.js +135 -0
  12. package/dist/midi/export.js.map +1 -0
  13. package/dist/midi/grid-detection.d.ts +41 -0
  14. package/dist/midi/grid-detection.d.ts.map +1 -0
  15. package/dist/midi/grid-detection.js +61 -0
  16. package/dist/midi/grid-detection.js.map +1 -0
  17. package/dist/midi/import-options.d.ts +45 -0
  18. package/dist/midi/import-options.d.ts.map +1 -0
  19. package/dist/midi/import-options.js +38 -0
  20. package/dist/midi/import-options.js.map +1 -0
  21. package/dist/midi/import.d.ts +43 -0
  22. package/dist/midi/import.d.ts.map +1 -0
  23. package/dist/midi/import.js +441 -0
  24. package/dist/midi/import.js.map +1 -0
  25. package/dist/midi/key-detection.d.ts +11 -0
  26. package/dist/midi/key-detection.d.ts.map +1 -0
  27. package/dist/midi/key-detection.js +96 -0
  28. package/dist/midi/key-detection.js.map +1 -0
  29. package/dist/midi/measures.d.ts +39 -0
  30. package/dist/midi/measures.d.ts.map +1 -0
  31. package/dist/midi/measures.js +141 -0
  32. package/dist/midi/measures.js.map +1 -0
  33. package/dist/mod/export.d.ts +54 -0
  34. package/dist/mod/export.d.ts.map +1 -0
  35. package/dist/mod/export.js +172 -0
  36. package/dist/mod/export.js.map +1 -0
  37. package/dist/mod/fill.d.ts +11 -0
  38. package/dist/mod/fill.d.ts.map +1 -0
  39. package/dist/mod/fill.js +50 -0
  40. package/dist/mod/fill.js.map +1 -0
  41. package/dist/mod/import.d.ts +14 -0
  42. package/dist/mod/import.d.ts.map +1 -0
  43. package/dist/mod/import.js +182 -0
  44. package/dist/mod/import.js.map +1 -0
  45. package/dist/mod/limits.d.ts +26 -0
  46. package/dist/mod/limits.d.ts.map +1 -0
  47. package/dist/mod/limits.js +14 -0
  48. package/dist/mod/limits.js.map +1 -0
  49. package/dist/mod/timing.d.ts +29 -0
  50. package/dist/mod/timing.d.ts.map +1 -0
  51. package/dist/mod/timing.js +61 -0
  52. package/dist/mod/timing.js.map +1 -0
  53. package/dist/mod/types.d.ts +8 -0
  54. package/dist/mod/types.d.ts.map +1 -0
  55. package/dist/mod/types.js +2 -0
  56. package/dist/mod/types.js.map +1 -0
  57. package/dist/musicxml/duration-map.d.ts +34 -0
  58. package/dist/musicxml/duration-map.d.ts.map +1 -0
  59. package/dist/musicxml/duration-map.js +79 -0
  60. package/dist/musicxml/duration-map.js.map +1 -0
  61. package/dist/musicxml/export.d.ts +35 -0
  62. package/dist/musicxml/export.d.ts.map +1 -0
  63. package/dist/musicxml/export.js +271 -0
  64. package/dist/musicxml/export.js.map +1 -0
  65. package/dist/musicxml/import.d.ts +52 -0
  66. package/dist/musicxml/import.d.ts.map +1 -0
  67. package/dist/musicxml/import.js +637 -0
  68. package/dist/musicxml/import.js.map +1 -0
  69. package/dist/test/fixtures.d.ts +45 -0
  70. package/dist/test/fixtures.d.ts.map +1 -0
  71. package/dist/test/fixtures.js +420 -0
  72. package/dist/test/fixtures.js.map +1 -0
  73. package/dist/test/musicxml-warnings.d.ts +11 -0
  74. package/dist/test/musicxml-warnings.d.ts.map +1 -0
  75. package/dist/test/musicxml-warnings.js +26 -0
  76. package/dist/test/musicxml-warnings.js.map +1 -0
  77. package/dist/test/platform.d.ts +13 -0
  78. package/dist/test/platform.d.ts.map +1 -0
  79. package/dist/test/platform.js +191 -0
  80. package/dist/test/platform.js.map +1 -0
  81. package/package.json +71 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/midi/import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAET,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAG7D,OAAO,KAAK,EAGV,KAAK,EAGN,MAAM,yBAAyB,CAAC;AA+cjC,MAAM,MAAM,gBAAgB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,iBAAiB,GACzB,gBAAgB,CAmGlB;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CACxB,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,SAAS,GACf,gBAAgB,CAElB"}
@@ -0,0 +1,441 @@
1
+ import { detectKeySignature } from './key-detection.js';
2
+ import { assembleTrackMeasures, buildMeasureSpans } from './measures.js';
3
+ import { midiToPitch, pitchToMidi } from '@sudobility/music_types';
4
+ import { createId } from '@sudobility/music_types';
5
+ import { isNoteEvent } from '@sudobility/music_types';
6
+ import { quantizeEvents } from '@sudobility/music_types';
7
+ import { ticksFor } from '@sudobility/music_types';
8
+ import { allocateVoices } from '@sudobility/music_types';
9
+ /** The score model's fixed internal PPQ (spec §4/§15: every import is normalized to 480). */
10
+ const SCORE_PPQ = 480;
11
+ const DEFAULT_TEMPO_BPM = 120;
12
+ const MIN_BPM = 20;
13
+ const MAX_BPM = 400;
14
+ const MAX_VELOCITY = 127;
15
+ const SUSTAIN_CC_NUMBER = 64;
16
+ const SUSTAIN_DOWN_THRESHOLD = 0.5;
17
+ /** Max simultaneous notated voices `allocateVoices` may open per staff. */
18
+ const DEFAULT_MAX_VOICES = 4;
19
+ const DEFAULT_KEY_SIGNATURE = { fifths: 0, mode: 'major' };
20
+ const PERCUSSION_CHANNEL = 9;
21
+ /**
22
+ * Onsets of the same pitch within this many ticks of each other are treated
23
+ * as accidental duplicate triggers (not a deliberate ornament/grace note) by
24
+ * `mergeNearDuplicates`. Deliberately independent of `quantizeGrid` — this
25
+ * is about performance-timing noise, not the notation grid — roughly a
26
+ * 128th note at 480 ppq. A near-duplicate this close is almost always a
27
+ * double trigger rather than an intentional repeated note.
28
+ */
29
+ const NEAR_DUPLICATE_TOLERANCE_TICKS = 15;
30
+ const ALWAYS_WARNING = 'MIDI import approximates performance timing as notation: quantization, voice/staff assignment, and key detection are best-effort. Review the result before use.';
31
+ // ---- Tempo / time signature -----------------------------------------------
32
+ function buildTempoMap(sourceTempos, ratio, warnings) {
33
+ const converted = sourceTempos
34
+ .map(t => ({ tick: Math.round(t.ticks * ratio), bpm: t.bpm }))
35
+ .sort((a, b) => a.tick - b.tick);
36
+ const withOrigin = converted.length > 0 && converted[0].tick === 0
37
+ ? converted
38
+ : [{ tick: 0, bpm: DEFAULT_TEMPO_BPM }, ...converted];
39
+ return withOrigin.map(t => {
40
+ const bpm = Math.min(MAX_BPM, Math.max(MIN_BPM, t.bpm));
41
+ if (bpm !== t.bpm) {
42
+ warnings.push(`Tempo event at tick ${t.tick} (${t.bpm.toFixed(1)} bpm) was outside the supported ${MIN_BPM}-${MAX_BPM} bpm range and was clamped to ${bpm} bpm.`);
43
+ }
44
+ return { id: createId(), tick: t.tick, bpm };
45
+ });
46
+ }
47
+ function convertTimeSignatures(sourceTimeSignatures, ratio) {
48
+ return sourceTimeSignatures.map(t => ({
49
+ tick: Math.round(t.ticks * ratio),
50
+ timeSignature: {
51
+ numerator: t.timeSignature[0],
52
+ denominator: t.timeSignature[1],
53
+ },
54
+ }));
55
+ }
56
+ /** Pedal-down `[start, end)` intervals (target ticks) from a track's CC64 events; an unreleased pedal closes at `trackEndTick`. */
57
+ function sustainIntervals(sourceTrack, ratio, trackEndTick) {
58
+ const events = [
59
+ ...(sourceTrack.controlChanges[SUSTAIN_CC_NUMBER] ?? []),
60
+ ].sort((a, b) => a.ticks - b.ticks);
61
+ const intervals = [];
62
+ let downStart = null;
63
+ for (const event of events) {
64
+ const tick = Math.round(event.ticks * ratio);
65
+ const down = event.value >= SUSTAIN_DOWN_THRESHOLD;
66
+ if (down && downStart === null) {
67
+ downStart = tick;
68
+ }
69
+ else if (!down && downStart !== null) {
70
+ intervals.push({ start: downStart, end: tick });
71
+ downStart = null;
72
+ }
73
+ }
74
+ if (downStart !== null)
75
+ intervals.push({ start: downStart, end: trackEndTick });
76
+ return intervals;
77
+ }
78
+ /** Extends `rawEnd` to the release tick of whichever pedal-down interval it falls inside, if any. */
79
+ function extendThroughSustain(rawEnd, intervals) {
80
+ for (const interval of intervals) {
81
+ if (rawEnd >= interval.start && rawEnd < interval.end)
82
+ return interval.end;
83
+ }
84
+ return rawEnd;
85
+ }
86
+ /**
87
+ * Converts a source track's notes to target-tick `RawNote`s, optionally
88
+ * extending each note's end through a sustain-pedal-down interval it falls
89
+ * in (`sustainPedal: "extend"`). Sustain-created same-pitch extensions are
90
+ * clamped so they do not run into the next onset of that same pitch. Raw MIDI
91
+ * overlaps are left intact; preserving performance timing means not shortening
92
+ * source notes unless a cleanup stage explicitly asks for it.
93
+ */
94
+ function extractRawNotes(sourceTrack, ratio, sustainPedal) {
95
+ const trackEndTick = Math.round(sourceTrack.durationTicks * ratio);
96
+ const intervals = sustainPedal === 'extend'
97
+ ? sustainIntervals(sourceTrack, ratio, trackEndTick)
98
+ : [];
99
+ const spans = sourceTrack.notes.map(note => {
100
+ const startTick = Math.round(note.ticks * ratio);
101
+ const rawEnd = Math.round((note.ticks + note.durationTicks) * ratio);
102
+ const endTick = intervals.length > 0 ? extendThroughSustain(rawEnd, intervals) : rawEnd;
103
+ return {
104
+ midi: note.midi,
105
+ startTick,
106
+ rawEnd,
107
+ endTick,
108
+ velocity: note.velocity,
109
+ };
110
+ });
111
+ const byPitch = new Map();
112
+ for (const span of spans) {
113
+ const bucket = byPitch.get(span.midi);
114
+ if (bucket)
115
+ bucket.push(span);
116
+ else
117
+ byPitch.set(span.midi, [span]);
118
+ }
119
+ for (const bucket of byPitch.values()) {
120
+ bucket.sort((a, b) => a.startTick - b.startTick);
121
+ for (let i = 0; i < bucket.length - 1; i += 1) {
122
+ if (bucket[i].rawEnd <= bucket[i + 1].startTick &&
123
+ bucket[i].endTick > bucket[i + 1].startTick) {
124
+ bucket[i].endTick = bucket[i + 1].startTick;
125
+ }
126
+ }
127
+ }
128
+ return spans.map(span => ({
129
+ midi: span.midi,
130
+ startTick: span.startTick,
131
+ durationTicks: Math.max(1, span.endTick - span.startTick),
132
+ velocity: Math.min(MAX_VELOCITY, Math.max(0, Math.round(span.velocity * MAX_VELOCITY))),
133
+ }));
134
+ }
135
+ // ---- Quantization -------------------------------------------------------------
136
+ /** Grid unit in ticks for `options.quantizeGrid`, or `1` (a no-op grid) when quantization is off. */
137
+ function gridTicksFor(options) {
138
+ return options.quantizeGrid !== null
139
+ ? ticksFor(options.quantizeGrid, SCORE_PPQ)
140
+ : 1;
141
+ }
142
+ /**
143
+ * Merges runs of same-pitch onsets within `toleranceTicks` of each other
144
+ * into a single note (earliest start, spanning to the latest end, keeping
145
+ * the louder velocity) — genuine accidental-duplicate-trigger cleanup, not
146
+ * just onset repositioning (contrast `quantizeEvents`' `chordToleranceTicks`,
147
+ * which only snaps near-simultaneous onsets onto a shared tick and is
148
+ * meant for *chords*, i.e. different pitches — it never reduces note
149
+ * count, so it can't by itself implement "merge near-duplicate notes").
150
+ * Only ever compares notes of the *same* pitch, so legitimate chords
151
+ * (different pitches, same or near onset) are never affected. Returns the
152
+ * merged notes (sorted by `startTick`) and how many source notes were
153
+ * folded into another (i.e. `input.length - notes.length`).
154
+ */
155
+ /** Folds `next` into `base`: keeps `base`'s start, extends to cover both spans, keeps the louder velocity. */
156
+ function mergeInto(base, next) {
157
+ const end = Math.max(base.startTick + base.durationTicks, next.startTick + next.durationTicks);
158
+ return {
159
+ ...base,
160
+ durationTicks: end - base.startTick,
161
+ velocity: Math.max(base.velocity, next.velocity),
162
+ };
163
+ }
164
+ function mergeNearDuplicateNotes(notes, toleranceTicks) {
165
+ const byPitch = new Map();
166
+ for (const note of notes) {
167
+ const midi = pitchToMidi(note.pitch);
168
+ const bucket = byPitch.get(midi);
169
+ if (bucket)
170
+ bucket.push(note);
171
+ else
172
+ byPitch.set(midi, [note]);
173
+ }
174
+ const kept = [];
175
+ let mergedCount = 0;
176
+ for (const bucket of byPitch.values()) {
177
+ const sorted = [...bucket].sort((a, b) => a.startTick - b.startTick);
178
+ let current = null;
179
+ for (const note of sorted) {
180
+ if (current !== null &&
181
+ note.startTick - current.startTick <= toleranceTicks) {
182
+ current = mergeInto(current, note);
183
+ mergedCount += 1;
184
+ }
185
+ else {
186
+ if (current)
187
+ kept.push(current);
188
+ current = note;
189
+ }
190
+ }
191
+ if (current)
192
+ kept.push(current);
193
+ }
194
+ kept.sort((a, b) => a.startTick - b.startTick);
195
+ return { notes: kept, mergedCount };
196
+ }
197
+ /**
198
+ * Turns a selection's raw notes into cleaned-up `NoteEvent`s per `options`,
199
+ * as three explicit, independently-measured stages so each cause of a note
200
+ * being dropped/merged/trimmed can be reported accurately (rather than one
201
+ * combined before/after count blamed entirely on `minDurationTicks`,
202
+ * regardless of which stage actually changed anything):
203
+ *
204
+ * 1. Drop notes shorter than `minDurationTicks` + snap starts/durations to
205
+ * the quantization grid (`quantizeGrid`/`tripletDetection`). Only this
206
+ * stage can remove notes, so any count delta here is `droppedShort`.
207
+ * 2. Merge near-duplicate same-pitch onsets (`mergeNearDuplicates`) via
208
+ * `mergeNearDuplicateNotes`. Any count delta here is `mergedDuplicates`.
209
+ * 3. When an explicit cleanup stage can create residual same-pitch overlaps,
210
+ * trim a repeated pitch down to the next same-pitch onset. Different-pitch
211
+ * overlaps are real polyphony and must survive into voice allocation.
212
+ */
213
+ function trimSamePitchOverlaps(notes) {
214
+ const byId = new Map(notes.map(note => [note.id, note]));
215
+ const byPitch = new Map();
216
+ for (const note of notes) {
217
+ const midi = pitchToMidi(note.pitch);
218
+ const bucket = byPitch.get(midi);
219
+ if (bucket)
220
+ bucket.push(note);
221
+ else
222
+ byPitch.set(midi, [note]);
223
+ }
224
+ let trimmedCount = 0;
225
+ for (const bucket of byPitch.values()) {
226
+ const sorted = [...bucket].sort((a, b) => a.startTick - b.startTick || a.durationTicks - b.durationTicks);
227
+ for (let i = 0; i < sorted.length; i += 1) {
228
+ let nextIndex = i + 1;
229
+ while (nextIndex < sorted.length &&
230
+ sorted[nextIndex].startTick === sorted[i].startTick) {
231
+ nextIndex += 1;
232
+ }
233
+ const next = sorted[nextIndex];
234
+ if (!next)
235
+ continue;
236
+ const current = byId.get(sorted[i].id);
237
+ const currentEnd = current.startTick + current.durationTicks;
238
+ if (currentEnd > next.startTick) {
239
+ const durationTicks = Math.max(1, next.startTick - current.startTick);
240
+ if (durationTicks !== current.durationTicks) {
241
+ byId.set(current.id, { ...current, durationTicks });
242
+ trimmedCount += 1;
243
+ }
244
+ }
245
+ }
246
+ }
247
+ return { notes: notes.map(note => byId.get(note.id)), trimmedCount };
248
+ }
249
+ function prepareSelectionNotes(raw, tempTrackId, options) {
250
+ const initialEvents = raw.map(n => ({
251
+ id: createId(),
252
+ pitch: midiToPitch(n.midi),
253
+ startTick: n.startTick,
254
+ durationTicks: n.durationTicks,
255
+ velocity: n.velocity,
256
+ voiceId: 'import',
257
+ trackId: tempTrackId,
258
+ }));
259
+ const quantizing = options.quantizeGrid !== null;
260
+ const grid = gridTicksFor(options);
261
+ const snapQuantizeOptions = {
262
+ grid,
263
+ quantizeStarts: quantizing,
264
+ quantizeDurations: quantizing,
265
+ tripletGrid: quantizing && options.tripletDetection,
266
+ minDurationTicks: options.minDurationTicks,
267
+ };
268
+ const snapped = quantizeEvents(initialEvents, snapQuantizeOptions).filter(isNoteEvent);
269
+ const droppedShort = initialEvents.length - snapped.length;
270
+ const { notes: deduped, mergedCount: mergedDuplicates } = options.mergeNearDuplicates
271
+ ? mergeNearDuplicateNotes(snapped, NEAR_DUPLICATE_TOLERANCE_TICKS)
272
+ : { notes: snapped, mergedCount: 0 };
273
+ const shouldTrimSamePitchOverlaps = quantizing || options.mergeNearDuplicates;
274
+ const { notes: resolved, trimmedCount: trimmedOverlaps } = shouldTrimSamePitchOverlaps
275
+ ? trimSamePitchOverlaps(deduped)
276
+ : { notes: deduped, trimmedCount: 0 };
277
+ return { notes: resolved, droppedShort, mergedDuplicates, trimmedOverlaps };
278
+ }
279
+ // ---- Track assembly -------------------------------------------------------------
280
+ function trackVolume(sourceTrack) {
281
+ const cc = sourceTrack.controlChanges[7]?.[0];
282
+ return cc ? cc.value : 1;
283
+ }
284
+ function trackPan(sourceTrack) {
285
+ const cc = sourceTrack.controlChanges[10]?.[0];
286
+ return cc ? cc.value * 2 - 1 : 0;
287
+ }
288
+ function buildSingleTrack(prepared, spans, keySignature) {
289
+ const trackId = createId();
290
+ const groups = allocateVoices(prepared.notes, {
291
+ maxVoices: DEFAULT_MAX_VOICES,
292
+ splitPoint: Number.NEGATIVE_INFINITY,
293
+ });
294
+ const lanes = groups.map(g => g.notes);
295
+ const measures = assembleTrackMeasures(lanes, spans, keySignature, trackId);
296
+ const clef = prepared.selection.clef;
297
+ return {
298
+ id: trackId,
299
+ name: prepared.selection.name,
300
+ instrumentName: prepared.sourceTrack.instrument.name || 'Instrument',
301
+ midiProgram: prepared.sourceTrack.instrument.number,
302
+ midiChannel: clef === 'percussion'
303
+ ? PERCUSSION_CHANNEL
304
+ : Math.min(15, Math.max(0, prepared.sourceTrack.channel)),
305
+ clef,
306
+ volume: trackVolume(prepared.sourceTrack),
307
+ pan: trackPan(prepared.sourceTrack),
308
+ muted: false,
309
+ solo: false,
310
+ measures,
311
+ };
312
+ }
313
+ /** Splits `prepared` into two linked grand-staff tracks ("Piano RH" upper/treble, "Piano LH" lower/bass) at `options.splitPointMidi`. */
314
+ function buildSplitTracks(prepared, spans, keySignature, splitPointMidi) {
315
+ const groups = allocateVoices(prepared.notes, {
316
+ maxVoices: DEFAULT_MAX_VOICES,
317
+ splitPoint: splitPointMidi,
318
+ });
319
+ const upperLanes = groups.filter(g => g.staff === 'upper').map(g => g.notes);
320
+ const lowerLanes = groups.filter(g => g.staff === 'lower').map(g => g.notes);
321
+ const rhId = createId();
322
+ const lhId = createId();
323
+ const shared = {
324
+ instrumentName: prepared.sourceTrack.instrument.name || 'Instrument',
325
+ midiProgram: prepared.sourceTrack.instrument.number,
326
+ midiChannel: Math.min(15, Math.max(0, prepared.sourceTrack.channel)),
327
+ volume: trackVolume(prepared.sourceTrack),
328
+ pan: trackPan(prepared.sourceTrack),
329
+ muted: false,
330
+ solo: false,
331
+ };
332
+ return [
333
+ {
334
+ id: rhId,
335
+ name: 'Piano RH',
336
+ clef: 'treble',
337
+ ...shared,
338
+ measures: assembleTrackMeasures(upperLanes, spans, keySignature, rhId),
339
+ },
340
+ {
341
+ id: lhId,
342
+ name: 'Piano LH',
343
+ clef: 'bass',
344
+ ...shared,
345
+ measures: assembleTrackMeasures(lowerLanes, spans, keySignature, lhId),
346
+ },
347
+ ];
348
+ }
349
+ /**
350
+ * Imports a Standard MIDI File as a `Score` (spec §15): converts note times
351
+ * to 480-ppq score ticks, imports the tempo map and time signatures,
352
+ * applies sustain-pedal extension, Task 4 quantization (grid/triplet/
353
+ * minimum-duration/near-duplicate merging), key estimation and enharmonic
354
+ * respelling, Task 4 voice allocation, optional piano grand-staff split,
355
+ * and measure generation (notes split across measure boundaries with
356
+ * ties) — per track selected in `options.trackSelections`. Always returns a
357
+ * `warnings` array (never throws for merely-imperfect input); the caller is
358
+ * expected to surface it before the import is committed (spec §15: MIDI
359
+ * import must be reviewable, and one undoable operation once committed).
360
+ */
361
+ /**
362
+ * Converts an already-decoded MIDI file into a `Score`.
363
+ *
364
+ * Split out from `importMidi` so the heavy work — quantization and voice
365
+ * allocation — can be handed to a worker without the worker needing a codec.
366
+ * `MidiFile` is plain data and therefore structured-cloneable; a `MidiCodec` is
367
+ * not, and giving the worker one would make music_lib depend on music_io.
368
+ */
369
+ export function importMidiFile(midi, options) {
370
+ const warnings = [ALWAYS_WARNING];
371
+ const ratio = SCORE_PPQ / midi.header.ppq;
372
+ const tempoMap = buildTempoMap(midi.header.tempos, ratio, warnings);
373
+ const timeSignatureChanges = convertTimeSignatures(midi.header.timeSignatures, ratio);
374
+ const preparedSelections = [];
375
+ for (const selection of options.trackSelections) {
376
+ if (!selection.include)
377
+ continue;
378
+ const sourceTrack = midi.tracks[selection.sourceIndex];
379
+ if (!sourceTrack) {
380
+ warnings.push(`Track selection references source track ${selection.sourceIndex}, which doesn't exist in this file; it was skipped.`);
381
+ continue;
382
+ }
383
+ const raw = extractRawNotes(sourceTrack, ratio, options.sustainPedal);
384
+ const prepared = prepareSelectionNotes(raw, `import-${selection.sourceIndex}`, options);
385
+ if (prepared.droppedShort > 0) {
386
+ warnings.push(`Track "${selection.name}": dropped ${prepared.droppedShort} note(s) shorter than ${options.minDurationTicks} ticks.`);
387
+ }
388
+ if (prepared.mergedDuplicates > 0) {
389
+ warnings.push(`Track "${selection.name}": merged ${prepared.mergedDuplicates} near-duplicate note(s).`);
390
+ }
391
+ if (prepared.trimmedOverlaps > 0) {
392
+ warnings.push(`Track "${selection.name}": trimmed ${prepared.trimmedOverlaps} overlapping repeated-pitch note(s).`);
393
+ }
394
+ preparedSelections.push({ selection, sourceTrack, notes: prepared.notes });
395
+ }
396
+ if (preparedSelections.length === 0) {
397
+ warnings.push('No tracks were selected for import; the imported score has no tracks.');
398
+ }
399
+ const allNotes = preparedSelections.flatMap(p => p.notes);
400
+ const keySignature = options.detectKey
401
+ ? detectKeySignature(allNotes)
402
+ : DEFAULT_KEY_SIGNATURE;
403
+ const respelledSelections = preparedSelections.map(p => ({
404
+ ...p,
405
+ notes: p.notes.map(n => ({
406
+ ...n,
407
+ pitch: midiToPitch(pitchToMidi(n.pitch), keySignature),
408
+ })),
409
+ }));
410
+ const endTick = respelledSelections.reduce((max, p) => {
411
+ const localMax = p.notes.reduce((m, n) => Math.max(m, n.startTick + n.durationTicks), 0);
412
+ return Math.max(max, localMax);
413
+ }, 0);
414
+ const spans = buildMeasureSpans(timeSignatureChanges, SCORE_PPQ, endTick);
415
+ const tracks = respelledSelections.flatMap(prepared => options.pianoStaffSplit
416
+ ? buildSplitTracks(prepared, spans, keySignature, options.splitPointMidi)
417
+ : [buildSingleTrack(prepared, spans, keySignature)]);
418
+ const now = new Date().toISOString();
419
+ const score = {
420
+ id: createId(),
421
+ version: 1,
422
+ ppq: SCORE_PPQ,
423
+ metadata: {
424
+ // The neutral model makes `name` optional, since not every Standard MIDI
425
+ // File carries one; @tonejs/midi papered over that with an empty string.
426
+ title: (midi.header.name ?? '').trim().length > 0
427
+ ? midi.header.name.trim()
428
+ : 'Imported MIDI',
429
+ createdAt: now,
430
+ updatedAt: now,
431
+ },
432
+ tempoMap,
433
+ tracks,
434
+ };
435
+ return { score, warnings };
436
+ }
437
+ /** Decodes `data` and imports it. The codec is the only platform-bound part. */
438
+ export function importMidi(data, options, codec) {
439
+ return importMidiFile(codec.decode(data), options);
440
+ }
441
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.js","sourceRoot":"","sources":["../../src/midi/import.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { KeySignature, NoteEvent } from '@sudobility/music_types';
2
+ /**
3
+ * Estimates the best-fitting `KeySignature` for `notes` by correlating
4
+ * their duration-weighted pitch-class histogram against the
5
+ * Krumhansl-Kessler profiles at all 12 tonics, both major and minor, and
6
+ * returning the highest-correlating (tonic, mode) pair. Falls back to C
7
+ * major for an empty or entirely-silent `notes` list (histogram all-zero,
8
+ * for which correlation is undefined).
9
+ */
10
+ export declare function detectKeySignature(notes: NoteEvent[]): KeySignature;
11
+ //# sourceMappingURL=key-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-detection.d.ts","sourceRoot":"","sources":["../../src/midi/key-detection.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAmEvE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,YAAY,CA6BnE"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Krumhansl-style key estimation for MIDI import (spec §15): builds a
3
+ * duration-weighted pitch-class histogram from a set of notes and
4
+ * correlates it, at every tonic/mode rotation, against the classic
5
+ * Krumhansl-Kessler major/minor key profiles, returning the best-matching
6
+ * `KeySignature`. A simple template match, not a full Krumhansl-Schmuckler
7
+ * implementation (spec explicitly allows this: "simple template match is
8
+ * fine") — ambiguous or atonal input has no guaranteed "correct" answer
9
+ * either way.
10
+ */
11
+ import { pitchToMidi } from '@sudobility/music_types';
12
+ // Inlined from the deleted services/generation/music-theory.ts (mock stack
13
+ // removed in Phase 2); pitch-class math only.
14
+ function majorTonicPitchClass(fifths) {
15
+ return (((7 * fifths) % 12) + 12) % 12;
16
+ }
17
+ /** The `KeySignature` (fifths -7..7) whose tonic is `pitchClass` in `mode`. */
18
+ function keySignatureForTonicPitchClass(pitchClass, mode) {
19
+ const majorPitchClass = mode === 'major' ? pitchClass : (pitchClass + 3) % 12;
20
+ for (let fifths = -7; fifths <= 7; fifths += 1) {
21
+ if (majorTonicPitchClass(fifths) === majorPitchClass) {
22
+ return { fifths, mode };
23
+ }
24
+ }
25
+ throw new Error(`keySignatureForTonicPitchClass: no key signature found for pitch class ${pitchClass}`);
26
+ }
27
+ /** Krumhansl-Kessler major-key profile (tonic at index 0), a canonical perceptual "fit" weighting per scale degree. */
28
+ const MAJOR_PROFILE = [
29
+ 6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88,
30
+ ];
31
+ /** Krumhansl-Kessler minor-key profile (tonic at index 0). */
32
+ const MINOR_PROFILE = [
33
+ 6.33, 2.68, 3.52, 5.38, 2.6, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17,
34
+ ];
35
+ const PITCH_CLASSES = 12;
36
+ /** Pearson correlation coefficient between two equal-length numeric vectors; 0 if either has zero variance. */
37
+ function correlate(a, b) {
38
+ const n = a.length;
39
+ const meanA = a.reduce((sum, v) => sum + v, 0) / n;
40
+ const meanB = b.reduce((sum, v) => sum + v, 0) / n;
41
+ let numerator = 0;
42
+ let sumSqA = 0;
43
+ let sumSqB = 0;
44
+ for (let i = 0; i < n; i += 1) {
45
+ const da = a[i] - meanA;
46
+ const db = b[i] - meanB;
47
+ numerator += da * db;
48
+ sumSqA += da * da;
49
+ sumSqB += db * db;
50
+ }
51
+ const denominator = Math.sqrt(sumSqA * sumSqB);
52
+ return denominator === 0 ? 0 : numerator / denominator;
53
+ }
54
+ /** Builds a duration-weighted pitch-class (0-11) histogram from `notes`. */
55
+ function pitchClassHistogram(notes) {
56
+ const histogram = new Array(PITCH_CLASSES).fill(0);
57
+ for (const note of notes) {
58
+ const pitchClass = ((pitchToMidi(note.pitch) % PITCH_CLASSES) + PITCH_CLASSES) %
59
+ PITCH_CLASSES;
60
+ histogram[pitchClass] += note.durationTicks;
61
+ }
62
+ return histogram;
63
+ }
64
+ /**
65
+ * Estimates the best-fitting `KeySignature` for `notes` by correlating
66
+ * their duration-weighted pitch-class histogram against the
67
+ * Krumhansl-Kessler profiles at all 12 tonics, both major and minor, and
68
+ * returning the highest-correlating (tonic, mode) pair. Falls back to C
69
+ * major for an empty or entirely-silent `notes` list (histogram all-zero,
70
+ * for which correlation is undefined).
71
+ */
72
+ export function detectKeySignature(notes) {
73
+ const histogram = pitchClassHistogram(notes);
74
+ if (histogram.every(v => v === 0)) {
75
+ return { fifths: 0, mode: 'major' };
76
+ }
77
+ let bestScore = -Infinity;
78
+ let bestTonic = 0;
79
+ let bestMode = 'major';
80
+ for (let tonic = 0; tonic < PITCH_CLASSES; tonic += 1) {
81
+ for (const [mode, profile] of [
82
+ ['major', MAJOR_PROFILE],
83
+ ['minor', MINOR_PROFILE],
84
+ ]) {
85
+ const expected = Array.from({ length: PITCH_CLASSES }, (_, pc) => profile[(pc - tonic + PITCH_CLASSES) % PITCH_CLASSES]);
86
+ const score = correlate(histogram, expected);
87
+ if (score > bestScore) {
88
+ bestScore = score;
89
+ bestTonic = tonic;
90
+ bestMode = mode;
91
+ }
92
+ }
93
+ }
94
+ return keySignatureForTonicPitchClass(bestTonic, bestMode);
95
+ }
96
+ //# sourceMappingURL=key-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-detection.js","sourceRoot":"","sources":["../../src/midi/key-detection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { KeySignature, Measure, NoteEvent, TimeSignature } from '@sudobility/music_types';
2
+ export declare const DEFAULT_TIME_SIGNATURE: TimeSignature;
3
+ export type TimeSignatureChange = {
4
+ tick: number;
5
+ timeSignature: TimeSignature;
6
+ };
7
+ export type MeasureSpan = {
8
+ index: number;
9
+ startTick: number;
10
+ durationTicks: number;
11
+ timeSignature: TimeSignature;
12
+ };
13
+ /**
14
+ * Computes consecutive measure spans covering `[0, endTick)` from a set of
15
+ * time-signature changes (need not include a tick-0 entry — one is
16
+ * synthesized at `DEFAULT_TIME_SIGNATURE` if missing; need not be sorted).
17
+ * Always returns at least one span (a silent/empty file still gets a single
18
+ * default-meter measure, mirroring `createEmptyScore`'s "at least one
19
+ * measure" convention).
20
+ *
21
+ * A change tick that doesn't land exactly on a measure boundary of the
22
+ * preceding meter (uncommon, but not disallowed by the MIDI format) is
23
+ * absorbed into whichever measure is in progress when it's reached, rather
24
+ * than truncating a partial measure — a documented simplification, not a
25
+ * full mid-measure meter change.
26
+ */
27
+ export declare function buildMeasureSpans(changes: TimeSignatureChange[], ppq: number, endTick: number): MeasureSpan[];
28
+ /**
29
+ * Assembles one track's `Measure[]` from `voiceLanes` — each inner array a
30
+ * "notated voice" of mutually non-overlapping, absolute-tick `NoteEvent`s
31
+ * (as produced by `allocateVoices`) — over `spans`. A note spanning more
32
+ * than one span is split at every internal span boundary via
33
+ * `splitNoteAcrossMeasures` (producing a tied chain); each span's voice
34
+ * content is then gap-filled with rests. An empty `voiceLanes` (a track
35
+ * with no notes at all) still produces one fully-rested voice per measure,
36
+ * satisfying the "every measure has >=1 voice per track" invariant.
37
+ */
38
+ export declare function assembleTrackMeasures(voiceLanes: NoteEvent[][], spans: MeasureSpan[], keySignature: KeySignature, trackId: string): Measure[];
39
+ //# sourceMappingURL=measures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measures.d.ts","sourceRoot":"","sources":["../../src/midi/measures.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EAEP,SAAS,EACT,aAAa,EACd,MAAM,yBAAyB,CAAC;AAGjC,eAAO,MAAM,sBAAsB,EAAE,aAGpC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,mBAAmB,EAAE,EAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,CA4Cf;AAgDD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,EAAE,EAAE,EACzB,KAAK,EAAE,WAAW,EAAE,EACpB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,EAAE,CAqCX"}