musicxml-io 0.12.0 → 0.12.2

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 (98) hide show
  1. package/README.md +96 -8
  2. package/dist/abc-HXITSXCS.js +9 -0
  3. package/dist/abc-IAD53SFI.mjs +11 -0
  4. package/dist/abc-MTGVOYKS.js +11 -0
  5. package/dist/abc-R7DIJSHG.mjs +9 -0
  6. package/dist/abc.d.mts +67 -0
  7. package/dist/abc.d.ts +67 -0
  8. package/dist/abc.js +17 -0
  9. package/dist/abc.mjs +17 -0
  10. package/dist/accessors/index.d.mts +1 -1
  11. package/dist/accessors/index.d.ts +1 -1
  12. package/dist/browser.d.mts +11 -187
  13. package/dist/browser.d.ts +11 -187
  14. package/dist/browser.js +30 -6
  15. package/dist/browser.mjs +96 -72
  16. package/dist/chunk-2EH2MGBY.js +1799 -0
  17. package/dist/chunk-33IQXDOH.js +1 -0
  18. package/dist/chunk-5EK7K5X3.js +29 -0
  19. package/dist/chunk-5VF442RA.mjs +29 -0
  20. package/dist/chunk-626TXMVP.js +706 -0
  21. package/dist/chunk-63UT3IEY.mjs +2788 -0
  22. package/dist/chunk-6EKVRPFF.js +407 -0
  23. package/dist/chunk-776XPK66.js +45 -0
  24. package/dist/chunk-7D4MLQKO.js +69 -0
  25. package/dist/chunk-A2CGW2UF.js +25 -0
  26. package/dist/chunk-AOSK4RGV.mjs +45 -0
  27. package/dist/{chunk-FQ3NEYIV.js → chunk-ASPCIBWD.js} +127 -1249
  28. package/dist/chunk-BDHPMB7Q.mjs +61 -0
  29. package/dist/chunk-BHHOS2E6.mjs +407 -0
  30. package/dist/chunk-F2UCOAO6.mjs +155 -0
  31. package/dist/chunk-HP4DAUGT.js +2788 -0
  32. package/dist/chunk-JF5TM534.js +1065 -0
  33. package/dist/chunk-K6DSQPVF.mjs +706 -0
  34. package/dist/chunk-KJK2IGNN.mjs +0 -0
  35. package/dist/chunk-KYGLANYG.mjs +2354 -0
  36. package/dist/chunk-LFQXT7PV.mjs +69 -0
  37. package/dist/chunk-LS5SHP75.js +155 -0
  38. package/dist/chunk-MMIZQMIO.mjs +25 -0
  39. package/dist/chunk-NIUEAHPN.js +2354 -0
  40. package/dist/{chunk-EWLB5X64.js → chunk-PMPROVJW.js} +66 -770
  41. package/dist/chunk-QFYOH4DY.mjs +0 -0
  42. package/dist/chunk-QKUNJEVW.mjs +1401 -0
  43. package/dist/chunk-SBT2RMUW.mjs +1065 -0
  44. package/dist/chunk-TCXDBRRT.mjs +1799 -0
  45. package/dist/chunk-WNQIUYWY.js +61 -0
  46. package/dist/{chunk-I4X4GRMP.mjs → chunk-WS5LSW3S.mjs} +13 -1135
  47. package/dist/chunk-YKMNCYOD.js +1 -0
  48. package/dist/{chunk-UMEH3ENQ.mjs → chunk-ZRKJKEP7.mjs} +0 -704
  49. package/dist/chunk-ZSEJQPBO.js +1401 -0
  50. package/dist/core.d.mts +33 -0
  51. package/dist/core.d.ts +33 -0
  52. package/dist/core.js +524 -0
  53. package/dist/core.mjs +524 -0
  54. package/dist/file.d.mts +37 -0
  55. package/dist/file.d.ts +37 -0
  56. package/dist/file.js +14 -0
  57. package/dist/file.mjs +14 -0
  58. package/dist/index-C9lLZwol.d.mts +185 -0
  59. package/dist/index-DNA970t1.d.ts +185 -0
  60. package/dist/index.d.mts +12 -38
  61. package/dist/index.d.ts +12 -38
  62. package/dist/index.js +34 -49
  63. package/dist/index.mjs +101 -116
  64. package/dist/midi.d.mts +54 -0
  65. package/dist/midi.d.ts +54 -0
  66. package/dist/midi.js +10 -0
  67. package/dist/midi.mjs +10 -0
  68. package/dist/musicxml-DWPClEqa.d.ts +25 -0
  69. package/dist/musicxml-DzuFr2c3.d.mts +25 -0
  70. package/dist/musicxml-compressed-7NUEGLSF.mjs +8 -0
  71. package/dist/musicxml-compressed-7RL6QRF7.js +12 -0
  72. package/dist/musicxml-compressed-WPEIBOZ3.js +8 -0
  73. package/dist/musicxml-compressed-ZHZYFJ7H.mjs +12 -0
  74. package/dist/musicxml-compressed.d.mts +33 -0
  75. package/dist/musicxml-compressed.d.ts +33 -0
  76. package/dist/musicxml-compressed.js +18 -0
  77. package/dist/musicxml-compressed.mjs +18 -0
  78. package/dist/musicxml.d.mts +8 -0
  79. package/dist/musicxml.d.ts +8 -0
  80. package/dist/musicxml.js +14 -0
  81. package/dist/musicxml.mjs +14 -0
  82. package/dist/operations/index.d.mts +1234 -2
  83. package/dist/operations/index.d.ts +1234 -2
  84. package/dist/operations/index.js +7 -3
  85. package/dist/operations/index.mjs +6 -2
  86. package/dist/playback-timeline-CVRHsikj.d.mts +129 -0
  87. package/dist/playback-timeline-CmWA8YDy.d.ts +129 -0
  88. package/dist/query/index.d.mts +3 -128
  89. package/dist/query/index.d.ts +3 -128
  90. package/dist/query/index.js +4 -2
  91. package/dist/query/index.mjs +7 -5
  92. package/dist/{types-Cy0veafI.d.mts → types-B-RGHY0c.d.mts} +1 -1
  93. package/dist/{types-Cy0veafI.d.ts → types-B-RGHY0c.d.ts} +1 -1
  94. package/package.json +36 -2
  95. package/dist/chunk-T2USSS5E.mjs +0 -8939
  96. package/dist/chunk-UT7LQW6V.js +0 -8939
  97. package/dist/index-CbUtBHLD.d.ts +0 -1415
  98. package/dist/index-CuMPBK7U.d.mts +0 -1415
@@ -0,0 +1,706 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/query/playback-sequence.ts
2
+ function parseEndingNumbers(numberStr) {
3
+ const numbers = [];
4
+ const parts = numberStr.split(/[,\s]+/);
5
+ for (const part of parts) {
6
+ if (part.includes("-")) {
7
+ const [start, end] = part.split("-").map((s) => parseInt(s.trim(), 10));
8
+ if (!isNaN(start) && !isNaN(end)) {
9
+ for (let i = start; i <= end; i++) {
10
+ numbers.push(i);
11
+ }
12
+ }
13
+ } else {
14
+ const num = parseInt(part.trim(), 10);
15
+ if (!isNaN(num)) {
16
+ numbers.push(num);
17
+ }
18
+ }
19
+ }
20
+ return numbers.length > 0 ? numbers : [1];
21
+ }
22
+ function parseJumpText(text) {
23
+ const t = text.toLowerCase().replace(/\s+/g, " ").trim();
24
+ if (t.includes("d.c. al coda") || t.includes("da capo al coda")) return "dc_al_coda";
25
+ if (t.includes("d.c. al fine") || t.includes("da capo al fine")) return "dc_al_fine";
26
+ if (t.includes("d.c.") || t.includes("da capo")) return "dc";
27
+ if (t.includes("d.s. al coda") || t.includes("dal segno al coda")) return "ds_al_coda";
28
+ if (t.includes("d.s. al fine") || t.includes("dal segno al fine")) return "ds_al_fine";
29
+ if (t.includes("d.s.") || t.includes("dal segno")) return "ds";
30
+ if (t === "fine") return "fine";
31
+ if (t.includes("to coda") || t.includes("al coda")) return "to_coda";
32
+ if (t === "coda" || t === "\u{1D14C}") return "coda";
33
+ if (t === "segno" || t === "\u{1D10B}") return "segno";
34
+ return null;
35
+ }
36
+ function getFirstWordsText(entry) {
37
+ for (const dt of entry.directionTypes) {
38
+ if (dt.kind === "words" && dt.text) return dt.text;
39
+ }
40
+ return void 0;
41
+ }
42
+ function hasDirectionKind(entry, kind) {
43
+ return entry.directionTypes.some((dt) => dt.kind === kind);
44
+ }
45
+ function extractPlaybackControls(part) {
46
+ const controls = {
47
+ repeatStarts: [],
48
+ repeatEnds: [],
49
+ voltas: [],
50
+ jumps: [],
51
+ segnoIndex: null,
52
+ codaIndex: null,
53
+ fineIndex: null,
54
+ toCodaIndex: null
55
+ };
56
+ const soundJumps = [];
57
+ for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
58
+ const measure = part.measures[measureIndex];
59
+ if (measure.barlines) {
60
+ for (const barline of measure.barlines) {
61
+ if (barline.repeat) {
62
+ if (barline.repeat.direction === "forward") {
63
+ controls.repeatStarts.push(measureIndex);
64
+ } else if (barline.repeat.direction === "backward") {
65
+ controls.repeatEnds.push({
66
+ measureIndex,
67
+ times: _nullishCoalesce(barline.repeat.times, () => ( 2))
68
+ });
69
+ }
70
+ }
71
+ if (barline.ending) {
72
+ const numbers = parseEndingNumbers(barline.ending.number);
73
+ controls.voltas.push({
74
+ measureIndex,
75
+ numbers,
76
+ type: barline.ending.type
77
+ });
78
+ }
79
+ }
80
+ }
81
+ for (const entry of measure.entries) {
82
+ if (entry.type === "direction") {
83
+ const dir = entry;
84
+ if (hasDirectionKind(dir, "segno") && controls.segnoIndex === null) {
85
+ controls.segnoIndex = measureIndex;
86
+ }
87
+ if (hasDirectionKind(dir, "coda") && controls.codaIndex === null) {
88
+ controls.codaIndex = measureIndex;
89
+ }
90
+ const words = getFirstWordsText(dir);
91
+ if (words) {
92
+ const jt = parseJumpText(words);
93
+ if (jt === "fine") {
94
+ if (controls.fineIndex === null) controls.fineIndex = measureIndex;
95
+ } else if (jt === "to_coda") {
96
+ if (controls.toCodaIndex === null) controls.toCodaIndex = measureIndex;
97
+ } else if (jt === "coda") {
98
+ if (controls.codaIndex === null) controls.codaIndex = measureIndex;
99
+ } else if (jt === "segno") {
100
+ if (controls.segnoIndex === null) controls.segnoIndex = measureIndex;
101
+ } else if (jt) {
102
+ controls.jumps.push({ measureIndex, type: jt });
103
+ }
104
+ }
105
+ } else if (entry.type === "sound") {
106
+ const sound = entry;
107
+ if (sound.segno && controls.segnoIndex === null) controls.segnoIndex = measureIndex;
108
+ if (sound.coda && controls.codaIndex === null) controls.codaIndex = measureIndex;
109
+ if (sound.tocoda && controls.toCodaIndex === null) controls.toCodaIndex = measureIndex;
110
+ if (sound.fine && controls.fineIndex === null) controls.fineIndex = measureIndex;
111
+ if (sound.dacapo) soundJumps.push({ measureIndex, base: "dc" });
112
+ if (sound.dalsegno) soundJumps.push({ measureIndex, base: "ds" });
113
+ }
114
+ }
115
+ }
116
+ for (const sj of soundJumps) {
117
+ if (controls.jumps.some((j) => j.measureIndex === sj.measureIndex)) continue;
118
+ let type;
119
+ if (controls.fineIndex !== null) {
120
+ type = sj.base === "dc" ? "dc_al_fine" : "ds_al_fine";
121
+ } else if (controls.toCodaIndex !== null || controls.codaIndex !== null) {
122
+ type = sj.base === "dc" ? "dc_al_coda" : "ds_al_coda";
123
+ } else {
124
+ type = sj.base;
125
+ }
126
+ controls.jumps.push({ measureIndex: sj.measureIndex, type });
127
+ }
128
+ return controls;
129
+ }
130
+ function buildVoltaRanges(voltas, measureCount) {
131
+ const voltaRanges = /* @__PURE__ */ new Map();
132
+ let currentVoltaStart = null;
133
+ let currentVoltaNumbers = [];
134
+ for (const volta of voltas) {
135
+ if (volta.type === "start") {
136
+ currentVoltaStart = volta.measureIndex;
137
+ currentVoltaNumbers = volta.numbers;
138
+ } else if ((volta.type === "stop" || volta.type === "discontinue") && currentVoltaStart !== null) {
139
+ for (let i = currentVoltaStart; i <= volta.measureIndex; i++) {
140
+ voltaRanges.set(i, currentVoltaNumbers);
141
+ }
142
+ currentVoltaStart = null;
143
+ }
144
+ }
145
+ if (currentVoltaStart !== null) {
146
+ for (let i = currentVoltaStart; i < measureCount; i++) {
147
+ voltaRanges.set(i, currentVoltaNumbers);
148
+ }
149
+ }
150
+ return voltaRanges;
151
+ }
152
+ function generatePlaybackSequence(score, options) {
153
+ const partIndex = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.partIndex]), () => ( 0));
154
+ const part = score.parts[partIndex];
155
+ if (!part) return [];
156
+ const measureCount = part.measures.length;
157
+ const sequence = [];
158
+ if (measureCount === 0) return sequence;
159
+ const controls = extractPlaybackControls(part);
160
+ if (controls.repeatStarts.length === 0 && controls.repeatEnds.length === 0 && controls.voltas.length === 0 && controls.jumps.length === 0) {
161
+ for (let i = 0; i < measureCount; i++) {
162
+ sequence.push({ measureIndex: i, repeatIteration: 0 });
163
+ }
164
+ return sequence;
165
+ }
166
+ const voltaRanges = buildVoltaRanges(controls.voltas, measureCount);
167
+ const sortedRepeatStarts = [...controls.repeatStarts].sort((a, b) => a - b);
168
+ const sortedRepeatEnds = [...controls.repeatEnds].sort((a, b) => a.measureIndex - b.measureIndex);
169
+ const repeatSections = /* @__PURE__ */ new Map();
170
+ const voltaToRepeatEnd = /* @__PURE__ */ new Map();
171
+ const usedRepeatStarts = /* @__PURE__ */ new Set();
172
+ let lastRepeatEndMeasure = -1;
173
+ for (const repeatEnd of sortedRepeatEnds) {
174
+ let startIndex = null;
175
+ for (const startMeasure of sortedRepeatStarts) {
176
+ if (startMeasure <= repeatEnd.measureIndex && startMeasure > lastRepeatEndMeasure && !usedRepeatStarts.has(startMeasure)) {
177
+ startIndex = startMeasure;
178
+ } else if (startMeasure > repeatEnd.measureIndex) {
179
+ break;
180
+ }
181
+ }
182
+ if (startIndex === null) {
183
+ startIndex = lastRepeatEndMeasure + 1;
184
+ } else {
185
+ usedRepeatStarts.add(startIndex);
186
+ }
187
+ repeatSections.set(repeatEnd.measureIndex, {
188
+ startIndex,
189
+ times: repeatEnd.times
190
+ });
191
+ lastRepeatEndMeasure = repeatEnd.measureIndex;
192
+ voltaRanges.forEach((_iterations, voltaMeasure) => {
193
+ const isInsideRepeat = voltaMeasure >= startIndex && voltaMeasure <= repeatEnd.measureIndex;
194
+ const isImmediatelyAfter = voltaMeasure > repeatEnd.measureIndex && !voltaToRepeatEnd.has(voltaMeasure);
195
+ if (isInsideRepeat) {
196
+ voltaToRepeatEnd.set(voltaMeasure, repeatEnd.measureIndex);
197
+ } else if (isImmediatelyAfter) {
198
+ let belongsToLaterRepeat = false;
199
+ for (const otherEnd of sortedRepeatEnds) {
200
+ if (otherEnd.measureIndex > repeatEnd.measureIndex) {
201
+ const otherSection = repeatSections.get(otherEnd.measureIndex);
202
+ if (otherSection && voltaMeasure >= otherSection.startIndex && voltaMeasure <= otherEnd.measureIndex) {
203
+ belongsToLaterRepeat = true;
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ if (!belongsToLaterRepeat) {
209
+ voltaToRepeatEnd.set(voltaMeasure, repeatEnd.measureIndex);
210
+ }
211
+ }
212
+ });
213
+ }
214
+ let currentMeasure = 0;
215
+ const repeatCounts = /* @__PURE__ */ new Map();
216
+ let lastRepeatEndIndex = null;
217
+ let lastRepeatIteration = 0;
218
+ let inRepeatJump = false;
219
+ let stopAtFine = false;
220
+ let jumpToCoda = false;
221
+ const maxIterations = measureCount * 10 + 10;
222
+ let iterations = 0;
223
+ while (currentMeasure < measureCount && iterations < maxIterations) {
224
+ iterations++;
225
+ const voltaIterations = voltaRanges.get(currentMeasure);
226
+ let repeatIteration = 0;
227
+ let inRepeatSection = false;
228
+ let currentRepeatEndIndex = null;
229
+ for (const [endIndex, section] of repeatSections) {
230
+ if (currentMeasure >= section.startIndex && currentMeasure <= endIndex) {
231
+ inRepeatSection = true;
232
+ currentRepeatEndIndex = endIndex;
233
+ repeatIteration = repeatCounts.get(endIndex) || 1;
234
+ break;
235
+ }
236
+ }
237
+ if (inRepeatSection && currentRepeatEndIndex !== null) {
238
+ lastRepeatEndIndex = currentRepeatEndIndex;
239
+ lastRepeatIteration = repeatIteration;
240
+ }
241
+ if (voltaIterations) {
242
+ let effectiveIteration = repeatIteration;
243
+ if (!inRepeatSection) {
244
+ const associatedRepeatEnd = voltaToRepeatEnd.get(currentMeasure);
245
+ if (associatedRepeatEnd !== void 0) {
246
+ effectiveIteration = repeatCounts.get(associatedRepeatEnd) || 1;
247
+ } else if (lastRepeatEndIndex !== null) {
248
+ effectiveIteration = lastRepeatIteration;
249
+ }
250
+ }
251
+ if (!voltaIterations.includes(effectiveIteration)) {
252
+ currentMeasure++;
253
+ continue;
254
+ }
255
+ }
256
+ sequence.push({
257
+ measureIndex: currentMeasure,
258
+ repeatIteration: inRepeatSection ? repeatIteration : 0
259
+ });
260
+ if (stopAtFine && controls.fineIndex === currentMeasure) {
261
+ break;
262
+ }
263
+ if (jumpToCoda && controls.toCodaIndex === currentMeasure && controls.codaIndex !== null) {
264
+ currentMeasure = controls.codaIndex;
265
+ jumpToCoda = false;
266
+ continue;
267
+ }
268
+ const repeatSection = repeatSections.get(currentMeasure);
269
+ if (repeatSection) {
270
+ const currentCount = repeatCounts.get(currentMeasure) || 1;
271
+ if (currentCount < repeatSection.times) {
272
+ repeatCounts.set(currentMeasure, currentCount + 1);
273
+ currentMeasure = repeatSection.startIndex;
274
+ continue;
275
+ }
276
+ }
277
+ if (voltaIterations && !inRepeatSection) {
278
+ const associatedRepeatEnd = voltaToRepeatEnd.get(currentMeasure);
279
+ if (associatedRepeatEnd !== void 0) {
280
+ const repeatSectionForVolta = repeatSections.get(associatedRepeatEnd);
281
+ if (repeatSectionForVolta) {
282
+ const currentCount = repeatCounts.get(associatedRepeatEnd) || 1;
283
+ if (currentCount < repeatSectionForVolta.times) {
284
+ repeatCounts.set(associatedRepeatEnd, currentCount + 1);
285
+ currentMeasure = repeatSectionForVolta.startIndex;
286
+ continue;
287
+ }
288
+ }
289
+ }
290
+ }
291
+ const jumpAtMeasure = controls.jumps.find((j) => j.measureIndex === currentMeasure);
292
+ if (jumpAtMeasure && !inRepeatJump) {
293
+ inRepeatJump = true;
294
+ switch (jumpAtMeasure.type) {
295
+ case "dc":
296
+ currentMeasure = 0;
297
+ continue;
298
+ case "dc_al_fine":
299
+ currentMeasure = 0;
300
+ stopAtFine = true;
301
+ continue;
302
+ case "dc_al_coda":
303
+ currentMeasure = 0;
304
+ jumpToCoda = true;
305
+ continue;
306
+ case "ds":
307
+ if (controls.segnoIndex !== null) {
308
+ currentMeasure = controls.segnoIndex;
309
+ continue;
310
+ }
311
+ break;
312
+ case "ds_al_fine":
313
+ if (controls.segnoIndex !== null) {
314
+ currentMeasure = controls.segnoIndex;
315
+ stopAtFine = true;
316
+ continue;
317
+ }
318
+ break;
319
+ case "ds_al_coda":
320
+ if (controls.segnoIndex !== null) {
321
+ currentMeasure = controls.segnoIndex;
322
+ jumpToCoda = true;
323
+ continue;
324
+ }
325
+ break;
326
+ }
327
+ }
328
+ currentMeasure++;
329
+ }
330
+ return sequence;
331
+ }
332
+ function hasPlaybackControls(score, options) {
333
+ const part = score.parts[_nullishCoalesce(_optionalChain([options, 'optionalAccess', _2 => _2.partIndex]), () => ( 0))];
334
+ if (!part) return false;
335
+ const controls = extractPlaybackControls(part);
336
+ return controls.repeatStarts.length > 0 || controls.repeatEnds.length > 0 || controls.voltas.length > 0 || controls.jumps.length > 0 || controls.segnoIndex !== null || controls.codaIndex !== null;
337
+ }
338
+
339
+ // src/query/playback-timeline.ts
340
+ var DEFAULT_FERMATA_HOLD = 1.75;
341
+ var DEFAULT_CAESURA_SECONDS = 0.4;
342
+ var DEFAULT_BREATH_SECONDS = 0.25;
343
+ var DEFAULT_RAMP_STEPS = 12;
344
+ function metronomeBpm(perMinute) {
345
+ if (perMinute === void 0) return null;
346
+ const bpm = typeof perMinute === "number" ? perMinute : parseFloat(perMinute);
347
+ return isNaN(bpm) ? null : bpm;
348
+ }
349
+ function measureMaxPosition(measure) {
350
+ let position = 0;
351
+ let max = 0;
352
+ for (const entry of measure.entries) {
353
+ if (entry.type === "note") {
354
+ if (!entry.chord && !entry.grace) {
355
+ position += entry.duration;
356
+ if (position > max) max = position;
357
+ }
358
+ } else if (entry.type === "backup") {
359
+ position -= entry.duration;
360
+ } else if (entry.type === "forward") {
361
+ position += entry.duration;
362
+ if (position > max) max = position;
363
+ }
364
+ }
365
+ return max;
366
+ }
367
+ function findTimeSignature(part, measureNumber) {
368
+ const targetMeasure = parseInt(String(measureNumber), 10);
369
+ let time;
370
+ for (const measure of part.measures) {
371
+ const mNum = parseInt(measure.number, 10);
372
+ if (!isNaN(targetMeasure) && !isNaN(mNum) && mNum > targetMeasure) break;
373
+ if (_optionalChain([measure, 'access', _3 => _3.attributes, 'optionalAccess', _4 => _4.time])) {
374
+ time = {
375
+ beats: parseInt(measure.attributes.time.beats, 10) || 4,
376
+ beatType: measure.attributes.time.beatType
377
+ };
378
+ }
379
+ }
380
+ return time;
381
+ }
382
+ function measureEndTick(measure, part, divisions, measureStartTick, maxPosition, ticksPerQuarterNote) {
383
+ const actualTicks = Math.round(maxPosition * ticksPerQuarterNote / divisions);
384
+ if (actualTicks > 0) {
385
+ return measureStartTick + actualTicks;
386
+ }
387
+ if (!measure.implicit) {
388
+ const timeAttrs = findTimeSignature(part, measure.number);
389
+ if (timeAttrs) {
390
+ const measureDuration = timeAttrs.beats / timeAttrs.beatType * 4 * divisions;
391
+ const calculatedTicks = Math.round(measureDuration * ticksPerQuarterNote / divisions);
392
+ return measureStartTick + calculatedTicks;
393
+ }
394
+ }
395
+ return measureStartTick;
396
+ }
397
+ function hasFermata(note) {
398
+ return _nullishCoalesce(_optionalChain([note, 'access', _5 => _5.notations, 'optionalAccess', _6 => _6.some, 'call', _7 => _7((n) => n.type === "fermata")]), () => ( false));
399
+ }
400
+ function pauseArticulation(note) {
401
+ if (!note.notations) return null;
402
+ for (const n of note.notations) {
403
+ if (n.type === "articulation") {
404
+ if (n.articulation === "caesura") return "caesura";
405
+ if (n.articulation === "breath-mark") return "breath";
406
+ }
407
+ }
408
+ return null;
409
+ }
410
+ function tempoRampWord(text) {
411
+ const t = text.trim().toLowerCase().replace(/[.\s]+$/, "");
412
+ if (!t) return null;
413
+ if (/^(rit|riten|rall|allarg|slarg|cala|smorz|morend)/.test(t)) return "rit";
414
+ if (/^(accel|string|affret|incalz|pi[uù]\s*mosso)/.test(t)) return "accel";
415
+ return null;
416
+ }
417
+ function buildGridTimeline(score, ticksPerQuarterNote, sequence, options = {}) {
418
+ const rawTempo = [];
419
+ const measures = [];
420
+ const rampMarkers = [];
421
+ const stretches = [];
422
+ if (score.parts.length === 0) {
423
+ return { tempoEvents: [], measures, totalTicks: 0, expressions: [] };
424
+ }
425
+ const fermataHold = _nullishCoalesce(options.fermataHoldMultiplier, () => ( DEFAULT_FERMATA_HOLD));
426
+ const caesuraSeconds = _nullishCoalesce(options.caesuraSeconds, () => ( DEFAULT_CAESURA_SECONDS));
427
+ const breathSeconds = _nullishCoalesce(options.breathSeconds, () => ( DEFAULT_BREATH_SECONDS));
428
+ const rampSteps = Math.max(1, Math.round(_nullishCoalesce(options.tempoRampSteps, () => ( DEFAULT_RAMP_STEPS))));
429
+ const defaultTempo = _nullishCoalesce(options.defaultTempo, () => ( 120));
430
+ const part = score.parts[0];
431
+ let divisions = 1;
432
+ let currentTick = 0;
433
+ let prevailingBpm = defaultTempo;
434
+ const pauseFactor = (windowTicks, bpm, pauseSeconds) => {
435
+ const baseSeconds = windowTicks / ticksPerQuarterNote * (60 / bpm);
436
+ if (baseSeconds <= 0) return 1;
437
+ return (baseSeconds + pauseSeconds) / baseSeconds;
438
+ };
439
+ for (const { measureIndex, repeatIteration } of sequence) {
440
+ const measure = part.measures[measureIndex];
441
+ if (!measure) continue;
442
+ if (_optionalChain([measure, 'access', _8 => _8.attributes, 'optionalAccess', _9 => _9.divisions])) {
443
+ divisions = measure.attributes.divisions;
444
+ }
445
+ const measureStartTick = currentTick;
446
+ let position = 0;
447
+ let chordBasePosition = 0;
448
+ const tickAt = (pos) => measureStartTick + Math.round(pos * ticksPerQuarterNote / divisions);
449
+ for (const entry of measure.entries) {
450
+ if (entry.type === "direction") {
451
+ for (const dirType of entry.directionTypes) {
452
+ if (dirType.kind === "metronome") {
453
+ const bpm = metronomeBpm(dirType.perMinute);
454
+ if (bpm !== null) {
455
+ rawTempo.push({ tick: tickAt(position), bpm });
456
+ prevailingBpm = bpm;
457
+ }
458
+ } else if (dirType.kind === "words") {
459
+ const kind = tempoRampWord(dirType.text);
460
+ if (kind) rampMarkers.push({ tick: tickAt(position), kind });
461
+ }
462
+ }
463
+ if (_optionalChain([entry, 'access', _10 => _10.sound, 'optionalAccess', _11 => _11.tempo])) {
464
+ rawTempo.push({ tick: tickAt(position), bpm: entry.sound.tempo });
465
+ prevailingBpm = entry.sound.tempo;
466
+ }
467
+ } else if (entry.type === "sound") {
468
+ if (entry.tempo) {
469
+ rawTempo.push({ tick: tickAt(position), bpm: entry.tempo });
470
+ prevailingBpm = entry.tempo;
471
+ }
472
+ } else if (entry.type === "note") {
473
+ const notePos = entry.chord ? chordBasePosition : position;
474
+ const noteStartTick = tickAt(notePos);
475
+ const noteEndTick = tickAt(notePos + entry.duration);
476
+ if (noteEndTick > noteStartTick) {
477
+ if (fermataHold !== 1 && hasFermata(entry)) {
478
+ stretches.push({
479
+ startTick: noteStartTick,
480
+ endTick: noteEndTick,
481
+ factor: fermataHold,
482
+ type: "fermata"
483
+ });
484
+ }
485
+ const pause = pauseArticulation(entry);
486
+ if (pause) {
487
+ const seconds = pause === "caesura" ? caesuraSeconds : breathSeconds;
488
+ if (seconds > 0) {
489
+ const window = Math.min(noteEndTick - noteStartTick, ticksPerQuarterNote);
490
+ stretches.push({
491
+ startTick: noteEndTick - window,
492
+ endTick: noteEndTick,
493
+ factor: pauseFactor(window, prevailingBpm, seconds),
494
+ type: pause
495
+ });
496
+ }
497
+ }
498
+ }
499
+ if (!entry.chord) {
500
+ chordBasePosition = position;
501
+ position += entry.duration;
502
+ }
503
+ } else if (entry.type === "backup") {
504
+ position -= entry.duration;
505
+ } else if (entry.type === "forward") {
506
+ position += entry.duration;
507
+ }
508
+ }
509
+ const endTick = measureEndTick(
510
+ measure,
511
+ part,
512
+ divisions,
513
+ measureStartTick,
514
+ measureMaxPosition(measure),
515
+ ticksPerQuarterNote
516
+ );
517
+ measures.push({
518
+ measureIndex,
519
+ repeatIteration,
520
+ measureNumber: measure.number,
521
+ startTick: measureStartTick,
522
+ endTick
523
+ });
524
+ currentTick = endTick;
525
+ }
526
+ const { tempoEvents, expressions } = bakeTempoMap(
527
+ rawTempo,
528
+ rampMarkers,
529
+ stretches,
530
+ defaultTempo,
531
+ rampSteps
532
+ );
533
+ return { tempoEvents, measures, totalTicks: currentTick, expressions };
534
+ }
535
+ function bakeTempoMap(rawTempo, rampMarkers, stretches, defaultTempo, rampSteps) {
536
+ const sortedRaw = [...rawTempo].sort((a, b) => a.tick - b.tick);
537
+ const startBpm = sortedRaw.length > 0 && sortedRaw[0].tick === 0 ? sortedRaw[0].bpm : defaultTempo;
538
+ const baseChanges = [{ tick: 0, bpm: startBpm }];
539
+ let lastBpm = startBpm;
540
+ for (const ev of sortedRaw) {
541
+ if (ev.tick === 0) continue;
542
+ if (ev.bpm === lastBpm) continue;
543
+ if (baseChanges[baseChanges.length - 1].tick === ev.tick) {
544
+ baseChanges[baseChanges.length - 1].bpm = ev.bpm;
545
+ } else {
546
+ baseChanges.push({ tick: ev.tick, bpm: ev.bpm });
547
+ }
548
+ lastBpm = ev.bpm;
549
+ }
550
+ const baseBpmAt = (tick) => {
551
+ let i = baseChanges.length - 1;
552
+ while (i > 0 && baseChanges[i].tick > tick) i--;
553
+ return baseChanges[i].bpm;
554
+ };
555
+ const rampExpressions = [];
556
+ const rampPoints = [];
557
+ for (const marker of rampMarkers) {
558
+ const from = baseBpmAt(marker.tick);
559
+ const next = baseChanges.find((c) => c.tick > marker.tick && c.bpm !== from);
560
+ if (!next) continue;
561
+ const span = next.tick - marker.tick;
562
+ if (span <= 0) continue;
563
+ for (let k = 1; k < rampSteps; k++) {
564
+ const tick = Math.round(marker.tick + span * k / rampSteps);
565
+ if (tick <= marker.tick || tick >= next.tick) continue;
566
+ const bpm = from + (next.bpm - from) * k / rampSteps;
567
+ rampPoints.push({ tick, bpm });
568
+ }
569
+ rampExpressions.push({ type: marker.kind, startTick: marker.tick, endTick: next.tick });
570
+ }
571
+ const baseTicks = new Set(baseChanges.map((c) => c.tick));
572
+ const stepList = [...baseChanges];
573
+ for (const p of rampPoints) {
574
+ if (!baseTicks.has(p.tick)) stepList.push(p);
575
+ }
576
+ stepList.sort((a, b) => a.tick - b.tick);
577
+ const stepBpmAt = (tick) => {
578
+ let i = stepList.length - 1;
579
+ while (i > 0 && stepList[i].tick > tick) i--;
580
+ return stepList[i].bpm;
581
+ };
582
+ const boundaries = new Set(stepList.map((s) => s.tick));
583
+ for (const s of stretches) {
584
+ boundaries.add(s.startTick);
585
+ boundaries.add(s.endTick);
586
+ }
587
+ const sortedBoundaries = [...boundaries].sort((a, b) => a - b);
588
+ const tempoEvents = [];
589
+ let emittedBpm = null;
590
+ for (const tick of sortedBoundaries) {
591
+ let bpm = stepBpmAt(tick);
592
+ for (const s of stretches) {
593
+ if (tick >= s.startTick && tick < s.endTick) bpm /= s.factor;
594
+ }
595
+ if (emittedBpm === null || bpm !== emittedBpm) {
596
+ tempoEvents.push({ tick, bpm });
597
+ emittedBpm = bpm;
598
+ }
599
+ }
600
+ const expressions = [
601
+ ...stretches.map((s) => ({ type: s.type, startTick: s.startTick, endTick: s.endTick })),
602
+ ...rampExpressions
603
+ ].sort((a, b) => a.startTick - b.startTick || a.endTick - b.endTick);
604
+ return { tempoEvents, expressions };
605
+ }
606
+ function bpmToUsPerQuarter(bpm) {
607
+ return Math.round(6e7 / bpm);
608
+ }
609
+ function makeTickToSec(tempoEvents, defaultTempo, ticksPerQuarterNote) {
610
+ const sorted = [...tempoEvents].sort((a, b) => a.tick - b.tick);
611
+ const startBpm = sorted.length > 0 && sorted[0].tick === 0 ? sorted[0].bpm : defaultTempo;
612
+ const changes = [
613
+ { tick: 0, usPerQuarter: bpmToUsPerQuarter(startBpm) }
614
+ ];
615
+ let lastBpm = startBpm;
616
+ for (const ev of sorted) {
617
+ if (ev.tick === 0) continue;
618
+ if (ev.bpm === lastBpm) continue;
619
+ changes.push({ tick: ev.tick, usPerQuarter: bpmToUsPerQuarter(ev.bpm) });
620
+ lastBpm = ev.bpm;
621
+ }
622
+ const secPerTick = (usPerQuarter) => usPerQuarter / 1e6 / ticksPerQuarterNote;
623
+ const cumSec = [0];
624
+ for (let i = 1; i < changes.length; i++) {
625
+ const dTick = changes[i].tick - changes[i - 1].tick;
626
+ cumSec[i] = cumSec[i - 1] + dTick * secPerTick(changes[i - 1].usPerQuarter);
627
+ }
628
+ return (tick) => {
629
+ let i = changes.length - 1;
630
+ while (i > 0 && changes[i].tick > tick) i--;
631
+ return cumSec[i] + (tick - changes[i].tick) * secPerTick(changes[i].usPerQuarter);
632
+ };
633
+ }
634
+ function buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote) {
635
+ const tickToSec = makeTickToSec(grid.tempoEvents, defaultTempo, ticksPerQuarterNote);
636
+ const breakpoints = [];
637
+ const measureStartTicks = new Set(grid.measures.map((m) => m.startTick));
638
+ for (const m of grid.measures) {
639
+ breakpoints.push({
640
+ midiSec: tickToSec(m.startTick),
641
+ quarterPos: m.startTick / ticksPerQuarterNote,
642
+ measureNumber: m.measureNumber,
643
+ beatInMeasure: 0,
644
+ repeatIteration: m.repeatIteration
645
+ });
646
+ }
647
+ for (const ev of grid.tempoEvents) {
648
+ if (measureStartTicks.has(ev.tick)) continue;
649
+ const m = grid.measures.find((mm) => ev.tick >= mm.startTick && ev.tick < mm.endTick);
650
+ if (!m) continue;
651
+ breakpoints.push({
652
+ midiSec: tickToSec(ev.tick),
653
+ quarterPos: ev.tick / ticksPerQuarterNote,
654
+ measureNumber: m.measureNumber,
655
+ beatInMeasure: (ev.tick - m.startTick) / ticksPerQuarterNote,
656
+ repeatIteration: m.repeatIteration
657
+ });
658
+ }
659
+ const last = grid.measures[grid.measures.length - 1];
660
+ breakpoints.push({
661
+ midiSec: tickToSec(grid.totalTicks),
662
+ quarterPos: grid.totalTicks / ticksPerQuarterNote,
663
+ measureNumber: last ? last.measureNumber : "0",
664
+ beatInMeasure: last ? (grid.totalTicks - last.startTick) / ticksPerQuarterNote : 0,
665
+ repeatIteration: last ? last.repeatIteration : 0
666
+ });
667
+ breakpoints.sort((a, b) => a.midiSec - b.midiSec || a.quarterPos - b.quarterPos);
668
+ const sidecar = {
669
+ version: "1",
670
+ durationSec: tickToSec(grid.totalTicks),
671
+ ticksPerQuarterNote,
672
+ breakpoints
673
+ };
674
+ if (grid.expressions.length > 0) {
675
+ sidecar.expressions = grid.expressions.map((e) => ({
676
+ type: e.type,
677
+ fromMidiSec: tickToSec(e.startTick),
678
+ toMidiSec: tickToSec(e.endTick)
679
+ })).sort((a, b) => a.fromMidiSec - b.fromMidiSec || a.toMidiSec - b.toMidiSec);
680
+ }
681
+ return sidecar;
682
+ }
683
+ function generatePlaybackTimeline(score, options = {}) {
684
+ const ticksPerQuarterNote = _nullishCoalesce(options.ticksPerQuarterNote, () => ( 480));
685
+ const defaultTempo = _nullishCoalesce(options.defaultTempo, () => ( 120));
686
+ const sequence = generatePlaybackSequence(score);
687
+ const grid = buildGridTimeline(score, ticksPerQuarterNote, sequence, {
688
+ defaultTempo,
689
+ fermataHoldMultiplier: options.fermataHoldMultiplier,
690
+ caesuraSeconds: options.caesuraSeconds,
691
+ breathSeconds: options.breathSeconds,
692
+ tempoRampSteps: options.tempoRampSteps
693
+ });
694
+ return buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote);
695
+ }
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
+ exports.extractPlaybackControls = extractPlaybackControls; exports.generatePlaybackSequence = generatePlaybackSequence; exports.hasPlaybackControls = hasPlaybackControls; exports.measureEndTick = measureEndTick; exports.buildGridTimeline = buildGridTimeline; exports.bpmToUsPerQuarter = bpmToUsPerQuarter; exports.buildTimingSidecar = buildTimingSidecar; exports.generatePlaybackTimeline = generatePlaybackTimeline;