musicxml-io 0.6.0 → 0.7.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.
- package/dist/{chunk-MCKPGXUF.mjs → chunk-CCSOG7HU.mjs} +341 -0
- package/dist/{chunk-VE2KCZMA.js → chunk-CJZK2DGV.js} +407 -66
- package/dist/{chunk-2RIFJYXX.mjs → chunk-LS5OLRZP.mjs} +1 -1
- package/dist/{chunk-TBGS7OIS.js → chunk-YCNKCOR2.js} +23 -23
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +383 -277
- package/dist/index.mjs +266 -160
- package/dist/operations/index.js +3 -3
- package/dist/operations/index.mjs +2 -2
- package/dist/query/index.d.mts +91 -3
- package/dist/query/index.d.ts +91 -3
- package/dist/query/index.js +8 -2
- package/dist/query/index.mjs +7 -1
- package/package.json +1 -1
|
@@ -171,6 +171,344 @@ function getMeasureEndPosition(measure) {
|
|
|
171
171
|
return state.position;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// src/query/playback-sequence.ts
|
|
175
|
+
function parseEndingNumbers(numberStr) {
|
|
176
|
+
const numbers = [];
|
|
177
|
+
const parts = numberStr.split(/[,\s]+/);
|
|
178
|
+
for (const part of parts) {
|
|
179
|
+
if (part.includes("-")) {
|
|
180
|
+
const [start, end] = part.split("-").map((s) => parseInt(s.trim(), 10));
|
|
181
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
182
|
+
for (let i = start; i <= end; i++) {
|
|
183
|
+
numbers.push(i);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
const num = parseInt(part.trim(), 10);
|
|
188
|
+
if (!isNaN(num)) {
|
|
189
|
+
numbers.push(num);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return numbers.length > 0 ? numbers : [1];
|
|
194
|
+
}
|
|
195
|
+
function parseJumpText(text) {
|
|
196
|
+
const t = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
197
|
+
if (t.includes("d.c. al coda") || t.includes("da capo al coda")) return "dc_al_coda";
|
|
198
|
+
if (t.includes("d.c. al fine") || t.includes("da capo al fine")) return "dc_al_fine";
|
|
199
|
+
if (t.includes("d.c.") || t.includes("da capo")) return "dc";
|
|
200
|
+
if (t.includes("d.s. al coda") || t.includes("dal segno al coda")) return "ds_al_coda";
|
|
201
|
+
if (t.includes("d.s. al fine") || t.includes("dal segno al fine")) return "ds_al_fine";
|
|
202
|
+
if (t.includes("d.s.") || t.includes("dal segno")) return "ds";
|
|
203
|
+
if (t === "fine") return "fine";
|
|
204
|
+
if (t.includes("to coda") || t.includes("al coda")) return "to_coda";
|
|
205
|
+
if (t === "coda" || t === "\u{1D14C}") return "coda";
|
|
206
|
+
if (t === "segno" || t === "\u{1D10B}") return "segno";
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
function getFirstWordsText(entry) {
|
|
210
|
+
for (const dt of entry.directionTypes) {
|
|
211
|
+
if (dt.kind === "words" && dt.text) return dt.text;
|
|
212
|
+
}
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
function hasDirectionKind(entry, kind) {
|
|
216
|
+
return entry.directionTypes.some((dt) => dt.kind === kind);
|
|
217
|
+
}
|
|
218
|
+
function extractPlaybackControls(part) {
|
|
219
|
+
const controls = {
|
|
220
|
+
repeatStarts: [],
|
|
221
|
+
repeatEnds: [],
|
|
222
|
+
voltas: [],
|
|
223
|
+
jumps: [],
|
|
224
|
+
segnoIndex: null,
|
|
225
|
+
codaIndex: null,
|
|
226
|
+
fineIndex: null,
|
|
227
|
+
toCodaIndex: null
|
|
228
|
+
};
|
|
229
|
+
const soundJumps = [];
|
|
230
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
231
|
+
const measure = part.measures[measureIndex];
|
|
232
|
+
if (measure.barlines) {
|
|
233
|
+
for (const barline of measure.barlines) {
|
|
234
|
+
if (barline.repeat) {
|
|
235
|
+
if (barline.repeat.direction === "forward") {
|
|
236
|
+
controls.repeatStarts.push(measureIndex);
|
|
237
|
+
} else if (barline.repeat.direction === "backward") {
|
|
238
|
+
controls.repeatEnds.push({
|
|
239
|
+
measureIndex,
|
|
240
|
+
times: barline.repeat.times ?? 2
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (barline.ending) {
|
|
245
|
+
const numbers = parseEndingNumbers(barline.ending.number);
|
|
246
|
+
controls.voltas.push({
|
|
247
|
+
measureIndex,
|
|
248
|
+
numbers,
|
|
249
|
+
type: barline.ending.type
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const entry of measure.entries) {
|
|
255
|
+
if (entry.type === "direction") {
|
|
256
|
+
const dir = entry;
|
|
257
|
+
if (hasDirectionKind(dir, "segno") && controls.segnoIndex === null) {
|
|
258
|
+
controls.segnoIndex = measureIndex;
|
|
259
|
+
}
|
|
260
|
+
if (hasDirectionKind(dir, "coda") && controls.codaIndex === null) {
|
|
261
|
+
controls.codaIndex = measureIndex;
|
|
262
|
+
}
|
|
263
|
+
const words = getFirstWordsText(dir);
|
|
264
|
+
if (words) {
|
|
265
|
+
const jt = parseJumpText(words);
|
|
266
|
+
if (jt === "fine") {
|
|
267
|
+
if (controls.fineIndex === null) controls.fineIndex = measureIndex;
|
|
268
|
+
} else if (jt === "to_coda") {
|
|
269
|
+
if (controls.toCodaIndex === null) controls.toCodaIndex = measureIndex;
|
|
270
|
+
} else if (jt === "coda") {
|
|
271
|
+
if (controls.codaIndex === null) controls.codaIndex = measureIndex;
|
|
272
|
+
} else if (jt === "segno") {
|
|
273
|
+
if (controls.segnoIndex === null) controls.segnoIndex = measureIndex;
|
|
274
|
+
} else if (jt) {
|
|
275
|
+
controls.jumps.push({ measureIndex, type: jt });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} else if (entry.type === "sound") {
|
|
279
|
+
const sound = entry;
|
|
280
|
+
if (sound.segno && controls.segnoIndex === null) controls.segnoIndex = measureIndex;
|
|
281
|
+
if (sound.coda && controls.codaIndex === null) controls.codaIndex = measureIndex;
|
|
282
|
+
if (sound.tocoda && controls.toCodaIndex === null) controls.toCodaIndex = measureIndex;
|
|
283
|
+
if (sound.fine && controls.fineIndex === null) controls.fineIndex = measureIndex;
|
|
284
|
+
if (sound.dacapo) soundJumps.push({ measureIndex, base: "dc" });
|
|
285
|
+
if (sound.dalsegno) soundJumps.push({ measureIndex, base: "ds" });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const sj of soundJumps) {
|
|
290
|
+
if (controls.jumps.some((j) => j.measureIndex === sj.measureIndex)) continue;
|
|
291
|
+
let type;
|
|
292
|
+
if (controls.fineIndex !== null) {
|
|
293
|
+
type = sj.base === "dc" ? "dc_al_fine" : "ds_al_fine";
|
|
294
|
+
} else if (controls.toCodaIndex !== null || controls.codaIndex !== null) {
|
|
295
|
+
type = sj.base === "dc" ? "dc_al_coda" : "ds_al_coda";
|
|
296
|
+
} else {
|
|
297
|
+
type = sj.base;
|
|
298
|
+
}
|
|
299
|
+
controls.jumps.push({ measureIndex: sj.measureIndex, type });
|
|
300
|
+
}
|
|
301
|
+
return controls;
|
|
302
|
+
}
|
|
303
|
+
function buildVoltaRanges(voltas, measureCount) {
|
|
304
|
+
const voltaRanges = /* @__PURE__ */ new Map();
|
|
305
|
+
let currentVoltaStart = null;
|
|
306
|
+
let currentVoltaNumbers = [];
|
|
307
|
+
for (const volta of voltas) {
|
|
308
|
+
if (volta.type === "start") {
|
|
309
|
+
currentVoltaStart = volta.measureIndex;
|
|
310
|
+
currentVoltaNumbers = volta.numbers;
|
|
311
|
+
} else if ((volta.type === "stop" || volta.type === "discontinue") && currentVoltaStart !== null) {
|
|
312
|
+
for (let i = currentVoltaStart; i <= volta.measureIndex; i++) {
|
|
313
|
+
voltaRanges.set(i, currentVoltaNumbers);
|
|
314
|
+
}
|
|
315
|
+
currentVoltaStart = null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (currentVoltaStart !== null) {
|
|
319
|
+
for (let i = currentVoltaStart; i < measureCount; i++) {
|
|
320
|
+
voltaRanges.set(i, currentVoltaNumbers);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return voltaRanges;
|
|
324
|
+
}
|
|
325
|
+
function generatePlaybackSequence(score, options) {
|
|
326
|
+
const partIndex = options?.partIndex ?? 0;
|
|
327
|
+
const part = score.parts[partIndex];
|
|
328
|
+
if (!part) return [];
|
|
329
|
+
const measureCount = part.measures.length;
|
|
330
|
+
const sequence = [];
|
|
331
|
+
if (measureCount === 0) return sequence;
|
|
332
|
+
const controls = extractPlaybackControls(part);
|
|
333
|
+
if (controls.repeatStarts.length === 0 && controls.repeatEnds.length === 0 && controls.voltas.length === 0 && controls.jumps.length === 0) {
|
|
334
|
+
for (let i = 0; i < measureCount; i++) {
|
|
335
|
+
sequence.push({ measureIndex: i, repeatIteration: 0 });
|
|
336
|
+
}
|
|
337
|
+
return sequence;
|
|
338
|
+
}
|
|
339
|
+
const voltaRanges = buildVoltaRanges(controls.voltas, measureCount);
|
|
340
|
+
const sortedRepeatStarts = [...controls.repeatStarts].sort((a, b) => a - b);
|
|
341
|
+
const sortedRepeatEnds = [...controls.repeatEnds].sort((a, b) => a.measureIndex - b.measureIndex);
|
|
342
|
+
const repeatSections = /* @__PURE__ */ new Map();
|
|
343
|
+
const voltaToRepeatEnd = /* @__PURE__ */ new Map();
|
|
344
|
+
const usedRepeatStarts = /* @__PURE__ */ new Set();
|
|
345
|
+
let lastRepeatEndMeasure = -1;
|
|
346
|
+
for (const repeatEnd of sortedRepeatEnds) {
|
|
347
|
+
let startIndex = null;
|
|
348
|
+
for (const startMeasure of sortedRepeatStarts) {
|
|
349
|
+
if (startMeasure <= repeatEnd.measureIndex && startMeasure > lastRepeatEndMeasure && !usedRepeatStarts.has(startMeasure)) {
|
|
350
|
+
startIndex = startMeasure;
|
|
351
|
+
} else if (startMeasure > repeatEnd.measureIndex) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (startIndex === null) {
|
|
356
|
+
startIndex = lastRepeatEndMeasure + 1;
|
|
357
|
+
} else {
|
|
358
|
+
usedRepeatStarts.add(startIndex);
|
|
359
|
+
}
|
|
360
|
+
repeatSections.set(repeatEnd.measureIndex, {
|
|
361
|
+
startIndex,
|
|
362
|
+
times: repeatEnd.times
|
|
363
|
+
});
|
|
364
|
+
lastRepeatEndMeasure = repeatEnd.measureIndex;
|
|
365
|
+
voltaRanges.forEach((_iterations, voltaMeasure) => {
|
|
366
|
+
const isInsideRepeat = voltaMeasure >= startIndex && voltaMeasure <= repeatEnd.measureIndex;
|
|
367
|
+
const isImmediatelyAfter = voltaMeasure > repeatEnd.measureIndex && !voltaToRepeatEnd.has(voltaMeasure);
|
|
368
|
+
if (isInsideRepeat) {
|
|
369
|
+
voltaToRepeatEnd.set(voltaMeasure, repeatEnd.measureIndex);
|
|
370
|
+
} else if (isImmediatelyAfter) {
|
|
371
|
+
let belongsToLaterRepeat = false;
|
|
372
|
+
for (const otherEnd of sortedRepeatEnds) {
|
|
373
|
+
if (otherEnd.measureIndex > repeatEnd.measureIndex) {
|
|
374
|
+
const otherSection = repeatSections.get(otherEnd.measureIndex);
|
|
375
|
+
if (otherSection && voltaMeasure >= otherSection.startIndex && voltaMeasure <= otherEnd.measureIndex) {
|
|
376
|
+
belongsToLaterRepeat = true;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (!belongsToLaterRepeat) {
|
|
382
|
+
voltaToRepeatEnd.set(voltaMeasure, repeatEnd.measureIndex);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
let currentMeasure = 0;
|
|
388
|
+
const repeatCounts = /* @__PURE__ */ new Map();
|
|
389
|
+
let lastRepeatEndIndex = null;
|
|
390
|
+
let lastRepeatIteration = 0;
|
|
391
|
+
let inRepeatJump = false;
|
|
392
|
+
let stopAtFine = false;
|
|
393
|
+
let jumpToCoda = false;
|
|
394
|
+
const maxIterations = measureCount * 10 + 10;
|
|
395
|
+
let iterations = 0;
|
|
396
|
+
while (currentMeasure < measureCount && iterations < maxIterations) {
|
|
397
|
+
iterations++;
|
|
398
|
+
const voltaIterations = voltaRanges.get(currentMeasure);
|
|
399
|
+
let repeatIteration = 0;
|
|
400
|
+
let inRepeatSection = false;
|
|
401
|
+
let currentRepeatEndIndex = null;
|
|
402
|
+
for (const [endIndex, section] of repeatSections) {
|
|
403
|
+
if (currentMeasure >= section.startIndex && currentMeasure <= endIndex) {
|
|
404
|
+
inRepeatSection = true;
|
|
405
|
+
currentRepeatEndIndex = endIndex;
|
|
406
|
+
repeatIteration = repeatCounts.get(endIndex) || 1;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (inRepeatSection && currentRepeatEndIndex !== null) {
|
|
411
|
+
lastRepeatEndIndex = currentRepeatEndIndex;
|
|
412
|
+
lastRepeatIteration = repeatIteration;
|
|
413
|
+
}
|
|
414
|
+
if (voltaIterations) {
|
|
415
|
+
let effectiveIteration = repeatIteration;
|
|
416
|
+
if (!inRepeatSection) {
|
|
417
|
+
const associatedRepeatEnd = voltaToRepeatEnd.get(currentMeasure);
|
|
418
|
+
if (associatedRepeatEnd !== void 0) {
|
|
419
|
+
effectiveIteration = repeatCounts.get(associatedRepeatEnd) || 1;
|
|
420
|
+
} else if (lastRepeatEndIndex !== null) {
|
|
421
|
+
effectiveIteration = lastRepeatIteration;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!voltaIterations.includes(effectiveIteration)) {
|
|
425
|
+
currentMeasure++;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
sequence.push({
|
|
430
|
+
measureIndex: currentMeasure,
|
|
431
|
+
repeatIteration: inRepeatSection ? repeatIteration : 0
|
|
432
|
+
});
|
|
433
|
+
if (stopAtFine && controls.fineIndex === currentMeasure) {
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
if (jumpToCoda && controls.toCodaIndex === currentMeasure && controls.codaIndex !== null) {
|
|
437
|
+
currentMeasure = controls.codaIndex;
|
|
438
|
+
jumpToCoda = false;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const repeatSection = repeatSections.get(currentMeasure);
|
|
442
|
+
if (repeatSection) {
|
|
443
|
+
const currentCount = repeatCounts.get(currentMeasure) || 1;
|
|
444
|
+
if (currentCount < repeatSection.times) {
|
|
445
|
+
repeatCounts.set(currentMeasure, currentCount + 1);
|
|
446
|
+
currentMeasure = repeatSection.startIndex;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (voltaIterations && !inRepeatSection) {
|
|
451
|
+
const associatedRepeatEnd = voltaToRepeatEnd.get(currentMeasure);
|
|
452
|
+
if (associatedRepeatEnd !== void 0) {
|
|
453
|
+
const repeatSectionForVolta = repeatSections.get(associatedRepeatEnd);
|
|
454
|
+
if (repeatSectionForVolta) {
|
|
455
|
+
const currentCount = repeatCounts.get(associatedRepeatEnd) || 1;
|
|
456
|
+
if (currentCount < repeatSectionForVolta.times) {
|
|
457
|
+
repeatCounts.set(associatedRepeatEnd, currentCount + 1);
|
|
458
|
+
currentMeasure = repeatSectionForVolta.startIndex;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const jumpAtMeasure = controls.jumps.find((j) => j.measureIndex === currentMeasure);
|
|
465
|
+
if (jumpAtMeasure && !inRepeatJump) {
|
|
466
|
+
inRepeatJump = true;
|
|
467
|
+
switch (jumpAtMeasure.type) {
|
|
468
|
+
case "dc":
|
|
469
|
+
currentMeasure = 0;
|
|
470
|
+
continue;
|
|
471
|
+
case "dc_al_fine":
|
|
472
|
+
currentMeasure = 0;
|
|
473
|
+
stopAtFine = true;
|
|
474
|
+
continue;
|
|
475
|
+
case "dc_al_coda":
|
|
476
|
+
currentMeasure = 0;
|
|
477
|
+
jumpToCoda = true;
|
|
478
|
+
continue;
|
|
479
|
+
case "ds":
|
|
480
|
+
if (controls.segnoIndex !== null) {
|
|
481
|
+
currentMeasure = controls.segnoIndex;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
break;
|
|
485
|
+
case "ds_al_fine":
|
|
486
|
+
if (controls.segnoIndex !== null) {
|
|
487
|
+
currentMeasure = controls.segnoIndex;
|
|
488
|
+
stopAtFine = true;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
break;
|
|
492
|
+
case "ds_al_coda":
|
|
493
|
+
if (controls.segnoIndex !== null) {
|
|
494
|
+
currentMeasure = controls.segnoIndex;
|
|
495
|
+
jumpToCoda = true;
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
currentMeasure++;
|
|
502
|
+
}
|
|
503
|
+
return sequence;
|
|
504
|
+
}
|
|
505
|
+
function hasPlaybackControls(score, options) {
|
|
506
|
+
const part = score.parts[options?.partIndex ?? 0];
|
|
507
|
+
if (!part) return false;
|
|
508
|
+
const controls = extractPlaybackControls(part);
|
|
509
|
+
return controls.repeatStarts.length > 0 || controls.repeatEnds.length > 0 || controls.voltas.length > 0 || controls.jumps.length > 0 || controls.segnoIndex !== null || controls.codaIndex !== null;
|
|
510
|
+
}
|
|
511
|
+
|
|
174
512
|
// src/query/index.ts
|
|
175
513
|
function getNotesForVoice(measure, filter) {
|
|
176
514
|
return measure.entries.filter((entry) => {
|
|
@@ -1721,6 +2059,9 @@ function pitchesEqual(a, b) {
|
|
|
1721
2059
|
}
|
|
1722
2060
|
|
|
1723
2061
|
export {
|
|
2062
|
+
extractPlaybackControls,
|
|
2063
|
+
generatePlaybackSequence,
|
|
2064
|
+
hasPlaybackControls,
|
|
1724
2065
|
STEPS,
|
|
1725
2066
|
STEP_SEMITONES,
|
|
1726
2067
|
pitchToSemitone,
|