musicxml-io 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -512,9 +512,11 @@ function parseDefaults(elements) {
512
512
  const lineWidths = collectElements(appContent, "line-width", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
513
513
  const noteSizes = collectElements(appContent, "note-size", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
514
514
  const distances = collectElements(appContent, "distance", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
515
+ const glyphs = collectElements(appContent, "glyph", (c, a) => ({ type: a["type"] || "", value: extractText(c) }));
515
516
  if (lineWidths.length > 0) appearance["line-widths"] = lineWidths;
516
517
  if (noteSizes.length > 0) appearance["note-sizes"] = noteSizes;
517
518
  if (distances.length > 0) appearance["distances"] = distances;
519
+ if (glyphs.length > 0) appearance["glyphs"] = glyphs;
518
520
  return Object.keys(appearance).length > 0 ? appearance : void 0;
519
521
  });
520
522
  if (appResult) defaults.appearance = appResult;
@@ -536,13 +538,25 @@ function parsePageLayout(elements) {
536
538
  m.type = attrs["type"];
537
539
  }
538
540
  const left = getElementText(content, "left-margin");
539
- if (left) m.leftMargin = parseFloat(left);
541
+ if (left) {
542
+ m.leftMargin = parseFloat(left);
543
+ m.leftMarginRaw = left;
544
+ }
540
545
  const right = getElementText(content, "right-margin");
541
- if (right) m.rightMargin = parseFloat(right);
546
+ if (right) {
547
+ m.rightMargin = parseFloat(right);
548
+ m.rightMarginRaw = right;
549
+ }
542
550
  const top = getElementText(content, "top-margin");
543
- if (top) m.topMargin = parseFloat(top);
551
+ if (top) {
552
+ m.topMargin = parseFloat(top);
553
+ m.topMarginRaw = top;
554
+ }
544
555
  const bottom = getElementText(content, "bottom-margin");
545
- if (bottom) m.bottomMargin = parseFloat(bottom);
556
+ if (bottom) {
557
+ m.bottomMargin = parseFloat(bottom);
558
+ m.bottomMarginRaw = bottom;
559
+ }
546
560
  margins.push(m);
547
561
  }
548
562
  }
@@ -555,14 +569,48 @@ function parseSystemLayout(elements) {
555
569
  if (margins) {
556
570
  layout.systemMargins = {};
557
571
  const left = getElementText(margins, "left-margin");
558
- if (left) layout.systemMargins.leftMargin = parseFloat(left);
572
+ if (left) {
573
+ layout.systemMargins.leftMargin = parseFloat(left);
574
+ layout.systemMargins.leftMarginRaw = left;
575
+ }
559
576
  const right = getElementText(margins, "right-margin");
560
- if (right) layout.systemMargins.rightMargin = parseFloat(right);
577
+ if (right) {
578
+ layout.systemMargins.rightMargin = parseFloat(right);
579
+ layout.systemMargins.rightMarginRaw = right;
580
+ }
561
581
  }
562
582
  const dist = getElementText(elements, "system-distance");
563
- if (dist) layout.systemDistance = parseFloat(dist);
583
+ if (dist) {
584
+ layout.systemDistance = parseFloat(dist);
585
+ layout.systemDistanceRaw = dist;
586
+ }
564
587
  const topDist = getElementText(elements, "top-system-distance");
565
- if (topDist) layout.topSystemDistance = parseFloat(topDist);
588
+ if (topDist) {
589
+ layout.topSystemDistance = parseFloat(topDist);
590
+ layout.topSystemDistanceRaw = topDist;
591
+ }
592
+ const dividers = getElementContent(elements, "system-dividers");
593
+ if (dividers) {
594
+ layout.systemDividers = {};
595
+ for (const el of dividers) {
596
+ if (el["left-divider"]) {
597
+ const attrs = getAttributes(el);
598
+ layout.systemDividers.leftDivider = {
599
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
600
+ halign: attrs["halign"],
601
+ valign: attrs["valign"]
602
+ };
603
+ }
604
+ if (el["right-divider"]) {
605
+ const attrs = getAttributes(el);
606
+ layout.systemDividers.rightDivider = {
607
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
608
+ halign: attrs["halign"],
609
+ valign: attrs["valign"]
610
+ };
611
+ }
612
+ }
613
+ }
566
614
  return layout;
567
615
  }
568
616
  function parseCredits(elements) {
@@ -574,6 +622,7 @@ function parseCredits(elements) {
574
622
  const cw = { text: extractText(c) };
575
623
  if (a["default-x"]) cw.defaultX = parseFloat(a["default-x"]);
576
624
  if (a["default-y"]) cw.defaultY = parseFloat(a["default-y"]);
625
+ if (a["font-family"]) cw.fontFamily = a["font-family"];
577
626
  if (a["font-size"]) cw.fontSize = a["font-size"];
578
627
  if (a["font-weight"]) cw.fontWeight = a["font-weight"];
579
628
  if (a["font-style"]) cw.fontStyle = a["font-style"];
@@ -797,6 +846,15 @@ function parseMeasure(elements, attrs) {
797
846
  measure.entries.push(parseFiguredBass(el["figured-bass"], getAttributes(el)));
798
847
  } else if (el["sound"]) {
799
848
  measure.entries.push(parseSound(el["sound"], getAttributes(el)));
849
+ } else if (el["grouping"] !== void 0) {
850
+ const grpAttrs = getAttributes(el);
851
+ const grouping = {
852
+ _id: generateId(),
853
+ type: "grouping",
854
+ groupingType: grpAttrs["type"] || "start"
855
+ };
856
+ if (grpAttrs["number"]) grouping.number = grpAttrs["number"];
857
+ measure.entries.push(grouping);
800
858
  }
801
859
  }
802
860
  if (barlines.length > 0) measure.barlines = barlines;
@@ -863,6 +921,7 @@ function parseAttributes(elements) {
863
921
  const key = parseKeySignature(keyContent);
864
922
  if (keyAttrs["number"]) key.number = parseInt(keyAttrs["number"], 10);
865
923
  if (keyAttrs["print-object"] === "no") key.printObject = false;
924
+ else if (keyAttrs["print-object"] === "yes") key.printObject = true;
866
925
  keys.push(key);
867
926
  }
868
927
  }
@@ -914,14 +973,15 @@ function parseTimeSignature(elements, parentElements) {
914
973
  return time2;
915
974
  }
916
975
  }
917
- const beatsList = collectElements(elements, "beats", (c) => parseInt(extractText(c), 10));
976
+ const beatsStrList = collectElements(elements, "beats", (c) => extractText(c));
918
977
  const beatTypeList = collectElements(elements, "beat-type", (c) => parseInt(extractText(c), 10));
919
978
  const time = {
920
- beats: beatsList.length > 0 ? String(beatsList[0]) : "4",
979
+ beats: beatsStrList.length > 0 ? beatsStrList[0] : "4",
921
980
  beatType: beatTypeList.length > 0 ? beatTypeList[0] : 4
922
981
  };
923
- if (beatsList.length > 1 || beatTypeList.length > 1) {
924
- time.beatsList = beatsList;
982
+ if (beatsStrList.length > 1 || beatTypeList.length > 1) {
983
+ time.beatsList = beatsStrList.map((b) => parseInt(b, 10));
984
+ time.beatsStrList = beatsStrList;
925
985
  time.beatTypeList = beatTypeList;
926
986
  }
927
987
  for (const el of parentElements) {
@@ -960,6 +1020,7 @@ function parseKeySignature(elements) {
960
1020
  const keyOctaves = collectElements(elements, "key-octave", (c, a) => {
961
1021
  const ko = { number: parseInt(a["number"] || "1", 10), octave: parseInt(extractText(c), 10) };
962
1022
  if (a["cancel"] === "yes") ko.cancel = true;
1023
+ else if (a["cancel"] === "no") ko.cancel = false;
963
1024
  return ko;
964
1025
  });
965
1026
  if (keySteps.length > 0) key.keySteps = keySteps;
@@ -969,8 +1030,11 @@ function parseKeySignature(elements) {
969
1030
  }
970
1031
  function parseClef(elements, attrs) {
971
1032
  const sign = getElementText(elements, "sign") || "G";
972
- const line = parseInt(getElementText(elements, "line") || "2", 10);
973
- const clef = { sign, line };
1033
+ const lineText = getElementText(elements, "line");
1034
+ const clef = { sign };
1035
+ if (lineText) {
1036
+ clef.line = parseInt(lineText, 10);
1037
+ }
974
1038
  if (attrs["number"]) {
975
1039
  clef.staff = parseInt(attrs["number"], 10);
976
1040
  }
@@ -980,6 +1044,8 @@ function parseClef(elements, attrs) {
980
1044
  }
981
1045
  if (attrs["print-object"] === "no") {
982
1046
  clef.printObject = false;
1047
+ } else if (attrs["print-object"] === "yes") {
1048
+ clef.printObject = true;
983
1049
  }
984
1050
  if (attrs["after-barline"] === "yes") {
985
1051
  clef.afterBarline = true;
@@ -1001,15 +1067,20 @@ function parseNote(elements, attrs) {
1001
1067
  const note = {
1002
1068
  _id: generateId(),
1003
1069
  type: "note",
1004
- duration: getElementTextAsInt(elements, "duration", 0),
1005
- voice: getElementTextAsInt(elements, "voice", 1)
1070
+ duration: getElementTextAsInt(elements, "duration", 0)
1006
1071
  };
1072
+ const voiceValue = getElementTextAsInt(elements, "voice");
1073
+ if (voiceValue !== void 0) {
1074
+ note.voice = voiceValue;
1075
+ }
1007
1076
  if (attrs["default-x"]) note.defaultX = parseFloat(attrs["default-x"]);
1008
1077
  if (attrs["default-y"]) note.defaultY = parseFloat(attrs["default-y"]);
1009
1078
  if (attrs["relative-x"]) note.relativeX = parseFloat(attrs["relative-x"]);
1010
1079
  if (attrs["relative-y"]) note.relativeY = parseFloat(attrs["relative-y"]);
1011
1080
  if (attrs["dynamics"]) note.dynamics = parseFloat(attrs["dynamics"]);
1012
1081
  if (attrs["print-object"] === "no") note.printObject = false;
1082
+ if (attrs["print-dot"] === "no") note.printDot = false;
1083
+ if (attrs["print-dot"] === "yes") note.printDot = true;
1013
1084
  if (attrs["print-spacing"] === "yes") note.printSpacing = true;
1014
1085
  if (attrs["print-spacing"] === "no") note.printSpacing = false;
1015
1086
  if (hasElement(elements, "cue")) {
@@ -1122,6 +1193,7 @@ function parseNote(elements, attrs) {
1122
1193
  const graceAttrs = getAttributes(el);
1123
1194
  note.grace = {};
1124
1195
  if (graceAttrs["slash"] === "yes") note.grace.slash = true;
1196
+ else if (graceAttrs["slash"] === "no") note.grace.slash = false;
1125
1197
  if (graceAttrs["steal-time-previous"]) {
1126
1198
  note.grace.stealTimePrevious = parseFloat(graceAttrs["steal-time-previous"]);
1127
1199
  }
@@ -1367,6 +1439,14 @@ function parseNotations(elements, notationsIndex = 0) {
1367
1439
  notations.push(tremNotation);
1368
1440
  }
1369
1441
  }
1442
+ const ornamentNotationsAdded = notations.filter((n) => n.type === "ornament" && n.notationsIndex === notationsIndex);
1443
+ if (ornamentNotationsAdded.length === 0) {
1444
+ notations.push({
1445
+ type: "ornament",
1446
+ ornament: "empty",
1447
+ notationsIndex
1448
+ });
1449
+ }
1370
1450
  } else if (el["technical"]) {
1371
1451
  const techContent = el["technical"];
1372
1452
  const technicalWithText = ["hammer-on", "pull-off", "tap", "pluck", "fingering", "other-technical"];
@@ -1416,8 +1496,7 @@ function parseNotations(elements, notationsIndex = 0) {
1416
1496
  if (bendAlter) techNotation.bendAlter = parseFloat(bendAlter);
1417
1497
  if (hasElement(bendContent, "pre-bend")) techNotation.preBend = true;
1418
1498
  if (hasElement(bendContent, "release")) techNotation.release = true;
1419
- const withBar = getElementText(bendContent, "with-bar");
1420
- if (withBar) techNotation.withBar = parseFloat(withBar);
1499
+ if (hasElement(bendContent, "with-bar")) techNotation.withBar = true;
1421
1500
  notations.push(techNotation);
1422
1501
  }
1423
1502
  for (const techType of technicalTypes) {
@@ -1454,6 +1533,7 @@ function parseNotations(elements, notationsIndex = 0) {
1454
1533
  if (typeAttr === "start" || typeAttr === "stop") {
1455
1534
  notation.startStop = typeAttr;
1456
1535
  }
1536
+ if (techAttrs["number"]) notation.number = parseInt(techAttrs["number"], 10);
1457
1537
  }
1458
1538
  if (techType === "fingering") {
1459
1539
  if (techAttrs["substitution"] === "yes") notation.fingeringSubstitution = true;
@@ -1536,11 +1616,33 @@ function parseNotations(elements, notationsIndex = 0) {
1536
1616
  notations.push(fermataNotation);
1537
1617
  } else if (el["arpeggiate"] !== void 0) {
1538
1618
  const arpAttrs = getAttributes(el);
1539
- notations.push({
1619
+ const arpNotation = {
1540
1620
  type: "arpeggiate",
1541
1621
  direction: arpAttrs["direction"],
1542
1622
  number: arpAttrs["number"] ? parseInt(arpAttrs["number"], 10) : void 0,
1543
1623
  notationsIndex
1624
+ };
1625
+ if (arpAttrs["default-x"]) arpNotation.defaultX = parseFloat(arpAttrs["default-x"]);
1626
+ if (arpAttrs["default-y"]) arpNotation.defaultY = parseFloat(arpAttrs["default-y"]);
1627
+ notations.push(arpNotation);
1628
+ } else if (el["non-arpeggiate"] !== void 0) {
1629
+ const nonArpAttrs = getAttributes(el);
1630
+ notations.push({
1631
+ type: "non-arpeggiate",
1632
+ nonArpeggiateType: nonArpAttrs["type"],
1633
+ number: nonArpAttrs["number"] ? parseInt(nonArpAttrs["number"], 10) : void 0,
1634
+ placement: nonArpAttrs["placement"],
1635
+ notationsIndex
1636
+ });
1637
+ } else if (el["accidental-mark"]) {
1638
+ const amAttrs = getAttributes(el);
1639
+ const amContent = el["accidental-mark"];
1640
+ const value = extractText(amContent);
1641
+ notations.push({
1642
+ type: "accidental-mark",
1643
+ value,
1644
+ placement: amAttrs["placement"],
1645
+ notationsIndex
1544
1646
  });
1545
1647
  } else if (el["glissando"]) {
1546
1648
  const glissAttrs = getAttributes(el);
@@ -1562,13 +1664,17 @@ function parseNotations(elements, notationsIndex = 0) {
1562
1664
  });
1563
1665
  } else if (el["slide"]) {
1564
1666
  const slideAttrs = getAttributes(el);
1565
- notations.push({
1667
+ const slideContent = el["slide"];
1668
+ const slideText = extractText(slideContent);
1669
+ const slideNotation = {
1566
1670
  type: "slide",
1567
1671
  slideType: slideAttrs["type"] === "stop" ? "stop" : "start",
1568
1672
  number: slideAttrs["number"] ? parseInt(slideAttrs["number"], 10) : void 0,
1569
1673
  lineType: slideAttrs["line-type"],
1570
1674
  notationsIndex
1571
- });
1675
+ };
1676
+ if (slideText) slideNotation.text = slideText;
1677
+ notations.push(slideNotation);
1572
1678
  }
1573
1679
  }
1574
1680
  return notations;
@@ -1589,8 +1695,9 @@ function parseLyric(elements, attrs) {
1589
1695
  break;
1590
1696
  }
1591
1697
  }
1592
- } else if (el["text"]) {
1698
+ } else if (el["text"] !== void 0) {
1593
1699
  const content = el["text"];
1700
+ let foundText = false;
1594
1701
  for (const item of content) {
1595
1702
  if (item["#text"] !== void 0) {
1596
1703
  textElements.push({
@@ -1598,9 +1705,17 @@ function parseLyric(elements, attrs) {
1598
1705
  syllabic: currentSyllabic
1599
1706
  });
1600
1707
  currentSyllabic = void 0;
1708
+ foundText = true;
1601
1709
  break;
1602
1710
  }
1603
1711
  }
1712
+ if (!foundText) {
1713
+ textElements.push({
1714
+ text: "",
1715
+ syllabic: currentSyllabic
1716
+ });
1717
+ currentSyllabic = void 0;
1718
+ }
1604
1719
  } else if (el["elision"] !== void 0) {
1605
1720
  hasElision = true;
1606
1721
  }
@@ -1697,8 +1812,8 @@ function parseDirection(elements, attrs) {
1697
1812
  });
1698
1813
  for (const el of elements) {
1699
1814
  if (el["direction-type"]) {
1700
- const parsed = parseDirectionType(el["direction-type"]);
1701
- if (parsed) {
1815
+ const parsedTypes = parseDirectionTypes(el["direction-type"]);
1816
+ for (const parsed of parsedTypes) {
1702
1817
  direction.directionTypes.push(parsed);
1703
1818
  }
1704
1819
  }
@@ -1735,7 +1850,8 @@ function parseDirection(elements, attrs) {
1735
1850
  }
1736
1851
  return direction;
1737
1852
  }
1738
- function parseDirectionType(elements) {
1853
+ function parseDirectionTypes(elements) {
1854
+ const results = [];
1739
1855
  for (const el of elements) {
1740
1856
  if (el["dynamics"]) {
1741
1857
  const dynAttrs = getAttributes(el);
@@ -1768,6 +1884,7 @@ function parseDirectionType(elements) {
1768
1884
  "pf"
1769
1885
  ];
1770
1886
  for (const dyn of dynContent) {
1887
+ let foundStandard = false;
1771
1888
  for (const dv of dynamicsValues) {
1772
1889
  if (dyn[dv] !== void 0) {
1773
1890
  const result = { kind: "dynamics", value: dv };
@@ -1775,10 +1892,25 @@ function parseDirectionType(elements) {
1775
1892
  if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
1776
1893
  if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1777
1894
  if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1778
- return result;
1895
+ results.push(result);
1896
+ foundStandard = true;
1897
+ break;
1898
+ }
1899
+ }
1900
+ if (!foundStandard && dyn["other-dynamics"] !== void 0) {
1901
+ const otherDynContent = dyn["other-dynamics"];
1902
+ const otherDynText = extractText(otherDynContent);
1903
+ if (otherDynText) {
1904
+ const result = { kind: "dynamics", otherDynamics: otherDynText };
1905
+ if (dynAttrs["default-x"]) result.defaultX = parseFloat(dynAttrs["default-x"]);
1906
+ if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
1907
+ if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1908
+ if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1909
+ results.push(result);
1779
1910
  }
1780
1911
  }
1781
1912
  }
1913
+ continue;
1782
1914
  }
1783
1915
  if (el["wedge"]) {
1784
1916
  const wedgeAttrs = getAttributes(el);
@@ -1788,8 +1920,9 @@ function parseDirectionType(elements) {
1788
1920
  if (wedgeAttrs["spread"]) result.spread = parseFloat(wedgeAttrs["spread"]);
1789
1921
  if (wedgeAttrs["default-y"]) result.defaultY = parseFloat(wedgeAttrs["default-y"]);
1790
1922
  if (wedgeAttrs["relative-x"]) result.relativeX = parseFloat(wedgeAttrs["relative-x"]);
1791
- return result;
1923
+ results.push(result);
1792
1924
  }
1925
+ continue;
1793
1926
  }
1794
1927
  if (el["metronome"]) {
1795
1928
  const metAttrs = getAttributes(el);
@@ -1829,28 +1962,29 @@ function parseDirectionType(elements) {
1829
1962
  if (metAttrs["default-y"]) result.defaultY = parseFloat(metAttrs["default-y"]);
1830
1963
  if (metAttrs["font-family"]) result.fontFamily = metAttrs["font-family"];
1831
1964
  if (metAttrs["font-size"]) result.fontSize = metAttrs["font-size"];
1832
- return result;
1965
+ results.push(result);
1833
1966
  }
1967
+ continue;
1834
1968
  }
1835
1969
  if (el["words"]) {
1836
1970
  const a = getAttributes(el);
1837
1971
  const text = extractText(el["words"]);
1838
- if (text) {
1839
- const result = { kind: "words", text };
1840
- if (a["default-x"]) result.defaultX = parseFloat(a["default-x"]);
1841
- if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
1842
- if (a["relative-x"]) result.relativeX = parseFloat(a["relative-x"]);
1843
- if (a["font-family"]) result.fontFamily = a["font-family"];
1844
- if (a["font-size"]) result.fontSize = a["font-size"];
1845
- if (a["font-style"]) result.fontStyle = a["font-style"];
1846
- if (a["font-weight"]) result.fontWeight = a["font-weight"];
1847
- if (a["xml:lang"]) result.xmlLang = a["xml:lang"];
1848
- if (a["justify"]) result.justify = a["justify"];
1849
- if (a["color"]) result.color = a["color"];
1850
- if (a["xml:space"]) result.xmlSpace = a["xml:space"];
1851
- if (a["halign"]) result.halign = a["halign"];
1852
- return result;
1853
- }
1972
+ const result = { kind: "words", text: text || "" };
1973
+ if (a["default-x"]) result.defaultX = parseFloat(a["default-x"]);
1974
+ if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
1975
+ if (a["relative-x"]) result.relativeX = parseFloat(a["relative-x"]);
1976
+ if (a["relative-y"]) result.relativeY = parseFloat(a["relative-y"]);
1977
+ if (a["font-family"]) result.fontFamily = a["font-family"];
1978
+ if (a["font-size"]) result.fontSize = a["font-size"];
1979
+ if (a["font-style"]) result.fontStyle = a["font-style"];
1980
+ if (a["font-weight"]) result.fontWeight = a["font-weight"];
1981
+ if (a["xml:lang"]) result.xmlLang = a["xml:lang"];
1982
+ if (a["justify"]) result.justify = a["justify"];
1983
+ if (a["color"]) result.color = a["color"];
1984
+ if (a["xml:space"]) result.xmlSpace = a["xml:space"];
1985
+ if (a["halign"]) result.halign = a["halign"];
1986
+ results.push(result);
1987
+ continue;
1854
1988
  }
1855
1989
  if (el["rehearsal"]) {
1856
1990
  const a = getAttributes(el);
@@ -1862,8 +1996,9 @@ function parseDirectionType(elements) {
1862
1996
  if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
1863
1997
  if (a["font-size"]) result.fontSize = a["font-size"];
1864
1998
  if (a["font-weight"]) result.fontWeight = a["font-weight"];
1865
- return result;
1999
+ results.push(result);
1866
2000
  }
2001
+ continue;
1867
2002
  }
1868
2003
  if (el["bracket"]) {
1869
2004
  const bracketAttrs = getAttributes(el);
@@ -1875,8 +2010,9 @@ function parseDirectionType(elements) {
1875
2010
  if (bracketAttrs["line-type"]) result.lineType = bracketAttrs["line-type"];
1876
2011
  if (bracketAttrs["default-y"]) result.defaultY = parseFloat(bracketAttrs["default-y"]);
1877
2012
  if (bracketAttrs["relative-x"]) result.relativeX = parseFloat(bracketAttrs["relative-x"]);
1878
- return result;
2013
+ results.push(result);
1879
2014
  }
2015
+ continue;
1880
2016
  }
1881
2017
  if (el["dashes"]) {
1882
2018
  const dashAttrs = getAttributes(el);
@@ -1887,8 +2023,9 @@ function parseDirectionType(elements) {
1887
2023
  if (dashAttrs["dash-length"]) result.dashLength = parseFloat(dashAttrs["dash-length"]);
1888
2024
  if (dashAttrs["default-y"]) result.defaultY = parseFloat(dashAttrs["default-y"]);
1889
2025
  if (dashAttrs["space-length"]) result.spaceLength = parseFloat(dashAttrs["space-length"]);
1890
- return result;
2026
+ results.push(result);
1891
2027
  }
2028
+ continue;
1892
2029
  }
1893
2030
  if (el["accordion-registration"]) {
1894
2031
  const accContent = el["accordion-registration"];
@@ -1896,11 +2033,14 @@ function parseDirectionType(elements) {
1896
2033
  for (const acc of accContent) {
1897
2034
  if (acc["accordion-high"] !== void 0) {
1898
2035
  result.high = true;
1899
- } else if (acc["accordion-middle"]) {
2036
+ } else if (acc["accordion-middle"] !== void 0) {
2037
+ result.middlePresent = true;
1900
2038
  const midContent = acc["accordion-middle"];
1901
2039
  for (const item of midContent) {
1902
2040
  if (item["#text"] !== void 0) {
1903
- result.middle = parseInt(String(item["#text"]), 10);
2041
+ const textValue = String(item["#text"]);
2042
+ const numValue = parseInt(textValue, 10);
2043
+ result.middle = !isNaN(numValue) ? numValue : textValue;
1904
2044
  break;
1905
2045
  }
1906
2046
  }
@@ -1908,7 +2048,8 @@ function parseDirectionType(elements) {
1908
2048
  result.low = true;
1909
2049
  }
1910
2050
  }
1911
- return result;
2051
+ results.push(result);
2052
+ continue;
1912
2053
  }
1913
2054
  if (el["other-direction"]) {
1914
2055
  const otherAttrs = getAttributes(el);
@@ -1920,24 +2061,31 @@ function parseDirectionType(elements) {
1920
2061
  if (otherAttrs["default-y"]) result.defaultY = parseFloat(otherAttrs["default-y"]);
1921
2062
  if (otherAttrs["halign"]) result.halign = otherAttrs["halign"];
1922
2063
  if (otherAttrs["print-object"] === "no") result.printObject = false;
1923
- return result;
2064
+ results.push(result);
2065
+ break;
1924
2066
  }
1925
2067
  }
2068
+ continue;
1926
2069
  }
1927
2070
  if (el["segno"] !== void 0) {
1928
- return { kind: "segno" };
2071
+ results.push({ kind: "segno" });
2072
+ continue;
1929
2073
  }
1930
2074
  if (el["coda"] !== void 0) {
1931
- return { kind: "coda" };
2075
+ results.push({ kind: "coda" });
2076
+ continue;
1932
2077
  }
1933
2078
  if (el["eyeglasses"] !== void 0) {
1934
- return { kind: "eyeglasses" };
2079
+ results.push({ kind: "eyeglasses" });
2080
+ continue;
1935
2081
  }
1936
2082
  if (el["damp"] !== void 0) {
1937
- return { kind: "damp" };
2083
+ results.push({ kind: "damp" });
2084
+ continue;
1938
2085
  }
1939
2086
  if (el["damp-all"] !== void 0) {
1940
- return { kind: "damp-all" };
2087
+ results.push({ kind: "damp-all" });
2088
+ continue;
1941
2089
  }
1942
2090
  if (el["scordatura"] !== void 0) {
1943
2091
  const scordContent = el["scordatura"];
@@ -1960,7 +2108,8 @@ function parseDirectionType(elements) {
1960
2108
  }
1961
2109
  }
1962
2110
  }
1963
- return { kind: "scordatura", accords: accords.length > 0 ? accords : void 0 };
2111
+ results.push({ kind: "scordatura", accords: accords.length > 0 ? accords : void 0 });
2112
+ continue;
1964
2113
  }
1965
2114
  if (el["harp-pedals"] !== void 0) {
1966
2115
  const harpContent = el["harp-pedals"];
@@ -1978,15 +2127,17 @@ function parseDirectionType(elements) {
1978
2127
  }
1979
2128
  }
1980
2129
  }
1981
- return { kind: "harp-pedals", pedalTunings: pedalTunings.length > 0 ? pedalTunings : void 0 };
2130
+ results.push({ kind: "harp-pedals", pedalTunings: pedalTunings.length > 0 ? pedalTunings : void 0 });
2131
+ continue;
1982
2132
  }
1983
2133
  if (el["image"] !== void 0) {
1984
2134
  const imgAttrs = getAttributes(el);
1985
- return {
2135
+ results.push({
1986
2136
  kind: "image",
1987
2137
  source: imgAttrs["source"],
1988
2138
  type: imgAttrs["type"]
1989
- };
2139
+ });
2140
+ continue;
1990
2141
  }
1991
2142
  if (el["pedal"]) {
1992
2143
  const pedalAttrs = getAttributes(el);
@@ -1998,8 +2149,9 @@ function parseDirectionType(elements) {
1998
2149
  if (pedalAttrs["default-y"]) result.defaultY = parseFloat(pedalAttrs["default-y"]);
1999
2150
  if (pedalAttrs["relative-x"]) result.relativeX = parseFloat(pedalAttrs["relative-x"]);
2000
2151
  if (pedalAttrs["halign"]) result.halign = pedalAttrs["halign"];
2001
- return result;
2152
+ results.push(result);
2002
2153
  }
2154
+ continue;
2003
2155
  }
2004
2156
  if (el["octave-shift"]) {
2005
2157
  const shiftAttrs = getAttributes(el);
@@ -2007,8 +2159,9 @@ function parseDirectionType(elements) {
2007
2159
  if (shiftType === "up" || shiftType === "down" || shiftType === "stop") {
2008
2160
  const result = { kind: "octave-shift", type: shiftType };
2009
2161
  if (shiftAttrs["size"]) result.size = parseInt(shiftAttrs["size"], 10);
2010
- return result;
2162
+ results.push(result);
2011
2163
  }
2164
+ continue;
2012
2165
  }
2013
2166
  if (el["swing"]) {
2014
2167
  const swingContent = el["swing"];
@@ -2042,10 +2195,11 @@ function parseDirectionType(elements) {
2042
2195
  }
2043
2196
  }
2044
2197
  }
2045
- return result;
2198
+ results.push(result);
2199
+ continue;
2046
2200
  }
2047
2201
  }
2048
- return null;
2202
+ return results;
2049
2203
  }
2050
2204
  function parseBarline(elements, attrs) {
2051
2205
  const location = attrs["location"] || "right";
@@ -2063,6 +2217,9 @@ function parseBarline(elements, attrs) {
2063
2217
  if (repeatAttrs["times"]) {
2064
2218
  barline.repeat.times = parseInt(repeatAttrs["times"], 10);
2065
2219
  }
2220
+ if (repeatAttrs["winged"]) {
2221
+ barline.repeat.winged = repeatAttrs["winged"];
2222
+ }
2066
2223
  }
2067
2224
  } else if (el["ending"]) {
2068
2225
  const endingAttrs = getAttributes(el);
@@ -2070,6 +2227,11 @@ function parseBarline(elements, attrs) {
2070
2227
  const type = endingAttrs["type"];
2071
2228
  if (number && (type === "start" || type === "stop" || type === "discontinue")) {
2072
2229
  barline.ending = { number, type };
2230
+ const endingContent = el["ending"];
2231
+ const endingText = extractText(endingContent);
2232
+ if (endingText) barline.ending.text = endingText;
2233
+ if (endingAttrs["default-y"]) barline.ending.defaultY = parseFloat(endingAttrs["default-y"]);
2234
+ if (endingAttrs["end-length"]) barline.ending.endLength = parseFloat(endingAttrs["end-length"]);
2073
2235
  }
2074
2236
  }
2075
2237
  }
@@ -2195,6 +2357,8 @@ function parseStaffDetails(elements, attrs) {
2195
2357
  if (attrs["number"]) sd.number = parseInt(attrs["number"], 10);
2196
2358
  if (attrs["print-object"] === "no") sd.printObject = false;
2197
2359
  else if (attrs["print-object"] === "yes") sd.printObject = true;
2360
+ if (attrs["print-spacing"] === "yes") sd.printSpacing = true;
2361
+ else if (attrs["print-spacing"] === "no") sd.printSpacing = false;
2198
2362
  const staffType = getElementText(elements, "staff-type");
2199
2363
  if (staffType && ["ossia", "cue", "editorial", "regular", "alternate"].includes(staffType)) {
2200
2364
  sd.staffType = staffType;
@@ -2258,7 +2422,9 @@ function parseMeasureStyle(elements, attrs) {
2258
2422
  const slAttrs = getAttributes(el);
2259
2423
  ms.slash = { type: slAttrs["type"] === "stop" ? "stop" : "start" };
2260
2424
  if (slAttrs["use-dots"] === "yes") ms.slash.useDots = true;
2425
+ else if (slAttrs["use-dots"] === "no") ms.slash.useDots = false;
2261
2426
  if (slAttrs["use-stems"] === "yes") ms.slash.useStems = true;
2427
+ else if (slAttrs["use-stems"] === "no") ms.slash.useStems = false;
2262
2428
  }
2263
2429
  }
2264
2430
  return ms;
@@ -2298,19 +2464,29 @@ function parseHarmony(elements, attrs) {
2298
2464
  break;
2299
2465
  }
2300
2466
  }
2301
- if (kindAttrs["text"]) harmony.kindText = kindAttrs["text"];
2467
+ if (kindAttrs["text"] !== void 0) harmony.kindText = kindAttrs["text"];
2468
+ if (kindAttrs["halign"]) harmony.kindHalign = kindAttrs["halign"];
2302
2469
  break;
2303
2470
  }
2304
2471
  }
2305
- const bass = getElementContent(elements, "bass");
2306
- if (bass) {
2307
- const bassStep = getElementText(bass, "bass-step");
2308
- if (bassStep) {
2309
- harmony.bass = { bassStep };
2310
- const bassAlter = getElementText(bass, "bass-alter");
2311
- if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2472
+ for (const el of elements) {
2473
+ if (el["bass"]) {
2474
+ const bassAttrs = getAttributes(el);
2475
+ const bassContent = el["bass"];
2476
+ const bassStep = getElementText(bassContent, "bass-step");
2477
+ if (bassStep) {
2478
+ harmony.bass = { bassStep };
2479
+ const bassAlter = getElementText(bassContent, "bass-alter");
2480
+ if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2481
+ if (bassAttrs["arrangement"]) harmony.bass.arrangement = bassAttrs["arrangement"];
2482
+ }
2483
+ break;
2312
2484
  }
2313
2485
  }
2486
+ const inversionText = getElementText(elements, "inversion");
2487
+ if (inversionText) {
2488
+ harmony.inversion = parseInt(inversionText, 10);
2489
+ }
2314
2490
  const degrees = [];
2315
2491
  for (const el of elements) {
2316
2492
  if (el["degree"]) {
@@ -2926,7 +3102,7 @@ function validateBeams(measure, location) {
2926
3102
  const entry = measure.entries[entryIndex];
2927
3103
  if (entry.type !== "note" || !entry.beam) continue;
2928
3104
  for (const beam of entry.beam) {
2929
- const beamKey = `${beam.number}-${entry.voice}-${entry.staff ?? 1}`;
3105
+ const beamKey = `${beam.number}-${entry.voice}`;
2930
3106
  if (beam.type === "begin") {
2931
3107
  if (openBeams.has(beamKey)) {
2932
3108
  errors.push({
@@ -2937,7 +3113,7 @@ function validateBeams(measure, location) {
2937
3113
  details: { beamNumber: beam.number }
2938
3114
  });
2939
3115
  }
2940
- openBeams.set(beamKey, entryIndex);
3116
+ openBeams.set(beamKey, { entryIndex, staff: entry.staff ?? 1 });
2941
3117
  } else if (beam.type === "end") {
2942
3118
  if (!openBeams.has(beamKey)) {
2943
3119
  errors.push({
@@ -2953,8 +3129,8 @@ function validateBeams(measure, location) {
2953
3129
  }
2954
3130
  }
2955
3131
  }
2956
- for (const [beamKey, startIndex] of openBeams.entries()) {
2957
- const [beamNumber, voice, staff] = beamKey.split("-").map(Number);
3132
+ for (const [beamKey, { entryIndex: startIndex, staff }] of openBeams.entries()) {
3133
+ const [beamNumber, voice] = beamKey.split("-").map(Number);
2958
3134
  errors.push({
2959
3135
  code: "BEAM_BEGIN_WITHOUT_END",
2960
3136
  level: "error",
@@ -3077,7 +3253,7 @@ function validateVoiceStaff(measure, staves, location) {
3077
3253
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
3078
3254
  const entry = measure.entries[entryIndex];
3079
3255
  if (entry.type !== "note") continue;
3080
- if (entry.voice <= 0) {
3256
+ if (entry.voice !== void 0 && entry.voice <= 0) {
3081
3257
  errors.push({
3082
3258
  code: "INVALID_VOICE_NUMBER",
3083
3259
  level: "error",
@@ -3726,6 +3902,11 @@ function serializeDefaults(defaults, indent) {
3726
3902
  lines.push(`${indent}${indent}${indent}<distance type="${escapeXml(d.type)}">${d.value}</distance>`);
3727
3903
  }
3728
3904
  }
3905
+ if (app["glyphs"]) {
3906
+ for (const g of app["glyphs"]) {
3907
+ lines.push(`${indent}${indent}${indent}<glyph type="${escapeXml(g.type)}">${escapeXml(g.value)}</glyph>`);
3908
+ }
3909
+ }
3729
3910
  lines.push(`${indent}${indent}</appearance>`);
3730
3911
  }
3731
3912
  if (defaults.musicFont) {
@@ -3782,16 +3963,16 @@ function serializePageLayout(layout, indent) {
3782
3963
  const typeAttr = m.type ? ` type="${m.type}"` : "";
3783
3964
  lines.push(`${indent} <page-margins${typeAttr}>`);
3784
3965
  if (m.leftMargin !== void 0) {
3785
- lines.push(`${indent} <left-margin>${m.leftMargin}</left-margin>`);
3966
+ lines.push(`${indent} <left-margin>${m.leftMarginRaw ?? m.leftMargin}</left-margin>`);
3786
3967
  }
3787
3968
  if (m.rightMargin !== void 0) {
3788
- lines.push(`${indent} <right-margin>${m.rightMargin}</right-margin>`);
3969
+ lines.push(`${indent} <right-margin>${m.rightMarginRaw ?? m.rightMargin}</right-margin>`);
3789
3970
  }
3790
3971
  if (m.topMargin !== void 0) {
3791
- lines.push(`${indent} <top-margin>${m.topMargin}</top-margin>`);
3972
+ lines.push(`${indent} <top-margin>${m.topMarginRaw ?? m.topMargin}</top-margin>`);
3792
3973
  }
3793
3974
  if (m.bottomMargin !== void 0) {
3794
- lines.push(`${indent} <bottom-margin>${m.bottomMargin}</bottom-margin>`);
3975
+ lines.push(`${indent} <bottom-margin>${m.bottomMarginRaw ?? m.bottomMargin}</bottom-margin>`);
3795
3976
  }
3796
3977
  lines.push(`${indent} </page-margins>`);
3797
3978
  }
@@ -3805,18 +3986,48 @@ function serializeSystemLayout(layout, indent) {
3805
3986
  if (layout.systemMargins) {
3806
3987
  lines.push(`${indent} <system-margins>`);
3807
3988
  if (layout.systemMargins.leftMargin !== void 0) {
3808
- lines.push(`${indent} <left-margin>${layout.systemMargins.leftMargin}</left-margin>`);
3989
+ lines.push(`${indent} <left-margin>${layout.systemMargins.leftMarginRaw ?? layout.systemMargins.leftMargin}</left-margin>`);
3809
3990
  }
3810
3991
  if (layout.systemMargins.rightMargin !== void 0) {
3811
- lines.push(`${indent} <right-margin>${layout.systemMargins.rightMargin}</right-margin>`);
3992
+ lines.push(`${indent} <right-margin>${layout.systemMargins.rightMarginRaw ?? layout.systemMargins.rightMargin}</right-margin>`);
3812
3993
  }
3813
3994
  lines.push(`${indent} </system-margins>`);
3814
3995
  }
3815
3996
  if (layout.systemDistance !== void 0) {
3816
- lines.push(`${indent} <system-distance>${layout.systemDistance}</system-distance>`);
3997
+ lines.push(`${indent} <system-distance>${layout.systemDistanceRaw ?? layout.systemDistance}</system-distance>`);
3817
3998
  }
3818
3999
  if (layout.topSystemDistance !== void 0) {
3819
- lines.push(`${indent} <top-system-distance>${layout.topSystemDistance}</top-system-distance>`);
4000
+ lines.push(`${indent} <top-system-distance>${layout.topSystemDistanceRaw ?? layout.topSystemDistance}</top-system-distance>`);
4001
+ }
4002
+ if (layout.systemDividers) {
4003
+ lines.push(`${indent} <system-dividers>`);
4004
+ if (layout.systemDividers.leftDivider) {
4005
+ let attrs = "";
4006
+ if (layout.systemDividers.leftDivider.printObject !== void 0) {
4007
+ attrs += ` print-object="${layout.systemDividers.leftDivider.printObject ? "yes" : "no"}"`;
4008
+ }
4009
+ if (layout.systemDividers.leftDivider.halign) {
4010
+ attrs += ` halign="${layout.systemDividers.leftDivider.halign}"`;
4011
+ }
4012
+ if (layout.systemDividers.leftDivider.valign) {
4013
+ attrs += ` valign="${layout.systemDividers.leftDivider.valign}"`;
4014
+ }
4015
+ lines.push(`${indent} <left-divider${attrs}/>`);
4016
+ }
4017
+ if (layout.systemDividers.rightDivider) {
4018
+ let attrs = "";
4019
+ if (layout.systemDividers.rightDivider.printObject !== void 0) {
4020
+ attrs += ` print-object="${layout.systemDividers.rightDivider.printObject ? "yes" : "no"}"`;
4021
+ }
4022
+ if (layout.systemDividers.rightDivider.halign) {
4023
+ attrs += ` halign="${layout.systemDividers.rightDivider.halign}"`;
4024
+ }
4025
+ if (layout.systemDividers.rightDivider.valign) {
4026
+ attrs += ` valign="${layout.systemDividers.rightDivider.valign}"`;
4027
+ }
4028
+ lines.push(`${indent} <right-divider${attrs}/>`);
4029
+ }
4030
+ lines.push(`${indent} </system-dividers>`);
3820
4031
  }
3821
4032
  lines.push(`${indent}</system-layout>`);
3822
4033
  return lines;
@@ -3837,6 +4048,7 @@ function serializeCredit(credit, indent) {
3837
4048
  let attrs2 = "";
3838
4049
  if (cw.defaultX !== void 0) attrs2 += ` default-x="${cw.defaultX}"`;
3839
4050
  if (cw.defaultY !== void 0) attrs2 += ` default-y="${cw.defaultY}"`;
4051
+ if (cw.fontFamily) attrs2 += ` font-family="${escapeXml(cw.fontFamily)}"`;
3840
4052
  if (cw.fontSize) attrs2 += ` font-size="${escapeXml(cw.fontSize)}"`;
3841
4053
  if (cw.fontWeight) attrs2 += ` font-weight="${escapeXml(cw.fontWeight)}"`;
3842
4054
  if (cw.fontStyle) attrs2 += ` font-style="${escapeXml(cw.fontStyle)}"`;
@@ -4131,6 +4343,7 @@ function serializeKey(key, indent) {
4131
4343
  let keyAttrs = "";
4132
4344
  if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
4133
4345
  if (key.printObject === false) keyAttrs += ' print-object="no"';
4346
+ else if (key.printObject === true) keyAttrs += ' print-object="yes"';
4134
4347
  lines.push(`${indent}<key${keyAttrs}>`);
4135
4348
  if (key.cancel !== void 0) {
4136
4349
  const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
@@ -4178,7 +4391,8 @@ function serializeTime(time, indent) {
4178
4391
  const maxLen = Math.max(time.beatsList.length, time.beatTypeList.length);
4179
4392
  for (let i = 0; i < maxLen; i++) {
4180
4393
  if (i < time.beatsList.length) {
4181
- lines.push(`${indent} <beats>${time.beatsList[i]}</beats>`);
4394
+ const beatsValue = time.beatsStrList && i < time.beatsStrList.length ? time.beatsStrList[i] : time.beatsList[i];
4395
+ lines.push(`${indent} <beats>${beatsValue}</beats>`);
4182
4396
  }
4183
4397
  if (i < time.beatTypeList.length) {
4184
4398
  lines.push(`${indent} <beat-type>${time.beatTypeList[i]}</beat-type>`);
@@ -4195,10 +4409,13 @@ function serializeClef(clef, indent) {
4195
4409
  const lines = [];
4196
4410
  let attrs = clef.staff ? ` number="${clef.staff}"` : "";
4197
4411
  if (clef.printObject === false) attrs += ' print-object="no"';
4412
+ else if (clef.printObject === true) attrs += ' print-object="yes"';
4198
4413
  if (clef.afterBarline) attrs += ' after-barline="yes"';
4199
4414
  lines.push(`${indent}<clef${attrs}>`);
4200
4415
  lines.push(`${indent} <sign>${clef.sign}</sign>`);
4201
- lines.push(`${indent} <line>${clef.line}</line>`);
4416
+ if (clef.line !== void 0) {
4417
+ lines.push(`${indent} <line>${clef.line}</line>`);
4418
+ }
4202
4419
  if (clef.clefOctaveChange !== void 0) {
4203
4420
  lines.push(`${indent} <clef-octave-change>${clef.clefOctaveChange}</clef-octave-change>`);
4204
4421
  }
@@ -4234,6 +4451,12 @@ function serializeEntry(entry, indent) {
4234
4451
  return serializeSound(entry, indent);
4235
4452
  case "attributes":
4236
4453
  return serializeAttributes(entry.attributes, indent, entry._id);
4454
+ case "grouping": {
4455
+ const grp = entry;
4456
+ let grpAttrs = ` type="${grp.groupingType}"`;
4457
+ if (grp.number) grpAttrs += ` number="${grp.number}"`;
4458
+ return [`${indent}<grouping${grpAttrs}/>`];
4459
+ }
4237
4460
  default:
4238
4461
  return [];
4239
4462
  }
@@ -4248,12 +4471,13 @@ function serializeNote(note, indent) {
4248
4471
  "relative-y": note.relativeY,
4249
4472
  "dynamics": note.dynamics,
4250
4473
  "print-object": note.printObject === false ? false : void 0,
4474
+ "print-dot": note.printDot !== void 0 ? note.printDot : void 0,
4251
4475
  "print-spacing": note.printSpacing
4252
4476
  });
4253
4477
  lines.push(`${indent}<note${noteAttrs}>`);
4254
4478
  if (note.grace) {
4255
4479
  const graceAttrs = buildAttrs({
4256
- "slash": note.grace.slash || void 0,
4480
+ "slash": note.grace.slash !== void 0 ? note.grace.slash : void 0,
4257
4481
  "steal-time-previous": note.grace.stealTimePrevious,
4258
4482
  "steal-time-following": note.grace.stealTimeFollowing
4259
4483
  });
@@ -4308,7 +4532,9 @@ function serializeNote(note, indent) {
4308
4532
  } else if (note.tie) {
4309
4533
  lines.push(`${indent} <tie type="${note.tie.type}"/>`);
4310
4534
  }
4311
- lines.push(`${indent} <voice>${note.voice}</voice>`);
4535
+ if (note.voice !== void 0) {
4536
+ lines.push(`${indent} <voice>${note.voice}</voice>`);
4537
+ }
4312
4538
  if (note.noteType) {
4313
4539
  const typeAttrs = note.noteTypeSize ? ` size="${escapeXml(note.noteTypeSize)}"` : "";
4314
4540
  lines.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
@@ -4525,7 +4751,18 @@ function serializeNotationsGroup(notations, indent) {
4525
4751
  let attrs = "";
4526
4752
  if (notation.direction) attrs += ` direction="${notation.direction}"`;
4527
4753
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4754
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4755
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4528
4756
  lines.push(`${indent} <arpeggiate${attrs}/>`);
4757
+ } else if (notation.type === "non-arpeggiate") {
4758
+ let attrs = ` type="${notation.nonArpeggiateType}"`;
4759
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4760
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4761
+ lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4762
+ } else if (notation.type === "accidental-mark") {
4763
+ let attrs = "";
4764
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4765
+ lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4529
4766
  } else if (notation.type === "glissando") {
4530
4767
  let attrs = ` type="${notation.glissandoType}"`;
4531
4768
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
@@ -4539,7 +4776,11 @@ function serializeNotationsGroup(notations, indent) {
4539
4776
  let attrs = ` type="${notation.slideType}"`;
4540
4777
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4541
4778
  if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4542
- lines.push(`${indent} <slide${attrs}/>`);
4779
+ if (notation.text) {
4780
+ lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4781
+ } else {
4782
+ lines.push(`${indent} <slide${attrs}/>`);
4783
+ }
4543
4784
  }
4544
4785
  }
4545
4786
  const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
@@ -4560,44 +4801,50 @@ function serializeNotationsGroup(notations, indent) {
4560
4801
  lines.push(`${indent} </articulations>`);
4561
4802
  }
4562
4803
  if (ornaments.length > 0) {
4563
- lines.push(`${indent} <ornaments>`);
4564
- const allAccidentalMarks = [];
4565
- for (const orn of ornaments) {
4566
- if (orn.type === "ornament") {
4567
- const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4568
- if (orn.ornament === "wavy-line") {
4569
- let wlAttrs = "";
4570
- if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4571
- if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4572
- wlAttrs += placementAttr;
4573
- if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4574
- lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4575
- } else if (orn.ornament === "tremolo") {
4576
- let tremAttrs = "";
4577
- if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4578
- tremAttrs += placementAttr;
4579
- if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4580
- if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4581
- if (orn.tremoloMarks !== void 0) {
4582
- lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4804
+ const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4805
+ if (hasOnlyEmptyMarker) {
4806
+ lines.push(`${indent} <ornaments/>`);
4807
+ } else {
4808
+ lines.push(`${indent} <ornaments>`);
4809
+ const allAccidentalMarks = [];
4810
+ for (const orn of ornaments) {
4811
+ if (orn.type === "ornament") {
4812
+ if (orn.ornament === "empty") continue;
4813
+ const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4814
+ if (orn.ornament === "wavy-line") {
4815
+ let wlAttrs = "";
4816
+ if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4817
+ if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4818
+ wlAttrs += placementAttr;
4819
+ if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4820
+ lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4821
+ } else if (orn.ornament === "tremolo") {
4822
+ let tremAttrs = "";
4823
+ if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4824
+ tremAttrs += placementAttr;
4825
+ if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4826
+ if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4827
+ if (orn.tremoloMarks !== void 0) {
4828
+ lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4829
+ } else {
4830
+ lines.push(`${indent} <tremolo${tremAttrs}/>`);
4831
+ }
4583
4832
  } else {
4584
- lines.push(`${indent} <tremolo${tremAttrs}/>`);
4833
+ let ornAttrs = placementAttr;
4834
+ if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4835
+ lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4836
+ }
4837
+ if (orn.accidentalMarks) {
4838
+ allAccidentalMarks.push(...orn.accidentalMarks);
4585
4839
  }
4586
- } else {
4587
- let ornAttrs = placementAttr;
4588
- if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4589
- lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4590
- }
4591
- if (orn.accidentalMarks) {
4592
- allAccidentalMarks.push(...orn.accidentalMarks);
4593
4840
  }
4594
4841
  }
4842
+ for (const am of allAccidentalMarks) {
4843
+ const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4844
+ lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4845
+ }
4846
+ lines.push(`${indent} </ornaments>`);
4595
4847
  }
4596
- for (const am of allAccidentalMarks) {
4597
- const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4598
- lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4599
- }
4600
- lines.push(`${indent} </ornaments>`);
4601
4848
  }
4602
4849
  if (technicals.length > 0) {
4603
4850
  lines.push(`${indent} <technical>`);
@@ -4618,8 +4865,8 @@ function serializeNotationsGroup(notations, indent) {
4618
4865
  if (techNotation.release) {
4619
4866
  lines.push(`${indent} <release/>`);
4620
4867
  }
4621
- if (techNotation.withBar !== void 0) {
4622
- lines.push(`${indent} <with-bar>${techNotation.withBar}</with-bar>`);
4868
+ if (techNotation.withBar) {
4869
+ lines.push(`${indent} <with-bar/>`);
4623
4870
  }
4624
4871
  lines.push(`${indent} </bend>`);
4625
4872
  } else if (tech.technical === "harmonic") {
@@ -4636,8 +4883,10 @@ function serializeNotationsGroup(notations, indent) {
4636
4883
  lines.push(`${indent} <harmonic${placementAttr}/>`);
4637
4884
  }
4638
4885
  } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4639
- let attrs = placementAttr;
4886
+ let attrs = "";
4887
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4640
4888
  if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4889
+ attrs += placementAttr;
4641
4890
  if (techNotation.text !== void 0) {
4642
4891
  lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4643
4892
  } else {
@@ -4693,7 +4942,7 @@ function serializeLyric(lyric, indent) {
4693
4942
  lines.push(`${indent} <elision/>`);
4694
4943
  }
4695
4944
  }
4696
- } else {
4945
+ } else if (lyric.syllabic || lyric.text) {
4697
4946
  if (lyric.syllabic) {
4698
4947
  lines.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
4699
4948
  }
@@ -4808,7 +5057,12 @@ function serializeDirectionType(dirType, indent) {
4808
5057
  if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
4809
5058
  if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
4810
5059
  lines.push(`${indent} <dynamics${dynAttrs}>`);
4811
- lines.push(`${indent} <${dirType.value}/>`);
5060
+ if (dirType.value) {
5061
+ lines.push(`${indent} <${dirType.value}/>`);
5062
+ }
5063
+ if (dirType.otherDynamics) {
5064
+ lines.push(`${indent} <other-dynamics>${escapeXml(dirType.otherDynamics)}</other-dynamics>`);
5065
+ }
4812
5066
  lines.push(`${indent} </dynamics>`);
4813
5067
  break;
4814
5068
  }
@@ -4848,6 +5102,7 @@ function serializeDirectionType(dirType, indent) {
4848
5102
  if (dirType.defaultX !== void 0) wordAttrs += ` default-x="${dirType.defaultX}"`;
4849
5103
  if (dirType.defaultY !== void 0) wordAttrs += ` default-y="${dirType.defaultY}"`;
4850
5104
  if (dirType.relativeX !== void 0) wordAttrs += ` relative-x="${dirType.relativeX}"`;
5105
+ if (dirType.relativeY !== void 0) wordAttrs += ` relative-y="${dirType.relativeY}"`;
4851
5106
  if (dirType.fontFamily) wordAttrs += ` font-family="${escapeXml(dirType.fontFamily)}"`;
4852
5107
  if (dirType.fontSize) wordAttrs += ` font-size="${escapeXml(dirType.fontSize)}"`;
4853
5108
  if (dirType.fontStyle) wordAttrs += ` font-style="${escapeXml(dirType.fontStyle)}"`;
@@ -4894,8 +5149,12 @@ function serializeDirectionType(dirType, indent) {
4894
5149
  if (dirType.high) {
4895
5150
  lines.push(`${indent} <accordion-high/>`);
4896
5151
  }
4897
- if (dirType.middle !== void 0) {
4898
- lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
5152
+ if (dirType.middlePresent || dirType.middle !== void 0) {
5153
+ if (dirType.middle !== void 0) {
5154
+ lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
5155
+ } else {
5156
+ lines.push(`${indent} <accordion-middle/>`);
5157
+ }
4899
5158
  }
4900
5159
  if (dirType.low) {
4901
5160
  lines.push(`${indent} <accordion-low/>`);
@@ -5008,11 +5267,20 @@ function serializeBarline(barline, indent) {
5008
5267
  lines.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
5009
5268
  }
5010
5269
  if (barline.ending) {
5011
- lines.push(`${indent} <ending number="${barline.ending.number}" type="${barline.ending.type}"/>`);
5270
+ let endingAttrs = ` number="${barline.ending.number}" type="${barline.ending.type}"`;
5271
+ if (barline.ending.defaultY !== void 0) endingAttrs += ` default-y="${barline.ending.defaultY}"`;
5272
+ if (barline.ending.endLength !== void 0) endingAttrs += ` end-length="${barline.ending.endLength}"`;
5273
+ if (barline.ending.text) {
5274
+ lines.push(`${indent} <ending${endingAttrs}>${escapeXml(barline.ending.text)}</ending>`);
5275
+ } else {
5276
+ lines.push(`${indent} <ending${endingAttrs}/>`);
5277
+ }
5012
5278
  }
5013
5279
  if (barline.repeat) {
5014
- const timesAttr = barline.repeat.times !== void 0 ? ` times="${barline.repeat.times}"` : "";
5015
- lines.push(`${indent} <repeat direction="${barline.repeat.direction}"${timesAttr}/>`);
5280
+ let repeatAttrs = ` direction="${barline.repeat.direction}"`;
5281
+ if (barline.repeat.times !== void 0) repeatAttrs += ` times="${barline.repeat.times}"`;
5282
+ if (barline.repeat.winged) repeatAttrs += ` winged="${barline.repeat.winged}"`;
5283
+ lines.push(`${indent} <repeat${repeatAttrs}/>`);
5016
5284
  }
5017
5285
  lines.push(`${indent}</barline>`);
5018
5286
  return lines;
@@ -5045,7 +5313,8 @@ function serializeStaffDetails(sd, indent) {
5045
5313
  const attrs = buildAttrs({
5046
5314
  "number": sd.number,
5047
5315
  "show-frets": sd.showFrets,
5048
- "print-object": sd.printObject
5316
+ "print-object": sd.printObject,
5317
+ "print-spacing": sd.printSpacing
5049
5318
  });
5050
5319
  lines.push(`${indent}<staff-details${attrs}>`);
5051
5320
  pushOptionalElement(lines, `${indent} `, "staff-type", sd.staffType);
@@ -5111,16 +5380,22 @@ function serializeHarmony(harmony, indent) {
5111
5380
  }
5112
5381
  lines.push(`${indent} </root>`);
5113
5382
  let kindAttrs = "";
5114
- if (harmony.kindText) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5383
+ if (harmony.kindText !== void 0) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5384
+ if (harmony.kindHalign) kindAttrs += ` halign="${escapeXml(harmony.kindHalign)}"`;
5115
5385
  lines.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
5116
5386
  if (harmony.bass) {
5117
- lines.push(`${indent} <bass>`);
5387
+ let bassAttrs = "";
5388
+ if (harmony.bass.arrangement) bassAttrs += ` arrangement="${escapeXml(harmony.bass.arrangement)}"`;
5389
+ lines.push(`${indent} <bass${bassAttrs}>`);
5118
5390
  lines.push(`${indent} <bass-step>${harmony.bass.bassStep}</bass-step>`);
5119
5391
  if (harmony.bass.bassAlter !== void 0) {
5120
5392
  lines.push(`${indent} <bass-alter>${harmony.bass.bassAlter}</bass-alter>`);
5121
5393
  }
5122
5394
  lines.push(`${indent} </bass>`);
5123
5395
  }
5396
+ if (harmony.inversion !== void 0) {
5397
+ lines.push(`${indent} <inversion>${harmony.inversion}</inversion>`);
5398
+ }
5124
5399
  if (harmony.degrees) {
5125
5400
  for (const deg of harmony.degrees) {
5126
5401
  lines.push(`${indent} <degree>`);
@@ -5806,7 +6081,7 @@ function groupByVoice(measure) {
5806
6081
  for (const entry of measure.entries) {
5807
6082
  if (entry.type !== "note") continue;
5808
6083
  const staff = entry.staff ?? 1;
5809
- const voice = entry.voice;
6084
+ const voice = entry.voice ?? 1;
5810
6085
  const key = `${staff}-${voice}`;
5811
6086
  if (!groups.has(key)) {
5812
6087
  groups.set(key, { staff, voice, notes: [] });
@@ -5900,7 +6175,7 @@ function getVoices(measure) {
5900
6175
  const voices = /* @__PURE__ */ new Set();
5901
6176
  for (const entry of measure.entries) {
5902
6177
  if (entry.type === "note") {
5903
- voices.add(entry.voice);
6178
+ voices.add(entry.voice ?? 1);
5904
6179
  }
5905
6180
  }
5906
6181
  return Array.from(voices).sort((a, b) => a - b);
@@ -5951,7 +6226,7 @@ function buildVoiceToStaffMap(measure) {
5951
6226
  const map = /* @__PURE__ */ new Map();
5952
6227
  for (const entry of measure.entries) {
5953
6228
  if (entry.type === "note" && entry.staff !== void 0) {
5954
- const voice = entry.voice;
6229
+ const voice = entry.voice ?? 1;
5955
6230
  const staff = entry.staff;
5956
6231
  if (!map.has(voice)) {
5957
6232
  map.set(voice, staff);
@@ -5970,7 +6245,7 @@ function buildVoiceToStaffMapForPart(part) {
5970
6245
  for (const measure of part.measures) {
5971
6246
  for (const entry of measure.entries) {
5972
6247
  if (entry.type === "note" && entry.staff !== void 0) {
5973
- const voice = entry.voice;
6248
+ const voice = entry.voice ?? 1;
5974
6249
  const staff = entry.staff;
5975
6250
  if (!map.has(voice)) {
5976
6251
  map.set(voice, staff);
@@ -5989,7 +6264,7 @@ function inferStaff(entry, voiceToStaffMap) {
5989
6264
  if (entry.staff !== void 0) {
5990
6265
  return entry.staff;
5991
6266
  }
5992
- const inferredStaff = voiceToStaffMap.get(entry.voice);
6267
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
5993
6268
  if (inferredStaff !== void 0) {
5994
6269
  return inferredStaff;
5995
6270
  }
@@ -6032,7 +6307,7 @@ function getVoicesForStaff(measure, staff) {
6032
6307
  if (entry.type === "note") {
6033
6308
  const entryStaff = entry.staff ?? 1;
6034
6309
  if (entryStaff === staff) {
6035
- voices.add(entry.voice);
6310
+ voices.add(entry.voice ?? 1);
6036
6311
  }
6037
6312
  }
6038
6313
  }
@@ -6368,6 +6643,7 @@ function getDynamics(score, options) {
6368
6643
  if (dirType.kind === "dynamics") {
6369
6644
  results.push({
6370
6645
  dynamic: dirType.value,
6646
+ otherDynamics: dirType.otherDynamics,
6371
6647
  direction: entry,
6372
6648
  part,
6373
6649
  partIndex,
@@ -7442,14 +7718,17 @@ function hasNotesInRange(voiceEntries, startPos, endPos) {
7442
7718
  return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
7443
7719
  }
7444
7720
  function createRest(duration, voice, staff) {
7445
- return {
7721
+ const note = {
7446
7722
  _id: generateId(),
7447
7723
  type: "note",
7448
7724
  rest: { displayStep: void 0, displayOctave: void 0 },
7449
7725
  duration,
7450
- voice,
7451
7726
  staff
7452
7727
  };
7728
+ if (voice !== void 0) {
7729
+ note.voice = voice;
7730
+ }
7731
+ return note;
7453
7732
  }
7454
7733
  function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
7455
7734
  const otherEntries = [];
@@ -9003,7 +9282,7 @@ function autoBeam(score, options) {
9003
9282
  for (const entry of measure.entries) {
9004
9283
  if (entry.type === "note") {
9005
9284
  if (!entry.chord && !entry.rest) {
9006
- const voice = entry.voice;
9285
+ const voice = entry.voice ?? 1;
9007
9286
  if (options.voice === void 0 || voice === options.voice) {
9008
9287
  if (!notesByVoice.has(voice)) {
9009
9288
  notesByVoice.set(voice, []);