musicxml-io 0.9.4 → 0.9.6
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 +114 -2
- package/dist/accessors/index.d.mts +1 -1
- package/dist/accessors/index.d.ts +1 -1
- package/dist/browser.d.mts +34 -17
- package/dist/browser.d.ts +34 -17
- package/dist/browser.js +13 -3
- package/dist/browser.mjs +12 -2
- package/dist/{chunk-67EEVKG4.mjs → chunk-2GFWDBCV.mjs} +1099 -360
- package/dist/{chunk-JRXV2ECW.js → chunk-K7W4FSFV.js} +156 -1
- package/dist/{chunk-FP4TCD6M.js → chunk-SK2CA5RQ.js} +1218 -479
- package/dist/{chunk-T6KNPR45.mjs → chunk-SQSIGPRD.mjs} +155 -0
- package/dist/{index-39ZF0nDC.d.ts → index-1S3pP9QV.d.ts} +62 -2
- package/dist/{index-Labr-S8-.d.mts → index-DOLSmZ0f.d.mts} +62 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +21 -11
- package/dist/index.mjs +12 -2
- package/dist/operations/index.d.mts +2 -2
- package/dist/operations/index.d.ts +2 -2
- package/dist/operations/index.js +8 -2
- package/dist/operations/index.mjs +7 -1
- package/dist/query/index.d.mts +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/{types-CZXBqGbS.d.mts → types-UAP6Zn7J.d.mts} +113 -3
- package/dist/{types-CZXBqGbS.d.ts → types-UAP6Zn7J.d.ts} +113 -3
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ValidationException,
|
|
7
7
|
generateId,
|
|
8
8
|
validate
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SQSIGPRD.mjs";
|
|
10
10
|
import {
|
|
11
11
|
bpmToUsPerQuarter,
|
|
12
12
|
buildGridTimeline,
|
|
@@ -45,12 +45,6 @@ var RBRACKET = 93;
|
|
|
45
45
|
function isXmlWhitespace(code) {
|
|
46
46
|
return code === 32 || code === 10 || code === 9 || code === 13;
|
|
47
47
|
}
|
|
48
|
-
function isWhitespaceOnly(s) {
|
|
49
|
-
for (let i = 0; i < s.length; i++) {
|
|
50
|
-
if (!isXmlWhitespace(s.charCodeAt(i))) return false;
|
|
51
|
-
}
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
48
|
function parseXml(xml) {
|
|
55
49
|
const root = [];
|
|
56
50
|
const stack = [];
|
|
@@ -61,14 +55,17 @@ function parseXml(xml) {
|
|
|
61
55
|
const lt = xml.indexOf("<", pos);
|
|
62
56
|
if (lt === -1) break;
|
|
63
57
|
if (lt > pos) {
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
let whitespaceOnly = true;
|
|
59
|
+
for (let i = pos; i < lt; i++) {
|
|
60
|
+
if (!isXmlWhitespace(xml.charCodeAt(i))) {
|
|
61
|
+
whitespaceOnly = false;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!whitespaceOnly) {
|
|
66
|
+
children.push(decodeXmlEntities(xml.slice(pos, lt)));
|
|
70
67
|
} else if (children.length === 0 && xml.charCodeAt(lt + 1) === SLASH) {
|
|
71
|
-
children.push(
|
|
68
|
+
children.push(xml.slice(pos, lt));
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
const c = xml.charCodeAt(lt + 1);
|
|
@@ -95,9 +92,7 @@ function parseXml(xml) {
|
|
|
95
92
|
pos = end === -1 ? len : end + 3;
|
|
96
93
|
} else if (xml.startsWith("<![CDATA[", lt)) {
|
|
97
94
|
const end = xml.indexOf("]]>", lt + 9);
|
|
98
|
-
|
|
99
|
-
if (INVALID_XML_CHARS_TEST.test(text)) text = text.replace(INVALID_XML_CHARS_RE, "");
|
|
100
|
-
children.push(text);
|
|
95
|
+
children.push(xml.slice(lt + 9, end === -1 ? len : end));
|
|
101
96
|
pos = end === -1 ? len : end + 3;
|
|
102
97
|
} else {
|
|
103
98
|
let i = lt + 2;
|
|
@@ -563,6 +558,7 @@ function parseCredits(elements) {
|
|
|
563
558
|
if (a["letter-spacing"]) cw.letterSpacing = a["letter-spacing"];
|
|
564
559
|
if (a["xml:lang"]) cw.xmlLang = a["xml:lang"];
|
|
565
560
|
if (a["xml:space"]) cw.xmlSpace = a["xml:space"];
|
|
561
|
+
if (a["color"]) cw.color = a["color"];
|
|
566
562
|
return cw;
|
|
567
563
|
});
|
|
568
564
|
const images = collectElements(content, "credit-image", (_c, a) => {
|
|
@@ -592,6 +588,7 @@ function parseDisplayTexts(elements) {
|
|
|
592
588
|
if (a["font-style"]) dt.fontStyle = a["font-style"];
|
|
593
589
|
if (a["font-weight"]) dt.fontWeight = a["font-weight"];
|
|
594
590
|
if (a["xml:space"]) dt.xmlSpace = a["xml:space"];
|
|
591
|
+
if (a["color"]) dt.color = a["color"];
|
|
595
592
|
return dt;
|
|
596
593
|
});
|
|
597
594
|
}
|
|
@@ -615,6 +612,7 @@ function parsePartList(elements) {
|
|
|
615
612
|
if (pnAttrs["print-object"] === "no") {
|
|
616
613
|
partInfo.namePrintObject = false;
|
|
617
614
|
}
|
|
615
|
+
if (pnAttrs["color"]) partInfo.nameColor = pnAttrs["color"];
|
|
618
616
|
break;
|
|
619
617
|
}
|
|
620
618
|
}
|
|
@@ -633,6 +631,7 @@ function parsePartList(elements) {
|
|
|
633
631
|
if (paAttrs["print-object"] === "no") {
|
|
634
632
|
partInfo.abbreviationPrintObject = false;
|
|
635
633
|
}
|
|
634
|
+
if (paAttrs["color"]) partInfo.abbreviationColor = paAttrs["color"];
|
|
636
635
|
break;
|
|
637
636
|
}
|
|
638
637
|
}
|
|
@@ -707,12 +706,18 @@ function parsePartList(elements) {
|
|
|
707
706
|
groupType: attrs["type"] === "stop" ? "stop" : "start"
|
|
708
707
|
};
|
|
709
708
|
if (attrs["number"]) group.number = parseInt(attrs["number"], 10);
|
|
710
|
-
|
|
711
|
-
|
|
709
|
+
parseFirstElement(content, "group-name", (c, a) => {
|
|
710
|
+
const name = extractText(c);
|
|
711
|
+
if (name) group.groupName = name;
|
|
712
|
+
if (a["color"]) group.groupNameColor = a["color"];
|
|
713
|
+
});
|
|
712
714
|
const gnd = parseFirstElement(content, "group-name-display", (c) => parseDisplayTexts(c));
|
|
713
715
|
if (gnd) group.groupNameDisplay = gnd;
|
|
714
|
-
|
|
715
|
-
|
|
716
|
+
parseFirstElement(content, "group-abbreviation", (c, a) => {
|
|
717
|
+
const abbr = extractText(c);
|
|
718
|
+
if (abbr) group.groupAbbreviation = abbr;
|
|
719
|
+
if (a["color"]) group.groupAbbreviationColor = a["color"];
|
|
720
|
+
});
|
|
716
721
|
const gad = parseFirstElement(content, "group-abbreviation-display", (c) => parseDisplayTexts(c));
|
|
717
722
|
if (gad) group.groupAbbreviationDisplay = gad;
|
|
718
723
|
parseFirstElement(content, "group-symbol", (c, a) => {
|
|
@@ -721,6 +726,7 @@ function parsePartList(elements) {
|
|
|
721
726
|
group.groupSymbol = symbol;
|
|
722
727
|
}
|
|
723
728
|
if (a["default-x"]) group.groupSymbolDefaultX = parseFloat(a["default-x"]);
|
|
729
|
+
if (a["color"]) group.groupSymbolColor = a["color"];
|
|
724
730
|
});
|
|
725
731
|
const barline = getElementText(content, "group-barline");
|
|
726
732
|
if (barline && ["yes", "no", "Mensurstrich"].includes(barline)) {
|
|
@@ -878,6 +884,7 @@ function parseAttributes(elements, xmlAttrs = {}) {
|
|
|
878
884
|
if (keyAttrs["number"]) key.number = parseInt(keyAttrs["number"], 10);
|
|
879
885
|
if (keyAttrs["print-object"] === "no") key.printObject = false;
|
|
880
886
|
else if (keyAttrs["print-object"] === "yes") key.printObject = true;
|
|
887
|
+
if (keyAttrs["color"]) key.color = keyAttrs["color"];
|
|
881
888
|
keys.push(key);
|
|
882
889
|
}
|
|
883
890
|
}
|
|
@@ -954,6 +961,7 @@ function parseTimeSignature(elements, parentElements) {
|
|
|
954
961
|
if (attrs["print-object"] === "no") {
|
|
955
962
|
time.printObject = false;
|
|
956
963
|
}
|
|
964
|
+
if (attrs["color"]) time.color = attrs["color"];
|
|
957
965
|
break;
|
|
958
966
|
}
|
|
959
967
|
}
|
|
@@ -1008,6 +1016,9 @@ function parseClef(elements, attrs) {
|
|
|
1008
1016
|
if (attrs["after-barline"] === "yes") {
|
|
1009
1017
|
clef.afterBarline = true;
|
|
1010
1018
|
}
|
|
1019
|
+
if (attrs["color"]) {
|
|
1020
|
+
clef.color = attrs["color"];
|
|
1021
|
+
}
|
|
1011
1022
|
return clef;
|
|
1012
1023
|
}
|
|
1013
1024
|
function parseTranspose(elements) {
|
|
@@ -1037,6 +1048,7 @@ function parseNote(elements, attrs) {
|
|
|
1037
1048
|
if (attrs["print-dot"] === "yes") note.printDot = true;
|
|
1038
1049
|
if (attrs["print-spacing"] === "yes") note.printSpacing = true;
|
|
1039
1050
|
if (attrs["print-spacing"] === "no") note.printSpacing = false;
|
|
1051
|
+
if (attrs["color"]) note.color = attrs["color"];
|
|
1040
1052
|
let dotCount = 0;
|
|
1041
1053
|
let notationsIndex = 0;
|
|
1042
1054
|
let tieElements;
|
|
@@ -1069,6 +1081,7 @@ function parseNote(elements, attrs) {
|
|
|
1069
1081
|
break;
|
|
1070
1082
|
case "dot":
|
|
1071
1083
|
dotCount++;
|
|
1084
|
+
if (elAttrs["color"] && note.dotColor === void 0) note.dotColor = elAttrs["color"];
|
|
1072
1085
|
break;
|
|
1073
1086
|
case "instrument":
|
|
1074
1087
|
if (elAttrs["id"]) note.instrument = elAttrs["id"];
|
|
@@ -1106,6 +1119,7 @@ function parseNote(elements, attrs) {
|
|
|
1106
1119
|
const noteType = extractText(c);
|
|
1107
1120
|
if (isValidNoteType(noteType)) note.noteType = noteType;
|
|
1108
1121
|
if (elAttrs["size"]) note.noteTypeSize = elAttrs["size"];
|
|
1122
|
+
if (elAttrs["color"]) note.noteTypeColor = elAttrs["color"];
|
|
1109
1123
|
break;
|
|
1110
1124
|
}
|
|
1111
1125
|
case "accidental": {
|
|
@@ -1131,6 +1145,7 @@ function parseNote(elements, attrs) {
|
|
|
1131
1145
|
note.stem = { value: stemValue };
|
|
1132
1146
|
if (elAttrs["default-x"]) note.stem.defaultX = parseFloat(elAttrs["default-x"]);
|
|
1133
1147
|
if (elAttrs["default-y"]) note.stem.defaultY = parseFloat(elAttrs["default-y"]);
|
|
1148
|
+
if (elAttrs["color"]) note.stem.color = elAttrs["color"];
|
|
1134
1149
|
}
|
|
1135
1150
|
break;
|
|
1136
1151
|
}
|
|
@@ -1141,6 +1156,7 @@ function parseNote(elements, attrs) {
|
|
|
1141
1156
|
if (elAttrs["filled"] === "yes") nhInfo.filled = true;
|
|
1142
1157
|
else if (elAttrs["filled"] === "no") nhInfo.filled = false;
|
|
1143
1158
|
if (elAttrs["parentheses"] === "yes") nhInfo.parentheses = true;
|
|
1159
|
+
if (elAttrs["color"]) nhInfo.color = elAttrs["color"];
|
|
1144
1160
|
note.notehead = nhInfo;
|
|
1145
1161
|
}
|
|
1146
1162
|
break;
|
|
@@ -1240,6 +1256,7 @@ function parseBeam(elements, attrs) {
|
|
|
1240
1256
|
if (attrs["fan"] && validFans.includes(attrs["fan"])) {
|
|
1241
1257
|
beam.fan = attrs["fan"];
|
|
1242
1258
|
}
|
|
1259
|
+
if (attrs["color"]) beam.color = attrs["color"];
|
|
1243
1260
|
return beam;
|
|
1244
1261
|
}
|
|
1245
1262
|
function parseNotations(elements, notationsIndex = 0) {
|
|
@@ -1254,6 +1271,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1254
1271
|
tiedType: attrs["type"] || "start",
|
|
1255
1272
|
number: attrs["number"] ? parseInt(attrs["number"], 10) : void 0,
|
|
1256
1273
|
orientation: attrs["orientation"],
|
|
1274
|
+
color: attrs["color"],
|
|
1257
1275
|
notationsIndex
|
|
1258
1276
|
};
|
|
1259
1277
|
notations.push(tied);
|
|
@@ -1272,6 +1290,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1272
1290
|
bezierX2: attrs["bezier-x2"] ? parseFloat(attrs["bezier-x2"]) : void 0,
|
|
1273
1291
|
bezierY2: attrs["bezier-y2"] ? parseFloat(attrs["bezier-y2"]) : void 0,
|
|
1274
1292
|
placement: attrs["placement"],
|
|
1293
|
+
color: attrs["color"],
|
|
1275
1294
|
notationsIndex
|
|
1276
1295
|
};
|
|
1277
1296
|
notations.push(slur);
|
|
@@ -1366,6 +1385,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1366
1385
|
if (artAttrs["default-y"]) {
|
|
1367
1386
|
artNotation.defaultY = parseFloat(artAttrs["default-y"]);
|
|
1368
1387
|
}
|
|
1388
|
+
if (artAttrs["color"]) artNotation.color = artAttrs["color"];
|
|
1369
1389
|
notations.push(artNotation);
|
|
1370
1390
|
}
|
|
1371
1391
|
}
|
|
@@ -1387,7 +1407,11 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1387
1407
|
];
|
|
1388
1408
|
const accidentalMarks = collectElements(ornContent, "accidental-mark", (c, a) => {
|
|
1389
1409
|
const value = extractText(c);
|
|
1390
|
-
return isValidAccidental(value) ? {
|
|
1410
|
+
return isValidAccidental(value) ? {
|
|
1411
|
+
value,
|
|
1412
|
+
placement: a["placement"],
|
|
1413
|
+
color: a["color"]
|
|
1414
|
+
} : null;
|
|
1391
1415
|
}).filter((m) => m !== null);
|
|
1392
1416
|
for (const orn of ornContent) {
|
|
1393
1417
|
if (typeof orn === "string") continue;
|
|
@@ -1403,6 +1427,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1403
1427
|
if (ornAttrs["default-y"]) {
|
|
1404
1428
|
ornNotation.defaultY = parseFloat(ornAttrs["default-y"]);
|
|
1405
1429
|
}
|
|
1430
|
+
if (ornAttrs["color"]) ornNotation.color = ornAttrs["color"];
|
|
1406
1431
|
if (accidentalMarks.length > 0 && notations.filter((n) => n.type === "ornament").length === 0) {
|
|
1407
1432
|
ornNotation.accidentalMarks = accidentalMarks;
|
|
1408
1433
|
}
|
|
@@ -1422,6 +1447,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1422
1447
|
if (wlAttrs["default-y"]) {
|
|
1423
1448
|
wlNotation.defaultY = parseFloat(wlAttrs["default-y"]);
|
|
1424
1449
|
}
|
|
1450
|
+
if (wlAttrs["color"]) wlNotation.color = wlAttrs["color"];
|
|
1425
1451
|
notations.push(wlNotation);
|
|
1426
1452
|
}
|
|
1427
1453
|
if (orn.tagName === "tremolo") {
|
|
@@ -1437,6 +1463,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1437
1463
|
};
|
|
1438
1464
|
if (tremAttrs["default-x"]) tremNotation.defaultX = parseFloat(tremAttrs["default-x"]);
|
|
1439
1465
|
if (tremAttrs["default-y"]) tremNotation.defaultY = parseFloat(tremAttrs["default-y"]);
|
|
1466
|
+
if (tremAttrs["color"]) tremNotation.color = tremAttrs["color"];
|
|
1440
1467
|
notations.push(tremNotation);
|
|
1441
1468
|
}
|
|
1442
1469
|
}
|
|
@@ -1499,6 +1526,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1499
1526
|
if (hasElement(bendContent, "pre-bend")) techNotation.preBend = true;
|
|
1500
1527
|
if (hasElement(bendContent, "release")) techNotation.release = true;
|
|
1501
1528
|
if (hasElement(bendContent, "with-bar")) techNotation.withBar = true;
|
|
1529
|
+
if (techAttrs["color"]) techNotation.color = techAttrs["color"];
|
|
1502
1530
|
notations.push(techNotation);
|
|
1503
1531
|
}
|
|
1504
1532
|
for (const techType of technicalTypes) {
|
|
@@ -1553,6 +1581,9 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1553
1581
|
if (techAttrs["font-size"]) {
|
|
1554
1582
|
notation.fontSize = techAttrs["font-size"];
|
|
1555
1583
|
}
|
|
1584
|
+
if (techAttrs["color"]) {
|
|
1585
|
+
notation.color = techAttrs["color"];
|
|
1586
|
+
}
|
|
1556
1587
|
notations.push(notation);
|
|
1557
1588
|
}
|
|
1558
1589
|
}
|
|
@@ -1605,6 +1636,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1605
1636
|
notationsIndex
|
|
1606
1637
|
};
|
|
1607
1638
|
if (otherDynamics) dynNotation.otherDynamics = otherDynamics;
|
|
1639
|
+
if (dynAttrs["color"]) dynNotation.color = dynAttrs["color"];
|
|
1608
1640
|
notations.push(dynNotation);
|
|
1609
1641
|
}
|
|
1610
1642
|
} else if (el.tagName === "fermata") {
|
|
@@ -1615,6 +1647,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1615
1647
|
shape: shape || void 0,
|
|
1616
1648
|
fermataType: a["type"],
|
|
1617
1649
|
placement: a["placement"],
|
|
1650
|
+
color: a["color"],
|
|
1618
1651
|
notationsIndex
|
|
1619
1652
|
};
|
|
1620
1653
|
if (a["default-x"]) fermataNotation.defaultX = parseFloat(a["default-x"]);
|
|
@@ -1630,6 +1663,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1630
1663
|
};
|
|
1631
1664
|
if (arpAttrs["default-x"]) arpNotation.defaultX = parseFloat(arpAttrs["default-x"]);
|
|
1632
1665
|
if (arpAttrs["default-y"]) arpNotation.defaultY = parseFloat(arpAttrs["default-y"]);
|
|
1666
|
+
if (arpAttrs["color"]) arpNotation.color = arpAttrs["color"];
|
|
1633
1667
|
notations.push(arpNotation);
|
|
1634
1668
|
} else if (el.tagName === "non-arpeggiate") {
|
|
1635
1669
|
const nonArpAttrs = el.attributes;
|
|
@@ -1638,6 +1672,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1638
1672
|
nonArpeggiateType: nonArpAttrs["type"],
|
|
1639
1673
|
number: nonArpAttrs["number"] ? parseInt(nonArpAttrs["number"], 10) : void 0,
|
|
1640
1674
|
placement: nonArpAttrs["placement"],
|
|
1675
|
+
color: nonArpAttrs["color"],
|
|
1641
1676
|
notationsIndex
|
|
1642
1677
|
});
|
|
1643
1678
|
} else if (el.tagName === "accidental-mark") {
|
|
@@ -1648,6 +1683,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1648
1683
|
type: "accidental-mark",
|
|
1649
1684
|
value,
|
|
1650
1685
|
placement: amAttrs["placement"],
|
|
1686
|
+
color: amAttrs["color"],
|
|
1651
1687
|
notationsIndex
|
|
1652
1688
|
});
|
|
1653
1689
|
} else if (el.tagName === "glissando") {
|
|
@@ -1666,6 +1702,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1666
1702
|
number: glissAttrs["number"] ? parseInt(glissAttrs["number"], 10) : void 0,
|
|
1667
1703
|
lineType: glissAttrs["line-type"],
|
|
1668
1704
|
text,
|
|
1705
|
+
color: glissAttrs["color"],
|
|
1669
1706
|
notationsIndex
|
|
1670
1707
|
});
|
|
1671
1708
|
} else if (el.tagName === "slide") {
|
|
@@ -1677,6 +1714,7 @@ function parseNotations(elements, notationsIndex = 0) {
|
|
|
1677
1714
|
slideType: slideAttrs["type"] === "stop" ? "stop" : "start",
|
|
1678
1715
|
number: slideAttrs["number"] ? parseInt(slideAttrs["number"], 10) : void 0,
|
|
1679
1716
|
lineType: slideAttrs["line-type"],
|
|
1717
|
+
color: slideAttrs["color"],
|
|
1680
1718
|
notationsIndex
|
|
1681
1719
|
};
|
|
1682
1720
|
if (slideText) slideNotation.text = slideText;
|
|
@@ -1704,12 +1742,14 @@ function parseLyric(elements, attrs) {
|
|
|
1704
1742
|
}
|
|
1705
1743
|
} else if (el.tagName === "text") {
|
|
1706
1744
|
const content = el.children;
|
|
1745
|
+
const textColor = el.attributes["color"];
|
|
1707
1746
|
let foundText = false;
|
|
1708
1747
|
for (const item of content) {
|
|
1709
1748
|
if (typeof item === "string") {
|
|
1710
1749
|
textElements.push({
|
|
1711
1750
|
text: item,
|
|
1712
|
-
syllabic: currentSyllabic
|
|
1751
|
+
syllabic: currentSyllabic,
|
|
1752
|
+
color: textColor
|
|
1713
1753
|
});
|
|
1714
1754
|
currentSyllabic = void 0;
|
|
1715
1755
|
foundText = true;
|
|
@@ -1719,7 +1759,8 @@ function parseLyric(elements, attrs) {
|
|
|
1719
1759
|
if (!foundText) {
|
|
1720
1760
|
textElements.push({
|
|
1721
1761
|
text: "",
|
|
1722
|
-
syllabic: currentSyllabic
|
|
1762
|
+
syllabic: currentSyllabic,
|
|
1763
|
+
color: textColor
|
|
1723
1764
|
});
|
|
1724
1765
|
currentSyllabic = void 0;
|
|
1725
1766
|
}
|
|
@@ -1748,9 +1789,15 @@ function parseLyric(elements, attrs) {
|
|
|
1748
1789
|
if (attrs["placement"]) {
|
|
1749
1790
|
lyric.placement = attrs["placement"];
|
|
1750
1791
|
}
|
|
1792
|
+
if (attrs["color"]) {
|
|
1793
|
+
lyric.color = attrs["color"];
|
|
1794
|
+
}
|
|
1751
1795
|
if (textElements.length > 0 && textElements[0].syllabic) {
|
|
1752
1796
|
lyric.syllabic = textElements[0].syllabic;
|
|
1753
1797
|
}
|
|
1798
|
+
if (textElements.length > 0 && textElements[0].color) {
|
|
1799
|
+
lyric.textColor = textElements[0].color;
|
|
1800
|
+
}
|
|
1754
1801
|
if (textElements.length > 1) {
|
|
1755
1802
|
lyric.textElements = textElements;
|
|
1756
1803
|
}
|
|
@@ -1762,8 +1809,9 @@ function parseLyric(elements, attrs) {
|
|
|
1762
1809
|
if (el.tagName === "extend") {
|
|
1763
1810
|
const extendAttrs = el.attributes;
|
|
1764
1811
|
const extendType = extendAttrs["type"];
|
|
1765
|
-
|
|
1766
|
-
|
|
1812
|
+
const extendColor = extendAttrs["color"];
|
|
1813
|
+
if (extendType || extendColor) {
|
|
1814
|
+
lyric.extend = { type: extendType, color: extendColor };
|
|
1767
1815
|
} else {
|
|
1768
1816
|
lyric.extend = true;
|
|
1769
1817
|
}
|
|
@@ -1905,6 +1953,7 @@ function parseDirectionTypes(elements) {
|
|
|
1905
1953
|
if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
|
|
1906
1954
|
if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
|
|
1907
1955
|
if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
|
|
1956
|
+
if (dynAttrs["color"]) result.color = dynAttrs["color"];
|
|
1908
1957
|
results.push(result);
|
|
1909
1958
|
foundStandard = true;
|
|
1910
1959
|
break;
|
|
@@ -1918,6 +1967,7 @@ function parseDirectionTypes(elements) {
|
|
|
1918
1967
|
if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
|
|
1919
1968
|
if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
|
|
1920
1969
|
if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
|
|
1970
|
+
if (dynAttrs["color"]) result.color = dynAttrs["color"];
|
|
1921
1971
|
results.push(result);
|
|
1922
1972
|
}
|
|
1923
1973
|
}
|
|
@@ -1932,6 +1982,7 @@ function parseDirectionTypes(elements) {
|
|
|
1932
1982
|
if (wedgeAttrs["spread"]) result.spread = parseFloat(wedgeAttrs["spread"]);
|
|
1933
1983
|
if (wedgeAttrs["default-y"]) result.defaultY = parseFloat(wedgeAttrs["default-y"]);
|
|
1934
1984
|
if (wedgeAttrs["relative-x"]) result.relativeX = parseFloat(wedgeAttrs["relative-x"]);
|
|
1985
|
+
if (wedgeAttrs["color"]) result.color = wedgeAttrs["color"];
|
|
1935
1986
|
results.push(result);
|
|
1936
1987
|
}
|
|
1937
1988
|
continue;
|
|
@@ -1976,6 +2027,7 @@ function parseDirectionTypes(elements) {
|
|
|
1976
2027
|
if (metAttrs["default-y"]) result.defaultY = parseFloat(metAttrs["default-y"]);
|
|
1977
2028
|
if (metAttrs["font-family"]) result.fontFamily = metAttrs["font-family"];
|
|
1978
2029
|
if (metAttrs["font-size"]) result.fontSize = metAttrs["font-size"];
|
|
2030
|
+
if (metAttrs["color"]) result.color = metAttrs["color"];
|
|
1979
2031
|
results.push(result);
|
|
1980
2032
|
}
|
|
1981
2033
|
continue;
|
|
@@ -2010,6 +2062,7 @@ function parseDirectionTypes(elements) {
|
|
|
2010
2062
|
if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
|
|
2011
2063
|
if (a["font-size"]) result.fontSize = a["font-size"];
|
|
2012
2064
|
if (a["font-weight"]) result.fontWeight = a["font-weight"];
|
|
2065
|
+
if (a["color"]) result.color = a["color"];
|
|
2013
2066
|
results.push(result);
|
|
2014
2067
|
}
|
|
2015
2068
|
continue;
|
|
@@ -2024,6 +2077,7 @@ function parseDirectionTypes(elements) {
|
|
|
2024
2077
|
if (bracketAttrs["line-type"]) result.lineType = bracketAttrs["line-type"];
|
|
2025
2078
|
if (bracketAttrs["default-y"]) result.defaultY = parseFloat(bracketAttrs["default-y"]);
|
|
2026
2079
|
if (bracketAttrs["relative-x"]) result.relativeX = parseFloat(bracketAttrs["relative-x"]);
|
|
2080
|
+
if (bracketAttrs["color"]) result.color = bracketAttrs["color"];
|
|
2027
2081
|
results.push(result);
|
|
2028
2082
|
}
|
|
2029
2083
|
continue;
|
|
@@ -2037,13 +2091,16 @@ function parseDirectionTypes(elements) {
|
|
|
2037
2091
|
if (dashAttrs["dash-length"]) result.dashLength = parseFloat(dashAttrs["dash-length"]);
|
|
2038
2092
|
if (dashAttrs["default-y"]) result.defaultY = parseFloat(dashAttrs["default-y"]);
|
|
2039
2093
|
if (dashAttrs["space-length"]) result.spaceLength = parseFloat(dashAttrs["space-length"]);
|
|
2094
|
+
if (dashAttrs["color"]) result.color = dashAttrs["color"];
|
|
2040
2095
|
results.push(result);
|
|
2041
2096
|
}
|
|
2042
2097
|
continue;
|
|
2043
2098
|
}
|
|
2044
2099
|
if (el.tagName === "accordion-registration") {
|
|
2045
2100
|
const accContent = el.children;
|
|
2101
|
+
const accRegAttrs = el.attributes;
|
|
2046
2102
|
const result = { kind: "accordion-registration" };
|
|
2103
|
+
if (accRegAttrs["color"]) result.color = accRegAttrs["color"];
|
|
2047
2104
|
for (const acc of accContent) {
|
|
2048
2105
|
if (typeof acc === "string") continue;
|
|
2049
2106
|
if (acc.tagName === "accordion-high") {
|
|
@@ -2076,34 +2133,23 @@ function parseDirectionTypes(elements) {
|
|
|
2076
2133
|
if (otherAttrs["default-y"]) result.defaultY = parseFloat(otherAttrs["default-y"]);
|
|
2077
2134
|
if (otherAttrs["halign"]) result.halign = otherAttrs["halign"];
|
|
2078
2135
|
if (otherAttrs["print-object"] === "no") result.printObject = false;
|
|
2136
|
+
if (otherAttrs["color"]) result.color = otherAttrs["color"];
|
|
2079
2137
|
results.push(result);
|
|
2080
2138
|
break;
|
|
2081
2139
|
}
|
|
2082
2140
|
}
|
|
2083
2141
|
continue;
|
|
2084
2142
|
}
|
|
2085
|
-
if (el.tagName === "segno") {
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
results.push({ kind: "coda" });
|
|
2091
|
-
continue;
|
|
2092
|
-
}
|
|
2093
|
-
if (el.tagName === "eyeglasses") {
|
|
2094
|
-
results.push({ kind: "eyeglasses" });
|
|
2095
|
-
continue;
|
|
2096
|
-
}
|
|
2097
|
-
if (el.tagName === "damp") {
|
|
2098
|
-
results.push({ kind: "damp" });
|
|
2099
|
-
continue;
|
|
2100
|
-
}
|
|
2101
|
-
if (el.tagName === "damp-all") {
|
|
2102
|
-
results.push({ kind: "damp-all" });
|
|
2143
|
+
if (el.tagName === "segno" || el.tagName === "coda" || el.tagName === "eyeglasses" || el.tagName === "damp" || el.tagName === "damp-all") {
|
|
2144
|
+
const markerColor = el.attributes["color"];
|
|
2145
|
+
const result = { kind: el.tagName };
|
|
2146
|
+
if (markerColor) result.color = markerColor;
|
|
2147
|
+
results.push(result);
|
|
2103
2148
|
continue;
|
|
2104
2149
|
}
|
|
2105
2150
|
if (el.tagName === "scordatura") {
|
|
2106
2151
|
const scordContent = el.children;
|
|
2152
|
+
const scordColor = el.attributes["color"];
|
|
2107
2153
|
const accords = [];
|
|
2108
2154
|
for (const sc of scordContent) {
|
|
2109
2155
|
if (typeof sc === "string") continue;
|
|
@@ -2124,11 +2170,12 @@ function parseDirectionTypes(elements) {
|
|
|
2124
2170
|
}
|
|
2125
2171
|
}
|
|
2126
2172
|
}
|
|
2127
|
-
results.push({ kind: "scordatura", accords: accords.length > 0 ? accords : void 0 });
|
|
2173
|
+
results.push({ kind: "scordatura", accords: accords.length > 0 ? accords : void 0, color: scordColor });
|
|
2128
2174
|
continue;
|
|
2129
2175
|
}
|
|
2130
2176
|
if (el.tagName === "harp-pedals") {
|
|
2131
2177
|
const harpContent = el.children;
|
|
2178
|
+
const harpColor = el.attributes["color"];
|
|
2132
2179
|
const pedalTunings = [];
|
|
2133
2180
|
for (const hp of harpContent) {
|
|
2134
2181
|
if (typeof hp === "string") continue;
|
|
@@ -2144,7 +2191,7 @@ function parseDirectionTypes(elements) {
|
|
|
2144
2191
|
}
|
|
2145
2192
|
}
|
|
2146
2193
|
}
|
|
2147
|
-
results.push({ kind: "harp-pedals", pedalTunings: pedalTunings.length > 0 ? pedalTunings : void 0 });
|
|
2194
|
+
results.push({ kind: "harp-pedals", pedalTunings: pedalTunings.length > 0 ? pedalTunings : void 0, color: harpColor });
|
|
2148
2195
|
continue;
|
|
2149
2196
|
}
|
|
2150
2197
|
if (el.tagName === "image") {
|
|
@@ -2166,6 +2213,7 @@ function parseDirectionTypes(elements) {
|
|
|
2166
2213
|
if (pedalAttrs["default-y"]) result.defaultY = parseFloat(pedalAttrs["default-y"]);
|
|
2167
2214
|
if (pedalAttrs["relative-x"]) result.relativeX = parseFloat(pedalAttrs["relative-x"]);
|
|
2168
2215
|
if (pedalAttrs["halign"]) result.halign = pedalAttrs["halign"];
|
|
2216
|
+
if (pedalAttrs["color"]) result.color = pedalAttrs["color"];
|
|
2169
2217
|
results.push(result);
|
|
2170
2218
|
}
|
|
2171
2219
|
continue;
|
|
@@ -2176,6 +2224,7 @@ function parseDirectionTypes(elements) {
|
|
|
2176
2224
|
if (shiftType === "up" || shiftType === "down" || shiftType === "stop") {
|
|
2177
2225
|
const result = { kind: "octave-shift", type: shiftType };
|
|
2178
2226
|
if (shiftAttrs["size"]) result.size = parseInt(shiftAttrs["size"], 10);
|
|
2227
|
+
if (shiftAttrs["color"]) result.color = shiftAttrs["color"];
|
|
2179
2228
|
results.push(result);
|
|
2180
2229
|
}
|
|
2181
2230
|
continue;
|
|
@@ -2222,10 +2271,13 @@ function parseDirectionTypes(elements) {
|
|
|
2222
2271
|
function parseBarline(elements, attrs) {
|
|
2223
2272
|
const location = attrs["location"] || "right";
|
|
2224
2273
|
const barline = { _id: attrs["id"] || generateId(), location };
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2274
|
+
parseFirstElement(elements, "bar-style", (c, a) => {
|
|
2275
|
+
const barStyle = extractText(c);
|
|
2276
|
+
if (barStyle && isValidBarStyle(barStyle)) {
|
|
2277
|
+
barline.barStyle = barStyle;
|
|
2278
|
+
if (a["color"]) barline.barStyleColor = a["color"];
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2229
2281
|
for (const el of elements) {
|
|
2230
2282
|
if (typeof el === "string") continue;
|
|
2231
2283
|
if (el.tagName === "repeat") {
|
|
@@ -2251,6 +2303,7 @@ function parseBarline(elements, attrs) {
|
|
|
2251
2303
|
if (endingText) barline.ending.text = endingText;
|
|
2252
2304
|
if (endingAttrs["default-y"]) barline.ending.defaultY = parseFloat(endingAttrs["default-y"]);
|
|
2253
2305
|
if (endingAttrs["end-length"]) barline.ending.endLength = parseFloat(endingAttrs["end-length"]);
|
|
2306
|
+
if (endingAttrs["color"]) barline.ending.color = endingAttrs["color"];
|
|
2254
2307
|
}
|
|
2255
2308
|
}
|
|
2256
2309
|
}
|
|
@@ -2428,6 +2481,7 @@ function parseStaffDetails(elements, attrs) {
|
|
|
2428
2481
|
function parseMeasureStyle(elements, attrs) {
|
|
2429
2482
|
const ms = {};
|
|
2430
2483
|
if (attrs["number"]) ms.number = parseInt(attrs["number"], 10);
|
|
2484
|
+
if (attrs["color"]) ms.color = attrs["color"];
|
|
2431
2485
|
const multipleRest = getElementText(elements, "multiple-rest");
|
|
2432
2486
|
if (multipleRest) ms.multipleRest = parseInt(multipleRest, 10);
|
|
2433
2487
|
for (const el of elements) {
|
|
@@ -2469,6 +2523,7 @@ function parseHarmony(elements, attrs) {
|
|
|
2469
2523
|
if (attrs["default-y"]) harmony.defaultY = parseFloat(attrs["default-y"]);
|
|
2470
2524
|
if (attrs["halign"]) harmony.halign = attrs["halign"];
|
|
2471
2525
|
if (attrs["font-size"]) harmony.fontSize = attrs["font-size"];
|
|
2526
|
+
if (attrs["color"]) harmony.color = attrs["color"];
|
|
2472
2527
|
const root = getElementContent(elements, "root");
|
|
2473
2528
|
if (root) {
|
|
2474
2529
|
const rootStep = getElementText(root, "root-step");
|
|
@@ -2489,6 +2544,7 @@ function parseHarmony(elements, attrs) {
|
|
|
2489
2544
|
}
|
|
2490
2545
|
if (kindAttrs["text"] !== void 0) harmony.kindText = kindAttrs["text"];
|
|
2491
2546
|
if (kindAttrs["halign"]) harmony.kindHalign = kindAttrs["halign"];
|
|
2547
|
+
if (kindAttrs["color"]) harmony.kindColor = kindAttrs["color"];
|
|
2492
2548
|
break;
|
|
2493
2549
|
}
|
|
2494
2550
|
}
|
|
@@ -2583,6 +2639,8 @@ function parseHarmony(elements, attrs) {
|
|
|
2583
2639
|
}
|
|
2584
2640
|
}
|
|
2585
2641
|
if (frameNotes.length > 0) frameObj.frameNotes = frameNotes;
|
|
2642
|
+
const frameColor = parseFirstElement(elements, "frame", (_c, a) => a["color"]);
|
|
2643
|
+
if (frameColor) frameObj.color = frameColor;
|
|
2586
2644
|
if (Object.keys(frameObj).length > 0) harmony.frame = frameObj;
|
|
2587
2645
|
}
|
|
2588
2646
|
const offset = getElementText(elements, "offset");
|
|
@@ -2598,6 +2656,7 @@ function parseFiguredBass(elements, attrs) {
|
|
|
2598
2656
|
figures: []
|
|
2599
2657
|
};
|
|
2600
2658
|
if (attrs["parentheses"] === "yes") fb.parentheses = true;
|
|
2659
|
+
if (attrs["color"]) fb.color = attrs["color"];
|
|
2601
2660
|
const duration = getElementText(elements, "duration");
|
|
2602
2661
|
if (duration) fb.duration = parseInt(duration, 10);
|
|
2603
2662
|
for (const el of elements) {
|
|
@@ -2628,8 +2687,9 @@ function parseFiguredBass(elements, attrs) {
|
|
|
2628
2687
|
} else if (figEl.tagName === "extend") {
|
|
2629
2688
|
const extendAttrs = figEl.attributes;
|
|
2630
2689
|
const extendType = extendAttrs["type"];
|
|
2631
|
-
|
|
2632
|
-
|
|
2690
|
+
const extendColor = extendAttrs["color"];
|
|
2691
|
+
if (extendType || extendColor) {
|
|
2692
|
+
figure.extend = { type: extendType, color: extendColor };
|
|
2633
2693
|
} else {
|
|
2634
2694
|
figure.extend = true;
|
|
2635
2695
|
}
|
|
@@ -2760,6 +2820,148 @@ function parseAuto(data) {
|
|
|
2760
2820
|
return parse(xmlString);
|
|
2761
2821
|
}
|
|
2762
2822
|
|
|
2823
|
+
// src/abc-decorations.ts
|
|
2824
|
+
var ABC_BODY_FIELD_MARKER = "__abc_field__";
|
|
2825
|
+
var DECORATIONS = [
|
|
2826
|
+
// --- Articulations ---------------------------------------------------
|
|
2827
|
+
{ spellings: [".", "staccato"], canonical: ".", target: { kind: "articulation", articulation: "staccato" } },
|
|
2828
|
+
{ spellings: ["L", "accent", "emphasis", ">"], canonical: "L", target: { kind: "articulation", articulation: "accent" } },
|
|
2829
|
+
{ spellings: ["marcato"], canonical: "!marcato!", target: { kind: "articulation", articulation: "strong-accent" } },
|
|
2830
|
+
{ spellings: ["tenuto"], canonical: "!tenuto!", target: { kind: "articulation", articulation: "tenuto" } },
|
|
2831
|
+
{ spellings: ["breath"], canonical: "!breath!", target: { kind: "articulation", articulation: "breath-mark" } },
|
|
2832
|
+
// --- Ornaments -------------------------------------------------------
|
|
2833
|
+
{ spellings: ["T", "trill"], canonical: "T", target: { kind: "ornament", ornament: "trill-mark" } },
|
|
2834
|
+
{ spellings: ["M", "lowermordent", "mordent"], canonical: "M", target: { kind: "ornament", ornament: "mordent" } },
|
|
2835
|
+
{ spellings: ["P", "uppermordent", "pralltriller"], canonical: "P", target: { kind: "ornament", ornament: "inverted-mordent" } },
|
|
2836
|
+
{ spellings: ["turn", "gruppetto"], canonical: "!turn!", target: { kind: "ornament", ornament: "turn" } },
|
|
2837
|
+
{ spellings: ["invertedturn"], canonical: "!invertedturn!", target: { kind: "ornament", ornament: "inverted-turn" } },
|
|
2838
|
+
{ spellings: ["trill("], canonical: "!trill(!", target: { kind: "trill-line", wavyLineType: "start" } },
|
|
2839
|
+
{ spellings: ["trill)"], canonical: "!trill)!", target: { kind: "trill-line", wavyLineType: "stop" } },
|
|
2840
|
+
{ spellings: ["arpeggio"], canonical: "!arpeggio!", target: { kind: "arpeggiate" } },
|
|
2841
|
+
// --- Technical -------------------------------------------------------
|
|
2842
|
+
{ spellings: ["u", "upbow"], canonical: "u", target: { kind: "technical", technical: "up-bow" } },
|
|
2843
|
+
{ spellings: ["v", "downbow"], canonical: "v", target: { kind: "technical", technical: "down-bow" } },
|
|
2844
|
+
{ spellings: ["open"], canonical: "!open!", target: { kind: "technical", technical: "open-string" } },
|
|
2845
|
+
{ spellings: ["thumb"], canonical: "!thumb!", target: { kind: "technical", technical: "thumb-position" } },
|
|
2846
|
+
{ spellings: ["snap"], canonical: "!snap!", target: { kind: "technical", technical: "snap-pizzicato" } },
|
|
2847
|
+
{ spellings: ["+", "plus"], canonical: "!plus!", target: { kind: "technical", technical: "stopped" } },
|
|
2848
|
+
// --- Fermatas --------------------------------------------------------
|
|
2849
|
+
{ spellings: ["H", "fermata"], canonical: "H", target: { kind: "fermata" } },
|
|
2850
|
+
{ spellings: ["invertedfermata"], canonical: "!invertedfermata!", target: { kind: "fermata", inverted: true } },
|
|
2851
|
+
// --- Wedges (hairpins) -----------------------------------------------
|
|
2852
|
+
{ spellings: ["crescendo(", "<("], canonical: "!crescendo(!", target: { kind: "wedge", wedge: "crescendo", stop: false } },
|
|
2853
|
+
{ spellings: ["crescendo)", "<)"], canonical: "!crescendo)!", target: { kind: "wedge", wedge: "crescendo", stop: true } },
|
|
2854
|
+
{ spellings: ["diminuendo(", ">("], canonical: "!diminuendo(!", target: { kind: "wedge", wedge: "diminuendo", stop: false } },
|
|
2855
|
+
{ spellings: ["diminuendo)", ">)"], canonical: "!diminuendo)!", target: { kind: "wedge", wedge: "diminuendo", stop: true } },
|
|
2856
|
+
// --- Navigation marks ------------------------------------------------
|
|
2857
|
+
{ spellings: ["S", "segno"], canonical: "S", target: { kind: "segno" } },
|
|
2858
|
+
{ spellings: ["O", "coda"], canonical: "O", target: { kind: "coda" } },
|
|
2859
|
+
{ spellings: ["D.S."], canonical: "!D.S.!", target: { kind: "words", text: "D.S." } },
|
|
2860
|
+
{ spellings: ["D.C."], canonical: "!D.C.!", target: { kind: "words", text: "D.C." } },
|
|
2861
|
+
{ spellings: ["dacoda"], canonical: "!dacoda!", target: { kind: "words", text: "Da Coda" } },
|
|
2862
|
+
{ spellings: ["dacapo"], canonical: "!dacapo!", target: { kind: "words", text: "Da Capo" } },
|
|
2863
|
+
{ spellings: ["fine"], canonical: "!fine!", target: { kind: "words", text: "Fine" } }
|
|
2864
|
+
];
|
|
2865
|
+
for (let n = 0; n <= 5; n++) {
|
|
2866
|
+
DECORATIONS.push({
|
|
2867
|
+
spellings: [String(n)],
|
|
2868
|
+
canonical: `!${n}!`,
|
|
2869
|
+
target: { kind: "fingering", fingering: String(n) }
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
var ABC_DYNAMICS = /* @__PURE__ */ new Set([
|
|
2873
|
+
"pppppp",
|
|
2874
|
+
"ppppp",
|
|
2875
|
+
"pppp",
|
|
2876
|
+
"ppp",
|
|
2877
|
+
"pp",
|
|
2878
|
+
"p",
|
|
2879
|
+
"mp",
|
|
2880
|
+
"mf",
|
|
2881
|
+
"f",
|
|
2882
|
+
"ff",
|
|
2883
|
+
"fff",
|
|
2884
|
+
"ffff",
|
|
2885
|
+
"fffff",
|
|
2886
|
+
"ffffff",
|
|
2887
|
+
"sf",
|
|
2888
|
+
"sfz",
|
|
2889
|
+
"sffz",
|
|
2890
|
+
"sfp",
|
|
2891
|
+
"sfpp",
|
|
2892
|
+
"fp",
|
|
2893
|
+
"rf",
|
|
2894
|
+
"rfz",
|
|
2895
|
+
"fz",
|
|
2896
|
+
"pf"
|
|
2897
|
+
]);
|
|
2898
|
+
var ABC_SHORTHAND_DECORATIONS = /* @__PURE__ */ new Set([
|
|
2899
|
+
".",
|
|
2900
|
+
"~",
|
|
2901
|
+
"H",
|
|
2902
|
+
"J",
|
|
2903
|
+
"L",
|
|
2904
|
+
"M",
|
|
2905
|
+
"O",
|
|
2906
|
+
"P",
|
|
2907
|
+
"R",
|
|
2908
|
+
"S",
|
|
2909
|
+
"T",
|
|
2910
|
+
"u",
|
|
2911
|
+
"v"
|
|
2912
|
+
]);
|
|
2913
|
+
var BY_SPELLING = /* @__PURE__ */ new Map();
|
|
2914
|
+
for (const def of DECORATIONS) {
|
|
2915
|
+
for (const spelling of def.spellings) {
|
|
2916
|
+
BY_SPELLING.set(spelling, def);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
function targetKey(target) {
|
|
2920
|
+
switch (target.kind) {
|
|
2921
|
+
case "articulation":
|
|
2922
|
+
return `articulation:${target.articulation}`;
|
|
2923
|
+
case "ornament":
|
|
2924
|
+
return `ornament:${target.ornament}`;
|
|
2925
|
+
case "technical":
|
|
2926
|
+
return `technical:${target.technical}`;
|
|
2927
|
+
case "fingering":
|
|
2928
|
+
return `fingering:${target.fingering}`;
|
|
2929
|
+
case "fermata":
|
|
2930
|
+
return `fermata:${target.inverted ? "inverted" : "upright"}`;
|
|
2931
|
+
case "arpeggiate":
|
|
2932
|
+
return "arpeggiate";
|
|
2933
|
+
case "trill-line":
|
|
2934
|
+
return `trill-line:${target.wavyLineType}`;
|
|
2935
|
+
case "dynamics":
|
|
2936
|
+
return `dynamics:${target.value}`;
|
|
2937
|
+
case "wedge":
|
|
2938
|
+
return `wedge:${target.wedge}:${target.stop ? "stop" : "start"}`;
|
|
2939
|
+
case "segno":
|
|
2940
|
+
return "segno";
|
|
2941
|
+
case "coda":
|
|
2942
|
+
return "coda";
|
|
2943
|
+
case "words":
|
|
2944
|
+
return `words:${target.text}`;
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
var CANONICAL_BY_TARGET = /* @__PURE__ */ new Map();
|
|
2948
|
+
for (const def of DECORATIONS) {
|
|
2949
|
+
CANONICAL_BY_TARGET.set(targetKey(def.target), def.canonical);
|
|
2950
|
+
}
|
|
2951
|
+
function lookupAbcDecoration(spelling) {
|
|
2952
|
+
if (ABC_DYNAMICS.has(spelling)) {
|
|
2953
|
+
return { kind: "dynamics", value: spelling };
|
|
2954
|
+
}
|
|
2955
|
+
const def = BY_SPELLING.get(spelling);
|
|
2956
|
+
return def ? def.target : null;
|
|
2957
|
+
}
|
|
2958
|
+
function abcDecorationFor(target) {
|
|
2959
|
+
if (target.kind === "dynamics") {
|
|
2960
|
+
return ABC_DYNAMICS.has(target.value) ? `!${target.value}!` : null;
|
|
2961
|
+
}
|
|
2962
|
+
return CANONICAL_BY_TARGET.get(targetKey(target)) ?? null;
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2763
2965
|
// src/importers/abc.ts
|
|
2764
2966
|
var DIVISIONS = 960;
|
|
2765
2967
|
var NOTE_TYPE_MAP = {
|
|
@@ -2774,6 +2976,7 @@ var NOTE_TYPE_MAP = {
|
|
|
2774
2976
|
0.125: "32nd",
|
|
2775
2977
|
0.0625: "64th"
|
|
2776
2978
|
};
|
|
2979
|
+
var NOTE_TYPE_ENTRIES = Object.entries(NOTE_TYPE_MAP).map(([quarterNotes, type]) => [parseFloat(quarterNotes), type]);
|
|
2777
2980
|
var KEY_FIFTHS = {
|
|
2778
2981
|
"Cb": -7,
|
|
2779
2982
|
"Gb": -6,
|
|
@@ -2835,38 +3038,38 @@ var MODE_NAME = {
|
|
|
2835
3038
|
"locrian": "locrian",
|
|
2836
3039
|
"loc": "locrian"
|
|
2837
3040
|
};
|
|
2838
|
-
var DYNAMICS_VALUES =
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
"
|
|
2842
|
-
"
|
|
2843
|
-
"
|
|
2844
|
-
"
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
]);
|
|
3041
|
+
var DYNAMICS_VALUES = ABC_DYNAMICS;
|
|
3042
|
+
var ENDING_SPEC = /^(\d+(?:[,-]\d+)*)/;
|
|
3043
|
+
var MICROTONE_ACCIDENTALS = {
|
|
3044
|
+
"0.5": "quarter-sharp",
|
|
3045
|
+
"-0.5": "quarter-flat",
|
|
3046
|
+
"1.5": "three-quarters-sharp",
|
|
3047
|
+
"-1.5": "three-quarters-flat"
|
|
3048
|
+
};
|
|
3049
|
+
function appendToHeaderField(header, field, text) {
|
|
3050
|
+
switch (field) {
|
|
3051
|
+
case "T":
|
|
3052
|
+
header.title = (header.title ?? "") + text;
|
|
3053
|
+
break;
|
|
3054
|
+
case "C":
|
|
3055
|
+
header.composer = (header.composer ?? "") + text;
|
|
3056
|
+
break;
|
|
3057
|
+
default: {
|
|
3058
|
+
const extra = header.extraFields;
|
|
3059
|
+
if (extra && extra.length > 0 && extra[extra.length - 1].field === field) {
|
|
3060
|
+
extra[extra.length - 1].value += text;
|
|
3061
|
+
}
|
|
3062
|
+
break;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2864
3066
|
function parseHeader(lines) {
|
|
2865
3067
|
const header = { voices: [], extraFields: [], directives: [] };
|
|
2866
3068
|
let bodyStartIndex = 0;
|
|
2867
3069
|
let foundKey = false;
|
|
2868
3070
|
let postKHeaderDone = false;
|
|
2869
3071
|
const headerFieldOrder = [];
|
|
3072
|
+
let lastField = null;
|
|
2870
3073
|
for (let i = 0; i < lines.length; i++) {
|
|
2871
3074
|
const line = lines[i].trim();
|
|
2872
3075
|
if (line === "") continue;
|
|
@@ -2890,10 +3093,18 @@ function parseHeader(lines) {
|
|
|
2890
3093
|
}
|
|
2891
3094
|
continue;
|
|
2892
3095
|
}
|
|
2893
|
-
const
|
|
3096
|
+
const continuationMatch = line.match(/^\+:(.*)$/);
|
|
3097
|
+
if (continuationMatch && !foundKey && lastField) {
|
|
3098
|
+
appendToHeaderField(header, lastField, continuationMatch[1]);
|
|
3099
|
+
headerFieldOrder.push(line);
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
const fieldMatch = line.match(/^([A-Za-z]):(.*)/);
|
|
2894
3103
|
const postKFields = /* @__PURE__ */ new Set(["I", "N"]);
|
|
2895
3104
|
if (fieldMatch && (!foundKey || fieldMatch[1] === "V" || foundKey && postKFields.has(fieldMatch[1]))) {
|
|
2896
|
-
const [, field,
|
|
3105
|
+
const [, field, rawFieldValue] = fieldMatch;
|
|
3106
|
+
const value = rawFieldValue.trimStart();
|
|
3107
|
+
lastField = field;
|
|
2897
3108
|
if (foundKey && field !== "V") {
|
|
2898
3109
|
if (!postKHeaderDone) {
|
|
2899
3110
|
header.extraFields.push({ field, value: value.trim() });
|
|
@@ -2929,6 +3140,7 @@ function parseHeader(lines) {
|
|
|
2929
3140
|
break;
|
|
2930
3141
|
case "K":
|
|
2931
3142
|
header.key = value.trim();
|
|
3143
|
+
header.clef = line;
|
|
2932
3144
|
foundKey = true;
|
|
2933
3145
|
bodyStartIndex = i + 1;
|
|
2934
3146
|
headerFieldOrder.push(line);
|
|
@@ -2939,6 +3151,7 @@ function parseHeader(lines) {
|
|
|
2939
3151
|
const nameMatch = voiceValue.match(/name=["']?([^"'\s]+)["']?/i);
|
|
2940
3152
|
const nmMatch = voiceValue.match(/nm=["']([^"']*)["']/i);
|
|
2941
3153
|
const clefMatch = voiceValue.match(/clef=(\S+)/i);
|
|
3154
|
+
const octaveMatch = voiceValue.match(/octave\s*=\s*(-?\d+)/i);
|
|
2942
3155
|
const displayName = nameMatch ? nameMatch[1] : nmMatch ? nmMatch[1] : voiceId;
|
|
2943
3156
|
const hasParams = clefMatch || nameMatch || nmMatch || /\b(Program|merge|up|down|bass|treble|alto|tenor|soprano|octave|snm|stem)\b/i.test(voiceValue);
|
|
2944
3157
|
const isBodyVoiceSwitch = foundKey && !hasParams;
|
|
@@ -2949,12 +3162,14 @@ function parseHeader(lines) {
|
|
|
2949
3162
|
if (existingVoice) {
|
|
2950
3163
|
if (nameMatch || nmMatch) existingVoice.name = displayName;
|
|
2951
3164
|
if (clefMatch) existingVoice.clef = clefMatch[1];
|
|
3165
|
+
if (octaveMatch) existingVoice.octave = parseInt(octaveMatch[1], 10);
|
|
2952
3166
|
if (foundKey && !isBodyVoiceSwitch) existingVoice.fullLine = lines[i];
|
|
2953
3167
|
} else {
|
|
2954
3168
|
header.voices.push({
|
|
2955
3169
|
id: voiceId,
|
|
2956
3170
|
name: displayName,
|
|
2957
3171
|
clef: clefMatch ? clefMatch[1] : void 0,
|
|
3172
|
+
octave: octaveMatch ? parseInt(octaveMatch[1], 10) : void 0,
|
|
2958
3173
|
fullLine: lines[i]
|
|
2959
3174
|
});
|
|
2960
3175
|
}
|
|
@@ -3020,12 +3235,29 @@ function parseTimeSignature2(meterStr) {
|
|
|
3020
3235
|
if (trimmed === "C|" || trimmed.toLowerCase() === "cut") {
|
|
3021
3236
|
return { beats: "2", beatType: 2, symbol: "cut" };
|
|
3022
3237
|
}
|
|
3238
|
+
if (trimmed.toLowerCase() === "none") {
|
|
3239
|
+
return { beats: "4", beatType: 4, senzaMisura: true };
|
|
3240
|
+
}
|
|
3023
3241
|
const match = trimmed.match(/^(\d+)\/(\d+)$/);
|
|
3024
3242
|
if (match) {
|
|
3025
3243
|
return { beats: match[1], beatType: parseInt(match[2], 10) };
|
|
3026
3244
|
}
|
|
3245
|
+
const additive = trimmed.match(/^\(?\s*(\d+(?:\s*\+\s*\d+)+)\s*\)?\/(\d+)$/);
|
|
3246
|
+
if (additive) {
|
|
3247
|
+
const groups = additive[1].split("+").map((g) => parseInt(g.trim(), 10));
|
|
3248
|
+
return {
|
|
3249
|
+
beats: groups.join("+"),
|
|
3250
|
+
beatType: parseInt(additive[2], 10),
|
|
3251
|
+
beatsList: groups
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3027
3254
|
return { beats: "4", beatType: 4 };
|
|
3028
3255
|
}
|
|
3256
|
+
function timeSignatureWholeNotes(time) {
|
|
3257
|
+
const beats = time.beatsList ? time.beatsList.reduce((sum, b) => sum + b, 0) : parseInt(time.beats, 10);
|
|
3258
|
+
if (!Number.isFinite(beats) || time.beatType === 0) return 1;
|
|
3259
|
+
return beats / time.beatType;
|
|
3260
|
+
}
|
|
3029
3261
|
function parseUnitNoteLength(lengthStr, meterStr) {
|
|
3030
3262
|
if (lengthStr) {
|
|
3031
3263
|
const match = lengthStr.trim().match(/^(\d+)\/(\d+)$/);
|
|
@@ -3033,12 +3265,9 @@ function parseUnitNoteLength(lengthStr, meterStr) {
|
|
|
3033
3265
|
return { num: parseInt(match[1], 10), den: parseInt(match[2], 10) };
|
|
3034
3266
|
}
|
|
3035
3267
|
}
|
|
3036
|
-
if (meterStr) {
|
|
3037
|
-
const
|
|
3038
|
-
|
|
3039
|
-
const ratio = parseInt(mMatch[1], 10) / parseInt(mMatch[2], 10);
|
|
3040
|
-
return ratio >= 0.75 ? { num: 1, den: 8 } : { num: 1, den: 16 };
|
|
3041
|
-
}
|
|
3268
|
+
if (meterStr && meterStr.trim() !== "") {
|
|
3269
|
+
const ratio = timeSignatureWholeNotes(parseTimeSignature2(meterStr));
|
|
3270
|
+
return ratio >= 0.75 ? { num: 1, den: 8 } : { num: 1, den: 16 };
|
|
3042
3271
|
}
|
|
3043
3272
|
return { num: 1, den: 8 };
|
|
3044
3273
|
}
|
|
@@ -3048,8 +3277,7 @@ function lengthToDuration(num, den, unitNote) {
|
|
|
3048
3277
|
}
|
|
3049
3278
|
function durationToNoteType(duration) {
|
|
3050
3279
|
const quarterNotes = duration / DIVISIONS;
|
|
3051
|
-
for (const [
|
|
3052
|
-
const qn = parseFloat(qnStr);
|
|
3280
|
+
for (const [qn, type] of NOTE_TYPE_ENTRIES) {
|
|
3053
3281
|
if (Math.abs(quarterNotes - qn) < 1e-3) {
|
|
3054
3282
|
return { noteType: type, dots: 0 };
|
|
3055
3283
|
}
|
|
@@ -3062,8 +3290,8 @@ function durationToNoteType(duration) {
|
|
|
3062
3290
|
}
|
|
3063
3291
|
let bestType = "quarter";
|
|
3064
3292
|
let bestDiff = Infinity;
|
|
3065
|
-
for (const [
|
|
3066
|
-
const diff = Math.abs(quarterNotes -
|
|
3293
|
+
for (const [qn, type] of NOTE_TYPE_ENTRIES) {
|
|
3294
|
+
const diff = Math.abs(quarterNotes - qn);
|
|
3067
3295
|
if (diff < bestDiff) {
|
|
3068
3296
|
bestDiff = diff;
|
|
3069
3297
|
bestType = type;
|
|
@@ -3165,18 +3393,18 @@ function tokenizeBody(bodyLines) {
|
|
|
3165
3393
|
isContinuation = false;
|
|
3166
3394
|
continue;
|
|
3167
3395
|
}
|
|
3168
|
-
const lyricsMatch = line.match(/^w
|
|
3396
|
+
const lyricsMatch = line.match(/^w:(.*)/);
|
|
3169
3397
|
if (lyricsMatch) {
|
|
3170
3398
|
const syllables = parseLyricLine(lyricsMatch[1]);
|
|
3171
3399
|
voiceTokens.get(currentVoice).push({
|
|
3172
3400
|
type: "lyrics",
|
|
3173
|
-
value: lyricsMatch[1],
|
|
3401
|
+
value: lyricsMatch[1].trimStart(),
|
|
3174
3402
|
syllables
|
|
3175
3403
|
});
|
|
3176
3404
|
isContinuation = false;
|
|
3177
3405
|
continue;
|
|
3178
3406
|
}
|
|
3179
|
-
const bodyKeyMatch = line.match(/^K
|
|
3407
|
+
const bodyKeyMatch = line.match(/^K:(.*)/);
|
|
3180
3408
|
if (bodyKeyMatch) {
|
|
3181
3409
|
const currentTokens2 = voiceTokens.get(currentVoice);
|
|
3182
3410
|
if (currentTokens2.length > 0) {
|
|
@@ -3185,12 +3413,17 @@ function tokenizeBody(bodyLines) {
|
|
|
3185
3413
|
currentTokens2.push({ type: "line_break", value: "\n" });
|
|
3186
3414
|
}
|
|
3187
3415
|
}
|
|
3188
|
-
currentTokens2.push({ type: "inline_field", value: `K:${bodyKeyMatch[1]}
|
|
3416
|
+
currentTokens2.push({ type: "inline_field", value: `K:${bodyKeyMatch[1]}`, standalone: true });
|
|
3189
3417
|
currentTokens2.push({ type: "line_break", value: "\n" });
|
|
3190
3418
|
isContinuation = false;
|
|
3191
3419
|
continue;
|
|
3192
3420
|
}
|
|
3193
|
-
|
|
3421
|
+
const bodyFieldMatch = /^[A-Za-z]:/.test(line);
|
|
3422
|
+
if (bodyFieldMatch && !/^\[/.test(line)) {
|
|
3423
|
+
voiceTokens.get(currentVoice).push({
|
|
3424
|
+
type: "inline_field",
|
|
3425
|
+
value: `${ABC_BODY_FIELD_MARKER}${line}`
|
|
3426
|
+
});
|
|
3194
3427
|
isContinuation = false;
|
|
3195
3428
|
continue;
|
|
3196
3429
|
}
|
|
@@ -3213,7 +3446,7 @@ function tokenizeBody(bodyLines) {
|
|
|
3213
3446
|
}
|
|
3214
3447
|
for (const token of tokens) {
|
|
3215
3448
|
if (token.type === "inline_field") {
|
|
3216
|
-
const fieldMatch = token.value.match(/^V
|
|
3449
|
+
const fieldMatch = token.value.match(/^V:(.+)/);
|
|
3217
3450
|
if (fieldMatch) {
|
|
3218
3451
|
const voiceId = fieldMatch[1].trim().split(/\s+/)[0];
|
|
3219
3452
|
currentVoice = voiceId;
|
|
@@ -3287,13 +3520,22 @@ function tokenizeMusicLine(line) {
|
|
|
3287
3520
|
if (ch === '"') {
|
|
3288
3521
|
const end = line.indexOf('"', i + 1);
|
|
3289
3522
|
if (end !== -1) {
|
|
3290
|
-
|
|
3523
|
+
const text = line.slice(i + 1, end);
|
|
3524
|
+
if (/^[\^_<>@]/.test(text)) {
|
|
3525
|
+
tokens.push({
|
|
3526
|
+
type: "annotation",
|
|
3527
|
+
value: text.slice(1),
|
|
3528
|
+
annotationPlacement: text[0]
|
|
3529
|
+
});
|
|
3530
|
+
} else {
|
|
3531
|
+
tokens.push({ type: "chord_symbol", value: text });
|
|
3532
|
+
}
|
|
3291
3533
|
i = end + 1;
|
|
3292
3534
|
continue;
|
|
3293
3535
|
}
|
|
3294
3536
|
}
|
|
3295
|
-
if (ch === "!") {
|
|
3296
|
-
const end = line.indexOf(
|
|
3537
|
+
if (ch === "!" || ch === "+") {
|
|
3538
|
+
const end = line.indexOf(ch, i + 1);
|
|
3297
3539
|
if (end !== -1) {
|
|
3298
3540
|
tokens.push({ type: "decoration", value: line.slice(i + 1, end) });
|
|
3299
3541
|
i = end + 1;
|
|
@@ -3301,8 +3543,9 @@ function tokenizeMusicLine(line) {
|
|
|
3301
3543
|
}
|
|
3302
3544
|
}
|
|
3303
3545
|
if (ch === "{") {
|
|
3304
|
-
|
|
3305
|
-
|
|
3546
|
+
const isSlash = i + 1 < line.length && line[i + 1] === "/";
|
|
3547
|
+
tokens.push({ type: "grace_start", value: isSlash ? "{/" : "{", slashGrace: isSlash });
|
|
3548
|
+
i += isSlash ? 2 : 1;
|
|
3306
3549
|
continue;
|
|
3307
3550
|
}
|
|
3308
3551
|
if (ch === "}") {
|
|
@@ -3310,6 +3553,22 @@ function tokenizeMusicLine(line) {
|
|
|
3310
3553
|
i++;
|
|
3311
3554
|
continue;
|
|
3312
3555
|
}
|
|
3556
|
+
if (ch === ".") {
|
|
3557
|
+
const next = i + 1 < line.length ? line[i + 1] : "";
|
|
3558
|
+
if (next === "(") {
|
|
3559
|
+
tokens.push({ type: "slur_start", value: ".(", dotted: true });
|
|
3560
|
+
i += 2;
|
|
3561
|
+
continue;
|
|
3562
|
+
}
|
|
3563
|
+
if (next === "|") {
|
|
3564
|
+
const dottedBar = parseBarLine(line, i + 1);
|
|
3565
|
+
if (dottedBar) {
|
|
3566
|
+
tokens.push({ type: "bar", value: "." + dottedBar.token.value, barType: "dotted" });
|
|
3567
|
+
i = dottedBar.nextIndex;
|
|
3568
|
+
continue;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3313
3572
|
if (ch === "(" && i + 1 < line.length && /\d/.test(line[i + 1])) {
|
|
3314
3573
|
const tupletResult = parseTuplet(line, i);
|
|
3315
3574
|
if (tupletResult) {
|
|
@@ -3339,7 +3598,7 @@ function tokenizeMusicLine(line) {
|
|
|
3339
3598
|
tokens.push(barResult.token);
|
|
3340
3599
|
i = barResult.nextIndex;
|
|
3341
3600
|
if (i < line.length && /\d/.test(line[i])) {
|
|
3342
|
-
const numMatch = line.slice(i).match(
|
|
3601
|
+
const numMatch = line.slice(i).match(ENDING_SPEC);
|
|
3343
3602
|
if (numMatch) {
|
|
3344
3603
|
tokens.push({ type: "ending", value: numMatch[1] });
|
|
3345
3604
|
i += numMatch[1].length;
|
|
@@ -3350,7 +3609,7 @@ function tokenizeMusicLine(line) {
|
|
|
3350
3609
|
}
|
|
3351
3610
|
}
|
|
3352
3611
|
if (ch === "[" && i + 1 < line.length && /\d/.test(line[i + 1])) {
|
|
3353
|
-
const numMatch = line.slice(i + 1).match(
|
|
3612
|
+
const numMatch = line.slice(i + 1).match(ENDING_SPEC);
|
|
3354
3613
|
if (numMatch) {
|
|
3355
3614
|
tokens.push({ type: "ending", value: numMatch[1], bracket: true });
|
|
3356
3615
|
i += 1 + numMatch[1].length;
|
|
@@ -3397,14 +3656,22 @@ function tokenizeMusicLine(line) {
|
|
|
3397
3656
|
tokens.push({ type: "chord_end", value: "]", durationNum: dur.num, durationDen: dur.den });
|
|
3398
3657
|
continue;
|
|
3399
3658
|
}
|
|
3400
|
-
if ((ch
|
|
3659
|
+
if (ABC_SHORTHAND_DECORATIONS.has(ch) && i + 1 < line.length) {
|
|
3401
3660
|
const nextCh = line[i + 1];
|
|
3402
|
-
if (isNoteStart(nextCh) || nextCh === "[" || nextCh === "
|
|
3661
|
+
if (isNoteStart(nextCh) || nextCh === "[" || nextCh === "{" || nextCh === "z" || nextCh === "Z" || nextCh === "x" || nextCh === "X" || nextCh === "y" || ABC_SHORTHAND_DECORATIONS.has(nextCh) || nextCh === "(" || nextCh === "!" || nextCh === "+" || nextCh === '"') {
|
|
3403
3662
|
tokens.push({ type: "decoration", value: ch });
|
|
3404
3663
|
i++;
|
|
3405
3664
|
continue;
|
|
3406
3665
|
}
|
|
3407
3666
|
}
|
|
3667
|
+
if (ch === "y") {
|
|
3668
|
+
const start = i;
|
|
3669
|
+
i++;
|
|
3670
|
+
const dur = parseDuration(line, i);
|
|
3671
|
+
i = dur.nextIndex;
|
|
3672
|
+
tokens.push({ type: "spacer", value: line.slice(start, i) });
|
|
3673
|
+
continue;
|
|
3674
|
+
}
|
|
3408
3675
|
if (isNoteStart(ch) || ch === "z" || ch === "Z" || ch === "x" || ch === "X") {
|
|
3409
3676
|
const noteResult = parseNoteToken(line, i);
|
|
3410
3677
|
if (noteResult) {
|
|
@@ -3450,11 +3717,13 @@ function parseTuplet(line, i) {
|
|
|
3450
3717
|
}
|
|
3451
3718
|
function parseBarLine(line, i) {
|
|
3452
3719
|
const patterns = [
|
|
3720
|
+
["[|]", "invisible"],
|
|
3453
3721
|
[":|]", "end-repeat-final"],
|
|
3454
3722
|
[":||:", "double-repeat"],
|
|
3455
3723
|
["::", "double-repeat"],
|
|
3456
3724
|
[":|:", "double-repeat"],
|
|
3457
3725
|
["|>|", "thick-thin"],
|
|
3726
|
+
["[|:", "start-repeat"],
|
|
3458
3727
|
["|:", "start-repeat"],
|
|
3459
3728
|
[":|", "end-repeat"],
|
|
3460
3729
|
["||", "double"],
|
|
@@ -3481,15 +3750,18 @@ function parseBarLine(line, i) {
|
|
|
3481
3750
|
function parseNoteToken(line, i) {
|
|
3482
3751
|
const start = i;
|
|
3483
3752
|
if (line[i] === "z" || line[i] === "Z" || line[i] === "x" || line[i] === "X") {
|
|
3753
|
+
const restLetter = line[i];
|
|
3484
3754
|
i++;
|
|
3485
3755
|
const dur2 = parseDuration(line, i);
|
|
3486
3756
|
i = dur2.nextIndex;
|
|
3757
|
+
const isMultiMeasure = restLetter === "Z";
|
|
3487
3758
|
return {
|
|
3488
3759
|
token: {
|
|
3489
3760
|
type: "rest",
|
|
3490
3761
|
value: line.slice(start, i),
|
|
3491
|
-
durationNum: dur2.num,
|
|
3492
|
-
durationDen: dur2.den
|
|
3762
|
+
durationNum: isMultiMeasure ? 1 : dur2.num,
|
|
3763
|
+
durationDen: isMultiMeasure ? 1 : dur2.den,
|
|
3764
|
+
restMeasures: isMultiMeasure ? Math.max(1, dur2.num) : void 0
|
|
3493
3765
|
},
|
|
3494
3766
|
nextIndex: i
|
|
3495
3767
|
};
|
|
@@ -3503,6 +3775,9 @@ function parseNoteToken(line, i) {
|
|
|
3503
3775
|
accidental = 2;
|
|
3504
3776
|
i++;
|
|
3505
3777
|
}
|
|
3778
|
+
const micro = parseMicrotoneFactor(line, i);
|
|
3779
|
+
accidental *= micro.factor;
|
|
3780
|
+
i = micro.nextIndex;
|
|
3506
3781
|
} else if (line[i] === "_") {
|
|
3507
3782
|
accidental = -1;
|
|
3508
3783
|
i++;
|
|
@@ -3510,6 +3785,9 @@ function parseNoteToken(line, i) {
|
|
|
3510
3785
|
accidental = -2;
|
|
3511
3786
|
i++;
|
|
3512
3787
|
}
|
|
3788
|
+
const micro = parseMicrotoneFactor(line, i);
|
|
3789
|
+
accidental *= micro.factor;
|
|
3790
|
+
i = micro.nextIndex;
|
|
3513
3791
|
} else if (line[i] === "=") {
|
|
3514
3792
|
accidental = 0;
|
|
3515
3793
|
explicitNatural = true;
|
|
@@ -3547,6 +3825,14 @@ function parseNoteToken(line, i) {
|
|
|
3547
3825
|
nextIndex: i
|
|
3548
3826
|
};
|
|
3549
3827
|
}
|
|
3828
|
+
function parseMicrotoneFactor(line, i) {
|
|
3829
|
+
const match = line.slice(i).match(/^(\d{0,3})\/(\d{0,3})/);
|
|
3830
|
+
if (!match) return { factor: 1, nextIndex: i };
|
|
3831
|
+
const num = match[1] === "" ? 1 : parseInt(match[1], 10);
|
|
3832
|
+
const den = match[2] === "" ? 2 : parseInt(match[2], 10);
|
|
3833
|
+
if (den === 0) return { factor: 1, nextIndex: i };
|
|
3834
|
+
return { factor: num / den, nextIndex: i + match[0].length };
|
|
3835
|
+
}
|
|
3550
3836
|
function abcNoteToPitch(letter, accidental) {
|
|
3551
3837
|
const isLower = letter === letter.toLowerCase();
|
|
3552
3838
|
const step = letter.toUpperCase();
|
|
@@ -3585,9 +3871,8 @@ function buildScore(header, voiceTokensList, voiceIds, headerFieldOrder, inlineV
|
|
|
3585
3871
|
const unitNote = parseUnitNoteLength(header.unitNoteLength, header.meter);
|
|
3586
3872
|
const timeSignature = parseTimeSignature2(header.meter || "4/4");
|
|
3587
3873
|
const keySignature = parseKeySignature2(header.key || "C");
|
|
3588
|
-
const
|
|
3589
|
-
const
|
|
3590
|
-
const measureDuration = Math.round(beatsNum / beatType * 4 * DIVISIONS);
|
|
3874
|
+
const headerClef = header.clef ? parseAbcClefSpec(header.clef) : void 0;
|
|
3875
|
+
const measureDuration = Math.round(timeSignatureWholeNotes(timeSignature) * 4 * DIVISIONS);
|
|
3591
3876
|
const parts = [];
|
|
3592
3877
|
const partListEntries = [];
|
|
3593
3878
|
const miscellaneous = [];
|
|
@@ -3688,8 +3973,12 @@ function buildScore(header, voiceTokensList, voiceIds, headerFieldOrder, inlineV
|
|
|
3688
3973
|
id: partId,
|
|
3689
3974
|
name: voiceName
|
|
3690
3975
|
});
|
|
3691
|
-
const voiceClef = headerVoice ? abcClefToMusicXml(headerVoice.clef) :
|
|
3692
|
-
const
|
|
3976
|
+
const voiceClef = headerVoice?.clef ? abcClefToMusicXml(headerVoice.clef) : headerClef;
|
|
3977
|
+
const octaveShift = headerVoice?.octave ?? 0;
|
|
3978
|
+
const buildResult = buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDuration, voiceClef, octaveShift);
|
|
3979
|
+
if (octaveShift !== 0) {
|
|
3980
|
+
miscellaneous.push({ name: `abc-voice-octave-${voiceIndex}`, value: String(octaveShift) });
|
|
3981
|
+
}
|
|
3693
3982
|
parts.push({
|
|
3694
3983
|
_id: generateId(),
|
|
3695
3984
|
id: partId,
|
|
@@ -3709,15 +3998,23 @@ function buildScore(header, voiceTokensList, voiceIds, headerFieldOrder, inlineV
|
|
|
3709
3998
|
let lyricsAfterAll = true;
|
|
3710
3999
|
let seenLyrics = false;
|
|
3711
4000
|
const lyricLineCounts = [];
|
|
3712
|
-
|
|
4001
|
+
const lyricLineVerses = [];
|
|
4002
|
+
let verse = 0;
|
|
4003
|
+
for (let ti = 0; ti < tokens.length; ti++) {
|
|
4004
|
+
const token = tokens[ti];
|
|
3713
4005
|
if (token.type === "lyrics") {
|
|
3714
4006
|
hasLyrics2 = true;
|
|
3715
4007
|
seenLyrics = true;
|
|
4008
|
+
verse = ti > 0 && tokens[ti - 1].type === "lyrics" ? verse + 1 : 1;
|
|
3716
4009
|
lyricLineCounts.push(token.syllables?.length || 0);
|
|
4010
|
+
lyricLineVerses.push(verse);
|
|
3717
4011
|
} else if (seenLyrics && (token.type === "note" || token.type === "rest" || token.type === "bar")) {
|
|
3718
4012
|
lyricsAfterAll = false;
|
|
3719
4013
|
}
|
|
3720
4014
|
}
|
|
4015
|
+
if (hasLyrics2 && lyricLineVerses.some((v) => v > 1)) {
|
|
4016
|
+
miscellaneous.push({ name: "abc-lyrics-line-verses", value: JSON.stringify(lyricLineVerses) });
|
|
4017
|
+
}
|
|
3721
4018
|
if (hasLyrics2 && lyricsAfterAll) {
|
|
3722
4019
|
miscellaneous.push({ name: "abc-lyrics-after-all", value: "true" });
|
|
3723
4020
|
}
|
|
@@ -3758,13 +4055,14 @@ function buildScore(header, voiceTokensList, voiceIds, headerFieldOrder, inlineV
|
|
|
3758
4055
|
};
|
|
3759
4056
|
}
|
|
3760
4057
|
function parseTempoToDirection(tempoStr) {
|
|
3761
|
-
const
|
|
3762
|
-
|
|
3763
|
-
|
|
4058
|
+
const withUnit = tempoStr.match(/(\d{1,4})\/(\d{1,4})\s*=\s*(\d{1,6})/);
|
|
4059
|
+
const rateOnly = withUnit ? null : tempoStr.match(/\d{1,6}/);
|
|
4060
|
+
if (!withUnit && !rateOnly) return null;
|
|
4061
|
+
const perMinute = parseInt(withUnit ? withUnit[3] : rateOnly[0], 10);
|
|
3764
4062
|
let beatUnit = "quarter";
|
|
3765
|
-
if (
|
|
3766
|
-
const num = parseInt(
|
|
3767
|
-
const den = parseInt(
|
|
4063
|
+
if (withUnit) {
|
|
4064
|
+
const num = parseInt(withUnit[1], 10);
|
|
4065
|
+
const den = parseInt(withUnit[2], 10);
|
|
3768
4066
|
const quarterNotes = num / den * 4;
|
|
3769
4067
|
const found = NOTE_TYPE_MAP[quarterNotes];
|
|
3770
4068
|
if (found) beatUnit = found;
|
|
@@ -3777,20 +4075,44 @@ function parseTempoToDirection(tempoStr) {
|
|
|
3777
4075
|
sound: { tempo: perMinute }
|
|
3778
4076
|
};
|
|
3779
4077
|
}
|
|
4078
|
+
var ABC_CLEF_NAMES = {
|
|
4079
|
+
"treble": { sign: "G", line: 2 },
|
|
4080
|
+
"bass": { sign: "F", line: 4 },
|
|
4081
|
+
"bass3": { sign: "F", line: 3 },
|
|
4082
|
+
"alto": { sign: "C", line: 3 },
|
|
4083
|
+
"tenor": { sign: "C", line: 4 },
|
|
4084
|
+
"soprano": { sign: "C", line: 1 },
|
|
4085
|
+
"mezzo": { sign: "C", line: 2 },
|
|
4086
|
+
"mezzo-soprano": { sign: "C", line: 2 },
|
|
4087
|
+
"baritone": { sign: "C", line: 5 },
|
|
4088
|
+
"perc": { sign: "percussion" },
|
|
4089
|
+
"percussion": { sign: "percussion" },
|
|
4090
|
+
"none": { sign: "G", line: 2 }
|
|
4091
|
+
};
|
|
3780
4092
|
function abcClefToMusicXml(abcClef) {
|
|
3781
4093
|
if (!abcClef) return { sign: "G", line: 2 };
|
|
3782
|
-
const
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
if (
|
|
3786
|
-
|
|
3787
|
-
if (
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
return
|
|
3792
|
-
}
|
|
3793
|
-
function
|
|
4094
|
+
const match = abcClef.trim().toLowerCase().match(/^([a-z-]*?)\s*([+-])?(8|15)(?:va|vb)?$/);
|
|
4095
|
+
const baseName = match ? match[1] : abcClef.trim().toLowerCase();
|
|
4096
|
+
const base = ABC_CLEF_NAMES[baseName] ?? ABC_CLEF_NAMES[abcClef.trim().toLowerCase()];
|
|
4097
|
+
if (!base) return { sign: "G", line: 2 };
|
|
4098
|
+
const clef = { ...base };
|
|
4099
|
+
if (match && match[3]) {
|
|
4100
|
+
const octaves = match[3] === "15" ? 2 : 1;
|
|
4101
|
+
clef.clefOctaveChange = match[2] === "+" ? octaves : -octaves;
|
|
4102
|
+
}
|
|
4103
|
+
return clef;
|
|
4104
|
+
}
|
|
4105
|
+
function parseAbcClefSpec(fieldValue) {
|
|
4106
|
+
const explicit = fieldValue.match(/clef\s*=\s*(\S+)/i);
|
|
4107
|
+
if (explicit) return abcClefToMusicXml(explicit[1]);
|
|
4108
|
+
for (const word of fieldValue.trim().split(/\s+/).slice(1)) {
|
|
4109
|
+
if (word.includes("=")) continue;
|
|
4110
|
+
const bare = word.toLowerCase().replace(/([+-]?(?:8|15)(?:va|vb)?)$/, "");
|
|
4111
|
+
if (ABC_CLEF_NAMES[bare] && bare !== "none") return abcClefToMusicXml(word);
|
|
4112
|
+
}
|
|
4113
|
+
return void 0;
|
|
4114
|
+
}
|
|
4115
|
+
function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDuration, clef, octaveShift = 0) {
|
|
3794
4116
|
const measures = [];
|
|
3795
4117
|
let currentEntries = [];
|
|
3796
4118
|
let hasIndividualChordDurations = false;
|
|
@@ -3801,40 +4123,40 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3801
4123
|
let pendingTie = false;
|
|
3802
4124
|
let slurDepth = 0;
|
|
3803
4125
|
let pendingSlurStarts = 0;
|
|
4126
|
+
const pendingSlurDotted = [];
|
|
3804
4127
|
let slurStartNotes = [];
|
|
3805
4128
|
let inGrace = false;
|
|
3806
4129
|
let pendingBrokenRhythm = null;
|
|
3807
4130
|
let tupletState = null;
|
|
3808
4131
|
const pendingPreNoteItems = [];
|
|
4132
|
+
let pendingNotations = [];
|
|
4133
|
+
let graceSlash = false;
|
|
3809
4134
|
let pendingEndingNumber = null;
|
|
3810
4135
|
let currentLyrics = [];
|
|
3811
4136
|
let noteCountForLyrics = 0;
|
|
4137
|
+
let lyricVerse = 0;
|
|
4138
|
+
let lyricTargets = [];
|
|
3812
4139
|
let inChord = false;
|
|
3813
4140
|
let chordNotes = [];
|
|
3814
4141
|
let chordNoteTies = [];
|
|
3815
4142
|
let currentUnitNote = { ...unitNote };
|
|
3816
4143
|
let pendingTupletStart = false;
|
|
3817
4144
|
let pendingKeyChange = null;
|
|
4145
|
+
let pendingTimeChange = null;
|
|
3818
4146
|
const lineBreaks = [];
|
|
4147
|
+
let pendingMultipleRest = null;
|
|
3819
4148
|
function flushPendingPreNoteItems() {
|
|
3820
4149
|
for (const item of pendingPreNoteItems) {
|
|
3821
|
-
|
|
3822
|
-
const harmony = createHarmonyEntry(item.value);
|
|
3823
|
-
if (harmony) currentEntries.push(harmony);
|
|
3824
|
-
} else if (item.kind === "dynamic") {
|
|
3825
|
-
const dynDir = createDynamicsDirection(item.value);
|
|
3826
|
-
if (dynDir) currentEntries.push(dynDir);
|
|
3827
|
-
} else if (item.kind === "decoration") {
|
|
3828
|
-
const decoDir = {
|
|
3829
|
-
_id: generateId(),
|
|
3830
|
-
type: "direction",
|
|
3831
|
-
directionTypes: [{ kind: "words", text: item.value }]
|
|
3832
|
-
};
|
|
3833
|
-
currentEntries.push(decoDir);
|
|
3834
|
-
}
|
|
4150
|
+
currentEntries.push(item);
|
|
3835
4151
|
}
|
|
3836
4152
|
pendingPreNoteItems.length = 0;
|
|
3837
4153
|
}
|
|
4154
|
+
function attachPendingNotations(entry) {
|
|
4155
|
+
if (pendingNotations.length === 0) return;
|
|
4156
|
+
if (!entry.notations) entry.notations = [];
|
|
4157
|
+
entry.notations.push(...pendingNotations);
|
|
4158
|
+
pendingNotations = [];
|
|
4159
|
+
}
|
|
3838
4160
|
function finalizeMeasure(endBarType) {
|
|
3839
4161
|
const measure = {
|
|
3840
4162
|
_id: generateId(),
|
|
@@ -3851,6 +4173,13 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3851
4173
|
};
|
|
3852
4174
|
isFirstMeasure = false;
|
|
3853
4175
|
}
|
|
4176
|
+
if (pendingMultipleRest !== null) {
|
|
4177
|
+
if (!measure.attributes) {
|
|
4178
|
+
measure.attributes = { _id: generateId() };
|
|
4179
|
+
}
|
|
4180
|
+
measure.attributes.measureStyle = [{ multipleRest: pendingMultipleRest }];
|
|
4181
|
+
pendingMultipleRest = null;
|
|
4182
|
+
}
|
|
3854
4183
|
if (pendingKeyChange) {
|
|
3855
4184
|
if (!measure.attributes) {
|
|
3856
4185
|
measure.attributes = { _id: generateId() };
|
|
@@ -3859,6 +4188,13 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3859
4188
|
measure.attributes.key = parseKeySignature2(kValue);
|
|
3860
4189
|
pendingKeyChange = null;
|
|
3861
4190
|
}
|
|
4191
|
+
if (pendingTimeChange) {
|
|
4192
|
+
if (!measure.attributes) {
|
|
4193
|
+
measure.attributes = { _id: generateId() };
|
|
4194
|
+
}
|
|
4195
|
+
measure.attributes.time = pendingTimeChange;
|
|
4196
|
+
pendingTimeChange = null;
|
|
4197
|
+
}
|
|
3862
4198
|
if (endBarType || currentBarlines.length > 0) {
|
|
3863
4199
|
measure.barlines = [...currentBarlines];
|
|
3864
4200
|
if (endBarType) {
|
|
@@ -3883,8 +4219,9 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3883
4219
|
chordNotes.push(token);
|
|
3884
4220
|
break;
|
|
3885
4221
|
}
|
|
3886
|
-
const entry = createNoteEntry(token, currentUnitNote, pendingTie, inGrace, tupletState);
|
|
4222
|
+
const entry = createNoteEntry(token, currentUnitNote, pendingTie, inGrace, tupletState, graceSlash, octaveShift);
|
|
3887
4223
|
pendingTie = false;
|
|
4224
|
+
attachPendingNotations(entry);
|
|
3888
4225
|
if (pendingBrokenRhythm) {
|
|
3889
4226
|
const n = pendingBrokenRhythm.length;
|
|
3890
4227
|
const divisor = Math.pow(2, n);
|
|
@@ -3904,7 +4241,7 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3904
4241
|
flushPendingPreNoteItems();
|
|
3905
4242
|
while (pendingSlurStarts > 0) {
|
|
3906
4243
|
if (!entry.notations) entry.notations = [];
|
|
3907
|
-
entry.notations.push(
|
|
4244
|
+
entry.notations.push(makeSlurStart(slurDepth - pendingSlurStarts + 1, pendingSlurDotted.shift() === true));
|
|
3908
4245
|
slurStartNotes.push(entry);
|
|
3909
4246
|
pendingSlurStarts--;
|
|
3910
4247
|
}
|
|
@@ -3947,11 +4284,25 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3947
4284
|
break;
|
|
3948
4285
|
}
|
|
3949
4286
|
case "rest": {
|
|
3950
|
-
if (inChord)
|
|
4287
|
+
if (inChord) {
|
|
4288
|
+
chordNotes.push(token);
|
|
4289
|
+
break;
|
|
4290
|
+
}
|
|
3951
4291
|
const restEntry = createRestEntry(token, currentUnitNote, tupletState, measureDuration);
|
|
3952
4292
|
flushPendingPreNoteItems();
|
|
4293
|
+
attachPendingNotations(restEntry);
|
|
3953
4294
|
currentEntries.push(restEntry);
|
|
3954
4295
|
currentPosition += restEntry.duration;
|
|
4296
|
+
const restMeasures = token.restMeasures || 1;
|
|
4297
|
+
if (restMeasures > 1) {
|
|
4298
|
+
pendingMultipleRest = restMeasures;
|
|
4299
|
+
for (let extra = 1; extra < restMeasures; extra++) {
|
|
4300
|
+
finalizeMeasure();
|
|
4301
|
+
const filler = createRestEntry(token, currentUnitNote, null, measureDuration);
|
|
4302
|
+
currentEntries.push(filler);
|
|
4303
|
+
currentPosition += filler.duration;
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
3955
4306
|
if (tupletState) {
|
|
3956
4307
|
tupletState.remaining--;
|
|
3957
4308
|
if (tupletState.remaining <= 0) {
|
|
@@ -3968,16 +4319,7 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3968
4319
|
case "chord_end": {
|
|
3969
4320
|
inChord = false;
|
|
3970
4321
|
if (chordNotes.length > 0) {
|
|
3971
|
-
|
|
3972
|
-
if (item.kind === "harmony") {
|
|
3973
|
-
const harmony = createHarmonyEntry(item.value);
|
|
3974
|
-
if (harmony) currentEntries.push(harmony);
|
|
3975
|
-
} else if (item.kind === "dynamic") {
|
|
3976
|
-
const dynDir = createDynamicsDirection(item.value);
|
|
3977
|
-
if (dynDir) currentEntries.push(dynDir);
|
|
3978
|
-
}
|
|
3979
|
-
}
|
|
3980
|
-
pendingPreNoteItems.length = 0;
|
|
4322
|
+
flushPendingPreNoteItems();
|
|
3981
4323
|
const chordDurNum = token.durationNum || 1;
|
|
3982
4324
|
const chordDurDen = token.durationDen || 1;
|
|
3983
4325
|
const hasIndividualDurations = chordNotes.some(
|
|
@@ -3993,13 +4335,14 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
3993
4335
|
chordToken.durationNum = chordDurNum;
|
|
3994
4336
|
chordToken.durationDen = chordDurDen;
|
|
3995
4337
|
}
|
|
3996
|
-
const entry = createNoteEntry(chordToken, currentUnitNote, false, inGrace, tupletState);
|
|
4338
|
+
const entry = chordToken.type === "rest" ? createRestEntry(chordToken, currentUnitNote, tupletState, measureDuration) : createNoteEntry(chordToken, currentUnitNote, false, inGrace, tupletState, graceSlash, octaveShift);
|
|
3997
4339
|
chordToken.durationNum = originalNum;
|
|
3998
4340
|
chordToken.durationDen = originalDen;
|
|
3999
4341
|
if (ci === 0) {
|
|
4342
|
+
attachPendingNotations(entry);
|
|
4000
4343
|
while (pendingSlurStarts > 0) {
|
|
4001
4344
|
if (!entry.notations) entry.notations = [];
|
|
4002
|
-
entry.notations.push(
|
|
4345
|
+
entry.notations.push(makeSlurStart(slurDepth - pendingSlurStarts + 1, pendingSlurDotted.shift() === true));
|
|
4003
4346
|
slurStartNotes.push(entry);
|
|
4004
4347
|
pendingSlurStarts--;
|
|
4005
4348
|
}
|
|
@@ -4075,7 +4418,7 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4075
4418
|
type: "start"
|
|
4076
4419
|
}
|
|
4077
4420
|
};
|
|
4078
|
-
if (ti > 0 && tokens[ti - 1].type === "bar" && (!tokens[ti - 1].barType || tokens[ti - 1].barType === "regular") && currentEntries.length === 0) {
|
|
4421
|
+
if (!token.bracket && ti > 0 && tokens[ti - 1].type === "bar" && (!tokens[ti - 1].barType || tokens[ti - 1].barType === "regular") && currentEntries.length === 0) {
|
|
4079
4422
|
endingBarline.barStyle = "regular";
|
|
4080
4423
|
}
|
|
4081
4424
|
currentBarlines.push(endingBarline);
|
|
@@ -4103,6 +4446,7 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4103
4446
|
case "slur_start":
|
|
4104
4447
|
slurDepth++;
|
|
4105
4448
|
pendingSlurStarts++;
|
|
4449
|
+
pendingSlurDotted.push(token.dotted === true);
|
|
4106
4450
|
break;
|
|
4107
4451
|
case "slur_end":
|
|
4108
4452
|
if (slurDepth > 0) {
|
|
@@ -4119,9 +4463,11 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4119
4463
|
break;
|
|
4120
4464
|
case "grace_start":
|
|
4121
4465
|
inGrace = true;
|
|
4466
|
+
graceSlash = token.slashGrace === true;
|
|
4122
4467
|
break;
|
|
4123
4468
|
case "grace_end":
|
|
4124
4469
|
inGrace = false;
|
|
4470
|
+
graceSlash = false;
|
|
4125
4471
|
break;
|
|
4126
4472
|
case "broken_rhythm": {
|
|
4127
4473
|
const brokenN = token.value.length;
|
|
@@ -4151,21 +4497,47 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4151
4497
|
};
|
|
4152
4498
|
pendingTupletStart = true;
|
|
4153
4499
|
break;
|
|
4154
|
-
case "chord_symbol":
|
|
4155
|
-
|
|
4500
|
+
case "chord_symbol": {
|
|
4501
|
+
const harmony = createHarmonyEntry(token.value);
|
|
4502
|
+
if (harmony) pendingPreNoteItems.push(harmony);
|
|
4156
4503
|
break;
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4504
|
+
}
|
|
4505
|
+
case "annotation":
|
|
4506
|
+
pendingPreNoteItems.push(
|
|
4507
|
+
createAnnotationDirection(token.value, token.annotationPlacement || "@")
|
|
4508
|
+
);
|
|
4509
|
+
break;
|
|
4510
|
+
case "spacer":
|
|
4511
|
+
pendingPreNoteItems.push(createRawWordsDirection(token.value));
|
|
4512
|
+
break;
|
|
4513
|
+
case "decoration": {
|
|
4514
|
+
const target = lookupAbcDecoration(token.value);
|
|
4515
|
+
if (!target) {
|
|
4516
|
+
const isShorthand = token.value.length === 1 && ABC_SHORTHAND_DECORATIONS.has(token.value);
|
|
4517
|
+
pendingPreNoteItems.push(
|
|
4518
|
+
createRawWordsDirection(isShorthand ? token.value : `!${token.value}!`)
|
|
4519
|
+
);
|
|
4520
|
+
break;
|
|
4521
|
+
}
|
|
4522
|
+
const notation = decorationToNotation(target);
|
|
4523
|
+
if (notation) {
|
|
4524
|
+
pendingNotations.push(notation);
|
|
4525
|
+
break;
|
|
4164
4526
|
}
|
|
4527
|
+
const direction = decorationToDirection(target);
|
|
4528
|
+
if (direction) pendingPreNoteItems.push(direction);
|
|
4165
4529
|
break;
|
|
4530
|
+
}
|
|
4166
4531
|
case "lyrics": {
|
|
4167
4532
|
const syllables = token.syllables || [];
|
|
4168
|
-
|
|
4533
|
+
const previousWasLyrics = ti > 0 && tokens[ti - 1].type === "lyrics";
|
|
4534
|
+
if (previousWasLyrics) {
|
|
4535
|
+
lyricVerse++;
|
|
4536
|
+
} else {
|
|
4537
|
+
lyricVerse = 1;
|
|
4538
|
+
lyricTargets = collectUnlyricedNotes(measures, currentEntries);
|
|
4539
|
+
}
|
|
4540
|
+
applyLyricsToNotes(lyricTargets, syllables, lyricVerse);
|
|
4169
4541
|
break;
|
|
4170
4542
|
}
|
|
4171
4543
|
case "overlay": {
|
|
@@ -4181,25 +4553,37 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4181
4553
|
break;
|
|
4182
4554
|
}
|
|
4183
4555
|
case "inline_field": {
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4556
|
+
if (token.value.startsWith(ABC_BODY_FIELD_MARKER)) {
|
|
4557
|
+
currentEntries.push(createRawWordsDirection(token.value));
|
|
4558
|
+
break;
|
|
4559
|
+
}
|
|
4560
|
+
const fieldMatch = token.value.match(/^([A-Za-z]):(.*)$/);
|
|
4561
|
+
if (!fieldMatch) break;
|
|
4562
|
+
const [, field, rawValue] = fieldMatch;
|
|
4563
|
+
const value = rawValue.trim();
|
|
4564
|
+
if (!token.standalone) {
|
|
4565
|
+
currentEntries.push(createRawWordsDirection(`[${field}:${value}]`));
|
|
4566
|
+
}
|
|
4567
|
+
if (field === "K") {
|
|
4568
|
+
pendingKeyChange = `K:${rawValue}`;
|
|
4569
|
+
break;
|
|
4193
4570
|
}
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4571
|
+
if (field === "L") {
|
|
4572
|
+
const lMatch = value.match(/^(\d+)\/(\d+)/);
|
|
4573
|
+
if (lMatch) {
|
|
4574
|
+
currentUnitNote = { num: parseInt(lMatch[1], 10), den: parseInt(lMatch[2], 10) };
|
|
4575
|
+
}
|
|
4576
|
+
} else if (field === "M") {
|
|
4577
|
+
pendingTimeChange = parseTimeSignature2(value);
|
|
4578
|
+
} else if (field === "Q") {
|
|
4579
|
+
const tempoDirection = parseTempoToDirection(value);
|
|
4580
|
+
if (tempoDirection) currentEntries.push(tempoDirection);
|
|
4197
4581
|
}
|
|
4198
4582
|
break;
|
|
4199
4583
|
}
|
|
4200
4584
|
case "space":
|
|
4201
4585
|
break;
|
|
4202
|
-
case "line_break":
|
|
4586
|
+
case "line_break": {
|
|
4203
4587
|
flushPendingPreNoteItems();
|
|
4204
4588
|
if (currentEntries.length > 0) {
|
|
4205
4589
|
const breakText = token.value === "\\\n" ? "__abc_line_cont__" : "__abc_line_break__";
|
|
@@ -4210,7 +4594,8 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4210
4594
|
};
|
|
4211
4595
|
currentEntries.push(lineBreakDir);
|
|
4212
4596
|
}
|
|
4213
|
-
|
|
4597
|
+
const onlyMarkersSoFar = currentEntries.length > 0 && !currentEntries.some((e) => e.type === "note");
|
|
4598
|
+
if (measures.length > 0 && !onlyMarkersSoFar) {
|
|
4214
4599
|
if (token.value === "\\\n") {
|
|
4215
4600
|
lineBreaks.push(-measures.length);
|
|
4216
4601
|
} else {
|
|
@@ -4218,6 +4603,7 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4218
4603
|
}
|
|
4219
4604
|
}
|
|
4220
4605
|
break;
|
|
4606
|
+
}
|
|
4221
4607
|
default:
|
|
4222
4608
|
break;
|
|
4223
4609
|
}
|
|
@@ -4228,22 +4614,20 @@ function buildMeasures(tokens, unitNote, keySignature, timeSignature, measureDur
|
|
|
4228
4614
|
applyTieStops(measures);
|
|
4229
4615
|
return { measures, lineBreaks, hasIndividualChordDurations };
|
|
4230
4616
|
}
|
|
4231
|
-
function
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
for (const entry of
|
|
4235
|
-
if (entry.type === "note" && !entry.rest && !entry.grace && !entry.chord) {
|
|
4236
|
-
|
|
4617
|
+
function collectUnlyricedNotes(finalizedMeasures, currentEntries) {
|
|
4618
|
+
const notes = [];
|
|
4619
|
+
const collect = (entries) => {
|
|
4620
|
+
for (const entry of entries) {
|
|
4621
|
+
if (entry.type === "note" && !entry.rest && !entry.grace && !entry.chord && (!entry.lyrics || entry.lyrics.length === 0)) {
|
|
4622
|
+
notes.push(entry);
|
|
4237
4623
|
}
|
|
4238
4624
|
}
|
|
4239
|
-
}
|
|
4240
|
-
for (const
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
const unlyricedNotes = allNotes.filter((n) => !n.lyrics || n.lyrics.length === 0);
|
|
4246
|
-
const targetNotes = unlyricedNotes.slice(0, syllables.length);
|
|
4625
|
+
};
|
|
4626
|
+
for (const measure of finalizedMeasures) collect(measure.entries);
|
|
4627
|
+
collect(currentEntries);
|
|
4628
|
+
return notes;
|
|
4629
|
+
}
|
|
4630
|
+
function applyLyricsToNotes(targetNotes, syllables, verse) {
|
|
4247
4631
|
for (let si = 0; si < syllables.length && si < targetNotes.length; si++) {
|
|
4248
4632
|
const syllable = syllables[si];
|
|
4249
4633
|
if (!syllable || syllable === "" || syllable === "*") continue;
|
|
@@ -4257,11 +4641,8 @@ function applyLyricsToExistingNotes(finalizedMeasures, currentEntries, syllables
|
|
|
4257
4641
|
syllabic = isHyphenated ? "middle" : "end";
|
|
4258
4642
|
}
|
|
4259
4643
|
}
|
|
4260
|
-
note.lyrics = [
|
|
4261
|
-
|
|
4262
|
-
text,
|
|
4263
|
-
syllabic
|
|
4264
|
-
}];
|
|
4644
|
+
if (!note.lyrics) note.lyrics = [];
|
|
4645
|
+
note.lyrics.push({ number: verse, text, syllabic });
|
|
4265
4646
|
}
|
|
4266
4647
|
}
|
|
4267
4648
|
function applyTieStops(measures) {
|
|
@@ -4294,7 +4675,7 @@ function applyTieStops(measures) {
|
|
|
4294
4675
|
}
|
|
4295
4676
|
}
|
|
4296
4677
|
}
|
|
4297
|
-
function createNoteEntry(token, unitNote, _hasTieStop, isGrace, tupletState) {
|
|
4678
|
+
function createNoteEntry(token, unitNote, _hasTieStop, isGrace, tupletState, graceSlash = false, octaveShift = 0) {
|
|
4298
4679
|
const num = token.durationNum || 1;
|
|
4299
4680
|
const den = token.durationDen || 1;
|
|
4300
4681
|
let duration;
|
|
@@ -4307,17 +4688,18 @@ function createNoteEntry(token, unitNote, _hasTieStop, isGrace, tupletState) {
|
|
|
4307
4688
|
}
|
|
4308
4689
|
}
|
|
4309
4690
|
const { noteType, dots } = durationToNoteType(isGrace ? lengthToDuration(num, den, unitNote) : duration);
|
|
4691
|
+
const pitch = token.pitch && octaveShift !== 0 ? { ...token.pitch, octave: token.pitch.octave + octaveShift } : token.pitch;
|
|
4310
4692
|
const entry = {
|
|
4311
4693
|
_id: generateId(),
|
|
4312
4694
|
type: "note",
|
|
4313
|
-
pitch
|
|
4695
|
+
pitch,
|
|
4314
4696
|
duration,
|
|
4315
4697
|
voice: "1",
|
|
4316
4698
|
noteType,
|
|
4317
4699
|
dots: dots > 0 ? dots : void 0
|
|
4318
4700
|
};
|
|
4319
4701
|
if (isGrace) {
|
|
4320
|
-
entry.grace = { slash:
|
|
4702
|
+
entry.grace = { slash: graceSlash };
|
|
4321
4703
|
entry.noteType = "eighth";
|
|
4322
4704
|
}
|
|
4323
4705
|
if (tupletState && !isGrace) {
|
|
@@ -4342,6 +4724,11 @@ function createNoteEntry(token, unitNote, _hasTieStop, isGrace, tupletState) {
|
|
|
4342
4724
|
case -2:
|
|
4343
4725
|
entry.accidental = { value: "double-flat" };
|
|
4344
4726
|
break;
|
|
4727
|
+
default: {
|
|
4728
|
+
const named = MICROTONE_ACCIDENTALS[String(token.accidental)];
|
|
4729
|
+
if (named) entry.accidental = { value: named };
|
|
4730
|
+
break;
|
|
4731
|
+
}
|
|
4345
4732
|
}
|
|
4346
4733
|
}
|
|
4347
4734
|
return entry;
|
|
@@ -4401,6 +4788,12 @@ function createBarline(barType, location, endingNumber) {
|
|
|
4401
4788
|
case "thick-thin":
|
|
4402
4789
|
barline.barStyle = "heavy-light";
|
|
4403
4790
|
break;
|
|
4791
|
+
case "invisible":
|
|
4792
|
+
barline.barStyle = "none";
|
|
4793
|
+
break;
|
|
4794
|
+
case "dotted":
|
|
4795
|
+
barline.barStyle = "dotted";
|
|
4796
|
+
break;
|
|
4404
4797
|
default:
|
|
4405
4798
|
return null;
|
|
4406
4799
|
}
|
|
@@ -4484,6 +4877,85 @@ function createHarmonyEntry(chordStr) {
|
|
|
4484
4877
|
}
|
|
4485
4878
|
return entry;
|
|
4486
4879
|
}
|
|
4880
|
+
function makeSlurStart(number, dotted) {
|
|
4881
|
+
return dotted ? { type: "slur", slurType: "start", number, lineType: "dotted" } : { type: "slur", slurType: "start", number };
|
|
4882
|
+
}
|
|
4883
|
+
function decorationToNotation(target) {
|
|
4884
|
+
switch (target.kind) {
|
|
4885
|
+
case "articulation":
|
|
4886
|
+
return { type: "articulation", articulation: target.articulation };
|
|
4887
|
+
case "ornament":
|
|
4888
|
+
return { type: "ornament", ornament: target.ornament };
|
|
4889
|
+
case "technical":
|
|
4890
|
+
return { type: "technical", technical: target.technical };
|
|
4891
|
+
case "fingering":
|
|
4892
|
+
return { type: "technical", technical: "fingering", text: target.fingering };
|
|
4893
|
+
case "fermata":
|
|
4894
|
+
return target.inverted ? { type: "fermata", fermataType: "inverted" } : { type: "fermata" };
|
|
4895
|
+
case "arpeggiate":
|
|
4896
|
+
return { type: "arpeggiate" };
|
|
4897
|
+
case "trill-line":
|
|
4898
|
+
return { type: "ornament", ornament: "wavy-line", wavyLineType: target.wavyLineType };
|
|
4899
|
+
default:
|
|
4900
|
+
return null;
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
function decorationToDirection(target) {
|
|
4904
|
+
switch (target.kind) {
|
|
4905
|
+
case "dynamics":
|
|
4906
|
+
return createDynamicsDirection(target.value);
|
|
4907
|
+
case "wedge":
|
|
4908
|
+
return {
|
|
4909
|
+
_id: generateId(),
|
|
4910
|
+
type: "direction",
|
|
4911
|
+
directionTypes: [{ kind: "wedge", type: target.stop ? "stop" : target.wedge }],
|
|
4912
|
+
placement: "below"
|
|
4913
|
+
};
|
|
4914
|
+
case "segno":
|
|
4915
|
+
return { _id: generateId(), type: "direction", directionTypes: [{ kind: "segno" }], placement: "above" };
|
|
4916
|
+
case "coda":
|
|
4917
|
+
return { _id: generateId(), type: "direction", directionTypes: [{ kind: "coda" }], placement: "above" };
|
|
4918
|
+
case "words":
|
|
4919
|
+
return {
|
|
4920
|
+
_id: generateId(),
|
|
4921
|
+
type: "direction",
|
|
4922
|
+
directionTypes: [{ kind: "words", text: target.text }],
|
|
4923
|
+
placement: "above"
|
|
4924
|
+
};
|
|
4925
|
+
default:
|
|
4926
|
+
return null;
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
function annotationPlacement(marker) {
|
|
4930
|
+
switch (marker) {
|
|
4931
|
+
case "^":
|
|
4932
|
+
return { placement: "above" };
|
|
4933
|
+
case "_":
|
|
4934
|
+
return { placement: "below" };
|
|
4935
|
+
case "<":
|
|
4936
|
+
return { halign: "right" };
|
|
4937
|
+
case ">":
|
|
4938
|
+
return { halign: "left" };
|
|
4939
|
+
default:
|
|
4940
|
+
return { halign: "center" };
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
function createAnnotationDirection(text, marker) {
|
|
4944
|
+
const { placement, halign } = annotationPlacement(marker);
|
|
4945
|
+
return {
|
|
4946
|
+
_id: generateId(),
|
|
4947
|
+
type: "direction",
|
|
4948
|
+
directionTypes: [{ kind: "words", text, ...halign ? { halign } : {} }],
|
|
4949
|
+
...placement ? { placement } : {}
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
function createRawWordsDirection(text) {
|
|
4953
|
+
return {
|
|
4954
|
+
_id: generateId(),
|
|
4955
|
+
type: "direction",
|
|
4956
|
+
directionTypes: [{ kind: "words", text }]
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4487
4959
|
function createDynamicsDirection(dynamic) {
|
|
4488
4960
|
if (!DYNAMICS_VALUES.has(dynamic)) return null;
|
|
4489
4961
|
return {
|
|
@@ -4496,8 +4968,30 @@ function createDynamicsDirection(dynamic) {
|
|
|
4496
4968
|
placement: "below"
|
|
4497
4969
|
};
|
|
4498
4970
|
}
|
|
4971
|
+
function splitTunes(lines) {
|
|
4972
|
+
const tunes = [];
|
|
4973
|
+
let fileHeader = [];
|
|
4974
|
+
let current = null;
|
|
4975
|
+
for (const line of lines) {
|
|
4976
|
+
if (/^X:/.test(line.trim())) {
|
|
4977
|
+
if (current) tunes.push(current);
|
|
4978
|
+
current = [...fileHeader, line];
|
|
4979
|
+
fileHeader = [];
|
|
4980
|
+
continue;
|
|
4981
|
+
}
|
|
4982
|
+
if (current) current.push(line);
|
|
4983
|
+
else fileHeader.push(line);
|
|
4984
|
+
}
|
|
4985
|
+
if (current) tunes.push(current);
|
|
4986
|
+
return tunes.length > 0 ? tunes : [lines];
|
|
4987
|
+
}
|
|
4988
|
+
function parseAbcTunes(abcString) {
|
|
4989
|
+
return splitTunes(abcString.split("\n")).map((tuneLines) => parseTune(tuneLines));
|
|
4990
|
+
}
|
|
4499
4991
|
function parseAbc(abcString) {
|
|
4500
|
-
|
|
4992
|
+
return parseTune(splitTunes(abcString.split("\n"))[0]);
|
|
4993
|
+
}
|
|
4994
|
+
function parseTune(lines) {
|
|
4501
4995
|
const { header, bodyStartIndex, headerFieldOrder } = parseHeader(lines);
|
|
4502
4996
|
const bodyLines = lines.slice(bodyStartIndex);
|
|
4503
4997
|
const { tokens: voiceTokensList, voiceIds, inlineVoiceMarkers, voiceDeclarationLines, bodyComments, bodyDirectives, wFields, voiceInterleavePattern, groupBarCounts, voiceComments, preVoiceComments, trailingComments } = tokenizeBody(bodyLines);
|
|
@@ -4822,6 +5316,7 @@ function serializeCredit(credit, indent, out) {
|
|
|
4822
5316
|
if (cw.letterSpacing) attrs2 += ` letter-spacing="${escapeXml(cw.letterSpacing)}"`;
|
|
4823
5317
|
if (cw.xmlLang) attrs2 += ` xml:lang="${escapeXml(cw.xmlLang)}"`;
|
|
4824
5318
|
if (cw.xmlSpace) attrs2 += ` xml:space="${escapeXml(cw.xmlSpace)}"`;
|
|
5319
|
+
if (cw.color) attrs2 += ` color="${escapeXml(cw.color)}"`;
|
|
4825
5320
|
out.push(`${indent}${indent}<credit-words${attrs2}>${escapeXml(cw.text)}</credit-words>`);
|
|
4826
5321
|
}
|
|
4827
5322
|
}
|
|
@@ -4846,6 +5341,7 @@ function serializeDisplayTexts(texts, indent, out) {
|
|
|
4846
5341
|
if (dt.fontStyle) attrs += ` font-style="${escapeXml(dt.fontStyle)}"`;
|
|
4847
5342
|
if (dt.fontWeight) attrs += ` font-weight="${escapeXml(dt.fontWeight)}"`;
|
|
4848
5343
|
if (dt.xmlSpace) attrs += ` xml:space="${escapeXml(dt.xmlSpace)}"`;
|
|
5344
|
+
if (dt.color) attrs += ` color="${escapeXml(dt.color)}"`;
|
|
4849
5345
|
out.push(`${indent}<display-text${attrs}>${escapeXml(dt.text)}</display-text>`);
|
|
4850
5346
|
}
|
|
4851
5347
|
}
|
|
@@ -4854,6 +5350,7 @@ function serializeScorePart(part, indent, out) {
|
|
|
4854
5350
|
if (part.name !== void 0) {
|
|
4855
5351
|
let pnAttrs = "";
|
|
4856
5352
|
if (part.namePrintObject === false) pnAttrs += ' print-object="no"';
|
|
5353
|
+
if (part.nameColor) pnAttrs += ` color="${escapeXml(part.nameColor)}"`;
|
|
4857
5354
|
out.push(`${indent} <part-name${pnAttrs}>${escapeXml(part.name)}</part-name>`);
|
|
4858
5355
|
}
|
|
4859
5356
|
if (part.partNameDisplay && part.partNameDisplay.length > 0) {
|
|
@@ -4864,6 +5361,7 @@ function serializeScorePart(part, indent, out) {
|
|
|
4864
5361
|
if (part.abbreviation !== void 0) {
|
|
4865
5362
|
let paAttrs = "";
|
|
4866
5363
|
if (part.abbreviationPrintObject === false) paAttrs += ' print-object="no"';
|
|
5364
|
+
if (part.abbreviationColor) paAttrs += ` color="${escapeXml(part.abbreviationColor)}"`;
|
|
4867
5365
|
out.push(`${indent} <part-abbreviation${paAttrs}>${escapeXml(part.abbreviation)}</part-abbreviation>`);
|
|
4868
5366
|
}
|
|
4869
5367
|
if (part.partAbbreviationDisplay && part.partAbbreviationDisplay.length > 0) {
|
|
@@ -4937,7 +5435,8 @@ function serializePartGroup(group, indent, out) {
|
|
|
4937
5435
|
if (group._id) attrs += ` id="${escapeXml(group._id)}"`;
|
|
4938
5436
|
out.push(`${indent}<part-group${attrs}>`);
|
|
4939
5437
|
if (group.groupName) {
|
|
4940
|
-
|
|
5438
|
+
const gnAttrs = group.groupNameColor ? ` color="${escapeXml(group.groupNameColor)}"` : "";
|
|
5439
|
+
out.push(`${indent} <group-name${gnAttrs}>${escapeXml(group.groupName)}</group-name>`);
|
|
4941
5440
|
}
|
|
4942
5441
|
if (group.groupNameDisplay && group.groupNameDisplay.length > 0) {
|
|
4943
5442
|
out.push(`${indent} <group-name-display>`);
|
|
@@ -4945,7 +5444,8 @@ function serializePartGroup(group, indent, out) {
|
|
|
4945
5444
|
out.push(`${indent} </group-name-display>`);
|
|
4946
5445
|
}
|
|
4947
5446
|
if (group.groupAbbreviation) {
|
|
4948
|
-
|
|
5447
|
+
const gaAttrs = group.groupAbbreviationColor ? ` color="${escapeXml(group.groupAbbreviationColor)}"` : "";
|
|
5448
|
+
out.push(`${indent} <group-abbreviation${gaAttrs}>${escapeXml(group.groupAbbreviation)}</group-abbreviation>`);
|
|
4949
5449
|
}
|
|
4950
5450
|
if (group.groupAbbreviationDisplay && group.groupAbbreviationDisplay.length > 0) {
|
|
4951
5451
|
out.push(`${indent} <group-abbreviation-display>`);
|
|
@@ -4953,8 +5453,9 @@ function serializePartGroup(group, indent, out) {
|
|
|
4953
5453
|
out.push(`${indent} </group-abbreviation-display>`);
|
|
4954
5454
|
}
|
|
4955
5455
|
if (group.groupSymbol) {
|
|
4956
|
-
|
|
4957
|
-
|
|
5456
|
+
let gsAttrs = group.groupSymbolDefaultX !== void 0 ? ` default-x="${group.groupSymbolDefaultX}"` : "";
|
|
5457
|
+
if (group.groupSymbolColor) gsAttrs += ` color="${escapeXml(group.groupSymbolColor)}"`;
|
|
5458
|
+
out.push(`${indent} <group-symbol${gsAttrs}>${group.groupSymbol}</group-symbol>`);
|
|
4958
5459
|
}
|
|
4959
5460
|
if (group.groupBarline) {
|
|
4960
5461
|
out.push(`${indent} <group-barline>${group.groupBarline}</group-barline>`);
|
|
@@ -5090,6 +5591,7 @@ function serializeKey(key, indent, out) {
|
|
|
5090
5591
|
if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
|
|
5091
5592
|
if (key.printObject === false) keyAttrs += ' print-object="no"';
|
|
5092
5593
|
else if (key.printObject === true) keyAttrs += ' print-object="yes"';
|
|
5594
|
+
if (key.color) keyAttrs += ` color="${escapeXml(key.color)}"`;
|
|
5093
5595
|
out.push(`${indent}<key${keyAttrs}>`);
|
|
5094
5596
|
if (key.cancel !== void 0) {
|
|
5095
5597
|
const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
|
|
@@ -5128,6 +5630,7 @@ function serializeTime(time, indent, out) {
|
|
|
5128
5630
|
let attrs = "";
|
|
5129
5631
|
if (time.symbol) attrs += ` symbol="${time.symbol}"`;
|
|
5130
5632
|
if (time.printObject === false) attrs += ' print-object="no"';
|
|
5633
|
+
if (time.color) attrs += ` color="${escapeXml(time.color)}"`;
|
|
5131
5634
|
out.push(`${indent}<time${attrs}>`);
|
|
5132
5635
|
if (time.senzaMisura) {
|
|
5133
5636
|
out.push(`${indent} <senza-misura/>`);
|
|
@@ -5153,6 +5656,7 @@ function serializeClef(clef, indent, out) {
|
|
|
5153
5656
|
if (clef.printObject === false) attrs += ' print-object="no"';
|
|
5154
5657
|
else if (clef.printObject === true) attrs += ' print-object="yes"';
|
|
5155
5658
|
if (clef.afterBarline) attrs += ' after-barline="yes"';
|
|
5659
|
+
if (clef.color) attrs += ` color="${escapeXml(clef.color)}"`;
|
|
5156
5660
|
out.push(`${indent}<clef${attrs}>`);
|
|
5157
5661
|
out.push(`${indent} <sign>${clef.sign}</sign>`);
|
|
5158
5662
|
if (clef.line !== void 0) {
|
|
@@ -5219,7 +5723,8 @@ function serializeNote(note, indent, out) {
|
|
|
5219
5723
|
"dynamics": note.dynamics,
|
|
5220
5724
|
"print-object": note.printObject === false ? false : void 0,
|
|
5221
5725
|
"print-dot": note.printDot !== void 0 ? note.printDot : void 0,
|
|
5222
|
-
"print-spacing": note.printSpacing
|
|
5726
|
+
"print-spacing": note.printSpacing,
|
|
5727
|
+
"color": note.color
|
|
5223
5728
|
});
|
|
5224
5729
|
out.push(`${indent}<note${noteAttrs}>`);
|
|
5225
5730
|
if (note.grace) {
|
|
@@ -5283,12 +5788,16 @@ function serializeNote(note, indent, out) {
|
|
|
5283
5788
|
out.push(`${indent} <voice>${note.voice}</voice>`);
|
|
5284
5789
|
}
|
|
5285
5790
|
if (note.noteType) {
|
|
5286
|
-
const typeAttrs =
|
|
5791
|
+
const typeAttrs = buildAttrs({
|
|
5792
|
+
"size": note.noteTypeSize,
|
|
5793
|
+
"color": note.noteTypeColor
|
|
5794
|
+
});
|
|
5287
5795
|
out.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
|
|
5288
5796
|
}
|
|
5289
5797
|
if (note.dots) {
|
|
5798
|
+
const dotAttrs = note.dotColor ? ` color="${escapeXml(note.dotColor)}"` : "";
|
|
5290
5799
|
for (let i = 0; i < note.dots; i++) {
|
|
5291
|
-
out.push(`${indent} <dot/>`);
|
|
5800
|
+
out.push(`${indent} <dot${dotAttrs}/>`);
|
|
5292
5801
|
}
|
|
5293
5802
|
}
|
|
5294
5803
|
if (note.accidental) {
|
|
@@ -5322,14 +5831,16 @@ function serializeNote(note, indent, out) {
|
|
|
5322
5831
|
if (note.stem) {
|
|
5323
5832
|
const stemAttrs = buildAttrs({
|
|
5324
5833
|
"default-x": note.stem.defaultX,
|
|
5325
|
-
"default-y": note.stem.defaultY
|
|
5834
|
+
"default-y": note.stem.defaultY,
|
|
5835
|
+
"color": note.stem.color
|
|
5326
5836
|
});
|
|
5327
5837
|
out.push(`${indent} <stem${stemAttrs}>${note.stem.value}</stem>`);
|
|
5328
5838
|
}
|
|
5329
5839
|
if (note.notehead) {
|
|
5330
5840
|
const nhAttrs = buildAttrs({
|
|
5331
5841
|
"filled": note.notehead.filled,
|
|
5332
|
-
"parentheses": note.notehead.parentheses || void 0
|
|
5842
|
+
"parentheses": note.notehead.parentheses || void 0,
|
|
5843
|
+
"color": note.notehead.color
|
|
5333
5844
|
});
|
|
5334
5845
|
out.push(`${indent} <notehead${nhAttrs}>${note.notehead.value}</notehead>`);
|
|
5335
5846
|
}
|
|
@@ -5364,8 +5875,9 @@ function serializePitch(pitch, indent, out) {
|
|
|
5364
5875
|
out.push(`${indent}</pitch>`);
|
|
5365
5876
|
}
|
|
5366
5877
|
function serializeBeam(beam, indent, out) {
|
|
5367
|
-
|
|
5368
|
-
|
|
5878
|
+
let beamAttrs = beam.fan ? ` fan="${beam.fan}"` : "";
|
|
5879
|
+
if (beam.color) beamAttrs += ` color="${escapeXml(beam.color)}"`;
|
|
5880
|
+
out.push(`${indent}<beam number="${beam.number}"${beamAttrs}>${beam.type}</beam>`);
|
|
5369
5881
|
}
|
|
5370
5882
|
function serializeNotations(notations, indent, out) {
|
|
5371
5883
|
const notationsGroups = /* @__PURE__ */ new Map();
|
|
@@ -5430,6 +5942,7 @@ function serializeStandaloneNotation(notation, indent, out) {
|
|
|
5430
5942
|
let attrs = ` type="${notation.tiedType}"`;
|
|
5431
5943
|
if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
|
|
5432
5944
|
if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
|
|
5945
|
+
attrs += colorAttr(notation.color);
|
|
5433
5946
|
out.push(`${indent} <tied${attrs}/>`);
|
|
5434
5947
|
} else if (notation.type === "slur") {
|
|
5435
5948
|
let attrs = "";
|
|
@@ -5444,6 +5957,7 @@ function serializeStandaloneNotation(notation, indent, out) {
|
|
|
5444
5957
|
if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
|
|
5445
5958
|
if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
|
|
5446
5959
|
if (notation.placement) attrs += ` placement="${notation.placement}"`;
|
|
5960
|
+
attrs += colorAttr(notation.color);
|
|
5447
5961
|
out.push(`${indent} <slur${attrs}/>`);
|
|
5448
5962
|
} else if (notation.type === "tuplet") {
|
|
5449
5963
|
let attrs = ` type="${notation.tupletType}"`;
|
|
@@ -5491,8 +6005,8 @@ function serializeStandaloneNotation(notation, indent, out) {
|
|
|
5491
6005
|
out.push(`${indent} <tuplet${attrs}/>`);
|
|
5492
6006
|
}
|
|
5493
6007
|
} else if (notation.type === "dynamics") {
|
|
5494
|
-
const
|
|
5495
|
-
out.push(`${indent} <dynamics${
|
|
6008
|
+
const dynAttrs = (notation.placement ? ` placement="${notation.placement}"` : "") + colorAttr(notation.color);
|
|
6009
|
+
out.push(`${indent} <dynamics${dynAttrs}>`);
|
|
5496
6010
|
for (const dyn of notation.dynamics) {
|
|
5497
6011
|
out.push(`${indent} <${dyn}/>`);
|
|
5498
6012
|
}
|
|
@@ -5506,6 +6020,7 @@ function serializeStandaloneNotation(notation, indent, out) {
|
|
|
5506
6020
|
if (notation.placement) attrs += ` placement="${notation.placement}"`;
|
|
5507
6021
|
if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
|
|
5508
6022
|
if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
|
|
6023
|
+
attrs += colorAttr(notation.color);
|
|
5509
6024
|
if (notation.shape) {
|
|
5510
6025
|
out.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
|
|
5511
6026
|
} else {
|
|
@@ -5517,20 +6032,24 @@ function serializeStandaloneNotation(notation, indent, out) {
|
|
|
5517
6032
|
if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
|
|
5518
6033
|
if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
|
|
5519
6034
|
if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
|
|
6035
|
+
attrs += colorAttr(notation.color);
|
|
5520
6036
|
out.push(`${indent} <arpeggiate${attrs}/>`);
|
|
5521
6037
|
} else if (notation.type === "non-arpeggiate") {
|
|
5522
6038
|
let attrs = ` type="${notation.nonArpeggiateType}"`;
|
|
5523
6039
|
if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
|
|
5524
6040
|
if (notation.placement) attrs += ` placement="${notation.placement}"`;
|
|
6041
|
+
attrs += colorAttr(notation.color);
|
|
5525
6042
|
out.push(`${indent} <non-arpeggiate${attrs}/>`);
|
|
5526
6043
|
} else if (notation.type === "accidental-mark") {
|
|
5527
6044
|
let attrs = "";
|
|
5528
6045
|
if (notation.placement) attrs += ` placement="${notation.placement}"`;
|
|
6046
|
+
attrs += colorAttr(notation.color);
|
|
5529
6047
|
out.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
|
|
5530
6048
|
} else if (notation.type === "glissando") {
|
|
5531
6049
|
let attrs = ` type="${notation.glissandoType}"`;
|
|
5532
6050
|
if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
|
|
5533
6051
|
if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
|
|
6052
|
+
attrs += colorAttr(notation.color);
|
|
5534
6053
|
if (notation.text) {
|
|
5535
6054
|
out.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
|
|
5536
6055
|
} else {
|
|
@@ -5540,6 +6059,7 @@ function serializeStandaloneNotation(notation, indent, out) {
|
|
|
5540
6059
|
let attrs = ` type="${notation.slideType}"`;
|
|
5541
6060
|
if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
|
|
5542
6061
|
if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
|
|
6062
|
+
attrs += colorAttr(notation.color);
|
|
5543
6063
|
if (notation.text) {
|
|
5544
6064
|
out.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
|
|
5545
6065
|
} else {
|
|
@@ -5557,6 +6077,7 @@ function serializeArticulationsGroup(artGroup, indent, out) {
|
|
|
5557
6077
|
}
|
|
5558
6078
|
if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
|
|
5559
6079
|
if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
|
|
6080
|
+
artAttrs += colorAttr(art.color);
|
|
5560
6081
|
out.push(`${indent} <${art.articulation}${artAttrs}/>`);
|
|
5561
6082
|
}
|
|
5562
6083
|
}
|
|
@@ -5579,6 +6100,7 @@ function serializeOrnamentsGroup(ornaments, indent, out) {
|
|
|
5579
6100
|
if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
|
|
5580
6101
|
wlAttrs += placementAttr;
|
|
5581
6102
|
if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
|
|
6103
|
+
wlAttrs += colorAttr(orn.color);
|
|
5582
6104
|
out.push(`${indent} <wavy-line${wlAttrs}/>`);
|
|
5583
6105
|
} else if (orn.ornament === "tremolo") {
|
|
5584
6106
|
let tremAttrs = "";
|
|
@@ -5586,6 +6108,7 @@ function serializeOrnamentsGroup(ornaments, indent, out) {
|
|
|
5586
6108
|
tremAttrs += placementAttr;
|
|
5587
6109
|
if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
|
|
5588
6110
|
if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
|
|
6111
|
+
tremAttrs += colorAttr(orn.color);
|
|
5589
6112
|
if (orn.tremoloMarks !== void 0) {
|
|
5590
6113
|
out.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
|
|
5591
6114
|
} else {
|
|
@@ -5594,6 +6117,7 @@ function serializeOrnamentsGroup(ornaments, indent, out) {
|
|
|
5594
6117
|
} else {
|
|
5595
6118
|
let ornAttrs = placementAttr;
|
|
5596
6119
|
if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
|
|
6120
|
+
ornAttrs += colorAttr(orn.color);
|
|
5597
6121
|
out.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
|
|
5598
6122
|
}
|
|
5599
6123
|
if (orn.accidentalMarks) {
|
|
@@ -5602,8 +6126,8 @@ function serializeOrnamentsGroup(ornaments, indent, out) {
|
|
|
5602
6126
|
}
|
|
5603
6127
|
}
|
|
5604
6128
|
for (const am of allAccidentalMarks) {
|
|
5605
|
-
const
|
|
5606
|
-
out.push(`${indent} <accidental-mark${
|
|
6129
|
+
const amAttrs = (am.placement ? ` placement="${am.placement}"` : "") + colorAttr(am.color);
|
|
6130
|
+
out.push(`${indent} <accidental-mark${amAttrs}>${am.value}</accidental-mark>`);
|
|
5607
6131
|
}
|
|
5608
6132
|
out.push(`${indent} </ornaments>`);
|
|
5609
6133
|
}
|
|
@@ -5617,6 +6141,7 @@ function serializeTechnicalGroup(technicals, indent, out) {
|
|
|
5617
6141
|
if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
|
|
5618
6142
|
if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
|
|
5619
6143
|
if (techNotation.fontSize !== void 0) placementAttr += ` font-size="${techNotation.fontSize}"`;
|
|
6144
|
+
placementAttr += colorAttr(techNotation.color);
|
|
5620
6145
|
if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
|
|
5621
6146
|
out.push(`${indent} <bend${placementAttr}>`);
|
|
5622
6147
|
if (techNotation.bendAlter !== void 0) {
|
|
@@ -5689,6 +6214,7 @@ function serializeLyric(lyric, indent, out) {
|
|
|
5689
6214
|
if (lyric.relativeX !== void 0) attrs += ` relative-x="${lyric.relativeX}"`;
|
|
5690
6215
|
if (lyric.justify) attrs += ` justify="${escapeXml(lyric.justify)}"`;
|
|
5691
6216
|
if (lyric.placement) attrs += ` placement="${lyric.placement}"`;
|
|
6217
|
+
attrs += colorAttr(lyric.color);
|
|
5692
6218
|
out.push(`${indent}<lyric${attrs}>`);
|
|
5693
6219
|
if (lyric.textElements && lyric.textElements.length > 1) {
|
|
5694
6220
|
for (let i = 0; i < lyric.textElements.length; i++) {
|
|
@@ -5696,7 +6222,7 @@ function serializeLyric(lyric, indent, out) {
|
|
|
5696
6222
|
if (te.syllabic) {
|
|
5697
6223
|
out.push(`${indent} <syllabic>${te.syllabic}</syllabic>`);
|
|
5698
6224
|
}
|
|
5699
|
-
out.push(`${indent} <text>${escapeXml(te.text)}</text>`);
|
|
6225
|
+
out.push(`${indent} <text${colorAttr(te.color)}>${escapeXml(te.text)}</text>`);
|
|
5700
6226
|
if (i < lyric.textElements.length - 1) {
|
|
5701
6227
|
out.push(`${indent} <elision/>`);
|
|
5702
6228
|
}
|
|
@@ -5705,11 +6231,13 @@ function serializeLyric(lyric, indent, out) {
|
|
|
5705
6231
|
if (lyric.syllabic) {
|
|
5706
6232
|
out.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
|
|
5707
6233
|
}
|
|
5708
|
-
|
|
6234
|
+
const textColor = lyric.textColor ?? lyric.textElements?.[0]?.color;
|
|
6235
|
+
out.push(`${indent} <text${colorAttr(textColor)}>${escapeXml(lyric.text)}</text>`);
|
|
5709
6236
|
}
|
|
5710
6237
|
if (lyric.extend) {
|
|
5711
|
-
if (typeof lyric.extend === "object"
|
|
5712
|
-
|
|
6238
|
+
if (typeof lyric.extend === "object") {
|
|
6239
|
+
const extendAttrs = (lyric.extend.type ? ` type="${lyric.extend.type}"` : "") + colorAttr(lyric.extend.color);
|
|
6240
|
+
out.push(`${indent} <extend${extendAttrs}/>`);
|
|
5713
6241
|
} else {
|
|
5714
6242
|
out.push(`${indent} <extend/>`);
|
|
5715
6243
|
}
|
|
@@ -5807,6 +6335,7 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5807
6335
|
if (dirType.defaultY !== void 0) dynAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5808
6336
|
if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
|
|
5809
6337
|
if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
|
|
6338
|
+
dynAttrs += colorAttr(dirType.color);
|
|
5810
6339
|
out.push(`${indent} <dynamics${dynAttrs}>`);
|
|
5811
6340
|
if (dirType.value) {
|
|
5812
6341
|
out.push(`${indent} <${dirType.value}/>`);
|
|
@@ -5822,6 +6351,7 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5822
6351
|
if (dirType.spread !== void 0) wedgeAttrs += ` spread="${dirType.spread}"`;
|
|
5823
6352
|
if (dirType.defaultY !== void 0) wedgeAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5824
6353
|
if (dirType.relativeX !== void 0) wedgeAttrs += ` relative-x="${dirType.relativeX}"`;
|
|
6354
|
+
wedgeAttrs += colorAttr(dirType.color);
|
|
5825
6355
|
out.push(`${indent} <wedge${wedgeAttrs}/>`);
|
|
5826
6356
|
break;
|
|
5827
6357
|
}
|
|
@@ -5832,6 +6362,7 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5832
6362
|
if (dirType.defaultY !== void 0) metAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5833
6363
|
if (dirType.fontFamily) metAttrs += ` font-family="${escapeXml(dirType.fontFamily)}"`;
|
|
5834
6364
|
if (dirType.fontSize) metAttrs += ` font-size="${escapeXml(dirType.fontSize)}"`;
|
|
6365
|
+
metAttrs += colorAttr(dirType.color);
|
|
5835
6366
|
out.push(`${indent} <metronome${metAttrs}>`);
|
|
5836
6367
|
out.push(`${indent} <beat-unit>${dirType.beatUnit}</beat-unit>`);
|
|
5837
6368
|
if (dirType.beatUnitDot) {
|
|
@@ -5874,6 +6405,7 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5874
6405
|
if (dirType.defaultY !== void 0) rehAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5875
6406
|
if (dirType.fontSize) rehAttrs += ` font-size="${escapeXml(dirType.fontSize)}"`;
|
|
5876
6407
|
if (dirType.fontWeight) rehAttrs += ` font-weight="${escapeXml(dirType.fontWeight)}"`;
|
|
6408
|
+
rehAttrs += colorAttr(dirType.color);
|
|
5877
6409
|
out.push(`${indent} <rehearsal${rehAttrs}>${escapeXml(dirType.text)}</rehearsal>`);
|
|
5878
6410
|
break;
|
|
5879
6411
|
}
|
|
@@ -5884,6 +6416,7 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5884
6416
|
if (dirType.lineType) bracketAttrs += ` line-type="${dirType.lineType}"`;
|
|
5885
6417
|
if (dirType.defaultY !== void 0) bracketAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5886
6418
|
if (dirType.relativeX !== void 0) bracketAttrs += ` relative-x="${dirType.relativeX}"`;
|
|
6419
|
+
bracketAttrs += colorAttr(dirType.color);
|
|
5887
6420
|
out.push(`${indent} <bracket${bracketAttrs}/>`);
|
|
5888
6421
|
break;
|
|
5889
6422
|
}
|
|
@@ -5893,11 +6426,12 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5893
6426
|
if (dirType.dashLength !== void 0) dashAttrs += ` dash-length="${dirType.dashLength}"`;
|
|
5894
6427
|
if (dirType.defaultY !== void 0) dashAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5895
6428
|
if (dirType.spaceLength !== void 0) dashAttrs += ` space-length="${dirType.spaceLength}"`;
|
|
6429
|
+
dashAttrs += colorAttr(dirType.color);
|
|
5896
6430
|
out.push(`${indent} <dashes${dashAttrs}/>`);
|
|
5897
6431
|
break;
|
|
5898
6432
|
}
|
|
5899
6433
|
case "accordion-registration":
|
|
5900
|
-
out.push(`${indent} <accordion-registration>`);
|
|
6434
|
+
out.push(`${indent} <accordion-registration${colorAttr(dirType.color)}>`);
|
|
5901
6435
|
if (dirType.high) {
|
|
5902
6436
|
out.push(`${indent} <accordion-high/>`);
|
|
5903
6437
|
}
|
|
@@ -5920,27 +6454,28 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5920
6454
|
if (dirType.defaultY !== void 0) otherAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5921
6455
|
if (dirType.halign) otherAttrs += ` halign="${escapeXml(dirType.halign)}"`;
|
|
5922
6456
|
if (dirType.printObject === false) otherAttrs += ' print-object="no"';
|
|
6457
|
+
otherAttrs += colorAttr(dirType.color);
|
|
5923
6458
|
out.push(`${indent} <other-direction${otherAttrs}>${escapeXml(dirType.text)}</other-direction>`);
|
|
5924
6459
|
}
|
|
5925
6460
|
break;
|
|
5926
6461
|
case "segno":
|
|
5927
|
-
out.push(`${indent} <segno/>`);
|
|
6462
|
+
out.push(`${indent} <segno${colorAttr(dirType.color)}/>`);
|
|
5928
6463
|
break;
|
|
5929
6464
|
case "coda":
|
|
5930
|
-
out.push(`${indent} <coda/>`);
|
|
6465
|
+
out.push(`${indent} <coda${colorAttr(dirType.color)}/>`);
|
|
5931
6466
|
break;
|
|
5932
6467
|
case "eyeglasses":
|
|
5933
|
-
out.push(`${indent} <eyeglasses/>`);
|
|
6468
|
+
out.push(`${indent} <eyeglasses${colorAttr(dirType.color)}/>`);
|
|
5934
6469
|
break;
|
|
5935
6470
|
case "damp":
|
|
5936
|
-
out.push(`${indent} <damp/>`);
|
|
6471
|
+
out.push(`${indent} <damp${colorAttr(dirType.color)}/>`);
|
|
5937
6472
|
break;
|
|
5938
6473
|
case "damp-all":
|
|
5939
|
-
out.push(`${indent} <damp-all/>`);
|
|
6474
|
+
out.push(`${indent} <damp-all${colorAttr(dirType.color)}/>`);
|
|
5940
6475
|
break;
|
|
5941
6476
|
case "scordatura":
|
|
5942
6477
|
if (dirType.accords && dirType.accords.length > 0) {
|
|
5943
|
-
out.push(`${indent} <scordatura>`);
|
|
6478
|
+
out.push(`${indent} <scordatura${colorAttr(dirType.color)}>`);
|
|
5944
6479
|
for (const accord of dirType.accords) {
|
|
5945
6480
|
out.push(`${indent} <accord string="${accord.string}">`);
|
|
5946
6481
|
out.push(`${indent} <tuning-step>${accord.tuningStep}</tuning-step>`);
|
|
@@ -5952,12 +6487,12 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5952
6487
|
}
|
|
5953
6488
|
out.push(`${indent} </scordatura>`);
|
|
5954
6489
|
} else {
|
|
5955
|
-
out.push(`${indent} <scordatura/>`);
|
|
6490
|
+
out.push(`${indent} <scordatura${colorAttr(dirType.color)}/>`);
|
|
5956
6491
|
}
|
|
5957
6492
|
break;
|
|
5958
6493
|
case "harp-pedals":
|
|
5959
6494
|
if (dirType.pedalTunings && dirType.pedalTunings.length > 0) {
|
|
5960
|
-
out.push(`${indent} <harp-pedals>`);
|
|
6495
|
+
out.push(`${indent} <harp-pedals${colorAttr(dirType.color)}>`);
|
|
5961
6496
|
for (const pt of dirType.pedalTunings) {
|
|
5962
6497
|
out.push(`${indent} <pedal-tuning>`);
|
|
5963
6498
|
out.push(`${indent} <pedal-step>${pt.pedalStep}</pedal-step>`);
|
|
@@ -5966,7 +6501,7 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5966
6501
|
}
|
|
5967
6502
|
out.push(`${indent} </harp-pedals>`);
|
|
5968
6503
|
} else {
|
|
5969
|
-
out.push(`${indent} <harp-pedals/>`);
|
|
6504
|
+
out.push(`${indent} <harp-pedals${colorAttr(dirType.color)}/>`);
|
|
5970
6505
|
}
|
|
5971
6506
|
break;
|
|
5972
6507
|
case "image":
|
|
@@ -5981,12 +6516,13 @@ function serializeDirectionType(dirType, indent, out) {
|
|
|
5981
6516
|
if (dirType.defaultY !== void 0) pedalAttrs += ` default-y="${dirType.defaultY}"`;
|
|
5982
6517
|
if (dirType.relativeX !== void 0) pedalAttrs += ` relative-x="${dirType.relativeX}"`;
|
|
5983
6518
|
if (dirType.halign) pedalAttrs += ` halign="${dirType.halign}"`;
|
|
6519
|
+
pedalAttrs += colorAttr(dirType.color);
|
|
5984
6520
|
out.push(`${indent} <pedal${pedalAttrs}/>`);
|
|
5985
6521
|
break;
|
|
5986
6522
|
}
|
|
5987
6523
|
case "octave-shift": {
|
|
5988
6524
|
const sizeAttr = dirType.size !== void 0 ? ` size="${dirType.size}"` : "";
|
|
5989
|
-
out.push(`${indent} <octave-shift type="${dirType.type}"${sizeAttr}/>`);
|
|
6525
|
+
out.push(`${indent} <octave-shift type="${dirType.type}"${sizeAttr}${colorAttr(dirType.color)}/>`);
|
|
5990
6526
|
break;
|
|
5991
6527
|
}
|
|
5992
6528
|
case "swing":
|
|
@@ -6014,12 +6550,13 @@ function serializeBarline(barline, indent, out) {
|
|
|
6014
6550
|
if (barline._id) attrs += ` id="${escapeXml(barline._id)}"`;
|
|
6015
6551
|
out.push(`${indent}<barline${attrs}>`);
|
|
6016
6552
|
if (barline.barStyle) {
|
|
6017
|
-
out.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
|
|
6553
|
+
out.push(`${indent} <bar-style${colorAttr(barline.barStyleColor)}>${barline.barStyle}</bar-style>`);
|
|
6018
6554
|
}
|
|
6019
6555
|
if (barline.ending) {
|
|
6020
6556
|
let endingAttrs = ` number="${barline.ending.number}" type="${barline.ending.type}"`;
|
|
6021
6557
|
if (barline.ending.defaultY !== void 0) endingAttrs += ` default-y="${barline.ending.defaultY}"`;
|
|
6022
6558
|
if (barline.ending.endLength !== void 0) endingAttrs += ` end-length="${barline.ending.endLength}"`;
|
|
6559
|
+
endingAttrs += colorAttr(barline.ending.color);
|
|
6023
6560
|
if (barline.ending.text) {
|
|
6024
6561
|
out.push(`${indent} <ending${endingAttrs}>${escapeXml(barline.ending.text)}</ending>`);
|
|
6025
6562
|
} else {
|
|
@@ -6047,6 +6584,9 @@ function escapeXml(str) {
|
|
|
6047
6584
|
if (!XML_ESCAPE_TEST.test(str)) return str;
|
|
6048
6585
|
return str.replace(XML_ESCAPE_RE, (ch) => XML_ESCAPE_MAP[ch]);
|
|
6049
6586
|
}
|
|
6587
|
+
function colorAttr(color) {
|
|
6588
|
+
return color ? ` color="${escapeXml(color)}"` : "";
|
|
6589
|
+
}
|
|
6050
6590
|
function buildAttrs(attrs) {
|
|
6051
6591
|
let result = "";
|
|
6052
6592
|
for (const key in attrs) {
|
|
@@ -6095,7 +6635,7 @@ function serializeStaffDetails(sd, indent, out) {
|
|
|
6095
6635
|
out.push(`${indent}</staff-details>`);
|
|
6096
6636
|
}
|
|
6097
6637
|
function serializeMeasureStyle(ms, indent, out) {
|
|
6098
|
-
out.push(`${indent}<measure-style${buildAttrs({ number: ms.number })}>`);
|
|
6638
|
+
out.push(`${indent}<measure-style${buildAttrs({ number: ms.number, color: ms.color })}>`);
|
|
6099
6639
|
pushOptionalElement(out, `${indent} `, "multiple-rest", ms.multipleRest);
|
|
6100
6640
|
if (ms.measureRepeat) {
|
|
6101
6641
|
const mrAttrs = buildAttrs({
|
|
@@ -6125,7 +6665,8 @@ function serializeHarmony(harmony, indent, out) {
|
|
|
6125
6665
|
"print-frame": harmony.printFrame,
|
|
6126
6666
|
"default-y": harmony.defaultY,
|
|
6127
6667
|
halign: harmony.halign,
|
|
6128
|
-
"font-size": harmony.fontSize
|
|
6668
|
+
"font-size": harmony.fontSize,
|
|
6669
|
+
color: harmony.color
|
|
6129
6670
|
});
|
|
6130
6671
|
out.push(`${indent}<harmony${attrs}>`);
|
|
6131
6672
|
out.push(`${indent} <root>`);
|
|
@@ -6137,6 +6678,7 @@ function serializeHarmony(harmony, indent, out) {
|
|
|
6137
6678
|
let kindAttrs = "";
|
|
6138
6679
|
if (harmony.kindText !== void 0) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
|
|
6139
6680
|
if (harmony.kindHalign) kindAttrs += ` halign="${escapeXml(harmony.kindHalign)}"`;
|
|
6681
|
+
kindAttrs += colorAttr(harmony.kindColor);
|
|
6140
6682
|
out.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
|
|
6141
6683
|
if (harmony.bass) {
|
|
6142
6684
|
let bassAttrs = "";
|
|
@@ -6163,7 +6705,7 @@ function serializeHarmony(harmony, indent, out) {
|
|
|
6163
6705
|
}
|
|
6164
6706
|
}
|
|
6165
6707
|
if (harmony.frame) {
|
|
6166
|
-
out.push(`${indent} <frame>`);
|
|
6708
|
+
out.push(`${indent} <frame${colorAttr(harmony.frame.color)}>`);
|
|
6167
6709
|
if (harmony.frame.frameStrings !== void 0) {
|
|
6168
6710
|
out.push(`${indent} <frame-strings>${harmony.frame.frameStrings}</frame-strings>`);
|
|
6169
6711
|
}
|
|
@@ -6204,6 +6746,7 @@ function serializeFiguredBass(fb, indent, out) {
|
|
|
6204
6746
|
let attrs = "";
|
|
6205
6747
|
if (fb._id) attrs += ` id="${escapeXml(fb._id)}"`;
|
|
6206
6748
|
if (fb.parentheses) attrs += ' parentheses="yes"';
|
|
6749
|
+
attrs += colorAttr(fb.color);
|
|
6207
6750
|
out.push(`${indent}<figured-bass${attrs}>`);
|
|
6208
6751
|
for (const fig of fb.figures) {
|
|
6209
6752
|
out.push(`${indent} <figure>`);
|
|
@@ -6217,8 +6760,9 @@ function serializeFiguredBass(fb, indent, out) {
|
|
|
6217
6760
|
out.push(`${indent} <suffix>${escapeXml(fig.suffix)}</suffix>`);
|
|
6218
6761
|
}
|
|
6219
6762
|
if (fig.extend) {
|
|
6220
|
-
if (typeof fig.extend === "object"
|
|
6221
|
-
|
|
6763
|
+
if (typeof fig.extend === "object") {
|
|
6764
|
+
const extendAttrs = (fig.extend.type ? ` type="${fig.extend.type}"` : "") + colorAttr(fig.extend.color);
|
|
6765
|
+
out.push(`${indent} <extend${extendAttrs}/>`);
|
|
6222
6766
|
} else {
|
|
6223
6767
|
out.push(`${indent} <extend/>`);
|
|
6224
6768
|
}
|
|
@@ -6401,8 +6945,10 @@ function buildMidiTracks(score, options) {
|
|
|
6401
6945
|
let program = 1;
|
|
6402
6946
|
if (partEntry && partEntry.midiInstruments?.[0]) {
|
|
6403
6947
|
const midiInst = partEntry.midiInstruments[0];
|
|
6404
|
-
|
|
6405
|
-
|
|
6948
|
+
if (midiInst.channel !== void 0) {
|
|
6949
|
+
channel = Math.min(16, Math.max(1, midiInst.channel)) - 1;
|
|
6950
|
+
}
|
|
6951
|
+
program = Math.min(128, Math.max(1, midiInst.program ?? program));
|
|
6406
6952
|
}
|
|
6407
6953
|
const trackData = createPartTrack(
|
|
6408
6954
|
part,
|
|
@@ -6682,6 +7228,8 @@ function buildMidiFile(tracks, ticksPerQuarterNote) {
|
|
|
6682
7228
|
var DIVISIONS_PER_QUARTER = 960;
|
|
6683
7229
|
var useExplicitHalf = false;
|
|
6684
7230
|
var useIndividualChordDurations = false;
|
|
7231
|
+
var openWedge = null;
|
|
7232
|
+
var voiceOctaveShift = 0;
|
|
6685
7233
|
var FIFTHS_TO_KEY_MAJOR = {
|
|
6686
7234
|
[-7]: "Cb",
|
|
6687
7235
|
[-6]: "Gb",
|
|
@@ -6740,15 +7288,21 @@ var NOTE_TYPE_TO_QUARTER_LENGTH = {
|
|
|
6740
7288
|
"128th": 0.03125
|
|
6741
7289
|
};
|
|
6742
7290
|
function musicXmlClefToAbc(clef) {
|
|
6743
|
-
|
|
6744
|
-
if (clef.sign === "
|
|
6745
|
-
if (clef.sign === "
|
|
6746
|
-
if (clef.sign === "
|
|
6747
|
-
if (clef.sign === "C" && clef.line ===
|
|
6748
|
-
if (clef.sign === "C" && clef.line ===
|
|
6749
|
-
if (clef.sign === "C" && clef.line ===
|
|
6750
|
-
if (clef.sign === "
|
|
6751
|
-
|
|
7291
|
+
let name = null;
|
|
7292
|
+
if (clef.sign === "G" && (clef.line === 2 || clef.line === void 0)) name = "treble";
|
|
7293
|
+
else if (clef.sign === "F" && clef.line === 3) name = "bass3";
|
|
7294
|
+
else if (clef.sign === "F" && (clef.line === 4 || clef.line === void 0)) name = "bass";
|
|
7295
|
+
else if (clef.sign === "C" && clef.line === 3) name = "alto";
|
|
7296
|
+
else if (clef.sign === "C" && clef.line === 4) name = "tenor";
|
|
7297
|
+
else if (clef.sign === "C" && clef.line === 1) name = "soprano";
|
|
7298
|
+
else if (clef.sign === "C" && clef.line === 2) name = "mezzo-soprano";
|
|
7299
|
+
else if (clef.sign === "C" && clef.line === 5) name = "baritone";
|
|
7300
|
+
else if (clef.sign === "percussion") name = "perc";
|
|
7301
|
+
if (name === null) return null;
|
|
7302
|
+
const shift = clef.clefOctaveChange;
|
|
7303
|
+
if (shift === void 0 || shift === 0) return name;
|
|
7304
|
+
const steps = Math.abs(shift) === 2 ? "15" : "8";
|
|
7305
|
+
return `${name}${shift > 0 ? "+" : "-"}${steps}`;
|
|
6752
7306
|
}
|
|
6753
7307
|
function serializeKey2(key) {
|
|
6754
7308
|
const mode = key.mode || "major";
|
|
@@ -6786,17 +7340,18 @@ function modeToAbcString(mode) {
|
|
|
6786
7340
|
}
|
|
6787
7341
|
}
|
|
6788
7342
|
function serializeTimeSignature(time) {
|
|
7343
|
+
if (time.senzaMisura) return "none";
|
|
6789
7344
|
if (time.symbol === "common") return "C";
|
|
6790
7345
|
if (time.symbol === "cut") return "C|";
|
|
7346
|
+
if (time.beats.includes("+")) return `(${time.beats})/${time.beatType}`;
|
|
6791
7347
|
return `${time.beats}/${time.beatType}`;
|
|
6792
7348
|
}
|
|
6793
7349
|
function computeUnitNoteLength(score) {
|
|
6794
7350
|
const firstMeasure = score.parts[0]?.measures[0];
|
|
6795
7351
|
const time = firstMeasure?.attributes?.time;
|
|
6796
7352
|
if (time) {
|
|
6797
|
-
const beats =
|
|
6798
|
-
const
|
|
6799
|
-
const ratio = beats / beatType;
|
|
7353
|
+
const beats = time.beats.split("+").reduce((sum, b) => sum + parseInt(b, 10), 0);
|
|
7354
|
+
const ratio = beats / time.beatType;
|
|
6800
7355
|
if (ratio < 0.75) {
|
|
6801
7356
|
return { num: 1, den: 16 };
|
|
6802
7357
|
}
|
|
@@ -6826,6 +7381,17 @@ function formatAbcDuration(num, den) {
|
|
|
6826
7381
|
}
|
|
6827
7382
|
return `${num}/${den}`;
|
|
6828
7383
|
}
|
|
7384
|
+
function serializeMicrotone(alter) {
|
|
7385
|
+
const symbol = alter > 0 ? "^" : "_";
|
|
7386
|
+
const magnitude = Math.abs(alter);
|
|
7387
|
+
for (let den = 2; den <= 64; den++) {
|
|
7388
|
+
const num = magnitude * den;
|
|
7389
|
+
if (Math.abs(num - Math.round(num)) < 1e-9) {
|
|
7390
|
+
return `${symbol}${Math.round(num)}/${den}`;
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
return magnitude >= 1.5 ? symbol + symbol : symbol;
|
|
7394
|
+
}
|
|
6829
7395
|
function serializePitch2(pitch, explicitNatural) {
|
|
6830
7396
|
let result = "";
|
|
6831
7397
|
if (explicitNatural) {
|
|
@@ -6835,9 +7401,10 @@ function serializePitch2(pitch, explicitNatural) {
|
|
|
6835
7401
|
else if (pitch.alter === 2) result += "^^";
|
|
6836
7402
|
else if (pitch.alter === -1) result += "_";
|
|
6837
7403
|
else if (pitch.alter === -2) result += "__";
|
|
7404
|
+
else result += serializeMicrotone(pitch.alter);
|
|
6838
7405
|
}
|
|
6839
7406
|
const step = pitch.step;
|
|
6840
|
-
const octave = pitch.octave;
|
|
7407
|
+
const octave = pitch.octave - voiceOctaveShift;
|
|
6841
7408
|
if (octave >= 5) {
|
|
6842
7409
|
result += step.toLowerCase();
|
|
6843
7410
|
for (let o = 6; o <= octave; o++) {
|
|
@@ -6912,14 +7479,85 @@ function serializeHarmony2(harmony) {
|
|
|
6912
7479
|
}
|
|
6913
7480
|
return '"' + result + '"';
|
|
6914
7481
|
}
|
|
6915
|
-
function
|
|
7482
|
+
function notationToDecoration(notation) {
|
|
7483
|
+
switch (notation.type) {
|
|
7484
|
+
case "articulation":
|
|
7485
|
+
return { kind: "articulation", articulation: notation.articulation };
|
|
7486
|
+
case "ornament":
|
|
7487
|
+
if (notation.ornament === "wavy-line") {
|
|
7488
|
+
if (notation.wavyLineType === "start" || notation.wavyLineType === "stop") {
|
|
7489
|
+
return { kind: "trill-line", wavyLineType: notation.wavyLineType };
|
|
7490
|
+
}
|
|
7491
|
+
return null;
|
|
7492
|
+
}
|
|
7493
|
+
return { kind: "ornament", ornament: notation.ornament };
|
|
7494
|
+
case "technical":
|
|
7495
|
+
if (notation.technical === "fingering") {
|
|
7496
|
+
const digits = notation.text ?? notation.fingering;
|
|
7497
|
+
return digits !== void 0 ? { kind: "fingering", fingering: digits } : null;
|
|
7498
|
+
}
|
|
7499
|
+
return { kind: "technical", technical: notation.technical };
|
|
7500
|
+
case "fermata":
|
|
7501
|
+
return { kind: "fermata", inverted: notation.fermataType === "inverted" };
|
|
7502
|
+
case "arpeggiate":
|
|
7503
|
+
return { kind: "arpeggiate" };
|
|
7504
|
+
default:
|
|
7505
|
+
return null;
|
|
7506
|
+
}
|
|
7507
|
+
}
|
|
7508
|
+
function serializeNoteDecorations(note) {
|
|
7509
|
+
if (!note.notations) return "";
|
|
7510
|
+
let result = "";
|
|
7511
|
+
for (const notation of note.notations) {
|
|
7512
|
+
const target = notationToDecoration(notation);
|
|
7513
|
+
if (!target) continue;
|
|
7514
|
+
const spelling = abcDecorationFor(target);
|
|
7515
|
+
if (spelling) result += spelling;
|
|
7516
|
+
}
|
|
7517
|
+
return result;
|
|
7518
|
+
}
|
|
7519
|
+
function serializeDirectionText(direction, opts) {
|
|
6916
7520
|
for (const dt of direction.directionTypes) {
|
|
6917
|
-
|
|
6918
|
-
|
|
7521
|
+
switch (dt.kind) {
|
|
7522
|
+
case "dynamics":
|
|
7523
|
+
return opts.includeDynamics && dt.value ? `!${dt.value}!` : null;
|
|
7524
|
+
case "wedge": {
|
|
7525
|
+
if (dt.type === "stop") {
|
|
7526
|
+
const closing = openWedge ?? "crescendo";
|
|
7527
|
+
openWedge = null;
|
|
7528
|
+
return abcDecorationFor({ kind: "wedge", wedge: closing, stop: true });
|
|
7529
|
+
}
|
|
7530
|
+
openWedge = dt.type;
|
|
7531
|
+
return abcDecorationFor({ kind: "wedge", wedge: dt.type, stop: false });
|
|
7532
|
+
}
|
|
7533
|
+
case "segno":
|
|
7534
|
+
return abcDecorationFor({ kind: "segno" });
|
|
7535
|
+
case "coda":
|
|
7536
|
+
return abcDecorationFor({ kind: "coda" });
|
|
7537
|
+
case "words":
|
|
7538
|
+
return serializeWordsDirection(dt.text, direction);
|
|
7539
|
+
default:
|
|
7540
|
+
break;
|
|
6919
7541
|
}
|
|
6920
7542
|
}
|
|
6921
7543
|
return null;
|
|
6922
7544
|
}
|
|
7545
|
+
function serializeWordsDirection(text, direction) {
|
|
7546
|
+
if (/^![^!]+!$/.test(text)) return text;
|
|
7547
|
+
if (text.length === 1 && ABC_SHORTHAND_DECORATIONS.has(text)) return text;
|
|
7548
|
+
if (/^y\d*$/.test(text)) return text;
|
|
7549
|
+
const asDecoration = abcDecorationFor({ kind: "words", text });
|
|
7550
|
+
if (asDecoration) return asDecoration;
|
|
7551
|
+
if (text === "") return null;
|
|
7552
|
+
const halign = direction.directionTypes.find((d) => d.kind === "words" && d.text === text);
|
|
7553
|
+
let marker = "^";
|
|
7554
|
+
if (direction.placement === "below") marker = "_";
|
|
7555
|
+
else if (direction.placement === "above") marker = "^";
|
|
7556
|
+
else if (halign?.halign === "right") marker = "<";
|
|
7557
|
+
else if (halign?.halign === "left") marker = ">";
|
|
7558
|
+
else if (halign?.halign === "center") marker = "@";
|
|
7559
|
+
return `"${marker}${text}"`;
|
|
7560
|
+
}
|
|
6923
7561
|
function serializeTempo(direction) {
|
|
6924
7562
|
for (const dt of direction.directionTypes) {
|
|
6925
7563
|
if (dt.kind === "metronome") {
|
|
@@ -6933,6 +7571,13 @@ function serializeTempo(direction) {
|
|
|
6933
7571
|
}
|
|
6934
7572
|
return null;
|
|
6935
7573
|
}
|
|
7574
|
+
function serializeAbcTunes(scores, options) {
|
|
7575
|
+
return scores.map((score, index) => serializeAbc(score, {
|
|
7576
|
+
...options,
|
|
7577
|
+
// Number the tunes when they carry no reference number of their own
|
|
7578
|
+
referenceNumber: options?.referenceNumber ?? index + 1
|
|
7579
|
+
})).join("\n");
|
|
7580
|
+
}
|
|
6936
7581
|
function serializeAbc(score, options) {
|
|
6937
7582
|
const opts = {
|
|
6938
7583
|
referenceNumber: options?.referenceNumber ?? 1,
|
|
@@ -6941,6 +7586,8 @@ function serializeAbc(score, options) {
|
|
|
6941
7586
|
includeDynamics: options?.includeDynamics ?? true,
|
|
6942
7587
|
includeLyrics: options?.includeLyrics ?? true
|
|
6943
7588
|
};
|
|
7589
|
+
openWedge = null;
|
|
7590
|
+
voiceOctaveShift = 0;
|
|
6944
7591
|
useExplicitHalf = score.metadata.miscellaneous?.find((m) => m.name === "abc-explicit-half")?.value === "true";
|
|
6945
7592
|
useIndividualChordDurations = score.metadata.miscellaneous?.some((m) => m.name === "abc-chord-individual-durations" && m.value === "true") ?? false;
|
|
6946
7593
|
const storedUnitNote = score.metadata.miscellaneous?.find((m) => m.name === "abc-unit-note-length")?.value;
|
|
@@ -7015,11 +7662,13 @@ function serializeAbc(score, options) {
|
|
|
7015
7662
|
lines.push(`Q:${tempoStr}`);
|
|
7016
7663
|
}
|
|
7017
7664
|
}
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7665
|
+
let clefSuffix = "";
|
|
7666
|
+
if (score.parts.length === 1) {
|
|
7667
|
+
const clef = attrs?.clef?.[0];
|
|
7668
|
+
const clefName = clef ? musicXmlClefToAbc(clef) : null;
|
|
7669
|
+
if (clefName && clefName !== "treble") clefSuffix = ` clef=${clefName}`;
|
|
7022
7670
|
}
|
|
7671
|
+
lines.push(`K:${attrs?.key ? serializeKey2(attrs.key) : "C"}${clefSuffix}`);
|
|
7023
7672
|
}
|
|
7024
7673
|
const lineBreaksStr = score.metadata.miscellaneous?.find((m) => m.name === "abc-line-breaks")?.value;
|
|
7025
7674
|
const lineBreaks = lineBreaksStr ? JSON.parse(lineBreaksStr) : [];
|
|
@@ -7028,6 +7677,8 @@ function serializeAbc(score, options) {
|
|
|
7028
7677
|
const lyricsAfterAll = score.metadata.miscellaneous?.find((m) => m.name === "abc-lyrics-after-all")?.value === "true";
|
|
7029
7678
|
const lyricsLineCountsStr = score.metadata.miscellaneous?.find((m) => m.name === "abc-lyrics-line-counts")?.value;
|
|
7030
7679
|
const lyricsLineCounts = lyricsLineCountsStr ? JSON.parse(lyricsLineCountsStr) : [];
|
|
7680
|
+
const lyricsLineVersesStr = score.metadata.miscellaneous?.find((m) => m.name === "abc-lyrics-line-verses")?.value;
|
|
7681
|
+
const lyricsLineVerses = lyricsLineVersesStr ? JSON.parse(lyricsLineVersesStr) : [];
|
|
7031
7682
|
const inlineVoiceMarkersStr = score.metadata.miscellaneous?.find((m) => m.name === "abc-inline-voice-markers")?.value;
|
|
7032
7683
|
const inlineVoiceMarkers = inlineVoiceMarkersStr ? JSON.parse(inlineVoiceMarkersStr) : {};
|
|
7033
7684
|
const useInlineVoiceMarkers = Object.keys(inlineVoiceMarkers).length > 0;
|
|
@@ -7063,19 +7714,27 @@ function serializeAbc(score, options) {
|
|
|
7063
7714
|
const partDivisions = [];
|
|
7064
7715
|
for (let partIdx = 0; partIdx < score.parts.length; partIdx++) {
|
|
7065
7716
|
const part = score.parts[partIdx];
|
|
7717
|
+
voiceOctaveShift = readVoiceOctaveShift(score, partIdx);
|
|
7066
7718
|
const divisions = getPartDivisions(part);
|
|
7067
7719
|
partDivisions.push(divisions);
|
|
7068
7720
|
const measStrings = [];
|
|
7069
7721
|
let currentUnitNote = { ...unitNote };
|
|
7070
7722
|
let currentKey = part.measures[0]?.attributes?.key;
|
|
7723
|
+
const absorbedMeasures = /* @__PURE__ */ new Set();
|
|
7071
7724
|
for (let mi = 0; mi < part.measures.length; mi++) {
|
|
7725
|
+
if (absorbedMeasures.has(mi)) {
|
|
7726
|
+
measStrings.push("");
|
|
7727
|
+
continue;
|
|
7728
|
+
}
|
|
7072
7729
|
const measure = part.measures[mi];
|
|
7073
7730
|
const measDivisions = measure.attributes?.divisions ?? divisions;
|
|
7074
7731
|
let measureStr = "";
|
|
7075
7732
|
if (mi > 0 && measure.attributes?.key) {
|
|
7076
7733
|
const newKey = measure.attributes.key;
|
|
7077
7734
|
if (currentKey === void 0 || newKey.fifths !== currentKey.fifths || (newKey.mode || "major") !== (currentKey.mode || "major")) {
|
|
7078
|
-
|
|
7735
|
+
if (!hasInlineKeyMarker(measure)) {
|
|
7736
|
+
measureStr += "\nK:" + serializeKey2(newKey) + "\n";
|
|
7737
|
+
}
|
|
7079
7738
|
currentKey = newKey;
|
|
7080
7739
|
}
|
|
7081
7740
|
}
|
|
@@ -7085,10 +7744,13 @@ function serializeAbc(score, options) {
|
|
|
7085
7744
|
}
|
|
7086
7745
|
const { noteStr, updatedUnitNote } = serializeMeasureEntries(measure, measDivisions, currentUnitNote, opts);
|
|
7087
7746
|
if (updatedUnitNote) currentUnitNote = updatedUnitNote;
|
|
7088
|
-
|
|
7747
|
+
const collapsed = applyMultiMeasureRest(measure, noteStr);
|
|
7748
|
+
for (let k = 1; k <= collapsed.absorbed; k++) absorbedMeasures.add(mi + k);
|
|
7749
|
+
measureStr += collapsed.text;
|
|
7089
7750
|
const rightBarline = measure.barlines?.find((b) => b.location === "right");
|
|
7090
7751
|
if (rightBarline) {
|
|
7091
7752
|
measureStr += serializeBarline2(rightBarline);
|
|
7753
|
+
} else if (!measureHasNotes(measure)) {
|
|
7092
7754
|
} else if (mi < part.measures.length - 1) {
|
|
7093
7755
|
const nextMeasure = part.measures[mi + 1];
|
|
7094
7756
|
const nextLeftBarline = nextMeasure?.barlines?.find((b) => b.location === "left" && (b.barStyle || b.repeat));
|
|
@@ -7177,7 +7839,7 @@ function serializeAbc(score, options) {
|
|
|
7177
7839
|
}
|
|
7178
7840
|
}
|
|
7179
7841
|
voiceMeasureCursor[voiceId] = endMeasure;
|
|
7180
|
-
lines.push(groupMusic);
|
|
7842
|
+
lines.push(stripLeadingBlankLines(groupMusic));
|
|
7181
7843
|
}
|
|
7182
7844
|
if (gi < voiceInterleavePattern.length - 1 && commentIdx < bodyComments.length) {
|
|
7183
7845
|
lines.push(bodyComments[commentIdx]);
|
|
@@ -7204,14 +7866,15 @@ function serializeAbc(score, options) {
|
|
|
7204
7866
|
}
|
|
7205
7867
|
}
|
|
7206
7868
|
const divisions = partDivisions[partIdx];
|
|
7207
|
-
|
|
7869
|
+
voiceOctaveShift = readVoiceOctaveShift(score, partIdx);
|
|
7870
|
+
const bodyResult = serializePartBody(part, divisions, unitNote, opts, lineBreaks, lyricsAfterAll, lyricsLineCounts, lyricsLineVerses);
|
|
7208
7871
|
let musicLine = bodyResult.music;
|
|
7209
7872
|
if (multiVoice && useInlineVoiceMarkers) {
|
|
7210
7873
|
const voiceId = storedVoiceIds[partIdx] || String(partIdx + 1);
|
|
7211
7874
|
const marker = inlineVoiceMarkers[voiceId] || `[V:${voiceId}]`;
|
|
7212
7875
|
musicLine = marker + musicLine;
|
|
7213
7876
|
}
|
|
7214
|
-
lines.push(musicLine);
|
|
7877
|
+
lines.push(stripLeadingBlankLines(musicLine));
|
|
7215
7878
|
if (opts.includeLyrics && bodyResult.lyrics) {
|
|
7216
7879
|
lines.push(bodyResult.lyrics);
|
|
7217
7880
|
}
|
|
@@ -7230,6 +7893,16 @@ function serializeAbc(score, options) {
|
|
|
7230
7893
|
}
|
|
7231
7894
|
return lines.join("\n") + "\n";
|
|
7232
7895
|
}
|
|
7896
|
+
function stripLeadingBlankLines(text) {
|
|
7897
|
+
return text.replace(/^\n+/, "");
|
|
7898
|
+
}
|
|
7899
|
+
function readVoiceOctaveShift(score, partIndex) {
|
|
7900
|
+
const stored = score.metadata.miscellaneous?.find(
|
|
7901
|
+
(m) => m.name === `abc-voice-octave-${partIndex}`
|
|
7902
|
+
)?.value;
|
|
7903
|
+
const shift = stored ? parseInt(stored, 10) : 0;
|
|
7904
|
+
return Number.isFinite(shift) ? shift : 0;
|
|
7905
|
+
}
|
|
7233
7906
|
function findTempoInMeasure(measure) {
|
|
7234
7907
|
if (!measure) return null;
|
|
7235
7908
|
for (const entry of measure.entries) {
|
|
@@ -7253,18 +7926,22 @@ function getPartDivisions(part) {
|
|
|
7253
7926
|
}
|
|
7254
7927
|
return DIVISIONS_PER_QUARTER;
|
|
7255
7928
|
}
|
|
7256
|
-
function serializePartBody(part, divisions, initialUnitNote, opts, lineBreaks = [], lyricsAfterAll = false, lyricsLineCounts = []) {
|
|
7929
|
+
function serializePartBody(part, divisions, initialUnitNote, opts, lineBreaks = [], lyricsAfterAll = false, lyricsLineCounts = [], lyricsLineVerses = []) {
|
|
7257
7930
|
let unitNote = { ...initialUnitNote };
|
|
7258
7931
|
const musicParts = [];
|
|
7259
7932
|
const allLyrics = /* @__PURE__ */ new Map();
|
|
7260
7933
|
let currentKey = part.measures[0]?.attributes?.key;
|
|
7934
|
+
const absorbedMeasures = /* @__PURE__ */ new Set();
|
|
7261
7935
|
for (let mi = 0; mi < part.measures.length; mi++) {
|
|
7936
|
+
if (absorbedMeasures.has(mi)) continue;
|
|
7262
7937
|
const measure = part.measures[mi];
|
|
7263
7938
|
const measDivisions = measure.attributes?.divisions ?? divisions;
|
|
7264
7939
|
if (mi > 0 && measure.attributes?.key) {
|
|
7265
7940
|
const newKey = measure.attributes.key;
|
|
7266
7941
|
if (currentKey === void 0 || newKey.fifths !== currentKey.fifths || (newKey.mode || "major") !== (currentKey.mode || "major")) {
|
|
7267
|
-
|
|
7942
|
+
if (!hasInlineKeyMarker(measure)) {
|
|
7943
|
+
musicParts.push("\nK:" + serializeKey2(newKey) + "\n");
|
|
7944
|
+
}
|
|
7268
7945
|
currentKey = newKey;
|
|
7269
7946
|
}
|
|
7270
7947
|
}
|
|
@@ -7281,13 +7958,16 @@ function serializePartBody(part, divisions, initialUnitNote, opts, lineBreaks =
|
|
|
7281
7958
|
if (updatedUnitNote) {
|
|
7282
7959
|
unitNote = updatedUnitNote;
|
|
7283
7960
|
}
|
|
7284
|
-
|
|
7285
|
-
|
|
7961
|
+
const collapsed = applyMultiMeasureRest(measure, noteStr);
|
|
7962
|
+
for (let k = 1; k <= collapsed.absorbed; k++) absorbedMeasures.add(mi + k);
|
|
7963
|
+
musicParts.push(collapsed.text);
|
|
7964
|
+
if (lyrics.size > 0) {
|
|
7286
7965
|
allLyrics.set(mi, lyrics);
|
|
7287
7966
|
}
|
|
7288
7967
|
const rightBarline = measure.barlines?.find((b) => b.location === "right");
|
|
7289
7968
|
if (rightBarline) {
|
|
7290
7969
|
musicParts.push(serializeBarline2(rightBarline));
|
|
7970
|
+
} else if (!measureHasNotes(measure)) {
|
|
7291
7971
|
} else if (mi < part.measures.length - 1) {
|
|
7292
7972
|
const nextMeas = part.measures[mi + 1];
|
|
7293
7973
|
const nextLeftBar = nextMeas?.barlines?.find((b) => b.location === "left" && (b.barStyle || b.repeat));
|
|
@@ -7314,18 +7994,27 @@ function serializePartBody(part, divisions, initialUnitNote, opts, lineBreaks =
|
|
|
7314
7994
|
if (allLyrics.size === 0) {
|
|
7315
7995
|
return { music: musicStr, lyrics: null };
|
|
7316
7996
|
}
|
|
7997
|
+
const verses = /* @__PURE__ */ new Set();
|
|
7998
|
+
for (const byVerse of allLyrics.values()) {
|
|
7999
|
+
for (const verse of byVerse.keys()) verses.add(verse);
|
|
8000
|
+
}
|
|
8001
|
+
const sortedVerses = Array.from(verses).sort((a, b) => a - b);
|
|
8002
|
+
function syllablesIn(verse, from, to) {
|
|
8003
|
+
const syllables = [];
|
|
8004
|
+
for (let m = from; m <= to; m++) {
|
|
8005
|
+
const forVerse = allLyrics.get(m)?.get(verse);
|
|
8006
|
+
if (forVerse) syllables.push(...forVerse);
|
|
8007
|
+
}
|
|
8008
|
+
return syllables;
|
|
8009
|
+
}
|
|
7317
8010
|
const lineBreakSet = new Set(lineBreaks.map((v) => Math.abs(v)));
|
|
7318
|
-
const
|
|
8011
|
+
const lyricLineRanges = [];
|
|
7319
8012
|
let lineStart = 0;
|
|
7320
8013
|
for (let mi = 0; mi < part.measures.length; mi++) {
|
|
7321
8014
|
if (lineBreakSet.has(mi + 1) || mi === part.measures.length - 1) {
|
|
7322
|
-
const
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
if (ls) syllables.push(...ls);
|
|
7326
|
-
}
|
|
7327
|
-
if (syllables.length > 0) {
|
|
7328
|
-
lyricsByLine.push({ startMeasure: lineStart, endMeasure: mi, syllables });
|
|
8015
|
+
const hasAny = sortedVerses.some((v) => syllablesIn(v, lineStart, mi).length > 0);
|
|
8016
|
+
if (hasAny) {
|
|
8017
|
+
lyricLineRanges.push({ startMeasure: lineStart, endMeasure: mi });
|
|
7329
8018
|
}
|
|
7330
8019
|
lineStart = mi + 1;
|
|
7331
8020
|
}
|
|
@@ -7345,57 +8034,60 @@ function serializePartBody(part, divisions, initialUnitNote, opts, lineBreaks =
|
|
|
7345
8034
|
}
|
|
7346
8035
|
return result;
|
|
7347
8036
|
}
|
|
7348
|
-
|
|
7349
|
-
const
|
|
7350
|
-
const
|
|
7351
|
-
|
|
7352
|
-
|
|
8037
|
+
function lyricLinesFor(from, to) {
|
|
8038
|
+
const out = [];
|
|
8039
|
+
for (const verse of sortedVerses) {
|
|
8040
|
+
const syllables = syllablesIn(verse, from, to);
|
|
8041
|
+
if (syllables.length > 0) out.push(formatLyrics(syllables));
|
|
7353
8042
|
}
|
|
8043
|
+
return out;
|
|
8044
|
+
}
|
|
8045
|
+
const lastMeasure = part.measures.length - 1;
|
|
8046
|
+
if (lyricsAfterAll) {
|
|
7354
8047
|
if (lyricsLineCounts.length > 0) {
|
|
7355
8048
|
const lyricsLines = [];
|
|
7356
|
-
|
|
7357
|
-
for (
|
|
7358
|
-
const
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
8049
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
8050
|
+
for (let li = 0; li < lyricsLineCounts.length; li++) {
|
|
8051
|
+
const verse = lyricsLineVerses[li] ?? 1;
|
|
8052
|
+
const all = syllablesIn(verse, 0, lastMeasure);
|
|
8053
|
+
const offset = offsets.get(verse) ?? 0;
|
|
8054
|
+
const chunk = all.slice(offset, offset + lyricsLineCounts[li]);
|
|
8055
|
+
if (chunk.length > 0) lyricsLines.push(formatLyrics(chunk));
|
|
8056
|
+
offsets.set(verse, offset + lyricsLineCounts[li]);
|
|
8057
|
+
}
|
|
8058
|
+
for (const verse of sortedVerses) {
|
|
8059
|
+
const all = syllablesIn(verse, 0, lastMeasure);
|
|
8060
|
+
const offset = offsets.get(verse) ?? 0;
|
|
8061
|
+
if (offset < all.length) lyricsLines.push(formatLyrics(all.slice(offset)));
|
|
7366
8062
|
}
|
|
7367
8063
|
return { music: musicStr, lyrics: lyricsLines.join("\n") };
|
|
7368
8064
|
}
|
|
7369
|
-
return { music: musicStr, lyrics:
|
|
8065
|
+
return { music: musicStr, lyrics: lyricLinesFor(0, lastMeasure).join("\n") };
|
|
7370
8066
|
}
|
|
7371
|
-
if (
|
|
8067
|
+
if (lyricLineRanges.length > 0 && lineBreaks.length > 0) {
|
|
7372
8068
|
const musicLines = musicStr.split("\n");
|
|
7373
8069
|
const resultLines = [];
|
|
7374
8070
|
let lyricIdx = 0;
|
|
7375
8071
|
for (let li = 0; li < musicLines.length; li++) {
|
|
7376
8072
|
resultLines.push(musicLines[li]);
|
|
7377
|
-
if (lyricIdx <
|
|
7378
|
-
const
|
|
7379
|
-
resultLines.push(
|
|
8073
|
+
if (lyricIdx < lyricLineRanges.length) {
|
|
8074
|
+
const range = lyricLineRanges[lyricIdx];
|
|
8075
|
+
resultLines.push(...lyricLinesFor(range.startMeasure, range.endMeasure));
|
|
7380
8076
|
lyricIdx++;
|
|
7381
8077
|
}
|
|
7382
8078
|
}
|
|
7383
|
-
while (lyricIdx <
|
|
7384
|
-
|
|
8079
|
+
while (lyricIdx < lyricLineRanges.length) {
|
|
8080
|
+
const range = lyricLineRanges[lyricIdx];
|
|
8081
|
+
resultLines.push(...lyricLinesFor(range.startMeasure, range.endMeasure));
|
|
7385
8082
|
lyricIdx++;
|
|
7386
8083
|
}
|
|
7387
8084
|
return { music: resultLines.join("\n"), lyrics: null };
|
|
7388
8085
|
}
|
|
7389
|
-
|
|
7390
|
-
const sortedMeasures = Array.from(allLyrics.keys()).sort((a, b) => a - b);
|
|
7391
|
-
for (const mi of sortedMeasures) {
|
|
7392
|
-
allSyllables.push(...allLyrics.get(mi));
|
|
7393
|
-
}
|
|
7394
|
-
return { music: musicStr, lyrics: formatLyrics(allSyllables) };
|
|
8086
|
+
return { music: musicStr, lyrics: lyricLinesFor(0, lastMeasure).join("\n") };
|
|
7395
8087
|
}
|
|
7396
8088
|
function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
7397
8089
|
const parts = [];
|
|
7398
|
-
const lyrics =
|
|
8090
|
+
const lyrics = /* @__PURE__ */ new Map();
|
|
7399
8091
|
let currentUnitNote = { ...unitNote };
|
|
7400
8092
|
let chordPitches = [];
|
|
7401
8093
|
let chordDurationStr = "";
|
|
@@ -7413,17 +8105,28 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7413
8105
|
if (note.grace) {
|
|
7414
8106
|
const graceNotes = [];
|
|
7415
8107
|
let gi = ei;
|
|
8108
|
+
let openChord = false;
|
|
7416
8109
|
while (gi < measure.entries.length) {
|
|
7417
8110
|
const ge = measure.entries[gi];
|
|
7418
8111
|
if (ge.type === "note" && ge.grace) {
|
|
7419
8112
|
const gs = serializeNote2(ge, divisions, currentUnitNote, false);
|
|
8113
|
+
const startsChord = !ge.chord && isChordHead(measure.entries, gi);
|
|
8114
|
+
if (startsChord && !openChord) {
|
|
8115
|
+
graceNotes.push("[");
|
|
8116
|
+
openChord = true;
|
|
8117
|
+
} else if (openChord && !ge.chord) {
|
|
8118
|
+
graceNotes.push("]");
|
|
8119
|
+
openChord = startsChord;
|
|
8120
|
+
if (startsChord) graceNotes.push("[");
|
|
8121
|
+
}
|
|
7420
8122
|
graceNotes.push(gs.pitch);
|
|
7421
8123
|
gi++;
|
|
7422
8124
|
} else {
|
|
7423
8125
|
break;
|
|
7424
8126
|
}
|
|
7425
8127
|
}
|
|
7426
|
-
|
|
8128
|
+
if (openChord) graceNotes.push("]");
|
|
8129
|
+
parts.push((note.grace.slash ? "{/" : "{") + graceNotes.join("") + "}");
|
|
7427
8130
|
ei = gi - 1;
|
|
7428
8131
|
break;
|
|
7429
8132
|
}
|
|
@@ -7453,11 +8156,14 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7453
8156
|
chordSlurEnd = "";
|
|
7454
8157
|
}
|
|
7455
8158
|
if (note.lyrics && note.lyrics.length > 0 && opts.includeLyrics) {
|
|
7456
|
-
const lyric
|
|
7457
|
-
|
|
8159
|
+
for (const lyric of note.lyrics) {
|
|
8160
|
+
if (!lyric.text) continue;
|
|
7458
8161
|
const syllabic = lyric.syllabic || "single";
|
|
7459
8162
|
const suffix = syllabic === "begin" || syllabic === "middle" ? "-" : "";
|
|
7460
|
-
|
|
8163
|
+
const verse = lyric.number ?? 1;
|
|
8164
|
+
const bucket = lyrics.get(verse);
|
|
8165
|
+
if (bucket) bucket.push(lyric.text + suffix);
|
|
8166
|
+
else lyrics.set(verse, [lyric.text + suffix]);
|
|
7461
8167
|
}
|
|
7462
8168
|
}
|
|
7463
8169
|
let tupletPrefix = "";
|
|
@@ -7484,18 +8190,20 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7484
8190
|
if (note.notations) {
|
|
7485
8191
|
for (const notation of note.notations) {
|
|
7486
8192
|
if (notation.type === "slur") {
|
|
7487
|
-
if (notation.slurType === "start") slurStart += "(";
|
|
8193
|
+
if (notation.slurType === "start") slurStart += notation.lineType === "dotted" ? ".(" : "(";
|
|
7488
8194
|
if (notation.slurType === "stop") slurEnd += ")";
|
|
7489
8195
|
}
|
|
7490
8196
|
}
|
|
7491
8197
|
}
|
|
8198
|
+
const decorations = serializeNoteDecorations(note);
|
|
7492
8199
|
effectiveSerialized = {
|
|
7493
|
-
full: slurStart + pitchStr + baseDurationStr + tieStr + slurEnd,
|
|
8200
|
+
full: slurStart + decorations + pitchStr + baseDurationStr + tieStr + slurEnd,
|
|
7494
8201
|
pitch: pitchStr,
|
|
7495
8202
|
duration: baseDurationStr,
|
|
7496
8203
|
slurStart,
|
|
7497
8204
|
slurEnd,
|
|
7498
|
-
tieStr
|
|
8205
|
+
tieStr,
|
|
8206
|
+
decorations
|
|
7499
8207
|
};
|
|
7500
8208
|
}
|
|
7501
8209
|
const nextEntry = ei + 1 < measure.entries.length ? measure.entries[ei + 1] : null;
|
|
@@ -7529,7 +8237,7 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7529
8237
|
let insertIdx = parts.length;
|
|
7530
8238
|
for (let pi = parts.length - 1; pi >= 0; pi--) {
|
|
7531
8239
|
const p = parts[pi];
|
|
7532
|
-
if (
|
|
8240
|
+
if (p.length === 1 && ABC_SHORTHAND_DECORATIONS.has(p) || /^![^!]+!$/.test(p) || /^"[^"]*"$/.test(p)) {
|
|
7533
8241
|
insertIdx = pi;
|
|
7534
8242
|
} else {
|
|
7535
8243
|
break;
|
|
@@ -7537,11 +8245,11 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7537
8245
|
}
|
|
7538
8246
|
if (insertIdx < parts.length) {
|
|
7539
8247
|
parts.splice(insertIdx, 0, chordSlurStart);
|
|
7540
|
-
parts.push(tupletPrefix);
|
|
8248
|
+
parts.push(tupletPrefix + effectiveSerialized.decorations);
|
|
7541
8249
|
break;
|
|
7542
8250
|
}
|
|
7543
8251
|
}
|
|
7544
|
-
parts.push(tupletPrefix + chordSlurStart);
|
|
8252
|
+
parts.push(tupletPrefix + chordSlurStart + effectiveSerialized.decorations);
|
|
7545
8253
|
break;
|
|
7546
8254
|
}
|
|
7547
8255
|
if (effectiveSerialized.slurStart) {
|
|
@@ -7556,7 +8264,7 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7556
8264
|
}
|
|
7557
8265
|
if (insertIdx < parts.length) {
|
|
7558
8266
|
parts.splice(insertIdx, 0, effectiveSerialized.slurStart);
|
|
7559
|
-
const noteOnly = effectiveSerialized.pitch + effectiveSerialized.duration + effectiveSerialized.tieStr + effectiveSerialized.slurEnd;
|
|
8267
|
+
const noteOnly = effectiveSerialized.decorations + effectiveSerialized.pitch + effectiveSerialized.duration + effectiveSerialized.tieStr + effectiveSerialized.slurEnd;
|
|
7560
8268
|
parts.push(tupletPrefix + noteOnly);
|
|
7561
8269
|
break;
|
|
7562
8270
|
}
|
|
@@ -7568,9 +8276,9 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7568
8276
|
const tie1 = effectiveSerialized.tieStr;
|
|
7569
8277
|
const slurS1 = effectiveSerialized.slurStart;
|
|
7570
8278
|
const slurE1 = effectiveSerialized.slurEnd;
|
|
7571
|
-
parts.push(tupletPrefix + slurS1 + pitchStr1 + baseDurStr1 + tie1 + brokenResult.marker);
|
|
8279
|
+
parts.push(tupletPrefix + slurS1 + effectiveSerialized.decorations + pitchStr1 + baseDurStr1 + tie1 + brokenResult.marker);
|
|
7572
8280
|
const note2 = brokenResult.nextNote;
|
|
7573
|
-
const pitchStr2 = serializePitch2(note2.pitch, note2.accidental?.value === "natural");
|
|
8281
|
+
const pitchStr2 = serializeNoteDecorations(note2) + serializePitch2(note2.pitch, note2.accidental?.value === "natural");
|
|
7574
8282
|
let tieStr2 = "";
|
|
7575
8283
|
if (note2.tie?.type === "start" || note2.ties?.some((t) => t.type === "start")) tieStr2 = "-";
|
|
7576
8284
|
let slurEnd2 = "";
|
|
@@ -7595,42 +8303,40 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7595
8303
|
case "direction": {
|
|
7596
8304
|
let handledAsSpecial = false;
|
|
7597
8305
|
for (const dt of entry.directionTypes) {
|
|
7598
|
-
if (dt.kind
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
if (
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
if (dt.text.length === 1 && /^[vuTM]$/.test(dt.text)) {
|
|
7623
|
-
parts.push(dt.text);
|
|
7624
|
-
handledAsSpecial = true;
|
|
7625
|
-
break;
|
|
8306
|
+
if (dt.kind !== "words") continue;
|
|
8307
|
+
if (dt.text === "__abc_line_cont__") {
|
|
8308
|
+
parts.push("\\\n");
|
|
8309
|
+
handledAsSpecial = true;
|
|
8310
|
+
break;
|
|
8311
|
+
}
|
|
8312
|
+
if (dt.text === "__abc_line_break__") {
|
|
8313
|
+
parts.push("\n");
|
|
8314
|
+
handledAsSpecial = true;
|
|
8315
|
+
break;
|
|
8316
|
+
}
|
|
8317
|
+
if (dt.text.startsWith(ABC_BODY_FIELD_MARKER)) {
|
|
8318
|
+
parts.push("\n" + dt.text.slice(ABC_BODY_FIELD_MARKER.length));
|
|
8319
|
+
handledAsSpecial = true;
|
|
8320
|
+
break;
|
|
8321
|
+
}
|
|
8322
|
+
const inlineField = dt.text.match(/^\[([A-Za-z]):([^\]]*)\]$/);
|
|
8323
|
+
if (inlineField) {
|
|
8324
|
+
parts.push(dt.text);
|
|
8325
|
+
if (inlineField[1] === "L") {
|
|
8326
|
+
const lMatch = inlineField[2].trim().match(/^(\d+)\/(\d+)$/);
|
|
8327
|
+
if (lMatch) {
|
|
8328
|
+
currentUnitNote = { num: parseInt(lMatch[1], 10), den: parseInt(lMatch[2], 10) };
|
|
8329
|
+
}
|
|
7626
8330
|
}
|
|
8331
|
+
handledAsSpecial = true;
|
|
8332
|
+
break;
|
|
7627
8333
|
}
|
|
7628
8334
|
}
|
|
7629
8335
|
if (handledAsSpecial) break;
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
if (
|
|
7633
|
-
parts.push(
|
|
8336
|
+
const text = serializeDirectionText(entry, opts);
|
|
8337
|
+
if (text) {
|
|
8338
|
+
if (opts.includeChordSymbols || !text.startsWith('"')) {
|
|
8339
|
+
parts.push(text);
|
|
7634
8340
|
}
|
|
7635
8341
|
}
|
|
7636
8342
|
break;
|
|
@@ -7650,6 +8356,29 @@ function serializeMeasureEntries(measure, divisions, unitNote, opts) {
|
|
|
7650
8356
|
const unitNoteChanged = currentUnitNote.num !== unitNote.num || currentUnitNote.den !== unitNote.den;
|
|
7651
8357
|
return { noteStr: parts.join(""), lyrics, updatedUnitNote: unitNoteChanged ? currentUnitNote : void 0 };
|
|
7652
8358
|
}
|
|
8359
|
+
function multipleRestCount(measure) {
|
|
8360
|
+
const styles = measure.attributes?.measureStyle;
|
|
8361
|
+
if (!styles) return 0;
|
|
8362
|
+
for (const style of styles) {
|
|
8363
|
+
if (style.multipleRest && style.multipleRest > 1) return style.multipleRest;
|
|
8364
|
+
}
|
|
8365
|
+
return 0;
|
|
8366
|
+
}
|
|
8367
|
+
function applyMultiMeasureRest(measure, noteStr) {
|
|
8368
|
+
const count = multipleRestCount(measure);
|
|
8369
|
+
if (count < 2 || noteStr.trim() !== "Z") return { text: noteStr, absorbed: 0 };
|
|
8370
|
+
return { text: `Z${count}`, absorbed: count - 1 };
|
|
8371
|
+
}
|
|
8372
|
+
function hasInlineKeyMarker(measure) {
|
|
8373
|
+
return measure.entries.some((e) => e.type === "direction" && e.directionTypes.some((dt) => dt.kind === "words" && /^\[K:/.test(dt.text)));
|
|
8374
|
+
}
|
|
8375
|
+
function measureHasNotes(measure) {
|
|
8376
|
+
return measure.entries.some((e) => e.type === "note");
|
|
8377
|
+
}
|
|
8378
|
+
function isChordHead(entries, index) {
|
|
8379
|
+
const next = entries[index + 1];
|
|
8380
|
+
return next !== void 0 && next.type === "note" && next.chord === true;
|
|
8381
|
+
}
|
|
7653
8382
|
function detectBrokenRhythm(entries, currentIdx, currentNote, divisions, unitNote) {
|
|
7654
8383
|
if (currentNote.chord || currentNote.grace || currentNote.rest || currentNote.timeModification) return null;
|
|
7655
8384
|
let nextIdx = currentIdx + 1;
|
|
@@ -7751,13 +8480,14 @@ function serializeNote2(note, divisions, unitNote, _inChord) {
|
|
|
7751
8480
|
if (note.notations) {
|
|
7752
8481
|
for (const notation of note.notations) {
|
|
7753
8482
|
if (notation.type === "slur") {
|
|
7754
|
-
if (notation.slurType === "start") slurStart += "(";
|
|
8483
|
+
if (notation.slurType === "start") slurStart += notation.lineType === "dotted" ? ".(" : "(";
|
|
7755
8484
|
if (notation.slurType === "stop") slurEnd += ")";
|
|
7756
8485
|
}
|
|
7757
8486
|
}
|
|
7758
8487
|
}
|
|
7759
|
-
const
|
|
7760
|
-
|
|
8488
|
+
const decorations = serializeNoteDecorations(note);
|
|
8489
|
+
const full = slurStart + decorations + pitchStr + durationStr + tieStr + slurEnd;
|
|
8490
|
+
return { full, pitch: pitchStr, duration: durationStr, slurStart, slurEnd, tieStr, decorations };
|
|
7761
8491
|
}
|
|
7762
8492
|
function serializeBarline2(barline) {
|
|
7763
8493
|
const hasRepeatForward = barline.repeat?.direction === "forward";
|
|
@@ -7780,6 +8510,13 @@ function serializeBarline2(barline) {
|
|
|
7780
8510
|
case "heavy-light":
|
|
7781
8511
|
result += "[|";
|
|
7782
8512
|
break;
|
|
8513
|
+
case "none":
|
|
8514
|
+
result += "[|]";
|
|
8515
|
+
break;
|
|
8516
|
+
case "dotted":
|
|
8517
|
+
case "dashed":
|
|
8518
|
+
result += ".|";
|
|
8519
|
+
break;
|
|
7783
8520
|
default:
|
|
7784
8521
|
result += "|";
|
|
7785
8522
|
break;
|
|
@@ -7800,10 +8537,12 @@ export {
|
|
|
7800
8537
|
parseCompressed,
|
|
7801
8538
|
isCompressed,
|
|
7802
8539
|
parseAuto,
|
|
8540
|
+
parseAbcTunes,
|
|
7803
8541
|
parseAbc,
|
|
7804
8542
|
serialize,
|
|
7805
8543
|
serializeCompressed,
|
|
7806
8544
|
exportMidi,
|
|
7807
8545
|
exportMidiWithTimingMap,
|
|
8546
|
+
serializeAbcTunes,
|
|
7808
8547
|
serializeAbc
|
|
7809
8548
|
};
|