musicxml-io 0.1.0 → 0.2.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/README.md +3 -3
- package/dist/accessors/index.d.mts +280 -2
- package/dist/accessors/index.d.ts +280 -2
- package/dist/accessors/index.js +1299 -1
- package/dist/accessors/index.mjs +1251 -1
- package/dist/index-DCty0U8p.d.ts +377 -0
- package/dist/index-nCaclfqu.d.mts +377 -0
- package/dist/index.d.mts +6 -184
- package/dist/index.d.ts +6 -184
- package/dist/index.js +2087 -309
- package/dist/index.mjs +2039 -308
- package/dist/operations/index.d.mts +2 -92
- package/dist/operations/index.d.ts +2 -92
- package/dist/operations/index.js +1580 -150
- package/dist/operations/index.mjs +1562 -148
- package/dist/query/index.d.mts +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +0 -1
- package/dist/query/index.mjs +0 -1
- package/dist/{types-DC_TnJu_.d.ts → types-Dp9zcgIg.d.mts} +298 -3
- package/dist/{types-DC_TnJu_.d.mts → types-Dp9zcgIg.d.ts} +298 -3
- package/package.json +6 -2
- package/dist/accessors/index.js.map +0 -1
- package/dist/accessors/index.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/operations/index.js.map +0 -1
- package/dist/operations/index.mjs.map +0 -1
- package/dist/query/index.js.map +0 -1
- package/dist/query/index.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -27,6 +27,8 @@ __export(src_exports, {
|
|
|
27
27
|
addNote: () => addNote,
|
|
28
28
|
assertMeasureValid: () => assertMeasureValid,
|
|
29
29
|
assertValid: () => assertValid,
|
|
30
|
+
buildVoiceToStaffMap: () => buildVoiceToStaffMap,
|
|
31
|
+
buildVoiceToStaffMapForPart: () => buildVoiceToStaffMapForPart,
|
|
30
32
|
changeKey: () => changeKey,
|
|
31
33
|
changeTime: () => changeTime,
|
|
32
34
|
countNotes: () => countNotes,
|
|
@@ -34,36 +36,82 @@ __export(src_exports, {
|
|
|
34
36
|
deleteMeasure: () => deleteMeasure,
|
|
35
37
|
deleteNote: () => deleteNote,
|
|
36
38
|
exportMidi: () => exportMidi,
|
|
39
|
+
findBarlines: () => findBarlines,
|
|
40
|
+
findDirectionsByType: () => findDirectionsByType,
|
|
37
41
|
findNotes: () => findNotes,
|
|
42
|
+
findNotesWithNotation: () => findNotesWithNotation,
|
|
38
43
|
formatLocation: () => formatLocation,
|
|
39
44
|
getAbsolutePosition: () => getAbsolutePosition,
|
|
45
|
+
getAdjacentNotes: () => getAdjacentNotes,
|
|
40
46
|
getAllNotes: () => getAllNotes,
|
|
41
47
|
getAttributesAtMeasure: () => getAttributesAtMeasure,
|
|
48
|
+
getBeamGroups: () => getBeamGroups,
|
|
49
|
+
getChordProgression: () => getChordProgression,
|
|
42
50
|
getChords: () => getChords,
|
|
51
|
+
getClefChanges: () => getClefChanges,
|
|
52
|
+
getClefForStaff: () => getClefForStaff,
|
|
53
|
+
getDirections: () => getDirections,
|
|
54
|
+
getDirectionsAtPosition: () => getDirectionsAtPosition,
|
|
43
55
|
getDivisions: () => getDivisions,
|
|
44
56
|
getDuration: () => getDuration,
|
|
57
|
+
getDynamics: () => getDynamics,
|
|
58
|
+
getEffectiveStaff: () => getEffectiveStaff,
|
|
59
|
+
getEndings: () => getEndings,
|
|
60
|
+
getEntriesAtPosition: () => getEntriesAtPosition,
|
|
61
|
+
getEntriesForStaff: () => getEntriesForStaff,
|
|
62
|
+
getEntriesInRange: () => getEntriesInRange,
|
|
63
|
+
getHarmonies: () => getHarmonies,
|
|
64
|
+
getHarmonyAtPosition: () => getHarmonyAtPosition,
|
|
65
|
+
getKeyChanges: () => getKeyChanges,
|
|
66
|
+
getLyricText: () => getLyricText,
|
|
67
|
+
getLyrics: () => getLyrics,
|
|
45
68
|
getMeasure: () => getMeasure,
|
|
46
69
|
getMeasureByIndex: () => getMeasureByIndex,
|
|
47
70
|
getMeasureContext: () => getMeasureContext,
|
|
48
71
|
getMeasureCount: () => getMeasureCount,
|
|
49
72
|
getMeasureEndPosition: () => getMeasureEndPosition,
|
|
73
|
+
getNextNote: () => getNextNote,
|
|
50
74
|
getNormalizedDuration: () => getNormalizedDuration,
|
|
51
75
|
getNormalizedPosition: () => getNormalizedPosition,
|
|
76
|
+
getNotesAtPosition: () => getNotesAtPosition,
|
|
52
77
|
getNotesForStaff: () => getNotesForStaff,
|
|
53
78
|
getNotesForVoice: () => getNotesForVoice,
|
|
79
|
+
getNotesInRange: () => getNotesInRange,
|
|
80
|
+
getOctaveShifts: () => getOctaveShifts,
|
|
54
81
|
getPartById: () => getPartById,
|
|
82
|
+
getPartByIndex: () => getPartByIndex,
|
|
83
|
+
getPartCount: () => getPartCount,
|
|
84
|
+
getPartIds: () => getPartIds,
|
|
55
85
|
getPartIndex: () => getPartIndex,
|
|
86
|
+
getPedalMarkings: () => getPedalMarkings,
|
|
87
|
+
getPrevNote: () => getPrevNote,
|
|
88
|
+
getRepeatStructure: () => getRepeatStructure,
|
|
89
|
+
getSlurSpans: () => getSlurSpans,
|
|
90
|
+
getStaffRange: () => getStaffRange,
|
|
56
91
|
getStaveCount: () => getStaveCount,
|
|
57
92
|
getStaves: () => getStaves,
|
|
93
|
+
getStructuralChanges: () => getStructuralChanges,
|
|
94
|
+
getTempoMarkings: () => getTempoMarkings,
|
|
95
|
+
getTiedNoteGroups: () => getTiedNoteGroups,
|
|
96
|
+
getTimeChanges: () => getTimeChanges,
|
|
97
|
+
getTupletGroups: () => getTupletGroups,
|
|
98
|
+
getVerseCount: () => getVerseCount,
|
|
99
|
+
getVerticalSlice: () => getVerticalSlice,
|
|
100
|
+
getVoiceLine: () => getVoiceLine,
|
|
101
|
+
getVoiceLineInRange: () => getVoiceLineInRange,
|
|
58
102
|
getVoices: () => getVoices,
|
|
103
|
+
getVoicesForStaff: () => getVoicesForStaff,
|
|
104
|
+
getWedges: () => getWedges,
|
|
59
105
|
groupByStaff: () => groupByStaff,
|
|
60
106
|
groupByVoice: () => groupByVoice,
|
|
61
107
|
hasMultipleStaves: () => hasMultipleStaves,
|
|
62
108
|
hasNotes: () => hasNotes,
|
|
109
|
+
inferStaff: () => inferStaff,
|
|
63
110
|
insertMeasure: () => insertMeasure,
|
|
64
111
|
isCompressed: () => isCompressed,
|
|
65
112
|
isRestMeasure: () => isRestMeasure,
|
|
66
113
|
isValid: () => isValid,
|
|
114
|
+
iterateEntries: () => iterateEntries,
|
|
67
115
|
iterateNotes: () => iterateNotes,
|
|
68
116
|
measureRoundtrip: () => measureRoundtrip,
|
|
69
117
|
modifyNoteDuration: () => modifyNoteDuration,
|
|
@@ -77,7 +125,6 @@ __export(src_exports, {
|
|
|
77
125
|
serialize: () => serialize,
|
|
78
126
|
serializeCompressed: () => serializeCompressed,
|
|
79
127
|
serializeToFile: () => serializeToFile,
|
|
80
|
-
setDivisions: () => setDivisions,
|
|
81
128
|
transpose: () => transpose,
|
|
82
129
|
validate: () => validate,
|
|
83
130
|
validateBackupForward: () => validateBackupForward,
|
|
@@ -478,8 +525,10 @@ function parsePartList(elements) {
|
|
|
478
525
|
const sound = getElementText(instContent, "instrument-sound");
|
|
479
526
|
if (sound) inst.sound = sound;
|
|
480
527
|
if (hasElement(instContent, "solo")) inst.solo = true;
|
|
481
|
-
|
|
482
|
-
|
|
528
|
+
if (hasElement(instContent, "ensemble")) {
|
|
529
|
+
const ensText = getElementText(instContent, "ensemble");
|
|
530
|
+
inst.ensemble = ensText ? parseInt(ensText, 10) : 0;
|
|
531
|
+
}
|
|
483
532
|
instruments.push(inst);
|
|
484
533
|
}
|
|
485
534
|
}
|
|
@@ -662,6 +711,8 @@ function parseAttributes(elements) {
|
|
|
662
711
|
if (divisions !== void 0) attrs.divisions = divisions;
|
|
663
712
|
const staves = getElementTextAsInt(elements, "staves");
|
|
664
713
|
if (staves !== void 0) attrs.staves = staves;
|
|
714
|
+
const instruments = getElementTextAsInt(elements, "instruments");
|
|
715
|
+
if (instruments !== void 0) attrs.instruments = instruments;
|
|
665
716
|
const time = getElementContent(elements, "time");
|
|
666
717
|
if (time) {
|
|
667
718
|
attrs.time = parseTimeSignature(time, elements);
|
|
@@ -758,7 +809,7 @@ function parseKeySignature(elements) {
|
|
|
758
809
|
const key = {
|
|
759
810
|
fifths: parseInt(fifths || "0", 10)
|
|
760
811
|
};
|
|
761
|
-
const validModes = ["major", "minor", "dorian", "phrygian", "lydian", "mixolydian", "aeolian", "ionian", "locrian"];
|
|
812
|
+
const validModes = ["major", "minor", "dorian", "phrygian", "lydian", "mixolydian", "aeolian", "ionian", "locrian", "none"];
|
|
762
813
|
if (mode && validModes.includes(mode)) {
|
|
763
814
|
key.mode = mode;
|
|
764
815
|
}
|
|
@@ -789,6 +840,12 @@ function parseClef(elements, attrs) {
|
|
|
789
840
|
if (octaveChange !== void 0) {
|
|
790
841
|
clef.clefOctaveChange = octaveChange;
|
|
791
842
|
}
|
|
843
|
+
if (attrs["print-object"] === "no") {
|
|
844
|
+
clef.printObject = false;
|
|
845
|
+
}
|
|
846
|
+
if (attrs["after-barline"] === "yes") {
|
|
847
|
+
clef.afterBarline = true;
|
|
848
|
+
}
|
|
792
849
|
return clef;
|
|
793
850
|
}
|
|
794
851
|
function parseTranspose(elements) {
|
|
@@ -998,6 +1055,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
998
1055
|
slurType: attrs["type"] || "start",
|
|
999
1056
|
number: attrs["number"] ? parseInt(attrs["number"], 10) : void 0,
|
|
1000
1057
|
lineType: attrs["line-type"],
|
|
1058
|
+
orientation: attrs["orientation"],
|
|
1001
1059
|
defaultX: attrs["default-x"] ? parseFloat(attrs["default-x"]) : void 0,
|
|
1002
1060
|
defaultY: attrs["default-y"] ? parseFloat(attrs["default-y"]) : void 0,
|
|
1003
1061
|
bezierX: attrs["bezier-x"] ? parseFloat(attrs["bezier-x"]) : void 0,
|
|
@@ -1510,6 +1568,9 @@ function parseDirection(elements, attrs) {
|
|
|
1510
1568
|
direction.sound = {};
|
|
1511
1569
|
if (soundAttrs["tempo"]) direction.sound.tempo = parseFloat(soundAttrs["tempo"]);
|
|
1512
1570
|
if (soundAttrs["dynamics"]) direction.sound.dynamics = parseFloat(soundAttrs["dynamics"]);
|
|
1571
|
+
if (soundAttrs["damper-pedal"]) direction.sound.damperPedal = soundAttrs["damper-pedal"];
|
|
1572
|
+
if (soundAttrs["soft-pedal"]) direction.sound.softPedal = soundAttrs["soft-pedal"];
|
|
1573
|
+
if (soundAttrs["sostenuto-pedal"]) direction.sound.sostenutoPedal = soundAttrs["sostenuto-pedal"];
|
|
1513
1574
|
for (const soundEl of soundContent) {
|
|
1514
1575
|
if (soundEl["midi-instrument"]) {
|
|
1515
1576
|
const midiAttrs = getAttributes(soundEl);
|
|
@@ -1670,6 +1731,8 @@ function parseDirectionType(elements) {
|
|
|
1670
1731
|
if (bracketAttrs["number"]) result.number = parseInt(bracketAttrs["number"], 10);
|
|
1671
1732
|
if (bracketAttrs["line-end"]) result.lineEnd = bracketAttrs["line-end"];
|
|
1672
1733
|
if (bracketAttrs["line-type"]) result.lineType = bracketAttrs["line-type"];
|
|
1734
|
+
if (bracketAttrs["default-y"]) result.defaultY = parseFloat(bracketAttrs["default-y"]);
|
|
1735
|
+
if (bracketAttrs["relative-x"]) result.relativeX = parseFloat(bracketAttrs["relative-x"]);
|
|
1673
1736
|
return result;
|
|
1674
1737
|
}
|
|
1675
1738
|
}
|
|
@@ -1969,6 +2032,8 @@ var VALID_BAR_STYLES = /* @__PURE__ */ new Set([
|
|
|
1969
2032
|
"light-heavy",
|
|
1970
2033
|
"heavy-light",
|
|
1971
2034
|
"heavy-heavy",
|
|
2035
|
+
"tick",
|
|
2036
|
+
"short",
|
|
1972
2037
|
"none"
|
|
1973
2038
|
]);
|
|
1974
2039
|
function isValidNoteType(value) {
|
|
@@ -2238,6 +2303,9 @@ function parseSound(elements, attrs) {
|
|
|
2238
2303
|
if (attrs["tocoda"]) sound.tocoda = attrs["tocoda"];
|
|
2239
2304
|
if (attrs["fine"] === "yes") sound.fine = true;
|
|
2240
2305
|
if (attrs["forward-repeat"] === "yes") sound.forwardRepeat = true;
|
|
2306
|
+
if (attrs["damper-pedal"]) sound.damperPedal = attrs["damper-pedal"];
|
|
2307
|
+
if (attrs["soft-pedal"]) sound.softPedal = attrs["soft-pedal"];
|
|
2308
|
+
if (attrs["sostenuto-pedal"]) sound.sostenutoPedal = attrs["sostenuto-pedal"];
|
|
2241
2309
|
for (const el of elements) {
|
|
2242
2310
|
if (el["swing"]) {
|
|
2243
2311
|
const swingContent = el["swing"];
|
|
@@ -2334,6 +2402,8 @@ function parseAuto(data) {
|
|
|
2334
2402
|
var DEFAULT_OPTIONS = {
|
|
2335
2403
|
checkDivisions: true,
|
|
2336
2404
|
checkMeasureDuration: true,
|
|
2405
|
+
checkMeasureFullness: false,
|
|
2406
|
+
// Piano Roll semantics - opt-in
|
|
2337
2407
|
checkPosition: true,
|
|
2338
2408
|
checkTies: true,
|
|
2339
2409
|
checkBeams: true,
|
|
@@ -2390,6 +2460,14 @@ function validate(score, options = {}) {
|
|
|
2390
2460
|
opts.durationTolerance
|
|
2391
2461
|
));
|
|
2392
2462
|
}
|
|
2463
|
+
if (opts.checkMeasureFullness && currentTime) {
|
|
2464
|
+
allErrors.push(...validateMeasureFullness(
|
|
2465
|
+
measure,
|
|
2466
|
+
currentDivisions,
|
|
2467
|
+
currentTime,
|
|
2468
|
+
location
|
|
2469
|
+
));
|
|
2470
|
+
}
|
|
2393
2471
|
if (opts.checkPosition) {
|
|
2394
2472
|
allErrors.push(...validateBackupForward(measure, location));
|
|
2395
2473
|
}
|
|
@@ -2538,6 +2616,92 @@ function calculateVoiceDurations(measure) {
|
|
|
2538
2616
|
}
|
|
2539
2617
|
return voiceDurations;
|
|
2540
2618
|
}
|
|
2619
|
+
function validateMeasureFullness(measure, divisions, time, location) {
|
|
2620
|
+
const errors = [];
|
|
2621
|
+
if (time.senzaMisura) {
|
|
2622
|
+
return errors;
|
|
2623
|
+
}
|
|
2624
|
+
const beats = parseInt(time.beats, 10);
|
|
2625
|
+
if (isNaN(beats)) {
|
|
2626
|
+
return errors;
|
|
2627
|
+
}
|
|
2628
|
+
const expectedDuration = beats / time.beatType * 4 * divisions;
|
|
2629
|
+
const voiceCoverage = /* @__PURE__ */ new Map();
|
|
2630
|
+
let currentPosition = 0;
|
|
2631
|
+
for (const entry of measure.entries) {
|
|
2632
|
+
if (entry.type === "note") {
|
|
2633
|
+
const staff = entry.staff ?? 1;
|
|
2634
|
+
const voice = entry.voice;
|
|
2635
|
+
const key = `${staff}-${voice}`;
|
|
2636
|
+
if (!entry.chord) {
|
|
2637
|
+
if (!voiceCoverage.has(key)) {
|
|
2638
|
+
voiceCoverage.set(key, { segments: [] });
|
|
2639
|
+
}
|
|
2640
|
+
voiceCoverage.get(key).segments.push({
|
|
2641
|
+
start: currentPosition,
|
|
2642
|
+
end: currentPosition + entry.duration
|
|
2643
|
+
});
|
|
2644
|
+
currentPosition += entry.duration;
|
|
2645
|
+
}
|
|
2646
|
+
} else if (entry.type === "backup") {
|
|
2647
|
+
currentPosition -= entry.duration;
|
|
2648
|
+
} else if (entry.type === "forward") {
|
|
2649
|
+
const staff = entry.staff ?? 1;
|
|
2650
|
+
const voice = entry.voice ?? 1;
|
|
2651
|
+
const key = `${staff}-${voice}`;
|
|
2652
|
+
if (!voiceCoverage.has(key)) {
|
|
2653
|
+
voiceCoverage.set(key, { segments: [] });
|
|
2654
|
+
}
|
|
2655
|
+
voiceCoverage.get(key).segments.push({
|
|
2656
|
+
start: currentPosition,
|
|
2657
|
+
end: currentPosition + entry.duration
|
|
2658
|
+
});
|
|
2659
|
+
currentPosition += entry.duration;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
for (const [voiceKey, { segments }] of voiceCoverage.entries()) {
|
|
2663
|
+
const [staff, voice] = voiceKey.split("-").map(Number);
|
|
2664
|
+
const sorted = [...segments].sort((a, b) => a.start - b.start);
|
|
2665
|
+
let lastEnd = 0;
|
|
2666
|
+
const gaps = [];
|
|
2667
|
+
for (const seg of sorted) {
|
|
2668
|
+
if (seg.start > lastEnd) {
|
|
2669
|
+
gaps.push({ start: lastEnd, end: seg.start });
|
|
2670
|
+
}
|
|
2671
|
+
lastEnd = Math.max(lastEnd, seg.end);
|
|
2672
|
+
}
|
|
2673
|
+
if (lastEnd < expectedDuration) {
|
|
2674
|
+
gaps.push({ start: lastEnd, end: expectedDuration });
|
|
2675
|
+
}
|
|
2676
|
+
for (const gap of gaps) {
|
|
2677
|
+
errors.push({
|
|
2678
|
+
code: "VOICE_GAP",
|
|
2679
|
+
level: "warning",
|
|
2680
|
+
message: `Voice ${voice} (staff ${staff}) has gap from position ${gap.start} to ${gap.end}`,
|
|
2681
|
+
location: { ...location, voice, staff },
|
|
2682
|
+
details: {
|
|
2683
|
+
gapStart: gap.start,
|
|
2684
|
+
gapEnd: gap.end,
|
|
2685
|
+
gapDuration: gap.end - gap.start
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
if (lastEnd < expectedDuration) {
|
|
2690
|
+
errors.push({
|
|
2691
|
+
code: "VOICE_INCOMPLETE",
|
|
2692
|
+
level: "warning",
|
|
2693
|
+
message: `Voice ${voice} (staff ${staff}) ends at ${lastEnd}, expected ${expectedDuration}`,
|
|
2694
|
+
location: { ...location, voice, staff },
|
|
2695
|
+
details: {
|
|
2696
|
+
actualEnd: lastEnd,
|
|
2697
|
+
expectedDuration,
|
|
2698
|
+
missing: expectedDuration - lastEnd
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
return errors;
|
|
2704
|
+
}
|
|
2541
2705
|
function validateBackupForward(measure, location) {
|
|
2542
2706
|
const errors = [];
|
|
2543
2707
|
let position = 0;
|
|
@@ -3004,6 +3168,8 @@ function validateStaffStructure(part, partIndex) {
|
|
|
3004
3168
|
}
|
|
3005
3169
|
var DEFAULT_LOCAL_OPTIONS = {
|
|
3006
3170
|
checkMeasureDuration: true,
|
|
3171
|
+
checkMeasureFullness: false,
|
|
3172
|
+
// Piano Roll semantics - opt-in
|
|
3007
3173
|
checkPosition: true,
|
|
3008
3174
|
checkBeams: true,
|
|
3009
3175
|
checkTuplets: true,
|
|
@@ -3028,6 +3194,14 @@ function validateMeasureLocal(measure, context, options = {}) {
|
|
|
3028
3194
|
opts.durationTolerance
|
|
3029
3195
|
));
|
|
3030
3196
|
}
|
|
3197
|
+
if (opts.checkMeasureFullness && context.time) {
|
|
3198
|
+
errors.push(...validateMeasureFullness(
|
|
3199
|
+
measure,
|
|
3200
|
+
context.divisions,
|
|
3201
|
+
context.time,
|
|
3202
|
+
location
|
|
3203
|
+
));
|
|
3204
|
+
}
|
|
3031
3205
|
if (opts.checkPosition) {
|
|
3032
3206
|
errors.push(...validateBackupForward(measure, location));
|
|
3033
3207
|
}
|
|
@@ -3594,7 +3768,11 @@ function serializeScorePart(part, indent) {
|
|
|
3594
3768
|
lines.push(`${indent} <solo/>`);
|
|
3595
3769
|
}
|
|
3596
3770
|
if (inst.ensemble !== void 0) {
|
|
3597
|
-
|
|
3771
|
+
if (inst.ensemble === 0) {
|
|
3772
|
+
lines.push(`${indent} <ensemble/>`);
|
|
3773
|
+
} else {
|
|
3774
|
+
lines.push(`${indent} <ensemble>${inst.ensemble}</ensemble>`);
|
|
3775
|
+
}
|
|
3598
3776
|
}
|
|
3599
3777
|
lines.push(`${indent} </score-instrument>`);
|
|
3600
3778
|
}
|
|
@@ -3774,6 +3952,9 @@ function serializeAttributes(attrs, indent) {
|
|
|
3774
3952
|
if (attrs.staves !== void 0) {
|
|
3775
3953
|
lines.push(`${indent} <staves>${attrs.staves}</staves>`);
|
|
3776
3954
|
}
|
|
3955
|
+
if (attrs.instruments !== void 0) {
|
|
3956
|
+
lines.push(`${indent} <instruments>${attrs.instruments}</instruments>`);
|
|
3957
|
+
}
|
|
3777
3958
|
if (attrs.clef) {
|
|
3778
3959
|
for (const clef of attrs.clef) {
|
|
3779
3960
|
lines.push(...serializeClef(clef, indent + " "));
|
|
@@ -3862,8 +4043,10 @@ function serializeTime(time, indent) {
|
|
|
3862
4043
|
}
|
|
3863
4044
|
function serializeClef(clef, indent) {
|
|
3864
4045
|
const lines = [];
|
|
3865
|
-
|
|
3866
|
-
|
|
4046
|
+
let attrs = clef.staff ? ` number="${clef.staff}"` : "";
|
|
4047
|
+
if (clef.printObject === false) attrs += ' print-object="no"';
|
|
4048
|
+
if (clef.afterBarline) attrs += ' after-barline="yes"';
|
|
4049
|
+
lines.push(`${indent}<clef${attrs}>`);
|
|
3867
4050
|
lines.push(`${indent} <sign>${clef.sign}</sign>`);
|
|
3868
4051
|
lines.push(`${indent} <line>${clef.line}</line>`);
|
|
3869
4052
|
if (clef.clefOctaveChange !== void 0) {
|
|
@@ -4112,6 +4295,7 @@ function serializeNotationsGroup(notations, indent) {
|
|
|
4112
4295
|
if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
|
|
4113
4296
|
attrs += ` type="${notation.slurType}"`;
|
|
4114
4297
|
if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
|
|
4298
|
+
if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
|
|
4115
4299
|
if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
|
|
4116
4300
|
if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
|
|
4117
4301
|
if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
|
|
@@ -4425,6 +4609,15 @@ function serializeDirection(direction, indent) {
|
|
|
4425
4609
|
if (direction.sound.dynamics !== void 0) {
|
|
4426
4610
|
attrs2.push(`dynamics="${direction.sound.dynamics}"`);
|
|
4427
4611
|
}
|
|
4612
|
+
if (direction.sound.damperPedal) {
|
|
4613
|
+
attrs2.push(`damper-pedal="${direction.sound.damperPedal}"`);
|
|
4614
|
+
}
|
|
4615
|
+
if (direction.sound.softPedal) {
|
|
4616
|
+
attrs2.push(`soft-pedal="${direction.sound.softPedal}"`);
|
|
4617
|
+
}
|
|
4618
|
+
if (direction.sound.sostenutoPedal) {
|
|
4619
|
+
attrs2.push(`sostenuto-pedal="${direction.sound.sostenutoPedal}"`);
|
|
4620
|
+
}
|
|
4428
4621
|
const attrStr = attrs2.length > 0 ? ` ${attrs2.join(" ")}` : "";
|
|
4429
4622
|
if (direction.sound.midiInstrument) {
|
|
4430
4623
|
lines.push(`${indent} <sound${attrStr}>`);
|
|
@@ -4529,6 +4722,8 @@ function serializeDirectionType(dirType, indent) {
|
|
|
4529
4722
|
if (dirType.number !== void 0) bracketAttrs += ` number="${dirType.number}"`;
|
|
4530
4723
|
if (dirType.lineEnd) bracketAttrs += ` line-end="${dirType.lineEnd}"`;
|
|
4531
4724
|
if (dirType.lineType) bracketAttrs += ` line-type="${dirType.lineType}"`;
|
|
4725
|
+
if (dirType.defaultY !== void 0) bracketAttrs += ` default-y="${dirType.defaultY}"`;
|
|
4726
|
+
if (dirType.relativeX !== void 0) bracketAttrs += ` relative-x="${dirType.relativeX}"`;
|
|
4532
4727
|
lines.push(`${indent} <bracket${bracketAttrs}/>`);
|
|
4533
4728
|
break;
|
|
4534
4729
|
}
|
|
@@ -4863,6 +5058,9 @@ function serializeSound(sound, indent) {
|
|
|
4863
5058
|
if (sound.tocoda) attrs.push(`tocoda="${escapeXml(sound.tocoda)}"`);
|
|
4864
5059
|
if (sound.fine) attrs.push('fine="yes"');
|
|
4865
5060
|
if (sound.forwardRepeat) attrs.push('forward-repeat="yes"');
|
|
5061
|
+
if (sound.damperPedal) attrs.push(`damper-pedal="${sound.damperPedal === true ? "yes" : sound.damperPedal}"`);
|
|
5062
|
+
if (sound.softPedal) attrs.push(`soft-pedal="${sound.softPedal === true ? "yes" : sound.softPedal}"`);
|
|
5063
|
+
if (sound.sostenutoPedal) attrs.push(`sostenuto-pedal="${sound.sostenutoPedal === true ? "yes" : sound.sostenutoPedal}"`);
|
|
4866
5064
|
const attrStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
4867
5065
|
if (sound.swing) {
|
|
4868
5066
|
lines.push(`${indent}<sound${attrStr}>`);
|
|
@@ -5455,178 +5653,1381 @@ function getNormalizedDuration(note, options) {
|
|
|
5455
5653
|
const currentDivisions = options.currentDivisions ?? 1;
|
|
5456
5654
|
return note.duration * options.baseDivisions / currentDivisions;
|
|
5457
5655
|
}
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
if (!part) return void 0;
|
|
5463
|
-
const targetMeasure = String(options.measure);
|
|
5464
|
-
return part.measures.find((m) => m.number === targetMeasure);
|
|
5465
|
-
}
|
|
5466
|
-
function getMeasureByIndex(score, options) {
|
|
5467
|
-
const part = score.parts[options.part];
|
|
5468
|
-
if (!part) return void 0;
|
|
5469
|
-
return part.measures[options.measureIndex];
|
|
5470
|
-
}
|
|
5471
|
-
function getMeasureCount(score) {
|
|
5472
|
-
if (score.parts.length === 0) return 0;
|
|
5473
|
-
return score.parts[0].measures.length;
|
|
5474
|
-
}
|
|
5475
|
-
function getDivisions(score, options) {
|
|
5476
|
-
const part = score.parts[options.part];
|
|
5477
|
-
if (!part) return 1;
|
|
5478
|
-
const targetMeasure = parseInt(String(options.measure), 10);
|
|
5479
|
-
if (isNaN(targetMeasure)) return 1;
|
|
5480
|
-
for (let i = 0; i < part.measures.length; i++) {
|
|
5481
|
-
const m = part.measures[i];
|
|
5482
|
-
const mNum = parseInt(m.number, 10);
|
|
5483
|
-
if (!isNaN(mNum) && mNum > targetMeasure) break;
|
|
5484
|
-
if (m.attributes?.divisions !== void 0) {
|
|
5656
|
+
function getEntriesForStaff(measure, staff) {
|
|
5657
|
+
return measure.entries.filter((entry) => {
|
|
5658
|
+
if (entry.type === "note") {
|
|
5659
|
+
return (entry.staff ?? 1) === staff;
|
|
5485
5660
|
}
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
for (const m of part.measures) {
|
|
5489
|
-
const mNum = parseInt(m.number, 10);
|
|
5490
|
-
if (!isNaN(mNum) && mNum > targetMeasure) break;
|
|
5491
|
-
if (m.attributes?.divisions !== void 0) {
|
|
5492
|
-
divisions = m.attributes.divisions;
|
|
5661
|
+
if (entry.type === "forward") {
|
|
5662
|
+
return (entry.staff ?? 1) === staff;
|
|
5493
5663
|
}
|
|
5494
|
-
|
|
5495
|
-
|
|
5664
|
+
if (entry.type === "direction") {
|
|
5665
|
+
return (entry.staff ?? 1) === staff;
|
|
5666
|
+
}
|
|
5667
|
+
if (entry.type === "backup") {
|
|
5668
|
+
return false;
|
|
5669
|
+
}
|
|
5670
|
+
return false;
|
|
5671
|
+
});
|
|
5496
5672
|
}
|
|
5497
|
-
function
|
|
5498
|
-
const
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
if (m.attributes.divisions !== void 0) result.divisions = m.attributes.divisions;
|
|
5507
|
-
if (m.attributes.time !== void 0) result.time = m.attributes.time;
|
|
5508
|
-
if (m.attributes.key !== void 0) result.key = m.attributes.key;
|
|
5509
|
-
if (m.attributes.clef !== void 0) result.clef = m.attributes.clef;
|
|
5510
|
-
if (m.attributes.staves !== void 0) result.staves = m.attributes.staves;
|
|
5511
|
-
if (m.attributes.transpose !== void 0) result.transpose = m.attributes.transpose;
|
|
5673
|
+
function buildVoiceToStaffMap(measure) {
|
|
5674
|
+
const map = /* @__PURE__ */ new Map();
|
|
5675
|
+
for (const entry of measure.entries) {
|
|
5676
|
+
if (entry.type === "note" && entry.staff !== void 0) {
|
|
5677
|
+
const voice = entry.voice;
|
|
5678
|
+
const staff = entry.staff;
|
|
5679
|
+
if (!map.has(voice)) {
|
|
5680
|
+
map.set(voice, staff);
|
|
5681
|
+
}
|
|
5512
5682
|
}
|
|
5513
5683
|
}
|
|
5514
|
-
return
|
|
5684
|
+
return {
|
|
5685
|
+
get: (voice) => map.get(voice),
|
|
5686
|
+
has: (voice) => map.has(voice),
|
|
5687
|
+
entries: () => map.entries(),
|
|
5688
|
+
size: map.size
|
|
5689
|
+
};
|
|
5515
5690
|
}
|
|
5516
|
-
function
|
|
5517
|
-
const
|
|
5518
|
-
for (const
|
|
5519
|
-
for (const
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
const minValue = pitchToSemitone(filter.pitchRange.min);
|
|
5526
|
-
if (noteValue < minValue) continue;
|
|
5527
|
-
}
|
|
5528
|
-
if (filter.pitchRange.max) {
|
|
5529
|
-
const maxValue = pitchToSemitone(filter.pitchRange.max);
|
|
5530
|
-
if (noteValue > maxValue) continue;
|
|
5531
|
-
}
|
|
5532
|
-
}
|
|
5533
|
-
if (filter.voice !== void 0 && entry.voice !== filter.voice) continue;
|
|
5534
|
-
if (filter.staff !== void 0 && (entry.staff ?? 1) !== filter.staff) continue;
|
|
5535
|
-
if (filter.noteType !== void 0 && entry.noteType !== filter.noteType) continue;
|
|
5536
|
-
if (filter.hasTie !== void 0) {
|
|
5537
|
-
const hasTie = entry.tie !== void 0;
|
|
5538
|
-
if (filter.hasTie !== hasTie) continue;
|
|
5691
|
+
function buildVoiceToStaffMapForPart(part) {
|
|
5692
|
+
const map = /* @__PURE__ */ new Map();
|
|
5693
|
+
for (const measure of part.measures) {
|
|
5694
|
+
for (const entry of measure.entries) {
|
|
5695
|
+
if (entry.type === "note" && entry.staff !== void 0) {
|
|
5696
|
+
const voice = entry.voice;
|
|
5697
|
+
const staff = entry.staff;
|
|
5698
|
+
if (!map.has(voice)) {
|
|
5699
|
+
map.set(voice, staff);
|
|
5539
5700
|
}
|
|
5540
|
-
results.push(entry);
|
|
5541
5701
|
}
|
|
5542
5702
|
}
|
|
5543
5703
|
}
|
|
5544
|
-
return
|
|
5704
|
+
return {
|
|
5705
|
+
get: (voice) => map.get(voice),
|
|
5706
|
+
has: (voice) => map.has(voice),
|
|
5707
|
+
entries: () => map.entries(),
|
|
5708
|
+
size: map.size
|
|
5709
|
+
};
|
|
5545
5710
|
}
|
|
5546
|
-
function
|
|
5547
|
-
if (
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5711
|
+
function inferStaff(entry, voiceToStaffMap) {
|
|
5712
|
+
if (entry.staff !== void 0) {
|
|
5713
|
+
return entry.staff;
|
|
5714
|
+
}
|
|
5715
|
+
const inferredStaff = voiceToStaffMap.get(entry.voice);
|
|
5716
|
+
if (inferredStaff !== void 0) {
|
|
5717
|
+
return inferredStaff;
|
|
5718
|
+
}
|
|
5719
|
+
return 1;
|
|
5720
|
+
}
|
|
5721
|
+
function getEffectiveStaff(entry, measure) {
|
|
5722
|
+
if (entry.staff !== void 0) {
|
|
5723
|
+
return entry.staff;
|
|
5724
|
+
}
|
|
5725
|
+
const map = buildVoiceToStaffMap(measure);
|
|
5726
|
+
return inferStaff(entry, map);
|
|
5727
|
+
}
|
|
5728
|
+
function getClefForStaff(score, options) {
|
|
5729
|
+
const part = score.parts[options.partIndex];
|
|
5730
|
+
if (!part) return void 0;
|
|
5731
|
+
for (let i = options.measureIndex; i >= 0; i--) {
|
|
5732
|
+
const measure = part.measures[i];
|
|
5556
5733
|
for (const entry of measure.entries) {
|
|
5557
|
-
if (entry.type === "
|
|
5558
|
-
|
|
5734
|
+
if (entry.type === "attributes" && entry.attributes.clef) {
|
|
5735
|
+
for (const clef of entry.attributes.clef) {
|
|
5736
|
+
if ((clef.staff ?? 1) === options.staff) {
|
|
5737
|
+
return clef;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5559
5740
|
}
|
|
5560
5741
|
}
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5742
|
+
if (measure.attributes?.clef) {
|
|
5743
|
+
for (const clef of measure.attributes.clef) {
|
|
5744
|
+
if ((clef.staff ?? 1) === options.staff) {
|
|
5745
|
+
return clef;
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5566
5748
|
}
|
|
5567
|
-
totalDuration += measureDuration;
|
|
5568
5749
|
}
|
|
5569
|
-
return
|
|
5570
|
-
}
|
|
5571
|
-
function getPartById(score, id) {
|
|
5572
|
-
return score.parts.find((p) => p.id === id);
|
|
5573
|
-
}
|
|
5574
|
-
function getPartIndex(score, id) {
|
|
5575
|
-
return score.parts.findIndex((p) => p.id === id);
|
|
5750
|
+
return void 0;
|
|
5576
5751
|
}
|
|
5577
|
-
function
|
|
5578
|
-
const
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5752
|
+
function getVoicesForStaff(measure, staff) {
|
|
5753
|
+
const voices = /* @__PURE__ */ new Set();
|
|
5754
|
+
for (const entry of measure.entries) {
|
|
5755
|
+
if (entry.type === "note") {
|
|
5756
|
+
const entryStaff = entry.staff ?? 1;
|
|
5757
|
+
if (entryStaff === staff) {
|
|
5758
|
+
voices.add(entry.voice);
|
|
5759
|
+
}
|
|
5583
5760
|
}
|
|
5584
5761
|
}
|
|
5585
|
-
return
|
|
5762
|
+
return Array.from(voices).sort((a, b) => a - b);
|
|
5586
5763
|
}
|
|
5587
|
-
function
|
|
5764
|
+
function getStaffRange(score, partIndex) {
|
|
5588
5765
|
const part = score.parts[partIndex];
|
|
5589
|
-
if (!part) return 1;
|
|
5766
|
+
if (!part) return { min: 1, max: 1 };
|
|
5767
|
+
let min = 1;
|
|
5768
|
+
let max = 1;
|
|
5590
5769
|
for (const measure of part.measures) {
|
|
5591
5770
|
if (measure.attributes?.staves !== void 0) {
|
|
5592
|
-
|
|
5771
|
+
max = Math.max(max, measure.attributes.staves);
|
|
5772
|
+
}
|
|
5773
|
+
for (const entry of measure.entries) {
|
|
5774
|
+
if (entry.type === "note" && entry.staff !== void 0) {
|
|
5775
|
+
max = Math.max(max, entry.staff);
|
|
5776
|
+
}
|
|
5593
5777
|
}
|
|
5594
5778
|
}
|
|
5595
|
-
return
|
|
5596
|
-
}
|
|
5597
|
-
function measureRoundtrip(original, exported) {
|
|
5598
|
-
const notesOriginal = countNotes(original);
|
|
5599
|
-
const notesExported = countNotes(exported);
|
|
5600
|
-
const notesPreserved = Math.min(notesOriginal, notesExported);
|
|
5601
|
-
const measuresOriginal = getMeasureCount(original);
|
|
5602
|
-
const measuresExported = getMeasureCount(exported);
|
|
5603
|
-
const measuresPreserved = Math.min(measuresOriginal, measuresExported);
|
|
5604
|
-
const partsOriginal = original.parts.length;
|
|
5605
|
-
const partsExported = exported.parts.length;
|
|
5606
|
-
const partsPreserved = Math.min(partsOriginal, partsExported);
|
|
5607
|
-
const preservationRate = notesOriginal > 0 ? notesPreserved / notesOriginal : 1;
|
|
5608
|
-
return {
|
|
5609
|
-
notesOriginal,
|
|
5610
|
-
notesPreserved,
|
|
5611
|
-
measuresOriginal,
|
|
5612
|
-
measuresPreserved,
|
|
5613
|
-
partsOriginal,
|
|
5614
|
-
partsPreserved,
|
|
5615
|
-
preservationRate
|
|
5616
|
-
};
|
|
5779
|
+
return { min, max };
|
|
5617
5780
|
}
|
|
5618
|
-
function
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5781
|
+
function getEntriesAtPosition(measure, position, options) {
|
|
5782
|
+
const result = [];
|
|
5783
|
+
const state = createPositionState();
|
|
5784
|
+
for (const entry of measure.entries) {
|
|
5785
|
+
const currentPosition = entry.type === "note" && entry.chord ? state.lastNonChordPosition : state.position;
|
|
5786
|
+
if (currentPosition === position) {
|
|
5787
|
+
if (entry.type === "note") {
|
|
5788
|
+
if (options?.staff !== void 0 && (entry.staff ?? 1) !== options.staff) {
|
|
5789
|
+
updatePositionForEntry(state, entry);
|
|
5790
|
+
continue;
|
|
5791
|
+
}
|
|
5792
|
+
if (options?.voice !== void 0 && entry.voice !== options.voice) {
|
|
5793
|
+
updatePositionForEntry(state, entry);
|
|
5794
|
+
continue;
|
|
5795
|
+
}
|
|
5796
|
+
if (options?.includeChordNotes === false && entry.chord) {
|
|
5797
|
+
updatePositionForEntry(state, entry);
|
|
5798
|
+
continue;
|
|
5625
5799
|
}
|
|
5626
5800
|
}
|
|
5801
|
+
result.push(entry);
|
|
5627
5802
|
}
|
|
5803
|
+
updatePositionForEntry(state, entry);
|
|
5628
5804
|
}
|
|
5629
|
-
return
|
|
5805
|
+
return result;
|
|
5806
|
+
}
|
|
5807
|
+
function getNotesAtPosition(measure, position, options) {
|
|
5808
|
+
return getEntriesAtPosition(measure, position, options).filter(
|
|
5809
|
+
(entry) => entry.type === "note"
|
|
5810
|
+
);
|
|
5811
|
+
}
|
|
5812
|
+
function getEntriesInRange(measure, range, options) {
|
|
5813
|
+
const result = [];
|
|
5814
|
+
const state = createPositionState();
|
|
5815
|
+
for (const entry of measure.entries) {
|
|
5816
|
+
const currentPosition = entry.type === "note" && entry.chord ? state.lastNonChordPosition : state.position;
|
|
5817
|
+
if (currentPosition >= range.start && currentPosition < range.end) {
|
|
5818
|
+
if (entry.type === "note") {
|
|
5819
|
+
if (options?.staff !== void 0 && (entry.staff ?? 1) !== options.staff) {
|
|
5820
|
+
updatePositionForEntry(state, entry);
|
|
5821
|
+
continue;
|
|
5822
|
+
}
|
|
5823
|
+
if (options?.voice !== void 0 && entry.voice !== options.voice) {
|
|
5824
|
+
updatePositionForEntry(state, entry);
|
|
5825
|
+
continue;
|
|
5826
|
+
}
|
|
5827
|
+
if (options?.includeChordNotes === false && entry.chord) {
|
|
5828
|
+
updatePositionForEntry(state, entry);
|
|
5829
|
+
continue;
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
result.push(entry);
|
|
5833
|
+
}
|
|
5834
|
+
updatePositionForEntry(state, entry);
|
|
5835
|
+
}
|
|
5836
|
+
return result;
|
|
5837
|
+
}
|
|
5838
|
+
function getNotesInRange(measure, range, options) {
|
|
5839
|
+
return getEntriesInRange(measure, range, options).filter(
|
|
5840
|
+
(entry) => entry.type === "note"
|
|
5841
|
+
);
|
|
5842
|
+
}
|
|
5843
|
+
function getVerticalSlice(score, options) {
|
|
5844
|
+
const parts = /* @__PURE__ */ new Map();
|
|
5845
|
+
for (let partIndex = 0; partIndex < score.parts.length; partIndex++) {
|
|
5846
|
+
const part = score.parts[partIndex];
|
|
5847
|
+
const measure = part.measures[options.measureIndex];
|
|
5848
|
+
if (!measure) continue;
|
|
5849
|
+
const notes = getNotesAtPosition(measure, options.position);
|
|
5850
|
+
if (notes.length > 0) {
|
|
5851
|
+
parts.set(partIndex, notes);
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
return {
|
|
5855
|
+
measureIndex: options.measureIndex,
|
|
5856
|
+
position: options.position,
|
|
5857
|
+
parts
|
|
5858
|
+
};
|
|
5859
|
+
}
|
|
5860
|
+
function getVoiceLine(score, options) {
|
|
5861
|
+
const part = score.parts[options.partIndex];
|
|
5862
|
+
if (!part) {
|
|
5863
|
+
return { partIndex: options.partIndex, voice: options.voice, staff: options.staff, notes: [] };
|
|
5864
|
+
}
|
|
5865
|
+
const notes = [];
|
|
5866
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
5867
|
+
const measure = part.measures[measureIndex];
|
|
5868
|
+
const state = createPositionState();
|
|
5869
|
+
for (const entry of measure.entries) {
|
|
5870
|
+
if (entry.type === "note") {
|
|
5871
|
+
const entryStaff = entry.staff ?? 1;
|
|
5872
|
+
const matchesVoice = entry.voice === options.voice;
|
|
5873
|
+
const matchesStaff = options.staff === void 0 || entryStaff === options.staff;
|
|
5874
|
+
if (matchesVoice && matchesStaff) {
|
|
5875
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
5876
|
+
notes.push({
|
|
5877
|
+
note: entry,
|
|
5878
|
+
part,
|
|
5879
|
+
partIndex: options.partIndex,
|
|
5880
|
+
measure,
|
|
5881
|
+
measureIndex,
|
|
5882
|
+
position
|
|
5883
|
+
});
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
updatePositionForEntry(state, entry);
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
return {
|
|
5890
|
+
partIndex: options.partIndex,
|
|
5891
|
+
voice: options.voice,
|
|
5892
|
+
staff: options.staff,
|
|
5893
|
+
notes
|
|
5894
|
+
};
|
|
5895
|
+
}
|
|
5896
|
+
function getVoiceLineInRange(score, options) {
|
|
5897
|
+
const part = score.parts[options.partIndex];
|
|
5898
|
+
if (!part) {
|
|
5899
|
+
return { partIndex: options.partIndex, voice: options.voice, staff: options.staff, notes: [] };
|
|
5900
|
+
}
|
|
5901
|
+
const notes = [];
|
|
5902
|
+
for (let measureIndex = options.startMeasure; measureIndex <= options.endMeasure && measureIndex < part.measures.length; measureIndex++) {
|
|
5903
|
+
const measure = part.measures[measureIndex];
|
|
5904
|
+
const state = createPositionState();
|
|
5905
|
+
for (const entry of measure.entries) {
|
|
5906
|
+
if (entry.type === "note") {
|
|
5907
|
+
const entryStaff = entry.staff ?? 1;
|
|
5908
|
+
const matchesVoice = entry.voice === options.voice;
|
|
5909
|
+
const matchesStaff = options.staff === void 0 || entryStaff === options.staff;
|
|
5910
|
+
if (matchesVoice && matchesStaff) {
|
|
5911
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
5912
|
+
notes.push({
|
|
5913
|
+
note: entry,
|
|
5914
|
+
part,
|
|
5915
|
+
partIndex: options.partIndex,
|
|
5916
|
+
measure,
|
|
5917
|
+
measureIndex,
|
|
5918
|
+
position
|
|
5919
|
+
});
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
updatePositionForEntry(state, entry);
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
return {
|
|
5926
|
+
partIndex: options.partIndex,
|
|
5927
|
+
voice: options.voice,
|
|
5928
|
+
staff: options.staff,
|
|
5929
|
+
notes
|
|
5930
|
+
};
|
|
5931
|
+
}
|
|
5932
|
+
function* iterateEntries(score) {
|
|
5933
|
+
for (let partIndex = 0; partIndex < score.parts.length; partIndex++) {
|
|
5934
|
+
const part = score.parts[partIndex];
|
|
5935
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
5936
|
+
const measure = part.measures[measureIndex];
|
|
5937
|
+
const state = createPositionState();
|
|
5938
|
+
for (const entry of measure.entries) {
|
|
5939
|
+
const position = entry.type === "note" && entry.chord ? state.lastNonChordPosition : state.position;
|
|
5940
|
+
yield {
|
|
5941
|
+
entry,
|
|
5942
|
+
part,
|
|
5943
|
+
partIndex,
|
|
5944
|
+
measure,
|
|
5945
|
+
measureIndex,
|
|
5946
|
+
position
|
|
5947
|
+
};
|
|
5948
|
+
updatePositionForEntry(state, entry);
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
function getNextNote(score, context) {
|
|
5954
|
+
const part = score.parts[context.partIndex];
|
|
5955
|
+
if (!part) return null;
|
|
5956
|
+
let foundCurrent = false;
|
|
5957
|
+
for (let measureIndex = context.measureIndex; measureIndex < part.measures.length; measureIndex++) {
|
|
5958
|
+
const measure = part.measures[measureIndex];
|
|
5959
|
+
const state = createPositionState();
|
|
5960
|
+
for (const entry of measure.entries) {
|
|
5961
|
+
if (entry.type === "note") {
|
|
5962
|
+
const entryPosition = entry.chord ? state.lastNonChordPosition : state.position;
|
|
5963
|
+
if (measureIndex === context.measureIndex && entry === context.note) {
|
|
5964
|
+
foundCurrent = true;
|
|
5965
|
+
updatePositionForEntry(state, entry);
|
|
5966
|
+
continue;
|
|
5967
|
+
}
|
|
5968
|
+
if (foundCurrent && entry.voice === context.note.voice && !entry.chord) {
|
|
5969
|
+
if (context.note.staff !== void 0) {
|
|
5970
|
+
if ((entry.staff ?? 1) !== context.note.staff) {
|
|
5971
|
+
updatePositionForEntry(state, entry);
|
|
5972
|
+
continue;
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
return {
|
|
5976
|
+
note: entry,
|
|
5977
|
+
part,
|
|
5978
|
+
partIndex: context.partIndex,
|
|
5979
|
+
measure,
|
|
5980
|
+
measureIndex,
|
|
5981
|
+
position: entryPosition
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
updatePositionForEntry(state, entry);
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
return null;
|
|
5989
|
+
}
|
|
5990
|
+
function getPrevNote(score, context) {
|
|
5991
|
+
const part = score.parts[context.partIndex];
|
|
5992
|
+
if (!part) return null;
|
|
5993
|
+
let lastCandidate = null;
|
|
5994
|
+
for (let measureIndex = 0; measureIndex <= context.measureIndex; measureIndex++) {
|
|
5995
|
+
const measure = part.measures[measureIndex];
|
|
5996
|
+
const state = createPositionState();
|
|
5997
|
+
for (const entry of measure.entries) {
|
|
5998
|
+
if (entry.type === "note") {
|
|
5999
|
+
const entryPosition = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6000
|
+
if (measureIndex === context.measureIndex && entry === context.note) {
|
|
6001
|
+
return lastCandidate;
|
|
6002
|
+
}
|
|
6003
|
+
if (entry.voice === context.note.voice && !entry.chord) {
|
|
6004
|
+
if (context.note.staff !== void 0) {
|
|
6005
|
+
if ((entry.staff ?? 1) !== context.note.staff) {
|
|
6006
|
+
updatePositionForEntry(state, entry);
|
|
6007
|
+
continue;
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
lastCandidate = {
|
|
6011
|
+
note: entry,
|
|
6012
|
+
part,
|
|
6013
|
+
partIndex: context.partIndex,
|
|
6014
|
+
measure,
|
|
6015
|
+
measureIndex,
|
|
6016
|
+
position: entryPosition
|
|
6017
|
+
};
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
updatePositionForEntry(state, entry);
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
return null;
|
|
6024
|
+
}
|
|
6025
|
+
function getAdjacentNotes(score, context) {
|
|
6026
|
+
return {
|
|
6027
|
+
prev: getPrevNote(score, context),
|
|
6028
|
+
next: getNextNote(score, context)
|
|
6029
|
+
};
|
|
6030
|
+
}
|
|
6031
|
+
function getDirections(score, options) {
|
|
6032
|
+
const results = [];
|
|
6033
|
+
const startPart = options?.partIndex ?? 0;
|
|
6034
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6035
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6036
|
+
const part = score.parts[partIndex];
|
|
6037
|
+
if (!part) continue;
|
|
6038
|
+
const startMeasure = options?.measureIndex ?? 0;
|
|
6039
|
+
const endMeasure = options?.measureIndex !== void 0 ? options.measureIndex + 1 : part.measures.length;
|
|
6040
|
+
for (let measureIndex = startMeasure; measureIndex < endMeasure; measureIndex++) {
|
|
6041
|
+
const measure = part.measures[measureIndex];
|
|
6042
|
+
if (!measure) continue;
|
|
6043
|
+
const state = createPositionState();
|
|
6044
|
+
for (const entry of measure.entries) {
|
|
6045
|
+
if (entry.type === "direction") {
|
|
6046
|
+
results.push({
|
|
6047
|
+
direction: entry,
|
|
6048
|
+
part,
|
|
6049
|
+
partIndex,
|
|
6050
|
+
measure,
|
|
6051
|
+
measureIndex,
|
|
6052
|
+
position: state.position
|
|
6053
|
+
});
|
|
6054
|
+
}
|
|
6055
|
+
updatePositionForEntry(state, entry);
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
return results;
|
|
6060
|
+
}
|
|
6061
|
+
function getDirectionsAtPosition(measure, position) {
|
|
6062
|
+
const results = [];
|
|
6063
|
+
const state = createPositionState();
|
|
6064
|
+
for (const entry of measure.entries) {
|
|
6065
|
+
if (entry.type === "direction" && state.position === position) {
|
|
6066
|
+
results.push(entry);
|
|
6067
|
+
}
|
|
6068
|
+
updatePositionForEntry(state, entry);
|
|
6069
|
+
}
|
|
6070
|
+
return results;
|
|
6071
|
+
}
|
|
6072
|
+
function findDirectionsByType(score, kind) {
|
|
6073
|
+
const allDirections = getDirections(score);
|
|
6074
|
+
return allDirections.filter(
|
|
6075
|
+
(d) => d.direction.directionTypes.some((dt) => dt.kind === kind)
|
|
6076
|
+
);
|
|
6077
|
+
}
|
|
6078
|
+
function getDynamics(score, options) {
|
|
6079
|
+
const results = [];
|
|
6080
|
+
const startPart = options?.partIndex ?? 0;
|
|
6081
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6082
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6083
|
+
const part = score.parts[partIndex];
|
|
6084
|
+
if (!part) continue;
|
|
6085
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6086
|
+
const measure = part.measures[measureIndex];
|
|
6087
|
+
const state = createPositionState();
|
|
6088
|
+
for (const entry of measure.entries) {
|
|
6089
|
+
if (entry.type === "direction") {
|
|
6090
|
+
for (const dirType of entry.directionTypes) {
|
|
6091
|
+
if (dirType.kind === "dynamics") {
|
|
6092
|
+
results.push({
|
|
6093
|
+
dynamic: dirType.value,
|
|
6094
|
+
direction: entry,
|
|
6095
|
+
part,
|
|
6096
|
+
partIndex,
|
|
6097
|
+
measure,
|
|
6098
|
+
measureIndex,
|
|
6099
|
+
position: state.position
|
|
6100
|
+
});
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
updatePositionForEntry(state, entry);
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
return results;
|
|
6109
|
+
}
|
|
6110
|
+
function getTempoMarkings(score) {
|
|
6111
|
+
const results = [];
|
|
6112
|
+
for (let partIndex = 0; partIndex < score.parts.length; partIndex++) {
|
|
6113
|
+
const part = score.parts[partIndex];
|
|
6114
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6115
|
+
const measure = part.measures[measureIndex];
|
|
6116
|
+
const state = createPositionState();
|
|
6117
|
+
for (const entry of measure.entries) {
|
|
6118
|
+
if (entry.type === "direction") {
|
|
6119
|
+
for (const dirType of entry.directionTypes) {
|
|
6120
|
+
if (dirType.kind === "metronome") {
|
|
6121
|
+
results.push({
|
|
6122
|
+
beatUnit: dirType.beatUnit,
|
|
6123
|
+
perMinute: dirType.perMinute,
|
|
6124
|
+
beatUnitDot: dirType.beatUnitDot,
|
|
6125
|
+
direction: entry,
|
|
6126
|
+
partIndex,
|
|
6127
|
+
measureIndex,
|
|
6128
|
+
position: state.position
|
|
6129
|
+
});
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
}
|
|
6133
|
+
updatePositionForEntry(state, entry);
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
}
|
|
6137
|
+
return results;
|
|
6138
|
+
}
|
|
6139
|
+
function getPedalMarkings(score, options) {
|
|
6140
|
+
const results = [];
|
|
6141
|
+
const startPart = options?.partIndex ?? 0;
|
|
6142
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6143
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6144
|
+
const part = score.parts[partIndex];
|
|
6145
|
+
if (!part) continue;
|
|
6146
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6147
|
+
const measure = part.measures[measureIndex];
|
|
6148
|
+
const state = createPositionState();
|
|
6149
|
+
for (const entry of measure.entries) {
|
|
6150
|
+
if (entry.type === "direction") {
|
|
6151
|
+
for (const dirType of entry.directionTypes) {
|
|
6152
|
+
if (dirType.kind === "pedal") {
|
|
6153
|
+
results.push({
|
|
6154
|
+
pedalType: dirType.type,
|
|
6155
|
+
direction: entry,
|
|
6156
|
+
partIndex,
|
|
6157
|
+
measureIndex,
|
|
6158
|
+
position: state.position
|
|
6159
|
+
});
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
updatePositionForEntry(state, entry);
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
return results;
|
|
6168
|
+
}
|
|
6169
|
+
function getWedges(score, options) {
|
|
6170
|
+
const results = [];
|
|
6171
|
+
const startPart = options?.partIndex ?? 0;
|
|
6172
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6173
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6174
|
+
const part = score.parts[partIndex];
|
|
6175
|
+
if (!part) continue;
|
|
6176
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6177
|
+
const measure = part.measures[measureIndex];
|
|
6178
|
+
const state = createPositionState();
|
|
6179
|
+
for (const entry of measure.entries) {
|
|
6180
|
+
if (entry.type === "direction") {
|
|
6181
|
+
for (const dirType of entry.directionTypes) {
|
|
6182
|
+
if (dirType.kind === "wedge") {
|
|
6183
|
+
results.push({
|
|
6184
|
+
wedgeType: dirType.type,
|
|
6185
|
+
direction: entry,
|
|
6186
|
+
partIndex,
|
|
6187
|
+
measureIndex,
|
|
6188
|
+
position: state.position
|
|
6189
|
+
});
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
updatePositionForEntry(state, entry);
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
}
|
|
6197
|
+
return results;
|
|
6198
|
+
}
|
|
6199
|
+
function getOctaveShifts(score, options) {
|
|
6200
|
+
const results = [];
|
|
6201
|
+
const startPart = options?.partIndex ?? 0;
|
|
6202
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6203
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6204
|
+
const part = score.parts[partIndex];
|
|
6205
|
+
if (!part) continue;
|
|
6206
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6207
|
+
const measure = part.measures[measureIndex];
|
|
6208
|
+
const state = createPositionState();
|
|
6209
|
+
for (const entry of measure.entries) {
|
|
6210
|
+
if (entry.type === "direction") {
|
|
6211
|
+
for (const dirType of entry.directionTypes) {
|
|
6212
|
+
if (dirType.kind === "octave-shift") {
|
|
6213
|
+
results.push({
|
|
6214
|
+
shiftType: dirType.type,
|
|
6215
|
+
size: dirType.size,
|
|
6216
|
+
direction: entry,
|
|
6217
|
+
partIndex,
|
|
6218
|
+
measureIndex,
|
|
6219
|
+
position: state.position
|
|
6220
|
+
});
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
updatePositionForEntry(state, entry);
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
return results;
|
|
6229
|
+
}
|
|
6230
|
+
function getTiedNoteGroups(score, options) {
|
|
6231
|
+
const results = [];
|
|
6232
|
+
const startPart = options?.partIndex ?? 0;
|
|
6233
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6234
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6235
|
+
const part = score.parts[partIndex];
|
|
6236
|
+
if (!part) continue;
|
|
6237
|
+
const pendingTies = /* @__PURE__ */ new Map();
|
|
6238
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6239
|
+
const measure = part.measures[measureIndex];
|
|
6240
|
+
const state = createPositionState();
|
|
6241
|
+
for (const entry of measure.entries) {
|
|
6242
|
+
if (entry.type === "note" && entry.pitch) {
|
|
6243
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6244
|
+
const pitchKey = `${entry.pitch.step}${entry.pitch.octave}-${entry.voice}`;
|
|
6245
|
+
const context = {
|
|
6246
|
+
note: entry,
|
|
6247
|
+
part,
|
|
6248
|
+
partIndex,
|
|
6249
|
+
measure,
|
|
6250
|
+
measureIndex,
|
|
6251
|
+
position
|
|
6252
|
+
};
|
|
6253
|
+
const hasTieStart = entry.tie?.type === "start" || entry.ties?.some((t) => t.type === "start") || entry.notations?.some((n) => n.type === "tied" && n.tiedType === "start");
|
|
6254
|
+
const hasTieStop = entry.tie?.type === "stop" || entry.ties?.some((t) => t.type === "stop") || entry.notations?.some((n) => n.type === "tied" && n.tiedType === "stop");
|
|
6255
|
+
if (hasTieStop && pendingTies.has(pitchKey)) {
|
|
6256
|
+
const group = pendingTies.get(pitchKey);
|
|
6257
|
+
group.push(context);
|
|
6258
|
+
if (!hasTieStart) {
|
|
6259
|
+
const totalDuration = group.reduce((sum, nc) => sum + nc.note.duration, 0);
|
|
6260
|
+
results.push({ notes: group, totalDuration });
|
|
6261
|
+
pendingTies.delete(pitchKey);
|
|
6262
|
+
}
|
|
6263
|
+
} else if (hasTieStart) {
|
|
6264
|
+
pendingTies.set(pitchKey, [context]);
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
updatePositionForEntry(state, entry);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
return results;
|
|
6272
|
+
}
|
|
6273
|
+
function getSlurSpans(score, options) {
|
|
6274
|
+
const results = [];
|
|
6275
|
+
const startPart = options?.partIndex ?? 0;
|
|
6276
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6277
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6278
|
+
const part = score.parts[partIndex];
|
|
6279
|
+
if (!part) continue;
|
|
6280
|
+
const pendingSlurs = /* @__PURE__ */ new Map();
|
|
6281
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6282
|
+
const measure = part.measures[measureIndex];
|
|
6283
|
+
const state = createPositionState();
|
|
6284
|
+
for (const entry of measure.entries) {
|
|
6285
|
+
if (entry.type === "note") {
|
|
6286
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6287
|
+
const context = {
|
|
6288
|
+
note: entry,
|
|
6289
|
+
part,
|
|
6290
|
+
partIndex,
|
|
6291
|
+
measure,
|
|
6292
|
+
measureIndex,
|
|
6293
|
+
position
|
|
6294
|
+
};
|
|
6295
|
+
if (entry.notations) {
|
|
6296
|
+
for (const notation of entry.notations) {
|
|
6297
|
+
if (notation.type === "slur") {
|
|
6298
|
+
const slurNumber = notation.number ?? 1;
|
|
6299
|
+
if (notation.slurType === "start") {
|
|
6300
|
+
pendingSlurs.set(slurNumber, { startNote: context, notes: [context] });
|
|
6301
|
+
} else if (notation.slurType === "stop" && pendingSlurs.has(slurNumber)) {
|
|
6302
|
+
const pending = pendingSlurs.get(slurNumber);
|
|
6303
|
+
pending.notes.push(context);
|
|
6304
|
+
results.push({
|
|
6305
|
+
number: slurNumber,
|
|
6306
|
+
startNote: pending.startNote,
|
|
6307
|
+
endNote: context,
|
|
6308
|
+
notes: pending.notes
|
|
6309
|
+
});
|
|
6310
|
+
pendingSlurs.delete(slurNumber);
|
|
6311
|
+
} else if (notation.slurType === "continue" && pendingSlurs.has(slurNumber)) {
|
|
6312
|
+
pendingSlurs.get(slurNumber).notes.push(context);
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
for (const [, pending] of pendingSlurs) {
|
|
6318
|
+
const lastNote = pending.notes[pending.notes.length - 1];
|
|
6319
|
+
if (lastNote !== context) {
|
|
6320
|
+
pending.notes.push(context);
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
updatePositionForEntry(state, entry);
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
return results;
|
|
6329
|
+
}
|
|
6330
|
+
function getTupletGroups(score, options) {
|
|
6331
|
+
const results = [];
|
|
6332
|
+
const startPart = options?.partIndex ?? 0;
|
|
6333
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6334
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6335
|
+
const part = score.parts[partIndex];
|
|
6336
|
+
if (!part) continue;
|
|
6337
|
+
const pendingTuplets = /* @__PURE__ */ new Map();
|
|
6338
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6339
|
+
const measure = part.measures[measureIndex];
|
|
6340
|
+
const state = createPositionState();
|
|
6341
|
+
for (const entry of measure.entries) {
|
|
6342
|
+
if (entry.type === "note") {
|
|
6343
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6344
|
+
const context = {
|
|
6345
|
+
note: entry,
|
|
6346
|
+
part,
|
|
6347
|
+
partIndex,
|
|
6348
|
+
measure,
|
|
6349
|
+
measureIndex,
|
|
6350
|
+
position
|
|
6351
|
+
};
|
|
6352
|
+
if (entry.notations) {
|
|
6353
|
+
for (const notation of entry.notations) {
|
|
6354
|
+
if (notation.type === "tuplet") {
|
|
6355
|
+
const tupletNumber = notation.number ?? 1;
|
|
6356
|
+
if (notation.tupletType === "start") {
|
|
6357
|
+
const actualNotes = entry.timeModification?.actualNotes ?? 3;
|
|
6358
|
+
const normalNotes = entry.timeModification?.normalNotes ?? 2;
|
|
6359
|
+
pendingTuplets.set(tupletNumber, {
|
|
6360
|
+
notes: [context],
|
|
6361
|
+
actualNotes,
|
|
6362
|
+
normalNotes
|
|
6363
|
+
});
|
|
6364
|
+
} else if (notation.tupletType === "stop" && pendingTuplets.has(tupletNumber)) {
|
|
6365
|
+
const pending = pendingTuplets.get(tupletNumber);
|
|
6366
|
+
pending.notes.push(context);
|
|
6367
|
+
results.push({
|
|
6368
|
+
number: tupletNumber,
|
|
6369
|
+
notes: pending.notes,
|
|
6370
|
+
actualNotes: pending.actualNotes,
|
|
6371
|
+
normalNotes: pending.normalNotes
|
|
6372
|
+
});
|
|
6373
|
+
pendingTuplets.delete(tupletNumber);
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
if (entry.timeModification && !entry.notations?.some((n) => n.type === "tuplet")) {
|
|
6379
|
+
for (const [, pending] of pendingTuplets) {
|
|
6380
|
+
if (entry.timeModification.actualNotes === pending.actualNotes && entry.timeModification.normalNotes === pending.normalNotes) {
|
|
6381
|
+
pending.notes.push(context);
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
updatePositionForEntry(state, entry);
|
|
6387
|
+
}
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
return results;
|
|
6391
|
+
}
|
|
6392
|
+
function getBeamGroups(measure) {
|
|
6393
|
+
const results = [];
|
|
6394
|
+
const state = createPositionState();
|
|
6395
|
+
const pendingBeams = /* @__PURE__ */ new Map();
|
|
6396
|
+
for (const entry of measure.entries) {
|
|
6397
|
+
if (entry.type === "note" && entry.beam) {
|
|
6398
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6399
|
+
const context = {
|
|
6400
|
+
note: entry,
|
|
6401
|
+
part: {},
|
|
6402
|
+
// Will be set by caller if needed
|
|
6403
|
+
partIndex: 0,
|
|
6404
|
+
measure,
|
|
6405
|
+
measureIndex: 0,
|
|
6406
|
+
position
|
|
6407
|
+
};
|
|
6408
|
+
for (const beam of entry.beam) {
|
|
6409
|
+
const beamNumber = beam.number;
|
|
6410
|
+
if (beam.type === "begin") {
|
|
6411
|
+
pendingBeams.set(beamNumber, [context]);
|
|
6412
|
+
} else if (beam.type === "continue" && pendingBeams.has(beamNumber)) {
|
|
6413
|
+
pendingBeams.get(beamNumber).push(context);
|
|
6414
|
+
} else if (beam.type === "end" && pendingBeams.has(beamNumber)) {
|
|
6415
|
+
const group = pendingBeams.get(beamNumber);
|
|
6416
|
+
group.push(context);
|
|
6417
|
+
results.push({
|
|
6418
|
+
notes: group,
|
|
6419
|
+
beamLevel: beamNumber
|
|
6420
|
+
});
|
|
6421
|
+
pendingBeams.delete(beamNumber);
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
updatePositionForEntry(state, entry);
|
|
6426
|
+
}
|
|
6427
|
+
return results;
|
|
6428
|
+
}
|
|
6429
|
+
function findNotesWithNotation(score, notationType, options) {
|
|
6430
|
+
const results = [];
|
|
6431
|
+
const startPart = options?.partIndex ?? 0;
|
|
6432
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6433
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6434
|
+
const part = score.parts[partIndex];
|
|
6435
|
+
if (!part) continue;
|
|
6436
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6437
|
+
const measure = part.measures[measureIndex];
|
|
6438
|
+
const state = createPositionState();
|
|
6439
|
+
for (const entry of measure.entries) {
|
|
6440
|
+
if (entry.type === "note") {
|
|
6441
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6442
|
+
if (entry.notations?.some((n) => n.type === notationType)) {
|
|
6443
|
+
results.push({
|
|
6444
|
+
note: entry,
|
|
6445
|
+
part,
|
|
6446
|
+
partIndex,
|
|
6447
|
+
measure,
|
|
6448
|
+
measureIndex,
|
|
6449
|
+
position
|
|
6450
|
+
});
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
updatePositionForEntry(state, entry);
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
return results;
|
|
6458
|
+
}
|
|
6459
|
+
function getHarmonies(score, options) {
|
|
6460
|
+
const results = [];
|
|
6461
|
+
const startPart = options?.partIndex ?? 0;
|
|
6462
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6463
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6464
|
+
const part = score.parts[partIndex];
|
|
6465
|
+
if (!part) continue;
|
|
6466
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6467
|
+
const measure = part.measures[measureIndex];
|
|
6468
|
+
const state = createPositionState();
|
|
6469
|
+
for (const entry of measure.entries) {
|
|
6470
|
+
if (entry.type === "harmony") {
|
|
6471
|
+
results.push({
|
|
6472
|
+
harmony: entry,
|
|
6473
|
+
part,
|
|
6474
|
+
partIndex,
|
|
6475
|
+
measure,
|
|
6476
|
+
measureIndex,
|
|
6477
|
+
position: state.position
|
|
6478
|
+
});
|
|
6479
|
+
}
|
|
6480
|
+
updatePositionForEntry(state, entry);
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
return results;
|
|
6485
|
+
}
|
|
6486
|
+
function getHarmonyAtPosition(measure, position) {
|
|
6487
|
+
const state = createPositionState();
|
|
6488
|
+
let lastHarmony;
|
|
6489
|
+
for (const entry of measure.entries) {
|
|
6490
|
+
if (entry.type === "harmony") {
|
|
6491
|
+
if (state.position <= position) {
|
|
6492
|
+
lastHarmony = entry;
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
updatePositionForEntry(state, entry);
|
|
6496
|
+
if (state.position > position && lastHarmony) {
|
|
6497
|
+
break;
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
return lastHarmony;
|
|
6501
|
+
}
|
|
6502
|
+
function getChordProgression(score, options) {
|
|
6503
|
+
const harmonies = getHarmonies(score, options);
|
|
6504
|
+
return harmonies.map((h) => {
|
|
6505
|
+
const rootAlter = h.harmony.root.rootAlter ?? 0;
|
|
6506
|
+
const rootAccidental = rootAlter > 0 ? "#".repeat(rootAlter) : "b".repeat(-rootAlter);
|
|
6507
|
+
const root = h.harmony.root.rootStep + rootAccidental;
|
|
6508
|
+
let bass;
|
|
6509
|
+
if (h.harmony.bass) {
|
|
6510
|
+
const bassAlter = h.harmony.bass.bassAlter ?? 0;
|
|
6511
|
+
const bassAccidental = bassAlter > 0 ? "#".repeat(bassAlter) : "b".repeat(-bassAlter);
|
|
6512
|
+
bass = h.harmony.bass.bassStep + bassAccidental;
|
|
6513
|
+
}
|
|
6514
|
+
return {
|
|
6515
|
+
root,
|
|
6516
|
+
kind: h.harmony.kind,
|
|
6517
|
+
bass,
|
|
6518
|
+
measureIndex: h.measureIndex,
|
|
6519
|
+
position: h.position
|
|
6520
|
+
};
|
|
6521
|
+
});
|
|
6522
|
+
}
|
|
6523
|
+
function getLyrics(score, options) {
|
|
6524
|
+
const results = [];
|
|
6525
|
+
const startPart = options?.partIndex ?? 0;
|
|
6526
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6527
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6528
|
+
const part = score.parts[partIndex];
|
|
6529
|
+
if (!part) continue;
|
|
6530
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6531
|
+
const measure = part.measures[measureIndex];
|
|
6532
|
+
const state = createPositionState();
|
|
6533
|
+
for (const entry of measure.entries) {
|
|
6534
|
+
if (entry.type === "note" && entry.lyrics) {
|
|
6535
|
+
const position = entry.chord ? state.lastNonChordPosition : state.position;
|
|
6536
|
+
for (const lyric of entry.lyrics) {
|
|
6537
|
+
const verse = lyric.number ?? 1;
|
|
6538
|
+
if (options?.verse !== void 0 && verse !== options.verse) {
|
|
6539
|
+
continue;
|
|
6540
|
+
}
|
|
6541
|
+
results.push({
|
|
6542
|
+
lyric,
|
|
6543
|
+
note: entry,
|
|
6544
|
+
part,
|
|
6545
|
+
partIndex,
|
|
6546
|
+
measure,
|
|
6547
|
+
measureIndex,
|
|
6548
|
+
position,
|
|
6549
|
+
verse
|
|
6550
|
+
});
|
|
6551
|
+
}
|
|
6552
|
+
}
|
|
6553
|
+
updatePositionForEntry(state, entry);
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
return results;
|
|
6558
|
+
}
|
|
6559
|
+
function getLyricText(score, options) {
|
|
6560
|
+
const lyrics = getLyrics(score, options);
|
|
6561
|
+
const verseMap = /* @__PURE__ */ new Map();
|
|
6562
|
+
for (const lyric of lyrics) {
|
|
6563
|
+
if (!verseMap.has(lyric.verse)) {
|
|
6564
|
+
verseMap.set(lyric.verse, []);
|
|
6565
|
+
}
|
|
6566
|
+
verseMap.get(lyric.verse).push(lyric);
|
|
6567
|
+
}
|
|
6568
|
+
const results = [];
|
|
6569
|
+
for (const [verse, verseLyrics] of verseMap) {
|
|
6570
|
+
const syllables = [];
|
|
6571
|
+
const textParts = [];
|
|
6572
|
+
for (const lyric of verseLyrics) {
|
|
6573
|
+
const text = lyric.lyric.text;
|
|
6574
|
+
syllables.push({
|
|
6575
|
+
text,
|
|
6576
|
+
position: lyric.position,
|
|
6577
|
+
measureIndex: lyric.measureIndex
|
|
6578
|
+
});
|
|
6579
|
+
const syllabic = lyric.lyric.syllabic;
|
|
6580
|
+
if (syllabic === "begin" || syllabic === "middle") {
|
|
6581
|
+
textParts.push(text + "-");
|
|
6582
|
+
} else if (syllabic === "end") {
|
|
6583
|
+
textParts.push(text + " ");
|
|
6584
|
+
} else {
|
|
6585
|
+
textParts.push(text + " ");
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
results.push({
|
|
6589
|
+
verse,
|
|
6590
|
+
text: textParts.join("").trim(),
|
|
6591
|
+
syllables
|
|
6592
|
+
});
|
|
6593
|
+
}
|
|
6594
|
+
return results.sort((a, b) => a.verse - b.verse);
|
|
6595
|
+
}
|
|
6596
|
+
function getVerseCount(score, options) {
|
|
6597
|
+
const verses = /* @__PURE__ */ new Set();
|
|
6598
|
+
const startPart = options?.partIndex ?? 0;
|
|
6599
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6600
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6601
|
+
const part = score.parts[partIndex];
|
|
6602
|
+
if (!part) continue;
|
|
6603
|
+
for (const measure of part.measures) {
|
|
6604
|
+
for (const entry of measure.entries) {
|
|
6605
|
+
if (entry.type === "note" && entry.lyrics) {
|
|
6606
|
+
for (const lyric of entry.lyrics) {
|
|
6607
|
+
verses.add(lyric.number ?? 1);
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
return verses.size;
|
|
6614
|
+
}
|
|
6615
|
+
function getRepeatStructure(score, options) {
|
|
6616
|
+
const results = [];
|
|
6617
|
+
const partIndex = options?.partIndex ?? 0;
|
|
6618
|
+
const part = score.parts[partIndex];
|
|
6619
|
+
if (!part) return results;
|
|
6620
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6621
|
+
const measure = part.measures[measureIndex];
|
|
6622
|
+
const measureNumber = measure.number ?? String(measureIndex + 1);
|
|
6623
|
+
if (measure.barlines) {
|
|
6624
|
+
for (const barline of measure.barlines) {
|
|
6625
|
+
if (barline.repeat) {
|
|
6626
|
+
results.push({
|
|
6627
|
+
type: barline.repeat.direction,
|
|
6628
|
+
times: barline.repeat.times,
|
|
6629
|
+
measureIndex,
|
|
6630
|
+
measureNumber
|
|
6631
|
+
});
|
|
6632
|
+
}
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
return results;
|
|
6637
|
+
}
|
|
6638
|
+
function findBarlines(score, options) {
|
|
6639
|
+
const results = [];
|
|
6640
|
+
const startPart = options?.partIndex ?? 0;
|
|
6641
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6642
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6643
|
+
const part = score.parts[partIndex];
|
|
6644
|
+
if (!part) continue;
|
|
6645
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6646
|
+
const measure = part.measures[measureIndex];
|
|
6647
|
+
const measureNumber = measure.number ?? String(measureIndex + 1);
|
|
6648
|
+
if (measure.barlines) {
|
|
6649
|
+
for (const barline of measure.barlines) {
|
|
6650
|
+
if (options?.style !== void 0 && barline.barStyle !== options.style) {
|
|
6651
|
+
continue;
|
|
6652
|
+
}
|
|
6653
|
+
if (options?.repeat !== void 0) {
|
|
6654
|
+
const hasRepeat = barline.repeat !== void 0;
|
|
6655
|
+
if (hasRepeat !== options.repeat) {
|
|
6656
|
+
continue;
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
results.push({
|
|
6660
|
+
barline,
|
|
6661
|
+
partIndex,
|
|
6662
|
+
measureIndex,
|
|
6663
|
+
measureNumber
|
|
6664
|
+
});
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
return results;
|
|
6670
|
+
}
|
|
6671
|
+
function getEndings(score, options) {
|
|
6672
|
+
const results = [];
|
|
6673
|
+
const startPart = options?.partIndex ?? 0;
|
|
6674
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6675
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6676
|
+
const part = score.parts[partIndex];
|
|
6677
|
+
if (!part) continue;
|
|
6678
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6679
|
+
const measure = part.measures[measureIndex];
|
|
6680
|
+
const measureNumber = measure.number ?? String(measureIndex + 1);
|
|
6681
|
+
if (measure.barlines) {
|
|
6682
|
+
for (const barline of measure.barlines) {
|
|
6683
|
+
if (barline.ending) {
|
|
6684
|
+
results.push({
|
|
6685
|
+
number: barline.ending.number,
|
|
6686
|
+
type: barline.ending.type,
|
|
6687
|
+
partIndex,
|
|
6688
|
+
measureIndex,
|
|
6689
|
+
measureNumber
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
return results;
|
|
6697
|
+
}
|
|
6698
|
+
function getKeyChanges(score, options) {
|
|
6699
|
+
const results = [];
|
|
6700
|
+
const startPart = options?.partIndex ?? 0;
|
|
6701
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6702
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6703
|
+
const part = score.parts[partIndex];
|
|
6704
|
+
if (!part) continue;
|
|
6705
|
+
let lastKey;
|
|
6706
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6707
|
+
const measure = part.measures[measureIndex];
|
|
6708
|
+
const measureNumber = measure.number ?? String(measureIndex + 1);
|
|
6709
|
+
const state = createPositionState();
|
|
6710
|
+
if (measure.attributes?.key) {
|
|
6711
|
+
const key = measure.attributes.key;
|
|
6712
|
+
if (!lastKey || lastKey.fifths !== key.fifths || lastKey.mode !== key.mode) {
|
|
6713
|
+
results.push({
|
|
6714
|
+
key,
|
|
6715
|
+
partIndex,
|
|
6716
|
+
measureIndex,
|
|
6717
|
+
measureNumber,
|
|
6718
|
+
position: 0
|
|
6719
|
+
});
|
|
6720
|
+
lastKey = key;
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
for (const entry of measure.entries) {
|
|
6724
|
+
if (entry.type === "attributes" && entry.attributes.key) {
|
|
6725
|
+
const key = entry.attributes.key;
|
|
6726
|
+
if (!lastKey || lastKey.fifths !== key.fifths || lastKey.mode !== key.mode) {
|
|
6727
|
+
results.push({
|
|
6728
|
+
key,
|
|
6729
|
+
partIndex,
|
|
6730
|
+
measureIndex,
|
|
6731
|
+
measureNumber,
|
|
6732
|
+
position: state.position
|
|
6733
|
+
});
|
|
6734
|
+
lastKey = key;
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
updatePositionForEntry(state, entry);
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
return results;
|
|
6742
|
+
}
|
|
6743
|
+
function getTimeChanges(score, options) {
|
|
6744
|
+
const results = [];
|
|
6745
|
+
const startPart = options?.partIndex ?? 0;
|
|
6746
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6747
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6748
|
+
const part = score.parts[partIndex];
|
|
6749
|
+
if (!part) continue;
|
|
6750
|
+
let lastTime;
|
|
6751
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6752
|
+
const measure = part.measures[measureIndex];
|
|
6753
|
+
const measureNumber = measure.number ?? String(measureIndex + 1);
|
|
6754
|
+
if (measure.attributes?.time) {
|
|
6755
|
+
const time = measure.attributes.time;
|
|
6756
|
+
if (!lastTime || lastTime.beats !== time.beats || lastTime.beatType !== time.beatType) {
|
|
6757
|
+
results.push({
|
|
6758
|
+
time,
|
|
6759
|
+
partIndex,
|
|
6760
|
+
measureIndex,
|
|
6761
|
+
measureNumber
|
|
6762
|
+
});
|
|
6763
|
+
lastTime = time;
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
for (const entry of measure.entries) {
|
|
6767
|
+
if (entry.type === "attributes" && entry.attributes.time) {
|
|
6768
|
+
const time = entry.attributes.time;
|
|
6769
|
+
if (!lastTime || lastTime.beats !== time.beats || lastTime.beatType !== time.beatType) {
|
|
6770
|
+
results.push({
|
|
6771
|
+
time,
|
|
6772
|
+
partIndex,
|
|
6773
|
+
measureIndex,
|
|
6774
|
+
measureNumber
|
|
6775
|
+
});
|
|
6776
|
+
lastTime = time;
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
return results;
|
|
6783
|
+
}
|
|
6784
|
+
function getClefChanges(score, options) {
|
|
6785
|
+
const results = [];
|
|
6786
|
+
const startPart = options?.partIndex ?? 0;
|
|
6787
|
+
const endPart = options?.partIndex !== void 0 ? options.partIndex + 1 : score.parts.length;
|
|
6788
|
+
for (let partIndex = startPart; partIndex < endPart; partIndex++) {
|
|
6789
|
+
const part = score.parts[partIndex];
|
|
6790
|
+
if (!part) continue;
|
|
6791
|
+
const lastClefs = /* @__PURE__ */ new Map();
|
|
6792
|
+
for (let measureIndex = 0; measureIndex < part.measures.length; measureIndex++) {
|
|
6793
|
+
const measure = part.measures[measureIndex];
|
|
6794
|
+
const measureNumber = measure.number ?? String(measureIndex + 1);
|
|
6795
|
+
const state = createPositionState();
|
|
6796
|
+
if (measure.attributes?.clef) {
|
|
6797
|
+
for (const clef of measure.attributes.clef) {
|
|
6798
|
+
const staff = clef.staff ?? 1;
|
|
6799
|
+
if (options?.staff !== void 0 && staff !== options.staff) {
|
|
6800
|
+
continue;
|
|
6801
|
+
}
|
|
6802
|
+
const lastClef = lastClefs.get(staff);
|
|
6803
|
+
if (!lastClef || lastClef.sign !== clef.sign || lastClef.line !== clef.line || lastClef.clefOctaveChange !== clef.clefOctaveChange) {
|
|
6804
|
+
results.push({
|
|
6805
|
+
clef,
|
|
6806
|
+
staff,
|
|
6807
|
+
partIndex,
|
|
6808
|
+
measureIndex,
|
|
6809
|
+
measureNumber,
|
|
6810
|
+
position: 0
|
|
6811
|
+
});
|
|
6812
|
+
lastClefs.set(staff, clef);
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
for (const entry of measure.entries) {
|
|
6817
|
+
if (entry.type === "attributes" && entry.attributes.clef) {
|
|
6818
|
+
for (const clef of entry.attributes.clef) {
|
|
6819
|
+
const staff = clef.staff ?? 1;
|
|
6820
|
+
if (options?.staff !== void 0 && staff !== options.staff) {
|
|
6821
|
+
continue;
|
|
6822
|
+
}
|
|
6823
|
+
const lastClef = lastClefs.get(staff);
|
|
6824
|
+
if (!lastClef || lastClef.sign !== clef.sign || lastClef.line !== clef.line || lastClef.clefOctaveChange !== clef.clefOctaveChange) {
|
|
6825
|
+
results.push({
|
|
6826
|
+
clef,
|
|
6827
|
+
staff,
|
|
6828
|
+
partIndex,
|
|
6829
|
+
measureIndex,
|
|
6830
|
+
measureNumber,
|
|
6831
|
+
position: state.position
|
|
6832
|
+
});
|
|
6833
|
+
lastClefs.set(staff, clef);
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
updatePositionForEntry(state, entry);
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
return results;
|
|
6842
|
+
}
|
|
6843
|
+
function getStructuralChanges(score, options) {
|
|
6844
|
+
return {
|
|
6845
|
+
keyChanges: getKeyChanges(score, options),
|
|
6846
|
+
timeChanges: getTimeChanges(score, options),
|
|
6847
|
+
clefChanges: getClefChanges(score, options)
|
|
6848
|
+
};
|
|
6849
|
+
}
|
|
6850
|
+
function getPartByIndex(score, index) {
|
|
6851
|
+
return score.parts[index];
|
|
6852
|
+
}
|
|
6853
|
+
function getPartCount(score) {
|
|
6854
|
+
return score.parts.length;
|
|
6855
|
+
}
|
|
6856
|
+
function getPartIds(score) {
|
|
6857
|
+
return score.parts.map((part) => part.id);
|
|
6858
|
+
}
|
|
6859
|
+
|
|
6860
|
+
// src/query/index.ts
|
|
6861
|
+
function getMeasure(score, options) {
|
|
6862
|
+
const part = score.parts[options.part];
|
|
6863
|
+
if (!part) return void 0;
|
|
6864
|
+
const targetMeasure = String(options.measure);
|
|
6865
|
+
return part.measures.find((m) => m.number === targetMeasure);
|
|
6866
|
+
}
|
|
6867
|
+
function getMeasureByIndex(score, options) {
|
|
6868
|
+
const part = score.parts[options.part];
|
|
6869
|
+
if (!part) return void 0;
|
|
6870
|
+
return part.measures[options.measureIndex];
|
|
6871
|
+
}
|
|
6872
|
+
function getMeasureCount(score) {
|
|
6873
|
+
if (score.parts.length === 0) return 0;
|
|
6874
|
+
return score.parts[0].measures.length;
|
|
6875
|
+
}
|
|
6876
|
+
function getDivisions(score, options) {
|
|
6877
|
+
const part = score.parts[options.part];
|
|
6878
|
+
if (!part) return 1;
|
|
6879
|
+
const targetMeasure = parseInt(String(options.measure), 10);
|
|
6880
|
+
if (isNaN(targetMeasure)) return 1;
|
|
6881
|
+
for (let i = 0; i < part.measures.length; i++) {
|
|
6882
|
+
const m = part.measures[i];
|
|
6883
|
+
const mNum = parseInt(m.number, 10);
|
|
6884
|
+
if (!isNaN(mNum) && mNum > targetMeasure) break;
|
|
6885
|
+
if (m.attributes?.divisions !== void 0) {
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
let divisions = 1;
|
|
6889
|
+
for (const m of part.measures) {
|
|
6890
|
+
const mNum = parseInt(m.number, 10);
|
|
6891
|
+
if (!isNaN(mNum) && mNum > targetMeasure) break;
|
|
6892
|
+
if (m.attributes?.divisions !== void 0) {
|
|
6893
|
+
divisions = m.attributes.divisions;
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
return divisions;
|
|
6897
|
+
}
|
|
6898
|
+
function getAttributesAtMeasure(score, options) {
|
|
6899
|
+
const part = score.parts[options.part];
|
|
6900
|
+
if (!part) return {};
|
|
6901
|
+
const targetMeasure = parseInt(String(options.measure), 10);
|
|
6902
|
+
const result = {};
|
|
6903
|
+
for (const m of part.measures) {
|
|
6904
|
+
const mNum = parseInt(m.number, 10);
|
|
6905
|
+
if (!isNaN(targetMeasure) && !isNaN(mNum) && mNum > targetMeasure) break;
|
|
6906
|
+
if (m.attributes) {
|
|
6907
|
+
if (m.attributes.divisions !== void 0) result.divisions = m.attributes.divisions;
|
|
6908
|
+
if (m.attributes.time !== void 0) result.time = m.attributes.time;
|
|
6909
|
+
if (m.attributes.key !== void 0) result.key = m.attributes.key;
|
|
6910
|
+
if (m.attributes.clef !== void 0) result.clef = m.attributes.clef;
|
|
6911
|
+
if (m.attributes.staves !== void 0) result.staves = m.attributes.staves;
|
|
6912
|
+
if (m.attributes.transpose !== void 0) result.transpose = m.attributes.transpose;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
return result;
|
|
6916
|
+
}
|
|
6917
|
+
function findNotes(score, filter) {
|
|
6918
|
+
const results = [];
|
|
6919
|
+
for (const part of score.parts) {
|
|
6920
|
+
for (const measure of part.measures) {
|
|
6921
|
+
for (const entry of measure.entries) {
|
|
6922
|
+
if (entry.type !== "note") continue;
|
|
6923
|
+
if (filter.pitchRange && entry.pitch) {
|
|
6924
|
+
const noteValue = pitchToSemitone(entry.pitch);
|
|
6925
|
+
if (filter.pitchRange.min) {
|
|
6926
|
+
const minValue = pitchToSemitone(filter.pitchRange.min);
|
|
6927
|
+
if (noteValue < minValue) continue;
|
|
6928
|
+
}
|
|
6929
|
+
if (filter.pitchRange.max) {
|
|
6930
|
+
const maxValue = pitchToSemitone(filter.pitchRange.max);
|
|
6931
|
+
if (noteValue > maxValue) continue;
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
if (filter.voice !== void 0 && entry.voice !== filter.voice) continue;
|
|
6935
|
+
if (filter.staff !== void 0 && (entry.staff ?? 1) !== filter.staff) continue;
|
|
6936
|
+
if (filter.noteType !== void 0 && entry.noteType !== filter.noteType) continue;
|
|
6937
|
+
if (filter.hasTie !== void 0) {
|
|
6938
|
+
const hasTie = entry.tie !== void 0;
|
|
6939
|
+
if (filter.hasTie !== hasTie) continue;
|
|
6940
|
+
}
|
|
6941
|
+
results.push(entry);
|
|
6942
|
+
}
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
return results;
|
|
6946
|
+
}
|
|
6947
|
+
function getDuration(score) {
|
|
6948
|
+
if (score.parts.length === 0) return 0;
|
|
6949
|
+
const part = score.parts[0];
|
|
6950
|
+
let totalDuration = 0;
|
|
6951
|
+
let divisions = 1;
|
|
6952
|
+
for (const measure of part.measures) {
|
|
6953
|
+
if (measure.attributes?.divisions !== void 0) {
|
|
6954
|
+
divisions = measure.attributes.divisions;
|
|
6955
|
+
}
|
|
6956
|
+
let measureDuration = 0;
|
|
6957
|
+
for (const entry of measure.entries) {
|
|
6958
|
+
if (entry.type === "note" && !entry.chord) {
|
|
6959
|
+
measureDuration = Math.max(measureDuration, entry.duration);
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
const attrs = getAttributesAtMeasure(score, { part: 0, measure: measure.number });
|
|
6963
|
+
if (attrs.time) {
|
|
6964
|
+
const beats = parseInt(attrs.time.beats, 10) || 4;
|
|
6965
|
+
const expectedDuration = beats / attrs.time.beatType * 4 * divisions;
|
|
6966
|
+
measureDuration = Math.max(measureDuration, expectedDuration);
|
|
6967
|
+
}
|
|
6968
|
+
totalDuration += measureDuration;
|
|
6969
|
+
}
|
|
6970
|
+
return totalDuration;
|
|
6971
|
+
}
|
|
6972
|
+
function getPartById(score, id) {
|
|
6973
|
+
return score.parts.find((p) => p.id === id);
|
|
6974
|
+
}
|
|
6975
|
+
function getPartIndex(score, id) {
|
|
6976
|
+
return score.parts.findIndex((p) => p.id === id);
|
|
6977
|
+
}
|
|
6978
|
+
function hasMultipleStaves(score, partIndex = 0) {
|
|
6979
|
+
const part = score.parts[partIndex];
|
|
6980
|
+
if (!part) return false;
|
|
6981
|
+
for (const measure of part.measures) {
|
|
6982
|
+
if (measure.attributes?.staves !== void 0 && measure.attributes.staves > 1) {
|
|
6983
|
+
return true;
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
return false;
|
|
6987
|
+
}
|
|
6988
|
+
function getStaveCount(score, partIndex = 0) {
|
|
6989
|
+
const part = score.parts[partIndex];
|
|
6990
|
+
if (!part) return 1;
|
|
6991
|
+
for (const measure of part.measures) {
|
|
6992
|
+
if (measure.attributes?.staves !== void 0) {
|
|
6993
|
+
return measure.attributes.staves;
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
return 1;
|
|
6997
|
+
}
|
|
6998
|
+
function measureRoundtrip(original, exported) {
|
|
6999
|
+
const notesOriginal = countNotes(original);
|
|
7000
|
+
const notesExported = countNotes(exported);
|
|
7001
|
+
const notesPreserved = Math.min(notesOriginal, notesExported);
|
|
7002
|
+
const measuresOriginal = getMeasureCount(original);
|
|
7003
|
+
const measuresExported = getMeasureCount(exported);
|
|
7004
|
+
const measuresPreserved = Math.min(measuresOriginal, measuresExported);
|
|
7005
|
+
const partsOriginal = original.parts.length;
|
|
7006
|
+
const partsExported = exported.parts.length;
|
|
7007
|
+
const partsPreserved = Math.min(partsOriginal, partsExported);
|
|
7008
|
+
const preservationRate = notesOriginal > 0 ? notesPreserved / notesOriginal : 1;
|
|
7009
|
+
return {
|
|
7010
|
+
notesOriginal,
|
|
7011
|
+
notesPreserved,
|
|
7012
|
+
measuresOriginal,
|
|
7013
|
+
measuresPreserved,
|
|
7014
|
+
partsOriginal,
|
|
7015
|
+
partsPreserved,
|
|
7016
|
+
preservationRate
|
|
7017
|
+
};
|
|
7018
|
+
}
|
|
7019
|
+
function countNotes(score) {
|
|
7020
|
+
let count = 0;
|
|
7021
|
+
for (const part of score.parts) {
|
|
7022
|
+
for (const measure of part.measures) {
|
|
7023
|
+
for (const entry of measure.entries) {
|
|
7024
|
+
if (entry.type === "note") {
|
|
7025
|
+
count++;
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
return count;
|
|
5630
7031
|
}
|
|
5631
7032
|
function scoresEqual(a, b) {
|
|
5632
7033
|
if (a.parts.length !== b.parts.length) return false;
|
|
@@ -5659,9 +7060,461 @@ function pitchesEqual(a, b) {
|
|
|
5659
7060
|
}
|
|
5660
7061
|
|
|
5661
7062
|
// src/operations/index.ts
|
|
7063
|
+
function success(data, warnings) {
|
|
7064
|
+
return { success: true, data, warnings };
|
|
7065
|
+
}
|
|
7066
|
+
function failure(errors) {
|
|
7067
|
+
return { success: false, errors };
|
|
7068
|
+
}
|
|
7069
|
+
function operationError(code, message, location = {}, details) {
|
|
7070
|
+
return {
|
|
7071
|
+
code,
|
|
7072
|
+
level: "error",
|
|
7073
|
+
message,
|
|
7074
|
+
location,
|
|
7075
|
+
details
|
|
7076
|
+
};
|
|
7077
|
+
}
|
|
5662
7078
|
function cloneScore(score) {
|
|
5663
7079
|
return JSON.parse(JSON.stringify(score));
|
|
5664
7080
|
}
|
|
7081
|
+
function getMeasureDuration(divisions, time) {
|
|
7082
|
+
const beats = parseInt(time.beats, 10);
|
|
7083
|
+
if (isNaN(beats)) return divisions * 4;
|
|
7084
|
+
return beats / time.beatType * 4 * divisions;
|
|
7085
|
+
}
|
|
7086
|
+
function getVoiceEntries(measure, voice, staff) {
|
|
7087
|
+
const result = [];
|
|
7088
|
+
let position = 0;
|
|
7089
|
+
for (let i = 0; i < measure.entries.length; i++) {
|
|
7090
|
+
const entry = measure.entries[i];
|
|
7091
|
+
if (entry.type === "note") {
|
|
7092
|
+
const noteStaff = entry.staff ?? 1;
|
|
7093
|
+
if (entry.voice === voice && (staff === void 0 || noteStaff === staff)) {
|
|
7094
|
+
if (!entry.chord) {
|
|
7095
|
+
result.push({
|
|
7096
|
+
entry,
|
|
7097
|
+
entryIndex: i,
|
|
7098
|
+
position,
|
|
7099
|
+
endPosition: position + entry.duration
|
|
7100
|
+
});
|
|
7101
|
+
position += entry.duration;
|
|
7102
|
+
} else {
|
|
7103
|
+
if (result.length > 0) {
|
|
7104
|
+
const prev = result[result.length - 1];
|
|
7105
|
+
result.push({
|
|
7106
|
+
entry,
|
|
7107
|
+
entryIndex: i,
|
|
7108
|
+
position: prev.position,
|
|
7109
|
+
endPosition: prev.endPosition
|
|
7110
|
+
});
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7113
|
+
} else if (!entry.chord) {
|
|
7114
|
+
position += entry.duration;
|
|
7115
|
+
}
|
|
7116
|
+
} else if (entry.type === "backup") {
|
|
7117
|
+
position -= entry.duration;
|
|
7118
|
+
} else if (entry.type === "forward") {
|
|
7119
|
+
if (entry.voice === voice) {
|
|
7120
|
+
result.push({
|
|
7121
|
+
entry,
|
|
7122
|
+
entryIndex: i,
|
|
7123
|
+
position,
|
|
7124
|
+
endPosition: position + entry.duration
|
|
7125
|
+
});
|
|
7126
|
+
}
|
|
7127
|
+
position += entry.duration;
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
return result;
|
|
7131
|
+
}
|
|
7132
|
+
function hasNotesInRange(voiceEntries, startPos, endPos) {
|
|
7133
|
+
const conflicting = voiceEntries.filter((e) => {
|
|
7134
|
+
if (e.entry.type !== "note") return false;
|
|
7135
|
+
const note = e.entry;
|
|
7136
|
+
if (note.rest) return false;
|
|
7137
|
+
return e.position < endPos && e.endPosition > startPos;
|
|
7138
|
+
});
|
|
7139
|
+
return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
|
|
7140
|
+
}
|
|
7141
|
+
function createRest(duration, voice, staff) {
|
|
7142
|
+
return {
|
|
7143
|
+
type: "note",
|
|
7144
|
+
rest: { displayStep: void 0, displayOctave: void 0 },
|
|
7145
|
+
duration,
|
|
7146
|
+
voice,
|
|
7147
|
+
staff
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
|
|
7151
|
+
const otherEntries = [];
|
|
7152
|
+
let position = 0;
|
|
7153
|
+
for (const entry of measure.entries) {
|
|
7154
|
+
if (entry.type === "note") {
|
|
7155
|
+
if (entry.voice !== voice || staff !== void 0 && (entry.staff ?? 1) !== staff) {
|
|
7156
|
+
if (!entry.chord) {
|
|
7157
|
+
otherEntries.push({ position, entry });
|
|
7158
|
+
position += entry.duration;
|
|
7159
|
+
} else {
|
|
7160
|
+
otherEntries.push({ position, entry });
|
|
7161
|
+
}
|
|
7162
|
+
} else if (!entry.chord) {
|
|
7163
|
+
position += entry.duration;
|
|
7164
|
+
}
|
|
7165
|
+
} else if (entry.type === "backup") {
|
|
7166
|
+
position -= entry.duration;
|
|
7167
|
+
} else if (entry.type === "forward") {
|
|
7168
|
+
if (entry.voice !== voice) {
|
|
7169
|
+
otherEntries.push({ position, entry });
|
|
7170
|
+
}
|
|
7171
|
+
position += entry.duration;
|
|
7172
|
+
} else {
|
|
7173
|
+
otherEntries.push({ position, entry });
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
const filledNewEntries = [];
|
|
7177
|
+
let currentPos = 0;
|
|
7178
|
+
const sortedNew = [...newEntries].sort((a, b) => a.position - b.position);
|
|
7179
|
+
for (const { position: notePos, entry } of sortedNew) {
|
|
7180
|
+
if (notePos > currentPos) {
|
|
7181
|
+
filledNewEntries.push({
|
|
7182
|
+
position: currentPos,
|
|
7183
|
+
entry: createRest(notePos - currentPos, voice, staff)
|
|
7184
|
+
});
|
|
7185
|
+
}
|
|
7186
|
+
filledNewEntries.push({ position: notePos, entry });
|
|
7187
|
+
if (!entry.chord) {
|
|
7188
|
+
currentPos = notePos + entry.duration;
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7191
|
+
if (currentPos < measureDuration) {
|
|
7192
|
+
filledNewEntries.push({
|
|
7193
|
+
position: currentPos,
|
|
7194
|
+
entry: createRest(measureDuration - currentPos, voice, staff)
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
7197
|
+
const allEntries = [...otherEntries, ...filledNewEntries];
|
|
7198
|
+
allEntries.sort((a, b) => a.position - b.position);
|
|
7199
|
+
const result = [];
|
|
7200
|
+
let currentPosition = 0;
|
|
7201
|
+
for (const { position: targetPos, entry } of allEntries) {
|
|
7202
|
+
const diff = targetPos - currentPosition;
|
|
7203
|
+
if (diff < 0) {
|
|
7204
|
+
result.push({ type: "backup", duration: -diff });
|
|
7205
|
+
currentPosition = targetPos;
|
|
7206
|
+
} else if (diff > 0) {
|
|
7207
|
+
result.push({
|
|
7208
|
+
type: "forward",
|
|
7209
|
+
duration: diff,
|
|
7210
|
+
voice: entry.type === "note" ? entry.voice : 1,
|
|
7211
|
+
staff: entry.type === "note" ? entry.staff : void 0
|
|
7212
|
+
});
|
|
7213
|
+
currentPosition = targetPos;
|
|
7214
|
+
}
|
|
7215
|
+
result.push(entry);
|
|
7216
|
+
if (entry.type === "note" && !entry.chord) {
|
|
7217
|
+
currentPosition += entry.duration;
|
|
7218
|
+
} else if (entry.type === "forward") {
|
|
7219
|
+
currentPosition += entry.duration;
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
return result;
|
|
7223
|
+
}
|
|
7224
|
+
function insertNote(score, options) {
|
|
7225
|
+
if (options.partIndex < 0 || options.partIndex >= score.parts.length) {
|
|
7226
|
+
return failure([operationError("PART_NOT_FOUND", `Part index ${options.partIndex} out of bounds`, { partIndex: options.partIndex })]);
|
|
7227
|
+
}
|
|
7228
|
+
const part = score.parts[options.partIndex];
|
|
7229
|
+
if (options.measureIndex < 0 || options.measureIndex >= part.measures.length) {
|
|
7230
|
+
return failure([operationError("MEASURE_NOT_FOUND", `Measure index ${options.measureIndex} out of bounds`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7231
|
+
}
|
|
7232
|
+
if (options.duration <= 0) {
|
|
7233
|
+
return failure([operationError("INVALID_DURATION", `Duration must be positive`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7234
|
+
}
|
|
7235
|
+
if (options.position < 0) {
|
|
7236
|
+
return failure([operationError("INVALID_POSITION", `Position cannot be negative`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7237
|
+
}
|
|
7238
|
+
const result = cloneScore(score);
|
|
7239
|
+
const measure = result.parts[options.partIndex].measures[options.measureIndex];
|
|
7240
|
+
const context = getMeasureContext(result, options.partIndex, options.measureIndex);
|
|
7241
|
+
const measureDuration = context.time ? getMeasureDuration(context.divisions, context.time) : context.divisions * 4;
|
|
7242
|
+
const noteEnd = options.position + options.duration;
|
|
7243
|
+
if (noteEnd > measureDuration) {
|
|
7244
|
+
return failure([operationError(
|
|
7245
|
+
"EXCEEDS_MEASURE",
|
|
7246
|
+
`Note ending at ${noteEnd} exceeds measure duration ${measureDuration}`,
|
|
7247
|
+
{ partIndex: options.partIndex, measureIndex: options.measureIndex },
|
|
7248
|
+
{ noteEnd, measureDuration }
|
|
7249
|
+
)]);
|
|
7250
|
+
}
|
|
7251
|
+
const voiceEntries = getVoiceEntries(measure, options.voice, options.staff);
|
|
7252
|
+
const { hasNotes: hasNotes2, conflictingNotes } = hasNotesInRange(voiceEntries, options.position, noteEnd);
|
|
7253
|
+
if (hasNotes2) {
|
|
7254
|
+
return failure([operationError(
|
|
7255
|
+
"NOTE_CONFLICT",
|
|
7256
|
+
`Position ${options.position}-${noteEnd} conflicts with existing note(s)`,
|
|
7257
|
+
{ partIndex: options.partIndex, measureIndex: options.measureIndex, voice: options.voice },
|
|
7258
|
+
{ conflictingPositions: conflictingNotes.map((n) => ({ start: n.position, end: n.endPosition })) }
|
|
7259
|
+
)]);
|
|
7260
|
+
}
|
|
7261
|
+
const newNote = {
|
|
7262
|
+
type: "note",
|
|
7263
|
+
pitch: options.pitch,
|
|
7264
|
+
duration: options.duration,
|
|
7265
|
+
voice: options.voice,
|
|
7266
|
+
staff: options.staff,
|
|
7267
|
+
noteType: options.noteType,
|
|
7268
|
+
dots: options.dots
|
|
7269
|
+
};
|
|
7270
|
+
const existingNotes = voiceEntries.filter((e) => {
|
|
7271
|
+
if (e.entry.type !== "note") return true;
|
|
7272
|
+
const note = e.entry;
|
|
7273
|
+
if (note.rest) {
|
|
7274
|
+
return !(e.position < noteEnd && e.endPosition > options.position);
|
|
7275
|
+
}
|
|
7276
|
+
return true;
|
|
7277
|
+
}).map((e) => ({ position: e.position, entry: e.entry }));
|
|
7278
|
+
existingNotes.push({ position: options.position, entry: newNote });
|
|
7279
|
+
measure.entries = rebuildMeasureWithVoice(
|
|
7280
|
+
measure,
|
|
7281
|
+
options.voice,
|
|
7282
|
+
existingNotes,
|
|
7283
|
+
measureDuration,
|
|
7284
|
+
options.staff
|
|
7285
|
+
);
|
|
7286
|
+
const errors = validateMeasureLocal(measure, context, {
|
|
7287
|
+
checkMeasureDuration: true,
|
|
7288
|
+
checkPosition: true,
|
|
7289
|
+
checkVoiceStaff: true
|
|
7290
|
+
});
|
|
7291
|
+
const criticalErrors = errors.filter((e) => e.level === "error");
|
|
7292
|
+
if (criticalErrors.length > 0) {
|
|
7293
|
+
return failure(criticalErrors);
|
|
7294
|
+
}
|
|
7295
|
+
return success(result, errors.filter((e) => e.level !== "error"));
|
|
7296
|
+
}
|
|
7297
|
+
function removeNote(score, options) {
|
|
7298
|
+
if (options.partIndex < 0 || options.partIndex >= score.parts.length) {
|
|
7299
|
+
return failure([operationError("PART_NOT_FOUND", `Part index ${options.partIndex} out of bounds`, { partIndex: options.partIndex })]);
|
|
7300
|
+
}
|
|
7301
|
+
const part = score.parts[options.partIndex];
|
|
7302
|
+
if (options.measureIndex < 0 || options.measureIndex >= part.measures.length) {
|
|
7303
|
+
return failure([operationError("MEASURE_NOT_FOUND", `Measure index ${options.measureIndex} out of bounds`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7304
|
+
}
|
|
7305
|
+
const result = cloneScore(score);
|
|
7306
|
+
const measure = result.parts[options.partIndex].measures[options.measureIndex];
|
|
7307
|
+
let noteCount = 0;
|
|
7308
|
+
let targetEntry = null;
|
|
7309
|
+
let targetIndex = -1;
|
|
7310
|
+
for (let i2 = 0; i2 < measure.entries.length; i2++) {
|
|
7311
|
+
const entry = measure.entries[i2];
|
|
7312
|
+
if (entry.type === "note" && !entry.rest) {
|
|
7313
|
+
if (noteCount === options.noteIndex) {
|
|
7314
|
+
targetEntry = entry;
|
|
7315
|
+
targetIndex = i2;
|
|
7316
|
+
break;
|
|
7317
|
+
}
|
|
7318
|
+
noteCount++;
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
if (!targetEntry || targetIndex === -1) {
|
|
7322
|
+
return failure([operationError("NOTE_NOT_FOUND", `Note index ${options.noteIndex} not found`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7323
|
+
}
|
|
7324
|
+
measure.entries[targetIndex] = createRest(
|
|
7325
|
+
targetEntry.duration,
|
|
7326
|
+
targetEntry.voice,
|
|
7327
|
+
targetEntry.staff
|
|
7328
|
+
);
|
|
7329
|
+
let i = targetIndex + 1;
|
|
7330
|
+
while (i < measure.entries.length) {
|
|
7331
|
+
const entry = measure.entries[i];
|
|
7332
|
+
if (entry.type === "note" && entry.chord) {
|
|
7333
|
+
measure.entries.splice(i, 1);
|
|
7334
|
+
} else {
|
|
7335
|
+
break;
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
return success(result);
|
|
7339
|
+
}
|
|
7340
|
+
function addChord(score, options) {
|
|
7341
|
+
if (options.partIndex < 0 || options.partIndex >= score.parts.length) {
|
|
7342
|
+
return failure([operationError("PART_NOT_FOUND", `Part index ${options.partIndex} out of bounds`, { partIndex: options.partIndex })]);
|
|
7343
|
+
}
|
|
7344
|
+
const part = score.parts[options.partIndex];
|
|
7345
|
+
if (options.measureIndex < 0 || options.measureIndex >= part.measures.length) {
|
|
7346
|
+
return failure([operationError("MEASURE_NOT_FOUND", `Measure index ${options.measureIndex} out of bounds`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7347
|
+
}
|
|
7348
|
+
const result = cloneScore(score);
|
|
7349
|
+
const measure = result.parts[options.partIndex].measures[options.measureIndex];
|
|
7350
|
+
let noteCount = 0;
|
|
7351
|
+
let targetEntry = null;
|
|
7352
|
+
let targetIndex = -1;
|
|
7353
|
+
for (let i = 0; i < measure.entries.length; i++) {
|
|
7354
|
+
const entry = measure.entries[i];
|
|
7355
|
+
if (entry.type === "note" && !entry.rest && !entry.chord) {
|
|
7356
|
+
if (noteCount === options.noteIndex) {
|
|
7357
|
+
targetEntry = entry;
|
|
7358
|
+
targetIndex = i;
|
|
7359
|
+
break;
|
|
7360
|
+
}
|
|
7361
|
+
noteCount++;
|
|
7362
|
+
}
|
|
7363
|
+
}
|
|
7364
|
+
if (!targetEntry || targetIndex === -1) {
|
|
7365
|
+
return failure([operationError("NOTE_NOT_FOUND", `Note index ${options.noteIndex} not found`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7366
|
+
}
|
|
7367
|
+
const chordNote = {
|
|
7368
|
+
type: "note",
|
|
7369
|
+
pitch: options.pitch,
|
|
7370
|
+
duration: targetEntry.duration,
|
|
7371
|
+
voice: targetEntry.voice,
|
|
7372
|
+
staff: targetEntry.staff,
|
|
7373
|
+
chord: true,
|
|
7374
|
+
noteType: targetEntry.noteType,
|
|
7375
|
+
dots: targetEntry.dots
|
|
7376
|
+
};
|
|
7377
|
+
let insertIndex = targetIndex + 1;
|
|
7378
|
+
while (insertIndex < measure.entries.length) {
|
|
7379
|
+
const entry = measure.entries[insertIndex];
|
|
7380
|
+
if (entry.type === "note" && entry.chord) {
|
|
7381
|
+
insertIndex++;
|
|
7382
|
+
} else {
|
|
7383
|
+
break;
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
measure.entries.splice(insertIndex, 0, chordNote);
|
|
7387
|
+
return success(result);
|
|
7388
|
+
}
|
|
7389
|
+
function changeNoteDuration(score, options) {
|
|
7390
|
+
if (options.partIndex < 0 || options.partIndex >= score.parts.length) {
|
|
7391
|
+
return failure([operationError("PART_NOT_FOUND", `Part index ${options.partIndex} out of bounds`, { partIndex: options.partIndex })]);
|
|
7392
|
+
}
|
|
7393
|
+
const part = score.parts[options.partIndex];
|
|
7394
|
+
if (options.measureIndex < 0 || options.measureIndex >= part.measures.length) {
|
|
7395
|
+
return failure([operationError("MEASURE_NOT_FOUND", `Measure index ${options.measureIndex} out of bounds`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7396
|
+
}
|
|
7397
|
+
if (options.newDuration <= 0) {
|
|
7398
|
+
return failure([operationError("INVALID_DURATION", `Duration must be positive`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7399
|
+
}
|
|
7400
|
+
const result = cloneScore(score);
|
|
7401
|
+
const measure = result.parts[options.partIndex].measures[options.measureIndex];
|
|
7402
|
+
const context = getMeasureContext(result, options.partIndex, options.measureIndex);
|
|
7403
|
+
const measureDuration = context.time ? getMeasureDuration(context.divisions, context.time) : context.divisions * 4;
|
|
7404
|
+
let noteCount = 0;
|
|
7405
|
+
let targetEntry = null;
|
|
7406
|
+
let targetPosition = 0;
|
|
7407
|
+
let position = 0;
|
|
7408
|
+
for (const entry of measure.entries) {
|
|
7409
|
+
if (entry.type === "note") {
|
|
7410
|
+
if (!entry.rest && !entry.chord) {
|
|
7411
|
+
if (noteCount === options.noteIndex) {
|
|
7412
|
+
targetEntry = entry;
|
|
7413
|
+
targetPosition = position;
|
|
7414
|
+
break;
|
|
7415
|
+
}
|
|
7416
|
+
noteCount++;
|
|
7417
|
+
}
|
|
7418
|
+
if (!entry.chord) {
|
|
7419
|
+
position += entry.duration;
|
|
7420
|
+
}
|
|
7421
|
+
} else if (entry.type === "backup") {
|
|
7422
|
+
position -= entry.duration;
|
|
7423
|
+
} else if (entry.type === "forward") {
|
|
7424
|
+
position += entry.duration;
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7427
|
+
if (!targetEntry) {
|
|
7428
|
+
return failure([operationError("NOTE_NOT_FOUND", `Note index ${options.noteIndex} not found`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7429
|
+
}
|
|
7430
|
+
const oldDuration = targetEntry.duration;
|
|
7431
|
+
const newEnd = targetPosition + options.newDuration;
|
|
7432
|
+
if (newEnd > measureDuration) {
|
|
7433
|
+
return failure([operationError(
|
|
7434
|
+
"EXCEEDS_MEASURE",
|
|
7435
|
+
`New duration would exceed measure (ends at ${newEnd}, measure is ${measureDuration})`,
|
|
7436
|
+
{ partIndex: options.partIndex, measureIndex: options.measureIndex },
|
|
7437
|
+
{ newEnd, measureDuration }
|
|
7438
|
+
)]);
|
|
7439
|
+
}
|
|
7440
|
+
const voiceEntries = getVoiceEntries(measure, targetEntry.voice, targetEntry.staff);
|
|
7441
|
+
if (options.newDuration > oldDuration) {
|
|
7442
|
+
const { hasNotes: hasNotes2, conflictingNotes } = hasNotesInRange(
|
|
7443
|
+
voiceEntries.filter((e) => e.position !== targetPosition),
|
|
7444
|
+
// Exclude current note
|
|
7445
|
+
targetPosition + oldDuration,
|
|
7446
|
+
newEnd
|
|
7447
|
+
);
|
|
7448
|
+
if (hasNotes2) {
|
|
7449
|
+
return failure([operationError(
|
|
7450
|
+
"NOTE_CONFLICT",
|
|
7451
|
+
`Cannot extend note: conflicts with existing note(s)`,
|
|
7452
|
+
{ partIndex: options.partIndex, measureIndex: options.measureIndex },
|
|
7453
|
+
{ conflictingPositions: conflictingNotes.map((n) => ({ start: n.position, end: n.endPosition })) }
|
|
7454
|
+
)]);
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
targetEntry.duration = options.newDuration;
|
|
7458
|
+
if (options.noteType !== void 0) {
|
|
7459
|
+
targetEntry.noteType = options.noteType;
|
|
7460
|
+
}
|
|
7461
|
+
if (options.dots !== void 0) {
|
|
7462
|
+
targetEntry.dots = options.dots;
|
|
7463
|
+
}
|
|
7464
|
+
const existingNotes = voiceEntries.filter((e) => {
|
|
7465
|
+
if (e.position === targetPosition) return true;
|
|
7466
|
+
const note = e.entry;
|
|
7467
|
+
if (note.rest) {
|
|
7468
|
+
if (options.newDuration > oldDuration) {
|
|
7469
|
+
return !(e.position >= targetPosition + oldDuration && e.position < newEnd);
|
|
7470
|
+
}
|
|
7471
|
+
}
|
|
7472
|
+
return true;
|
|
7473
|
+
}).map((e) => ({ position: e.position, entry: e.entry }));
|
|
7474
|
+
const modifiedIdx = existingNotes.findIndex((e) => e.position === targetPosition);
|
|
7475
|
+
if (modifiedIdx >= 0) {
|
|
7476
|
+
existingNotes[modifiedIdx].entry = targetEntry;
|
|
7477
|
+
}
|
|
7478
|
+
measure.entries = rebuildMeasureWithVoice(
|
|
7479
|
+
measure,
|
|
7480
|
+
targetEntry.voice,
|
|
7481
|
+
existingNotes,
|
|
7482
|
+
measureDuration,
|
|
7483
|
+
targetEntry.staff
|
|
7484
|
+
);
|
|
7485
|
+
const errors = validateMeasureLocal(measure, context, {
|
|
7486
|
+
checkMeasureDuration: true,
|
|
7487
|
+
checkPosition: true,
|
|
7488
|
+
checkVoiceStaff: true
|
|
7489
|
+
});
|
|
7490
|
+
const criticalErrors = errors.filter((e) => e.level === "error");
|
|
7491
|
+
if (criticalErrors.length > 0) {
|
|
7492
|
+
return failure(criticalErrors);
|
|
7493
|
+
}
|
|
7494
|
+
return success(result, errors.filter((e) => e.level !== "error"));
|
|
7495
|
+
}
|
|
7496
|
+
function setNotePitch(score, options) {
|
|
7497
|
+
if (options.partIndex < 0 || options.partIndex >= score.parts.length) {
|
|
7498
|
+
return failure([operationError("PART_NOT_FOUND", `Part index ${options.partIndex} out of bounds`, { partIndex: options.partIndex })]);
|
|
7499
|
+
}
|
|
7500
|
+
const part = score.parts[options.partIndex];
|
|
7501
|
+
if (options.measureIndex < 0 || options.measureIndex >= part.measures.length) {
|
|
7502
|
+
return failure([operationError("MEASURE_NOT_FOUND", `Measure index ${options.measureIndex} out of bounds`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7503
|
+
}
|
|
7504
|
+
const result = cloneScore(score);
|
|
7505
|
+
const measure = result.parts[options.partIndex].measures[options.measureIndex];
|
|
7506
|
+
let noteCount = 0;
|
|
7507
|
+
for (const entry of measure.entries) {
|
|
7508
|
+
if (entry.type === "note" && !entry.rest) {
|
|
7509
|
+
if (noteCount === options.noteIndex) {
|
|
7510
|
+
entry.pitch = options.pitch;
|
|
7511
|
+
return success(result);
|
|
7512
|
+
}
|
|
7513
|
+
noteCount++;
|
|
7514
|
+
}
|
|
7515
|
+
}
|
|
7516
|
+
return failure([operationError("NOTE_NOT_FOUND", `Note index ${options.noteIndex} not found`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
|
|
7517
|
+
}
|
|
5665
7518
|
function transposePitch(pitch, semitones) {
|
|
5666
7519
|
const currentSemitone = STEP_SEMITONES[pitch.step] + (pitch.alter ?? 0) + pitch.octave * 12;
|
|
5667
7520
|
const targetSemitone = currentSemitone + semitones;
|
|
@@ -5688,7 +7541,7 @@ function transposePitch(pitch, semitones) {
|
|
|
5688
7541
|
};
|
|
5689
7542
|
}
|
|
5690
7543
|
function transpose(score, semitones) {
|
|
5691
|
-
if (semitones === 0) return score;
|
|
7544
|
+
if (semitones === 0) return success(score);
|
|
5692
7545
|
const result = cloneScore(score);
|
|
5693
7546
|
for (const part of result.parts) {
|
|
5694
7547
|
for (const measure of part.measures) {
|
|
@@ -5699,59 +7552,7 @@ function transpose(score, semitones) {
|
|
|
5699
7552
|
}
|
|
5700
7553
|
}
|
|
5701
7554
|
}
|
|
5702
|
-
return result;
|
|
5703
|
-
}
|
|
5704
|
-
function addNote(score, options) {
|
|
5705
|
-
const result = cloneScore(score);
|
|
5706
|
-
const part = result.parts[options.partIndex];
|
|
5707
|
-
if (!part) return result;
|
|
5708
|
-
const measure = part.measures[options.measureIndex];
|
|
5709
|
-
if (!measure) return result;
|
|
5710
|
-
const newNote = {
|
|
5711
|
-
type: "note",
|
|
5712
|
-
voice: options.voice,
|
|
5713
|
-
staff: options.staff,
|
|
5714
|
-
...options.note
|
|
5715
|
-
};
|
|
5716
|
-
const currentPosition = getMeasureEndPosition(measure);
|
|
5717
|
-
const positionDiff = options.position - currentPosition;
|
|
5718
|
-
if (positionDiff < 0) {
|
|
5719
|
-
measure.entries.push({
|
|
5720
|
-
type: "backup",
|
|
5721
|
-
duration: -positionDiff
|
|
5722
|
-
});
|
|
5723
|
-
} else if (positionDiff > 0) {
|
|
5724
|
-
measure.entries.push({
|
|
5725
|
-
type: "forward",
|
|
5726
|
-
duration: positionDiff,
|
|
5727
|
-
voice: options.voice,
|
|
5728
|
-
staff: options.staff
|
|
5729
|
-
});
|
|
5730
|
-
}
|
|
5731
|
-
measure.entries.push(newNote);
|
|
5732
|
-
return result;
|
|
5733
|
-
}
|
|
5734
|
-
function deleteNote(score, options) {
|
|
5735
|
-
const result = cloneScore(score);
|
|
5736
|
-
const part = result.parts[options.partIndex];
|
|
5737
|
-
if (!part) return result;
|
|
5738
|
-
const measure = part.measures[options.measureIndex];
|
|
5739
|
-
if (!measure) return result;
|
|
5740
|
-
let noteCount = 0;
|
|
5741
|
-
let entryIndex = -1;
|
|
5742
|
-
for (let i = 0; i < measure.entries.length; i++) {
|
|
5743
|
-
if (measure.entries[i].type === "note") {
|
|
5744
|
-
if (noteCount === options.noteIndex) {
|
|
5745
|
-
entryIndex = i;
|
|
5746
|
-
break;
|
|
5747
|
-
}
|
|
5748
|
-
noteCount++;
|
|
5749
|
-
}
|
|
5750
|
-
}
|
|
5751
|
-
if (entryIndex !== -1) {
|
|
5752
|
-
measure.entries.splice(entryIndex, 1);
|
|
5753
|
-
}
|
|
5754
|
-
return result;
|
|
7555
|
+
return success(result);
|
|
5755
7556
|
}
|
|
5756
7557
|
function changeKey(score, key, options) {
|
|
5757
7558
|
const result = cloneScore(score);
|
|
@@ -5759,9 +7560,7 @@ function changeKey(score, key, options) {
|
|
|
5759
7560
|
for (const part of result.parts) {
|
|
5760
7561
|
for (const measure of part.measures) {
|
|
5761
7562
|
if (measure.number === targetMeasure) {
|
|
5762
|
-
if (!measure.attributes) {
|
|
5763
|
-
measure.attributes = {};
|
|
5764
|
-
}
|
|
7563
|
+
if (!measure.attributes) measure.attributes = {};
|
|
5765
7564
|
measure.attributes.key = key;
|
|
5766
7565
|
}
|
|
5767
7566
|
}
|
|
@@ -5774,9 +7573,7 @@ function changeTime(score, time, options) {
|
|
|
5774
7573
|
for (const part of result.parts) {
|
|
5775
7574
|
for (const measure of part.measures) {
|
|
5776
7575
|
if (measure.number === targetMeasure) {
|
|
5777
|
-
if (!measure.attributes) {
|
|
5778
|
-
measure.attributes = {};
|
|
5779
|
-
}
|
|
7576
|
+
if (!measure.attributes) measure.attributes = {};
|
|
5780
7577
|
measure.attributes.time = time;
|
|
5781
7578
|
}
|
|
5782
7579
|
}
|
|
@@ -5791,15 +7588,9 @@ function insertMeasure(score, options) {
|
|
|
5791
7588
|
if (insertIndex === -1) continue;
|
|
5792
7589
|
const numericPart = parseInt(targetMeasure, 10);
|
|
5793
7590
|
const newMeasureNumber = String(isNaN(numericPart) ? insertIndex + 2 : numericPart + 1);
|
|
5794
|
-
const newMeasure = {
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
};
|
|
5798
|
-
if (options.copyAttributes) {
|
|
5799
|
-
const sourceMeasure = part.measures[insertIndex];
|
|
5800
|
-
if (sourceMeasure.attributes) {
|
|
5801
|
-
newMeasure.attributes = { ...sourceMeasure.attributes };
|
|
5802
|
-
}
|
|
7591
|
+
const newMeasure = { number: newMeasureNumber, entries: [] };
|
|
7592
|
+
if (options.copyAttributes && part.measures[insertIndex].attributes) {
|
|
7593
|
+
newMeasure.attributes = { ...part.measures[insertIndex].attributes };
|
|
5803
7594
|
}
|
|
5804
7595
|
part.measures.splice(insertIndex + 1, 0, newMeasure);
|
|
5805
7596
|
for (let i = insertIndex + 2; i < part.measures.length; i++) {
|
|
@@ -5827,95 +7618,36 @@ function deleteMeasure(score, measureNumber) {
|
|
|
5827
7618
|
}
|
|
5828
7619
|
return result;
|
|
5829
7620
|
}
|
|
5830
|
-
|
|
5831
|
-
const result =
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
}
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
}
|
|
5861
|
-
}
|
|
5862
|
-
if (entryIndex !== -1 && targetNote) {
|
|
5863
|
-
const chordNote = {
|
|
5864
|
-
type: "note",
|
|
5865
|
-
pitch: options.pitch,
|
|
5866
|
-
duration: targetNote.duration,
|
|
5867
|
-
voice: targetNote.voice,
|
|
5868
|
-
staff: targetNote.staff,
|
|
5869
|
-
chord: true,
|
|
5870
|
-
noteType: targetNote.noteType,
|
|
5871
|
-
dots: targetNote.dots
|
|
5872
|
-
};
|
|
5873
|
-
measure.entries.splice(entryIndex + 1, 0, chordNote);
|
|
5874
|
-
}
|
|
5875
|
-
return result;
|
|
5876
|
-
}
|
|
5877
|
-
function modifyNotePitch(score, options) {
|
|
5878
|
-
const result = cloneScore(score);
|
|
5879
|
-
const part = result.parts[options.partIndex];
|
|
5880
|
-
if (!part) return result;
|
|
5881
|
-
const measure = part.measures[options.measureIndex];
|
|
5882
|
-
if (!measure) return result;
|
|
5883
|
-
let noteCount = 0;
|
|
5884
|
-
for (const entry of measure.entries) {
|
|
5885
|
-
if (entry.type === "note") {
|
|
5886
|
-
if (noteCount === options.noteIndex) {
|
|
5887
|
-
entry.pitch = options.pitch;
|
|
5888
|
-
break;
|
|
5889
|
-
}
|
|
5890
|
-
noteCount++;
|
|
5891
|
-
}
|
|
5892
|
-
}
|
|
5893
|
-
return result;
|
|
5894
|
-
}
|
|
5895
|
-
function modifyNoteDuration(score, options) {
|
|
5896
|
-
const result = cloneScore(score);
|
|
5897
|
-
const part = result.parts[options.partIndex];
|
|
5898
|
-
if (!part) return result;
|
|
5899
|
-
const measure = part.measures[options.measureIndex];
|
|
5900
|
-
if (!measure) return result;
|
|
5901
|
-
let noteCount = 0;
|
|
5902
|
-
for (const entry of measure.entries) {
|
|
5903
|
-
if (entry.type === "note") {
|
|
5904
|
-
if (noteCount === options.noteIndex) {
|
|
5905
|
-
entry.duration = options.duration;
|
|
5906
|
-
if (options.noteType !== void 0) {
|
|
5907
|
-
entry.noteType = options.noteType;
|
|
5908
|
-
}
|
|
5909
|
-
if (options.dots !== void 0) {
|
|
5910
|
-
entry.dots = options.dots;
|
|
5911
|
-
}
|
|
5912
|
-
break;
|
|
5913
|
-
}
|
|
5914
|
-
noteCount++;
|
|
5915
|
-
}
|
|
5916
|
-
}
|
|
5917
|
-
return result;
|
|
5918
|
-
}
|
|
7621
|
+
var addNote = (score, options) => {
|
|
7622
|
+
const result = insertNote(score, {
|
|
7623
|
+
partIndex: options.partIndex,
|
|
7624
|
+
measureIndex: options.measureIndex,
|
|
7625
|
+
voice: options.voice,
|
|
7626
|
+
staff: options.staff,
|
|
7627
|
+
position: options.position,
|
|
7628
|
+
pitch: options.note.pitch ?? { step: "C", octave: 4 },
|
|
7629
|
+
duration: options.note.duration,
|
|
7630
|
+
noteType: options.note.noteType,
|
|
7631
|
+
dots: options.note.dots
|
|
7632
|
+
});
|
|
7633
|
+
return result.success ? result.data : score;
|
|
7634
|
+
};
|
|
7635
|
+
var deleteNote = (score, options) => {
|
|
7636
|
+
const result = removeNote(score, options);
|
|
7637
|
+
return result.success ? result.data : score;
|
|
7638
|
+
};
|
|
7639
|
+
var addChordNote = (score, options) => {
|
|
7640
|
+
const result = addChord(score, { ...options, noteIndex: options.afterNoteIndex });
|
|
7641
|
+
return result.success ? result.data : score;
|
|
7642
|
+
};
|
|
7643
|
+
var modifyNotePitch = (score, options) => {
|
|
7644
|
+
const result = setNotePitch(score, options);
|
|
7645
|
+
return result.success ? result.data : score;
|
|
7646
|
+
};
|
|
7647
|
+
var modifyNoteDuration = (score, options) => {
|
|
7648
|
+
const result = changeNoteDuration(score, { ...options, newDuration: options.duration });
|
|
7649
|
+
return result.success ? result.data : score;
|
|
7650
|
+
};
|
|
5919
7651
|
|
|
5920
7652
|
// src/file.ts
|
|
5921
7653
|
var import_promises = require("fs/promises");
|
|
@@ -5961,6 +7693,8 @@ async function serializeToFile(score, filePath, options = {}) {
|
|
|
5961
7693
|
addNote,
|
|
5962
7694
|
assertMeasureValid,
|
|
5963
7695
|
assertValid,
|
|
7696
|
+
buildVoiceToStaffMap,
|
|
7697
|
+
buildVoiceToStaffMapForPart,
|
|
5964
7698
|
changeKey,
|
|
5965
7699
|
changeTime,
|
|
5966
7700
|
countNotes,
|
|
@@ -5968,36 +7702,82 @@ async function serializeToFile(score, filePath, options = {}) {
|
|
|
5968
7702
|
deleteMeasure,
|
|
5969
7703
|
deleteNote,
|
|
5970
7704
|
exportMidi,
|
|
7705
|
+
findBarlines,
|
|
7706
|
+
findDirectionsByType,
|
|
5971
7707
|
findNotes,
|
|
7708
|
+
findNotesWithNotation,
|
|
5972
7709
|
formatLocation,
|
|
5973
7710
|
getAbsolutePosition,
|
|
7711
|
+
getAdjacentNotes,
|
|
5974
7712
|
getAllNotes,
|
|
5975
7713
|
getAttributesAtMeasure,
|
|
7714
|
+
getBeamGroups,
|
|
7715
|
+
getChordProgression,
|
|
5976
7716
|
getChords,
|
|
7717
|
+
getClefChanges,
|
|
7718
|
+
getClefForStaff,
|
|
7719
|
+
getDirections,
|
|
7720
|
+
getDirectionsAtPosition,
|
|
5977
7721
|
getDivisions,
|
|
5978
7722
|
getDuration,
|
|
7723
|
+
getDynamics,
|
|
7724
|
+
getEffectiveStaff,
|
|
7725
|
+
getEndings,
|
|
7726
|
+
getEntriesAtPosition,
|
|
7727
|
+
getEntriesForStaff,
|
|
7728
|
+
getEntriesInRange,
|
|
7729
|
+
getHarmonies,
|
|
7730
|
+
getHarmonyAtPosition,
|
|
7731
|
+
getKeyChanges,
|
|
7732
|
+
getLyricText,
|
|
7733
|
+
getLyrics,
|
|
5979
7734
|
getMeasure,
|
|
5980
7735
|
getMeasureByIndex,
|
|
5981
7736
|
getMeasureContext,
|
|
5982
7737
|
getMeasureCount,
|
|
5983
7738
|
getMeasureEndPosition,
|
|
7739
|
+
getNextNote,
|
|
5984
7740
|
getNormalizedDuration,
|
|
5985
7741
|
getNormalizedPosition,
|
|
7742
|
+
getNotesAtPosition,
|
|
5986
7743
|
getNotesForStaff,
|
|
5987
7744
|
getNotesForVoice,
|
|
7745
|
+
getNotesInRange,
|
|
7746
|
+
getOctaveShifts,
|
|
5988
7747
|
getPartById,
|
|
7748
|
+
getPartByIndex,
|
|
7749
|
+
getPartCount,
|
|
7750
|
+
getPartIds,
|
|
5989
7751
|
getPartIndex,
|
|
7752
|
+
getPedalMarkings,
|
|
7753
|
+
getPrevNote,
|
|
7754
|
+
getRepeatStructure,
|
|
7755
|
+
getSlurSpans,
|
|
7756
|
+
getStaffRange,
|
|
5990
7757
|
getStaveCount,
|
|
5991
7758
|
getStaves,
|
|
7759
|
+
getStructuralChanges,
|
|
7760
|
+
getTempoMarkings,
|
|
7761
|
+
getTiedNoteGroups,
|
|
7762
|
+
getTimeChanges,
|
|
7763
|
+
getTupletGroups,
|
|
7764
|
+
getVerseCount,
|
|
7765
|
+
getVerticalSlice,
|
|
7766
|
+
getVoiceLine,
|
|
7767
|
+
getVoiceLineInRange,
|
|
5992
7768
|
getVoices,
|
|
7769
|
+
getVoicesForStaff,
|
|
7770
|
+
getWedges,
|
|
5993
7771
|
groupByStaff,
|
|
5994
7772
|
groupByVoice,
|
|
5995
7773
|
hasMultipleStaves,
|
|
5996
7774
|
hasNotes,
|
|
7775
|
+
inferStaff,
|
|
5997
7776
|
insertMeasure,
|
|
5998
7777
|
isCompressed,
|
|
5999
7778
|
isRestMeasure,
|
|
6000
7779
|
isValid,
|
|
7780
|
+
iterateEntries,
|
|
6001
7781
|
iterateNotes,
|
|
6002
7782
|
measureRoundtrip,
|
|
6003
7783
|
modifyNoteDuration,
|
|
@@ -6011,7 +7791,6 @@ async function serializeToFile(score, filePath, options = {}) {
|
|
|
6011
7791
|
serialize,
|
|
6012
7792
|
serializeCompressed,
|
|
6013
7793
|
serializeToFile,
|
|
6014
|
-
setDivisions,
|
|
6015
7794
|
transpose,
|
|
6016
7795
|
validate,
|
|
6017
7796
|
validateBackupForward,
|
|
@@ -6030,4 +7809,3 @@ async function serializeToFile(score, filePath, options = {}) {
|
|
|
6030
7809
|
validateVoiceStaff,
|
|
6031
7810
|
withAbsolutePositions
|
|
6032
7811
|
});
|
|
6033
|
-
//# sourceMappingURL=index.js.map
|