musicxml-io 0.2.12 → 0.3.1

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
@@ -290,11 +290,22 @@ var xmlParser = new import_fast_xml_parser.XMLParser({
290
290
  });
291
291
  function parse(xmlString) {
292
292
  const parsed = xmlParser.parse(xmlString);
293
- const scorePartwise = findElement(parsed, "score-partwise");
293
+ let scorePartwiseVersion;
294
+ let scorePartwise;
295
+ for (const el of parsed) {
296
+ if (el["score-partwise"]) {
297
+ scorePartwise = el["score-partwise"];
298
+ const attrs = getAttributes(el);
299
+ if (attrs["version"]) scorePartwiseVersion = attrs["version"];
300
+ break;
301
+ }
302
+ }
294
303
  if (!scorePartwise) {
295
304
  throw new Error("Unsupported MusicXML format: only score-partwise is supported");
296
305
  }
297
- return parseScorePartwise(scorePartwise);
306
+ const score = parseScorePartwise(scorePartwise);
307
+ if (scorePartwiseVersion) score.version = scorePartwiseVersion;
308
+ return score;
298
309
  }
299
310
  function findElement(elements, tagName) {
300
311
  for (const el of elements) {
@@ -512,9 +523,11 @@ function parseDefaults(elements) {
512
523
  const lineWidths = collectElements(appContent, "line-width", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
513
524
  const noteSizes = collectElements(appContent, "note-size", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
514
525
  const distances = collectElements(appContent, "distance", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
526
+ const glyphs = collectElements(appContent, "glyph", (c, a) => ({ type: a["type"] || "", value: extractText(c) }));
515
527
  if (lineWidths.length > 0) appearance["line-widths"] = lineWidths;
516
528
  if (noteSizes.length > 0) appearance["note-sizes"] = noteSizes;
517
529
  if (distances.length > 0) appearance["distances"] = distances;
530
+ if (glyphs.length > 0) appearance["glyphs"] = glyphs;
518
531
  return Object.keys(appearance).length > 0 ? appearance : void 0;
519
532
  });
520
533
  if (appResult) defaults.appearance = appResult;
@@ -536,13 +549,25 @@ function parsePageLayout(elements) {
536
549
  m.type = attrs["type"];
537
550
  }
538
551
  const left = getElementText(content, "left-margin");
539
- if (left) m.leftMargin = parseFloat(left);
552
+ if (left) {
553
+ m.leftMargin = parseFloat(left);
554
+ m.leftMarginRaw = left;
555
+ }
540
556
  const right = getElementText(content, "right-margin");
541
- if (right) m.rightMargin = parseFloat(right);
557
+ if (right) {
558
+ m.rightMargin = parseFloat(right);
559
+ m.rightMarginRaw = right;
560
+ }
542
561
  const top = getElementText(content, "top-margin");
543
- if (top) m.topMargin = parseFloat(top);
562
+ if (top) {
563
+ m.topMargin = parseFloat(top);
564
+ m.topMarginRaw = top;
565
+ }
544
566
  const bottom = getElementText(content, "bottom-margin");
545
- if (bottom) m.bottomMargin = parseFloat(bottom);
567
+ if (bottom) {
568
+ m.bottomMargin = parseFloat(bottom);
569
+ m.bottomMarginRaw = bottom;
570
+ }
546
571
  margins.push(m);
547
572
  }
548
573
  }
@@ -555,14 +580,48 @@ function parseSystemLayout(elements) {
555
580
  if (margins) {
556
581
  layout.systemMargins = {};
557
582
  const left = getElementText(margins, "left-margin");
558
- if (left) layout.systemMargins.leftMargin = parseFloat(left);
583
+ if (left) {
584
+ layout.systemMargins.leftMargin = parseFloat(left);
585
+ layout.systemMargins.leftMarginRaw = left;
586
+ }
559
587
  const right = getElementText(margins, "right-margin");
560
- if (right) layout.systemMargins.rightMargin = parseFloat(right);
588
+ if (right) {
589
+ layout.systemMargins.rightMargin = parseFloat(right);
590
+ layout.systemMargins.rightMarginRaw = right;
591
+ }
561
592
  }
562
593
  const dist = getElementText(elements, "system-distance");
563
- if (dist) layout.systemDistance = parseFloat(dist);
594
+ if (dist) {
595
+ layout.systemDistance = parseFloat(dist);
596
+ layout.systemDistanceRaw = dist;
597
+ }
564
598
  const topDist = getElementText(elements, "top-system-distance");
565
- if (topDist) layout.topSystemDistance = parseFloat(topDist);
599
+ if (topDist) {
600
+ layout.topSystemDistance = parseFloat(topDist);
601
+ layout.topSystemDistanceRaw = topDist;
602
+ }
603
+ const dividers = getElementContent(elements, "system-dividers");
604
+ if (dividers) {
605
+ layout.systemDividers = {};
606
+ for (const el of dividers) {
607
+ if (el["left-divider"]) {
608
+ const attrs = getAttributes(el);
609
+ layout.systemDividers.leftDivider = {
610
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
611
+ halign: attrs["halign"],
612
+ valign: attrs["valign"]
613
+ };
614
+ }
615
+ if (el["right-divider"]) {
616
+ const attrs = getAttributes(el);
617
+ layout.systemDividers.rightDivider = {
618
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
619
+ halign: attrs["halign"],
620
+ valign: attrs["valign"]
621
+ };
622
+ }
623
+ }
624
+ }
566
625
  return layout;
567
626
  }
568
627
  function parseCredits(elements) {
@@ -574,6 +633,7 @@ function parseCredits(elements) {
574
633
  const cw = { text: extractText(c) };
575
634
  if (a["default-x"]) cw.defaultX = parseFloat(a["default-x"]);
576
635
  if (a["default-y"]) cw.defaultY = parseFloat(a["default-y"]);
636
+ if (a["font-family"]) cw.fontFamily = a["font-family"];
577
637
  if (a["font-size"]) cw.fontSize = a["font-size"];
578
638
  if (a["font-weight"]) cw.fontWeight = a["font-weight"];
579
639
  if (a["font-style"]) cw.fontStyle = a["font-style"];
@@ -797,6 +857,15 @@ function parseMeasure(elements, attrs) {
797
857
  measure.entries.push(parseFiguredBass(el["figured-bass"], getAttributes(el)));
798
858
  } else if (el["sound"]) {
799
859
  measure.entries.push(parseSound(el["sound"], getAttributes(el)));
860
+ } else if (el["grouping"] !== void 0) {
861
+ const grpAttrs = getAttributes(el);
862
+ const grouping = {
863
+ _id: generateId(),
864
+ type: "grouping",
865
+ groupingType: grpAttrs["type"] || "start"
866
+ };
867
+ if (grpAttrs["number"]) grouping.number = grpAttrs["number"];
868
+ measure.entries.push(grouping);
800
869
  }
801
870
  }
802
871
  if (barlines.length > 0) measure.barlines = barlines;
@@ -863,6 +932,7 @@ function parseAttributes(elements) {
863
932
  const key = parseKeySignature(keyContent);
864
933
  if (keyAttrs["number"]) key.number = parseInt(keyAttrs["number"], 10);
865
934
  if (keyAttrs["print-object"] === "no") key.printObject = false;
935
+ else if (keyAttrs["print-object"] === "yes") key.printObject = true;
866
936
  keys.push(key);
867
937
  }
868
938
  }
@@ -914,14 +984,15 @@ function parseTimeSignature(elements, parentElements) {
914
984
  return time2;
915
985
  }
916
986
  }
917
- const beatsList = collectElements(elements, "beats", (c) => parseInt(extractText(c), 10));
987
+ const beatsStrList = collectElements(elements, "beats", (c) => extractText(c));
918
988
  const beatTypeList = collectElements(elements, "beat-type", (c) => parseInt(extractText(c), 10));
919
989
  const time = {
920
- beats: beatsList.length > 0 ? String(beatsList[0]) : "4",
990
+ beats: beatsStrList.length > 0 ? beatsStrList[0] : "4",
921
991
  beatType: beatTypeList.length > 0 ? beatTypeList[0] : 4
922
992
  };
923
- if (beatsList.length > 1 || beatTypeList.length > 1) {
924
- time.beatsList = beatsList;
993
+ if (beatsStrList.length > 1 || beatTypeList.length > 1) {
994
+ time.beatsList = beatsStrList.map((b) => parseInt(b, 10));
995
+ time.beatsStrList = beatsStrList;
925
996
  time.beatTypeList = beatTypeList;
926
997
  }
927
998
  for (const el of parentElements) {
@@ -960,6 +1031,7 @@ function parseKeySignature(elements) {
960
1031
  const keyOctaves = collectElements(elements, "key-octave", (c, a) => {
961
1032
  const ko = { number: parseInt(a["number"] || "1", 10), octave: parseInt(extractText(c), 10) };
962
1033
  if (a["cancel"] === "yes") ko.cancel = true;
1034
+ else if (a["cancel"] === "no") ko.cancel = false;
963
1035
  return ko;
964
1036
  });
965
1037
  if (keySteps.length > 0) key.keySteps = keySteps;
@@ -969,8 +1041,11 @@ function parseKeySignature(elements) {
969
1041
  }
970
1042
  function parseClef(elements, attrs) {
971
1043
  const sign = getElementText(elements, "sign") || "G";
972
- const line = parseInt(getElementText(elements, "line") || "2", 10);
973
- const clef = { sign, line };
1044
+ const lineText = getElementText(elements, "line");
1045
+ const clef = { sign };
1046
+ if (lineText) {
1047
+ clef.line = parseInt(lineText, 10);
1048
+ }
974
1049
  if (attrs["number"]) {
975
1050
  clef.staff = parseInt(attrs["number"], 10);
976
1051
  }
@@ -980,6 +1055,8 @@ function parseClef(elements, attrs) {
980
1055
  }
981
1056
  if (attrs["print-object"] === "no") {
982
1057
  clef.printObject = false;
1058
+ } else if (attrs["print-object"] === "yes") {
1059
+ clef.printObject = true;
983
1060
  }
984
1061
  if (attrs["after-barline"] === "yes") {
985
1062
  clef.afterBarline = true;
@@ -1001,15 +1078,20 @@ function parseNote(elements, attrs) {
1001
1078
  const note = {
1002
1079
  _id: generateId(),
1003
1080
  type: "note",
1004
- duration: getElementTextAsInt(elements, "duration", 0),
1005
- voice: getElementTextAsInt(elements, "voice", 1)
1081
+ duration: getElementTextAsInt(elements, "duration", 0)
1006
1082
  };
1083
+ const voiceValue = getElementTextAsInt(elements, "voice");
1084
+ if (voiceValue !== void 0) {
1085
+ note.voice = voiceValue;
1086
+ }
1007
1087
  if (attrs["default-x"]) note.defaultX = parseFloat(attrs["default-x"]);
1008
1088
  if (attrs["default-y"]) note.defaultY = parseFloat(attrs["default-y"]);
1009
1089
  if (attrs["relative-x"]) note.relativeX = parseFloat(attrs["relative-x"]);
1010
1090
  if (attrs["relative-y"]) note.relativeY = parseFloat(attrs["relative-y"]);
1011
1091
  if (attrs["dynamics"]) note.dynamics = parseFloat(attrs["dynamics"]);
1012
1092
  if (attrs["print-object"] === "no") note.printObject = false;
1093
+ if (attrs["print-dot"] === "no") note.printDot = false;
1094
+ if (attrs["print-dot"] === "yes") note.printDot = true;
1013
1095
  if (attrs["print-spacing"] === "yes") note.printSpacing = true;
1014
1096
  if (attrs["print-spacing"] === "no") note.printSpacing = false;
1015
1097
  if (hasElement(elements, "cue")) {
@@ -1122,6 +1204,7 @@ function parseNote(elements, attrs) {
1122
1204
  const graceAttrs = getAttributes(el);
1123
1205
  note.grace = {};
1124
1206
  if (graceAttrs["slash"] === "yes") note.grace.slash = true;
1207
+ else if (graceAttrs["slash"] === "no") note.grace.slash = false;
1125
1208
  if (graceAttrs["steal-time-previous"]) {
1126
1209
  note.grace.stealTimePrevious = parseFloat(graceAttrs["steal-time-previous"]);
1127
1210
  }
@@ -1367,6 +1450,14 @@ function parseNotations(elements, notationsIndex = 0) {
1367
1450
  notations.push(tremNotation);
1368
1451
  }
1369
1452
  }
1453
+ const ornamentNotationsAdded = notations.filter((n) => n.type === "ornament" && n.notationsIndex === notationsIndex);
1454
+ if (ornamentNotationsAdded.length === 0) {
1455
+ notations.push({
1456
+ type: "ornament",
1457
+ ornament: "empty",
1458
+ notationsIndex
1459
+ });
1460
+ }
1370
1461
  } else if (el["technical"]) {
1371
1462
  const techContent = el["technical"];
1372
1463
  const technicalWithText = ["hammer-on", "pull-off", "tap", "pluck", "fingering", "other-technical"];
@@ -1416,8 +1507,7 @@ function parseNotations(elements, notationsIndex = 0) {
1416
1507
  if (bendAlter) techNotation.bendAlter = parseFloat(bendAlter);
1417
1508
  if (hasElement(bendContent, "pre-bend")) techNotation.preBend = true;
1418
1509
  if (hasElement(bendContent, "release")) techNotation.release = true;
1419
- const withBar = getElementText(bendContent, "with-bar");
1420
- if (withBar) techNotation.withBar = parseFloat(withBar);
1510
+ if (hasElement(bendContent, "with-bar")) techNotation.withBar = true;
1421
1511
  notations.push(techNotation);
1422
1512
  }
1423
1513
  for (const techType of technicalTypes) {
@@ -1454,6 +1544,7 @@ function parseNotations(elements, notationsIndex = 0) {
1454
1544
  if (typeAttr === "start" || typeAttr === "stop") {
1455
1545
  notation.startStop = typeAttr;
1456
1546
  }
1547
+ if (techAttrs["number"]) notation.number = parseInt(techAttrs["number"], 10);
1457
1548
  }
1458
1549
  if (techType === "fingering") {
1459
1550
  if (techAttrs["substitution"] === "yes") notation.fingeringSubstitution = true;
@@ -1536,11 +1627,33 @@ function parseNotations(elements, notationsIndex = 0) {
1536
1627
  notations.push(fermataNotation);
1537
1628
  } else if (el["arpeggiate"] !== void 0) {
1538
1629
  const arpAttrs = getAttributes(el);
1539
- notations.push({
1630
+ const arpNotation = {
1540
1631
  type: "arpeggiate",
1541
1632
  direction: arpAttrs["direction"],
1542
1633
  number: arpAttrs["number"] ? parseInt(arpAttrs["number"], 10) : void 0,
1543
1634
  notationsIndex
1635
+ };
1636
+ if (arpAttrs["default-x"]) arpNotation.defaultX = parseFloat(arpAttrs["default-x"]);
1637
+ if (arpAttrs["default-y"]) arpNotation.defaultY = parseFloat(arpAttrs["default-y"]);
1638
+ notations.push(arpNotation);
1639
+ } else if (el["non-arpeggiate"] !== void 0) {
1640
+ const nonArpAttrs = getAttributes(el);
1641
+ notations.push({
1642
+ type: "non-arpeggiate",
1643
+ nonArpeggiateType: nonArpAttrs["type"],
1644
+ number: nonArpAttrs["number"] ? parseInt(nonArpAttrs["number"], 10) : void 0,
1645
+ placement: nonArpAttrs["placement"],
1646
+ notationsIndex
1647
+ });
1648
+ } else if (el["accidental-mark"]) {
1649
+ const amAttrs = getAttributes(el);
1650
+ const amContent = el["accidental-mark"];
1651
+ const value = extractText(amContent);
1652
+ notations.push({
1653
+ type: "accidental-mark",
1654
+ value,
1655
+ placement: amAttrs["placement"],
1656
+ notationsIndex
1544
1657
  });
1545
1658
  } else if (el["glissando"]) {
1546
1659
  const glissAttrs = getAttributes(el);
@@ -1562,13 +1675,17 @@ function parseNotations(elements, notationsIndex = 0) {
1562
1675
  });
1563
1676
  } else if (el["slide"]) {
1564
1677
  const slideAttrs = getAttributes(el);
1565
- notations.push({
1678
+ const slideContent = el["slide"];
1679
+ const slideText = extractText(slideContent);
1680
+ const slideNotation = {
1566
1681
  type: "slide",
1567
1682
  slideType: slideAttrs["type"] === "stop" ? "stop" : "start",
1568
1683
  number: slideAttrs["number"] ? parseInt(slideAttrs["number"], 10) : void 0,
1569
1684
  lineType: slideAttrs["line-type"],
1570
1685
  notationsIndex
1571
- });
1686
+ };
1687
+ if (slideText) slideNotation.text = slideText;
1688
+ notations.push(slideNotation);
1572
1689
  }
1573
1690
  }
1574
1691
  return notations;
@@ -1589,8 +1706,9 @@ function parseLyric(elements, attrs) {
1589
1706
  break;
1590
1707
  }
1591
1708
  }
1592
- } else if (el["text"]) {
1709
+ } else if (el["text"] !== void 0) {
1593
1710
  const content = el["text"];
1711
+ let foundText = false;
1594
1712
  for (const item of content) {
1595
1713
  if (item["#text"] !== void 0) {
1596
1714
  textElements.push({
@@ -1598,9 +1716,17 @@ function parseLyric(elements, attrs) {
1598
1716
  syllabic: currentSyllabic
1599
1717
  });
1600
1718
  currentSyllabic = void 0;
1719
+ foundText = true;
1601
1720
  break;
1602
1721
  }
1603
1722
  }
1723
+ if (!foundText) {
1724
+ textElements.push({
1725
+ text: "",
1726
+ syllabic: currentSyllabic
1727
+ });
1728
+ currentSyllabic = void 0;
1729
+ }
1604
1730
  } else if (el["elision"] !== void 0) {
1605
1731
  hasElision = true;
1606
1732
  }
@@ -1769,6 +1895,7 @@ function parseDirectionTypes(elements) {
1769
1895
  "pf"
1770
1896
  ];
1771
1897
  for (const dyn of dynContent) {
1898
+ let foundStandard = false;
1772
1899
  for (const dv of dynamicsValues) {
1773
1900
  if (dyn[dv] !== void 0) {
1774
1901
  const result = { kind: "dynamics", value: dv };
@@ -1777,9 +1904,22 @@ function parseDirectionTypes(elements) {
1777
1904
  if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1778
1905
  if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1779
1906
  results.push(result);
1907
+ foundStandard = true;
1780
1908
  break;
1781
1909
  }
1782
1910
  }
1911
+ if (!foundStandard && dyn["other-dynamics"] !== void 0) {
1912
+ const otherDynContent = dyn["other-dynamics"];
1913
+ const otherDynText = extractText(otherDynContent);
1914
+ if (otherDynText) {
1915
+ const result = { kind: "dynamics", otherDynamics: otherDynText };
1916
+ if (dynAttrs["default-x"]) result.defaultX = parseFloat(dynAttrs["default-x"]);
1917
+ if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
1918
+ if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1919
+ if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1920
+ results.push(result);
1921
+ }
1922
+ }
1783
1923
  }
1784
1924
  continue;
1785
1925
  }
@@ -1904,11 +2044,14 @@ function parseDirectionTypes(elements) {
1904
2044
  for (const acc of accContent) {
1905
2045
  if (acc["accordion-high"] !== void 0) {
1906
2046
  result.high = true;
1907
- } else if (acc["accordion-middle"]) {
2047
+ } else if (acc["accordion-middle"] !== void 0) {
2048
+ result.middlePresent = true;
1908
2049
  const midContent = acc["accordion-middle"];
1909
2050
  for (const item of midContent) {
1910
2051
  if (item["#text"] !== void 0) {
1911
- result.middle = parseInt(String(item["#text"]), 10);
2052
+ const textValue = String(item["#text"]);
2053
+ const numValue = parseInt(textValue, 10);
2054
+ result.middle = !isNaN(numValue) ? numValue : textValue;
1912
2055
  break;
1913
2056
  }
1914
2057
  }
@@ -2085,6 +2228,9 @@ function parseBarline(elements, attrs) {
2085
2228
  if (repeatAttrs["times"]) {
2086
2229
  barline.repeat.times = parseInt(repeatAttrs["times"], 10);
2087
2230
  }
2231
+ if (repeatAttrs["winged"]) {
2232
+ barline.repeat.winged = repeatAttrs["winged"];
2233
+ }
2088
2234
  }
2089
2235
  } else if (el["ending"]) {
2090
2236
  const endingAttrs = getAttributes(el);
@@ -2092,6 +2238,11 @@ function parseBarline(elements, attrs) {
2092
2238
  const type = endingAttrs["type"];
2093
2239
  if (number && (type === "start" || type === "stop" || type === "discontinue")) {
2094
2240
  barline.ending = { number, type };
2241
+ const endingContent = el["ending"];
2242
+ const endingText = extractText(endingContent);
2243
+ if (endingText) barline.ending.text = endingText;
2244
+ if (endingAttrs["default-y"]) barline.ending.defaultY = parseFloat(endingAttrs["default-y"]);
2245
+ if (endingAttrs["end-length"]) barline.ending.endLength = parseFloat(endingAttrs["end-length"]);
2095
2246
  }
2096
2247
  }
2097
2248
  }
@@ -2217,6 +2368,8 @@ function parseStaffDetails(elements, attrs) {
2217
2368
  if (attrs["number"]) sd.number = parseInt(attrs["number"], 10);
2218
2369
  if (attrs["print-object"] === "no") sd.printObject = false;
2219
2370
  else if (attrs["print-object"] === "yes") sd.printObject = true;
2371
+ if (attrs["print-spacing"] === "yes") sd.printSpacing = true;
2372
+ else if (attrs["print-spacing"] === "no") sd.printSpacing = false;
2220
2373
  const staffType = getElementText(elements, "staff-type");
2221
2374
  if (staffType && ["ossia", "cue", "editorial", "regular", "alternate"].includes(staffType)) {
2222
2375
  sd.staffType = staffType;
@@ -2280,7 +2433,9 @@ function parseMeasureStyle(elements, attrs) {
2280
2433
  const slAttrs = getAttributes(el);
2281
2434
  ms.slash = { type: slAttrs["type"] === "stop" ? "stop" : "start" };
2282
2435
  if (slAttrs["use-dots"] === "yes") ms.slash.useDots = true;
2436
+ else if (slAttrs["use-dots"] === "no") ms.slash.useDots = false;
2283
2437
  if (slAttrs["use-stems"] === "yes") ms.slash.useStems = true;
2438
+ else if (slAttrs["use-stems"] === "no") ms.slash.useStems = false;
2284
2439
  }
2285
2440
  }
2286
2441
  return ms;
@@ -2320,19 +2475,29 @@ function parseHarmony(elements, attrs) {
2320
2475
  break;
2321
2476
  }
2322
2477
  }
2323
- if (kindAttrs["text"]) harmony.kindText = kindAttrs["text"];
2478
+ if (kindAttrs["text"] !== void 0) harmony.kindText = kindAttrs["text"];
2479
+ if (kindAttrs["halign"]) harmony.kindHalign = kindAttrs["halign"];
2324
2480
  break;
2325
2481
  }
2326
2482
  }
2327
- const bass = getElementContent(elements, "bass");
2328
- if (bass) {
2329
- const bassStep = getElementText(bass, "bass-step");
2330
- if (bassStep) {
2331
- harmony.bass = { bassStep };
2332
- const bassAlter = getElementText(bass, "bass-alter");
2333
- if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2483
+ for (const el of elements) {
2484
+ if (el["bass"]) {
2485
+ const bassAttrs = getAttributes(el);
2486
+ const bassContent = el["bass"];
2487
+ const bassStep = getElementText(bassContent, "bass-step");
2488
+ if (bassStep) {
2489
+ harmony.bass = { bassStep };
2490
+ const bassAlter = getElementText(bassContent, "bass-alter");
2491
+ if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2492
+ if (bassAttrs["arrangement"]) harmony.bass.arrangement = bassAttrs["arrangement"];
2493
+ }
2494
+ break;
2334
2495
  }
2335
2496
  }
2497
+ const inversionText = getElementText(elements, "inversion");
2498
+ if (inversionText) {
2499
+ harmony.inversion = parseInt(inversionText, 10);
2500
+ }
2336
2501
  const degrees = [];
2337
2502
  for (const el of elements) {
2338
2503
  if (el["degree"]) {
@@ -3099,7 +3264,7 @@ function validateVoiceStaff(measure, staves, location) {
3099
3264
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
3100
3265
  const entry = measure.entries[entryIndex];
3101
3266
  if (entry.type !== "note") continue;
3102
- if (entry.voice <= 0) {
3267
+ if (entry.voice !== void 0 && entry.voice <= 0) {
3103
3268
  errors.push({
3104
3269
  code: "INVALID_VOICE_NUMBER",
3105
3270
  level: "error",
@@ -3595,7 +3760,7 @@ function validateSlursAcrossMeasures(part) {
3595
3760
 
3596
3761
  // src/exporters/musicxml.ts
3597
3762
  function serialize(score, options = {}) {
3598
- const version = options.version || "4.0";
3763
+ const version = options.version || score.version || "4.0";
3599
3764
  const indent = options.indent ?? " ";
3600
3765
  if (options.validate) {
3601
3766
  const result = validate(score, options.validateOptions);
@@ -3610,11 +3775,7 @@ ${errorMessages}`);
3610
3775
  }
3611
3776
  const lines = [];
3612
3777
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
3613
- if (version === "4.0") {
3614
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3615
- } else {
3616
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3617
- }
3778
+ lines.push(`<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML ${version} Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">`);
3618
3779
  lines.push(`<score-partwise version="${version}">`);
3619
3780
  lines.push(...serializeMetadata(score.metadata, indent));
3620
3781
  if (score.defaults) {
@@ -3748,6 +3909,11 @@ function serializeDefaults(defaults, indent) {
3748
3909
  lines.push(`${indent}${indent}${indent}<distance type="${escapeXml(d.type)}">${d.value}</distance>`);
3749
3910
  }
3750
3911
  }
3912
+ if (app["glyphs"]) {
3913
+ for (const g of app["glyphs"]) {
3914
+ lines.push(`${indent}${indent}${indent}<glyph type="${escapeXml(g.type)}">${escapeXml(g.value)}</glyph>`);
3915
+ }
3916
+ }
3751
3917
  lines.push(`${indent}${indent}</appearance>`);
3752
3918
  }
3753
3919
  if (defaults.musicFont) {
@@ -3804,16 +3970,16 @@ function serializePageLayout(layout, indent) {
3804
3970
  const typeAttr = m.type ? ` type="${m.type}"` : "";
3805
3971
  lines.push(`${indent} <page-margins${typeAttr}>`);
3806
3972
  if (m.leftMargin !== void 0) {
3807
- lines.push(`${indent} <left-margin>${m.leftMargin}</left-margin>`);
3973
+ lines.push(`${indent} <left-margin>${m.leftMarginRaw ?? m.leftMargin}</left-margin>`);
3808
3974
  }
3809
3975
  if (m.rightMargin !== void 0) {
3810
- lines.push(`${indent} <right-margin>${m.rightMargin}</right-margin>`);
3976
+ lines.push(`${indent} <right-margin>${m.rightMarginRaw ?? m.rightMargin}</right-margin>`);
3811
3977
  }
3812
3978
  if (m.topMargin !== void 0) {
3813
- lines.push(`${indent} <top-margin>${m.topMargin}</top-margin>`);
3979
+ lines.push(`${indent} <top-margin>${m.topMarginRaw ?? m.topMargin}</top-margin>`);
3814
3980
  }
3815
3981
  if (m.bottomMargin !== void 0) {
3816
- lines.push(`${indent} <bottom-margin>${m.bottomMargin}</bottom-margin>`);
3982
+ lines.push(`${indent} <bottom-margin>${m.bottomMarginRaw ?? m.bottomMargin}</bottom-margin>`);
3817
3983
  }
3818
3984
  lines.push(`${indent} </page-margins>`);
3819
3985
  }
@@ -3827,18 +3993,48 @@ function serializeSystemLayout(layout, indent) {
3827
3993
  if (layout.systemMargins) {
3828
3994
  lines.push(`${indent} <system-margins>`);
3829
3995
  if (layout.systemMargins.leftMargin !== void 0) {
3830
- lines.push(`${indent} <left-margin>${layout.systemMargins.leftMargin}</left-margin>`);
3996
+ lines.push(`${indent} <left-margin>${layout.systemMargins.leftMarginRaw ?? layout.systemMargins.leftMargin}</left-margin>`);
3831
3997
  }
3832
3998
  if (layout.systemMargins.rightMargin !== void 0) {
3833
- lines.push(`${indent} <right-margin>${layout.systemMargins.rightMargin}</right-margin>`);
3999
+ lines.push(`${indent} <right-margin>${layout.systemMargins.rightMarginRaw ?? layout.systemMargins.rightMargin}</right-margin>`);
3834
4000
  }
3835
4001
  lines.push(`${indent} </system-margins>`);
3836
4002
  }
3837
4003
  if (layout.systemDistance !== void 0) {
3838
- lines.push(`${indent} <system-distance>${layout.systemDistance}</system-distance>`);
4004
+ lines.push(`${indent} <system-distance>${layout.systemDistanceRaw ?? layout.systemDistance}</system-distance>`);
3839
4005
  }
3840
4006
  if (layout.topSystemDistance !== void 0) {
3841
- lines.push(`${indent} <top-system-distance>${layout.topSystemDistance}</top-system-distance>`);
4007
+ lines.push(`${indent} <top-system-distance>${layout.topSystemDistanceRaw ?? layout.topSystemDistance}</top-system-distance>`);
4008
+ }
4009
+ if (layout.systemDividers) {
4010
+ lines.push(`${indent} <system-dividers>`);
4011
+ if (layout.systemDividers.leftDivider) {
4012
+ let attrs = "";
4013
+ if (layout.systemDividers.leftDivider.printObject !== void 0) {
4014
+ attrs += ` print-object="${layout.systemDividers.leftDivider.printObject ? "yes" : "no"}"`;
4015
+ }
4016
+ if (layout.systemDividers.leftDivider.halign) {
4017
+ attrs += ` halign="${layout.systemDividers.leftDivider.halign}"`;
4018
+ }
4019
+ if (layout.systemDividers.leftDivider.valign) {
4020
+ attrs += ` valign="${layout.systemDividers.leftDivider.valign}"`;
4021
+ }
4022
+ lines.push(`${indent} <left-divider${attrs}/>`);
4023
+ }
4024
+ if (layout.systemDividers.rightDivider) {
4025
+ let attrs = "";
4026
+ if (layout.systemDividers.rightDivider.printObject !== void 0) {
4027
+ attrs += ` print-object="${layout.systemDividers.rightDivider.printObject ? "yes" : "no"}"`;
4028
+ }
4029
+ if (layout.systemDividers.rightDivider.halign) {
4030
+ attrs += ` halign="${layout.systemDividers.rightDivider.halign}"`;
4031
+ }
4032
+ if (layout.systemDividers.rightDivider.valign) {
4033
+ attrs += ` valign="${layout.systemDividers.rightDivider.valign}"`;
4034
+ }
4035
+ lines.push(`${indent} <right-divider${attrs}/>`);
4036
+ }
4037
+ lines.push(`${indent} </system-dividers>`);
3842
4038
  }
3843
4039
  lines.push(`${indent}</system-layout>`);
3844
4040
  return lines;
@@ -3859,6 +4055,7 @@ function serializeCredit(credit, indent) {
3859
4055
  let attrs2 = "";
3860
4056
  if (cw.defaultX !== void 0) attrs2 += ` default-x="${cw.defaultX}"`;
3861
4057
  if (cw.defaultY !== void 0) attrs2 += ` default-y="${cw.defaultY}"`;
4058
+ if (cw.fontFamily) attrs2 += ` font-family="${escapeXml(cw.fontFamily)}"`;
3862
4059
  if (cw.fontSize) attrs2 += ` font-size="${escapeXml(cw.fontSize)}"`;
3863
4060
  if (cw.fontWeight) attrs2 += ` font-weight="${escapeXml(cw.fontWeight)}"`;
3864
4061
  if (cw.fontStyle) attrs2 += ` font-style="${escapeXml(cw.fontStyle)}"`;
@@ -4153,6 +4350,7 @@ function serializeKey(key, indent) {
4153
4350
  let keyAttrs = "";
4154
4351
  if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
4155
4352
  if (key.printObject === false) keyAttrs += ' print-object="no"';
4353
+ else if (key.printObject === true) keyAttrs += ' print-object="yes"';
4156
4354
  lines.push(`${indent}<key${keyAttrs}>`);
4157
4355
  if (key.cancel !== void 0) {
4158
4356
  const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
@@ -4200,7 +4398,8 @@ function serializeTime(time, indent) {
4200
4398
  const maxLen = Math.max(time.beatsList.length, time.beatTypeList.length);
4201
4399
  for (let i = 0; i < maxLen; i++) {
4202
4400
  if (i < time.beatsList.length) {
4203
- lines.push(`${indent} <beats>${time.beatsList[i]}</beats>`);
4401
+ const beatsValue = time.beatsStrList && i < time.beatsStrList.length ? time.beatsStrList[i] : time.beatsList[i];
4402
+ lines.push(`${indent} <beats>${beatsValue}</beats>`);
4204
4403
  }
4205
4404
  if (i < time.beatTypeList.length) {
4206
4405
  lines.push(`${indent} <beat-type>${time.beatTypeList[i]}</beat-type>`);
@@ -4217,10 +4416,13 @@ function serializeClef(clef, indent) {
4217
4416
  const lines = [];
4218
4417
  let attrs = clef.staff ? ` number="${clef.staff}"` : "";
4219
4418
  if (clef.printObject === false) attrs += ' print-object="no"';
4419
+ else if (clef.printObject === true) attrs += ' print-object="yes"';
4220
4420
  if (clef.afterBarline) attrs += ' after-barline="yes"';
4221
4421
  lines.push(`${indent}<clef${attrs}>`);
4222
4422
  lines.push(`${indent} <sign>${clef.sign}</sign>`);
4223
- lines.push(`${indent} <line>${clef.line}</line>`);
4423
+ if (clef.line !== void 0) {
4424
+ lines.push(`${indent} <line>${clef.line}</line>`);
4425
+ }
4224
4426
  if (clef.clefOctaveChange !== void 0) {
4225
4427
  lines.push(`${indent} <clef-octave-change>${clef.clefOctaveChange}</clef-octave-change>`);
4226
4428
  }
@@ -4256,6 +4458,12 @@ function serializeEntry(entry, indent) {
4256
4458
  return serializeSound(entry, indent);
4257
4459
  case "attributes":
4258
4460
  return serializeAttributes(entry.attributes, indent, entry._id);
4461
+ case "grouping": {
4462
+ const grp = entry;
4463
+ let grpAttrs = ` type="${grp.groupingType}"`;
4464
+ if (grp.number) grpAttrs += ` number="${grp.number}"`;
4465
+ return [`${indent}<grouping${grpAttrs}/>`];
4466
+ }
4259
4467
  default:
4260
4468
  return [];
4261
4469
  }
@@ -4270,12 +4478,13 @@ function serializeNote(note, indent) {
4270
4478
  "relative-y": note.relativeY,
4271
4479
  "dynamics": note.dynamics,
4272
4480
  "print-object": note.printObject === false ? false : void 0,
4481
+ "print-dot": note.printDot !== void 0 ? note.printDot : void 0,
4273
4482
  "print-spacing": note.printSpacing
4274
4483
  });
4275
4484
  lines.push(`${indent}<note${noteAttrs}>`);
4276
4485
  if (note.grace) {
4277
4486
  const graceAttrs = buildAttrs({
4278
- "slash": note.grace.slash || void 0,
4487
+ "slash": note.grace.slash !== void 0 ? note.grace.slash : void 0,
4279
4488
  "steal-time-previous": note.grace.stealTimePrevious,
4280
4489
  "steal-time-following": note.grace.stealTimeFollowing
4281
4490
  });
@@ -4330,7 +4539,9 @@ function serializeNote(note, indent) {
4330
4539
  } else if (note.tie) {
4331
4540
  lines.push(`${indent} <tie type="${note.tie.type}"/>`);
4332
4541
  }
4333
- lines.push(`${indent} <voice>${note.voice}</voice>`);
4542
+ if (note.voice !== void 0) {
4543
+ lines.push(`${indent} <voice>${note.voice}</voice>`);
4544
+ }
4334
4545
  if (note.noteType) {
4335
4546
  const typeAttrs = note.noteTypeSize ? ` size="${escapeXml(note.noteTypeSize)}"` : "";
4336
4547
  lines.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
@@ -4438,154 +4649,200 @@ function serializeNotations(notations, indent) {
4438
4649
  function serializeNotationsGroup(notations, indent) {
4439
4650
  const lines = [];
4440
4651
  lines.push(`${indent}<notations>`);
4441
- const articulationsGroups = /* @__PURE__ */ new Map();
4442
- const ornaments = [];
4443
- const technicals = [];
4444
- const others = [];
4652
+ const chunks = [];
4445
4653
  for (const notation of notations) {
4446
4654
  if (notation.type === "articulation") {
4447
4655
  const artIdx = notation.articulationsIndex ?? 0;
4448
- if (!articulationsGroups.has(artIdx)) {
4449
- articulationsGroups.set(artIdx, []);
4656
+ const last = chunks[chunks.length - 1];
4657
+ if (last && last.kind === "articulations" && last.articulationsIndex === artIdx) {
4658
+ last.items.push(notation);
4659
+ } else {
4660
+ chunks.push({ kind: "articulations", items: [notation], articulationsIndex: artIdx });
4450
4661
  }
4451
- articulationsGroups.get(artIdx).push(notation);
4452
4662
  } else if (notation.type === "ornament") {
4453
- ornaments.push(notation);
4663
+ const last = chunks[chunks.length - 1];
4664
+ if (last && last.kind === "ornaments") {
4665
+ last.items.push(notation);
4666
+ } else {
4667
+ chunks.push({ kind: "ornaments", items: [notation] });
4668
+ }
4454
4669
  } else if (notation.type === "technical") {
4455
- technicals.push(notation);
4670
+ const last = chunks[chunks.length - 1];
4671
+ if (last && last.kind === "technical") {
4672
+ last.items.push(notation);
4673
+ } else {
4674
+ chunks.push({ kind: "technical", items: [notation] });
4675
+ }
4456
4676
  } else {
4457
- others.push(notation);
4677
+ chunks.push({ kind: "standalone", notation });
4458
4678
  }
4459
4679
  }
4460
- for (const notation of others) {
4461
- if (notation.type === "tied") {
4462
- let attrs = ` type="${notation.tiedType}"`;
4463
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4464
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4465
- lines.push(`${indent} <tied${attrs}/>`);
4466
- } else if (notation.type === "slur") {
4467
- let attrs = "";
4468
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4469
- attrs += ` type="${notation.slurType}"`;
4470
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4471
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4472
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4473
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4474
- if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4475
- if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4476
- if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4477
- if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4478
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4479
- lines.push(`${indent} <slur${attrs}/>`);
4480
- } else if (notation.type === "tuplet") {
4481
- let attrs = ` type="${notation.tupletType}"`;
4482
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4483
- if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4484
- if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4485
- if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4486
- if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4487
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4488
- const tup = notation;
4489
- if (tup.tupletActual || tup.tupletNormal) {
4490
- lines.push(`${indent} <tuplet${attrs}>`);
4491
- if (tup.tupletActual) {
4492
- lines.push(`${indent} <tuplet-actual>`);
4493
- if (tup.tupletActual.tupletNumber !== void 0) {
4494
- lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4495
- }
4496
- if (tup.tupletActual.tupletType) {
4497
- lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4498
- }
4499
- if (tup.tupletActual.tupletDots) {
4500
- for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4501
- lines.push(`${indent} <tuplet-dot/>`);
4502
- }
4680
+ for (const chunk of chunks) {
4681
+ if (chunk.kind === "standalone") {
4682
+ lines.push(...serializeStandaloneNotation(chunk.notation, indent));
4683
+ } else if (chunk.kind === "articulations") {
4684
+ lines.push(...serializeArticulationsGroup(chunk.items, indent));
4685
+ } else if (chunk.kind === "ornaments") {
4686
+ lines.push(...serializeOrnamentsGroup(chunk.items, indent));
4687
+ } else if (chunk.kind === "technical") {
4688
+ lines.push(...serializeTechnicalGroup(chunk.items, indent));
4689
+ }
4690
+ }
4691
+ lines.push(`${indent}</notations>`);
4692
+ return lines;
4693
+ }
4694
+ function serializeStandaloneNotation(notation, indent) {
4695
+ const lines = [];
4696
+ if (notation.type === "tied") {
4697
+ let attrs = ` type="${notation.tiedType}"`;
4698
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4699
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4700
+ lines.push(`${indent} <tied${attrs}/>`);
4701
+ } else if (notation.type === "slur") {
4702
+ let attrs = "";
4703
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4704
+ attrs += ` type="${notation.slurType}"`;
4705
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4706
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4707
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4708
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4709
+ if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4710
+ if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4711
+ if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4712
+ if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4713
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4714
+ lines.push(`${indent} <slur${attrs}/>`);
4715
+ } else if (notation.type === "tuplet") {
4716
+ let attrs = ` type="${notation.tupletType}"`;
4717
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4718
+ if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4719
+ if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4720
+ if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4721
+ if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4722
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4723
+ const tup = notation;
4724
+ if (tup.tupletActual || tup.tupletNormal) {
4725
+ lines.push(`${indent} <tuplet${attrs}>`);
4726
+ if (tup.tupletActual) {
4727
+ lines.push(`${indent} <tuplet-actual>`);
4728
+ if (tup.tupletActual.tupletNumber !== void 0) {
4729
+ lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4730
+ }
4731
+ if (tup.tupletActual.tupletType) {
4732
+ lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4733
+ }
4734
+ if (tup.tupletActual.tupletDots) {
4735
+ for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4736
+ lines.push(`${indent} <tuplet-dot/>`);
4503
4737
  }
4504
- lines.push(`${indent} </tuplet-actual>`);
4505
4738
  }
4506
- if (tup.tupletNormal) {
4507
- lines.push(`${indent} <tuplet-normal>`);
4508
- if (tup.tupletNormal.tupletNumber !== void 0) {
4509
- lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4510
- }
4511
- if (tup.tupletNormal.tupletType) {
4512
- lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4513
- }
4514
- if (tup.tupletNormal.tupletDots) {
4515
- for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4516
- lines.push(`${indent} <tuplet-dot/>`);
4517
- }
4739
+ lines.push(`${indent} </tuplet-actual>`);
4740
+ }
4741
+ if (tup.tupletNormal) {
4742
+ lines.push(`${indent} <tuplet-normal>`);
4743
+ if (tup.tupletNormal.tupletNumber !== void 0) {
4744
+ lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4745
+ }
4746
+ if (tup.tupletNormal.tupletType) {
4747
+ lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4748
+ }
4749
+ if (tup.tupletNormal.tupletDots) {
4750
+ for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4751
+ lines.push(`${indent} <tuplet-dot/>`);
4518
4752
  }
4519
- lines.push(`${indent} </tuplet-normal>`);
4520
4753
  }
4521
- lines.push(`${indent} </tuplet>`);
4522
- } else {
4523
- lines.push(`${indent} <tuplet${attrs}/>`);
4524
- }
4525
- } else if (notation.type === "dynamics") {
4526
- const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4527
- lines.push(`${indent} <dynamics${placementAttr}>`);
4528
- for (const dyn of notation.dynamics) {
4529
- lines.push(`${indent} <${dyn}/>`);
4530
- }
4531
- if (notation.otherDynamics) {
4532
- lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4533
- }
4534
- lines.push(`${indent} </dynamics>`);
4535
- } else if (notation.type === "fermata") {
4536
- let attrs = "";
4537
- if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4538
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4539
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4540
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4541
- if (notation.shape) {
4542
- lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4543
- } else {
4544
- lines.push(`${indent} <fermata${attrs}/>`);
4545
- }
4546
- } else if (notation.type === "arpeggiate") {
4547
- let attrs = "";
4548
- if (notation.direction) attrs += ` direction="${notation.direction}"`;
4549
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4550
- lines.push(`${indent} <arpeggiate${attrs}/>`);
4551
- } else if (notation.type === "glissando") {
4552
- let attrs = ` type="${notation.glissandoType}"`;
4553
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4554
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4555
- if (notation.text) {
4556
- lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4557
- } else {
4558
- lines.push(`${indent} <glissando${attrs}/>`);
4754
+ lines.push(`${indent} </tuplet-normal>`);
4559
4755
  }
4560
- } else if (notation.type === "slide") {
4561
- let attrs = ` type="${notation.slideType}"`;
4562
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4563
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4756
+ lines.push(`${indent} </tuplet>`);
4757
+ } else {
4758
+ lines.push(`${indent} <tuplet${attrs}/>`);
4759
+ }
4760
+ } else if (notation.type === "dynamics") {
4761
+ const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4762
+ lines.push(`${indent} <dynamics${placementAttr}>`);
4763
+ for (const dyn of notation.dynamics) {
4764
+ lines.push(`${indent} <${dyn}/>`);
4765
+ }
4766
+ if (notation.otherDynamics) {
4767
+ lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4768
+ }
4769
+ lines.push(`${indent} </dynamics>`);
4770
+ } else if (notation.type === "fermata") {
4771
+ let attrs = "";
4772
+ if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4773
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4774
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4775
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4776
+ if (notation.shape) {
4777
+ lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4778
+ } else {
4779
+ lines.push(`${indent} <fermata${attrs}/>`);
4780
+ }
4781
+ } else if (notation.type === "arpeggiate") {
4782
+ let attrs = "";
4783
+ if (notation.direction) attrs += ` direction="${notation.direction}"`;
4784
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4785
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4786
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4787
+ lines.push(`${indent} <arpeggiate${attrs}/>`);
4788
+ } else if (notation.type === "non-arpeggiate") {
4789
+ let attrs = ` type="${notation.nonArpeggiateType}"`;
4790
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4791
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4792
+ lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4793
+ } else if (notation.type === "accidental-mark") {
4794
+ let attrs = "";
4795
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4796
+ lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4797
+ } else if (notation.type === "glissando") {
4798
+ let attrs = ` type="${notation.glissandoType}"`;
4799
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4800
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4801
+ if (notation.text) {
4802
+ lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4803
+ } else {
4804
+ lines.push(`${indent} <glissando${attrs}/>`);
4805
+ }
4806
+ } else if (notation.type === "slide") {
4807
+ let attrs = ` type="${notation.slideType}"`;
4808
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4809
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4810
+ if (notation.text) {
4811
+ lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4812
+ } else {
4564
4813
  lines.push(`${indent} <slide${attrs}/>`);
4565
4814
  }
4566
4815
  }
4567
- const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
4568
- for (const artIdx of sortedArtIndices) {
4569
- const artGroup = articulationsGroups.get(artIdx);
4570
- lines.push(`${indent} <articulations>`);
4571
- for (const art of artGroup) {
4572
- if (art.type === "articulation") {
4573
- let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4574
- if (art.articulation === "strong-accent" && art.strongAccentType) {
4575
- artAttrs += ` type="${art.strongAccentType}"`;
4576
- }
4577
- if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4578
- if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4579
- lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4816
+ return lines;
4817
+ }
4818
+ function serializeArticulationsGroup(artGroup, indent) {
4819
+ const lines = [];
4820
+ lines.push(`${indent} <articulations>`);
4821
+ for (const art of artGroup) {
4822
+ if (art.type === "articulation") {
4823
+ let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4824
+ if (art.articulation === "strong-accent" && art.strongAccentType) {
4825
+ artAttrs += ` type="${art.strongAccentType}"`;
4580
4826
  }
4827
+ if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4828
+ if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4829
+ lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4581
4830
  }
4582
- lines.push(`${indent} </articulations>`);
4583
4831
  }
4584
- if (ornaments.length > 0) {
4832
+ lines.push(`${indent} </articulations>`);
4833
+ return lines;
4834
+ }
4835
+ function serializeOrnamentsGroup(ornaments, indent) {
4836
+ const lines = [];
4837
+ const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4838
+ if (hasOnlyEmptyMarker) {
4839
+ lines.push(`${indent} <ornaments/>`);
4840
+ } else {
4585
4841
  lines.push(`${indent} <ornaments>`);
4586
4842
  const allAccidentalMarks = [];
4587
4843
  for (const orn of ornaments) {
4588
4844
  if (orn.type === "ornament") {
4845
+ if (orn.ornament === "empty") continue;
4589
4846
  const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4590
4847
  if (orn.ornament === "wavy-line") {
4591
4848
  let wlAttrs = "";
@@ -4621,77 +4878,80 @@ function serializeNotationsGroup(notations, indent) {
4621
4878
  }
4622
4879
  lines.push(`${indent} </ornaments>`);
4623
4880
  }
4624
- if (technicals.length > 0) {
4625
- lines.push(`${indent} <technical>`);
4626
- for (const tech of technicals) {
4627
- if (tech.type === "technical") {
4628
- let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4629
- const techNotation = tech;
4630
- if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4631
- if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4632
- if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4633
- lines.push(`${indent} <bend${placementAttr}>`);
4634
- if (techNotation.bendAlter !== void 0) {
4635
- lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4636
- }
4637
- if (techNotation.preBend) {
4638
- lines.push(`${indent} <pre-bend/>`);
4639
- }
4640
- if (techNotation.release) {
4641
- lines.push(`${indent} <release/>`);
4642
- }
4643
- if (techNotation.withBar !== void 0) {
4644
- lines.push(`${indent} <with-bar>${techNotation.withBar}</with-bar>`);
4645
- }
4646
- lines.push(`${indent} </bend>`);
4647
- } else if (tech.technical === "harmonic") {
4648
- const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4649
- if (hasChildren) {
4650
- lines.push(`${indent} <harmonic${placementAttr}>`);
4651
- if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4652
- if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4653
- if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4654
- if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4655
- if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4656
- lines.push(`${indent} </harmonic>`);
4657
- } else {
4658
- lines.push(`${indent} <harmonic${placementAttr}/>`);
4659
- }
4660
- } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4661
- let attrs = placementAttr;
4662
- if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4663
- if (techNotation.text !== void 0) {
4664
- lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4665
- } else {
4666
- lines.push(`${indent} <${tech.technical}${attrs}/>`);
4667
- }
4668
- } else if (tech.technical === "string" && techNotation.string !== void 0) {
4669
- lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4670
- } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4671
- lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4672
- } else if (tech.technical === "fingering") {
4673
- let fAttrs = placementAttr;
4674
- if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4675
- if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4676
- if (techNotation.text !== void 0) {
4677
- lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4678
- } else {
4679
- lines.push(`${indent} <fingering${fAttrs}/>`);
4680
- }
4681
- } else if (tech.technical === "heel" || tech.technical === "toe") {
4682
- let htAttrs = placementAttr;
4683
- if (techNotation.substitution) htAttrs += ' substitution="yes"';
4684
- lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4685
- } else if (techNotation.text !== void 0) {
4686
- lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4881
+ return lines;
4882
+ }
4883
+ function serializeTechnicalGroup(technicals, indent) {
4884
+ const lines = [];
4885
+ lines.push(`${indent} <technical>`);
4886
+ for (const tech of technicals) {
4887
+ if (tech.type === "technical") {
4888
+ let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4889
+ const techNotation = tech;
4890
+ if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4891
+ if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4892
+ if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4893
+ lines.push(`${indent} <bend${placementAttr}>`);
4894
+ if (techNotation.bendAlter !== void 0) {
4895
+ lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4896
+ }
4897
+ if (techNotation.preBend) {
4898
+ lines.push(`${indent} <pre-bend/>`);
4899
+ }
4900
+ if (techNotation.release) {
4901
+ lines.push(`${indent} <release/>`);
4902
+ }
4903
+ if (techNotation.withBar) {
4904
+ lines.push(`${indent} <with-bar/>`);
4905
+ }
4906
+ lines.push(`${indent} </bend>`);
4907
+ } else if (tech.technical === "harmonic") {
4908
+ const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4909
+ if (hasChildren) {
4910
+ lines.push(`${indent} <harmonic${placementAttr}>`);
4911
+ if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4912
+ if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4913
+ if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4914
+ if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4915
+ if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4916
+ lines.push(`${indent} </harmonic>`);
4687
4917
  } else {
4688
- lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4689
- }
4918
+ lines.push(`${indent} <harmonic${placementAttr}/>`);
4919
+ }
4920
+ } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4921
+ let attrs = "";
4922
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4923
+ if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4924
+ attrs += placementAttr;
4925
+ if (techNotation.text !== void 0) {
4926
+ lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4927
+ } else {
4928
+ lines.push(`${indent} <${tech.technical}${attrs}/>`);
4929
+ }
4930
+ } else if (tech.technical === "string" && techNotation.string !== void 0) {
4931
+ lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4932
+ } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4933
+ lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4934
+ } else if (tech.technical === "fingering") {
4935
+ let fAttrs = placementAttr;
4936
+ if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4937
+ if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4938
+ if (techNotation.text !== void 0) {
4939
+ lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4940
+ } else {
4941
+ lines.push(`${indent} <fingering${fAttrs}/>`);
4942
+ }
4943
+ } else if (tech.technical === "heel" || tech.technical === "toe") {
4944
+ let htAttrs = placementAttr;
4945
+ if (techNotation.substitution) htAttrs += ' substitution="yes"';
4946
+ lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4947
+ } else if (techNotation.text !== void 0) {
4948
+ lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4949
+ } else {
4950
+ lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4690
4951
  }
4691
4952
  }
4692
- lines.push(`${indent} </technical>`);
4693
4953
  }
4694
- lines.push(`${indent}</notations>`);
4954
+ lines.push(`${indent} </technical>`);
4695
4955
  return lines;
4696
4956
  }
4697
4957
  function serializeLyric(lyric, indent) {
@@ -4715,7 +4975,7 @@ function serializeLyric(lyric, indent) {
4715
4975
  lines.push(`${indent} <elision/>`);
4716
4976
  }
4717
4977
  }
4718
- } else {
4978
+ } else if (lyric.syllabic || lyric.text) {
4719
4979
  if (lyric.syllabic) {
4720
4980
  lines.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
4721
4981
  }
@@ -4830,7 +5090,12 @@ function serializeDirectionType(dirType, indent) {
4830
5090
  if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
4831
5091
  if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
4832
5092
  lines.push(`${indent} <dynamics${dynAttrs}>`);
4833
- lines.push(`${indent} <${dirType.value}/>`);
5093
+ if (dirType.value) {
5094
+ lines.push(`${indent} <${dirType.value}/>`);
5095
+ }
5096
+ if (dirType.otherDynamics) {
5097
+ lines.push(`${indent} <other-dynamics>${escapeXml(dirType.otherDynamics)}</other-dynamics>`);
5098
+ }
4834
5099
  lines.push(`${indent} </dynamics>`);
4835
5100
  break;
4836
5101
  }
@@ -4917,8 +5182,12 @@ function serializeDirectionType(dirType, indent) {
4917
5182
  if (dirType.high) {
4918
5183
  lines.push(`${indent} <accordion-high/>`);
4919
5184
  }
4920
- if (dirType.middle !== void 0) {
4921
- lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
5185
+ if (dirType.middlePresent || dirType.middle !== void 0) {
5186
+ if (dirType.middle !== void 0) {
5187
+ lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
5188
+ } else {
5189
+ lines.push(`${indent} <accordion-middle/>`);
5190
+ }
4922
5191
  }
4923
5192
  if (dirType.low) {
4924
5193
  lines.push(`${indent} <accordion-low/>`);
@@ -5031,11 +5300,20 @@ function serializeBarline(barline, indent) {
5031
5300
  lines.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
5032
5301
  }
5033
5302
  if (barline.ending) {
5034
- lines.push(`${indent} <ending number="${barline.ending.number}" type="${barline.ending.type}"/>`);
5303
+ let endingAttrs = ` number="${barline.ending.number}" type="${barline.ending.type}"`;
5304
+ if (barline.ending.defaultY !== void 0) endingAttrs += ` default-y="${barline.ending.defaultY}"`;
5305
+ if (barline.ending.endLength !== void 0) endingAttrs += ` end-length="${barline.ending.endLength}"`;
5306
+ if (barline.ending.text) {
5307
+ lines.push(`${indent} <ending${endingAttrs}>${escapeXml(barline.ending.text)}</ending>`);
5308
+ } else {
5309
+ lines.push(`${indent} <ending${endingAttrs}/>`);
5310
+ }
5035
5311
  }
5036
5312
  if (barline.repeat) {
5037
- const timesAttr = barline.repeat.times !== void 0 ? ` times="${barline.repeat.times}"` : "";
5038
- lines.push(`${indent} <repeat direction="${barline.repeat.direction}"${timesAttr}/>`);
5313
+ let repeatAttrs = ` direction="${barline.repeat.direction}"`;
5314
+ if (barline.repeat.times !== void 0) repeatAttrs += ` times="${barline.repeat.times}"`;
5315
+ if (barline.repeat.winged) repeatAttrs += ` winged="${barline.repeat.winged}"`;
5316
+ lines.push(`${indent} <repeat${repeatAttrs}/>`);
5039
5317
  }
5040
5318
  lines.push(`${indent}</barline>`);
5041
5319
  return lines;
@@ -5068,7 +5346,8 @@ function serializeStaffDetails(sd, indent) {
5068
5346
  const attrs = buildAttrs({
5069
5347
  "number": sd.number,
5070
5348
  "show-frets": sd.showFrets,
5071
- "print-object": sd.printObject
5349
+ "print-object": sd.printObject,
5350
+ "print-spacing": sd.printSpacing
5072
5351
  });
5073
5352
  lines.push(`${indent}<staff-details${attrs}>`);
5074
5353
  pushOptionalElement(lines, `${indent} `, "staff-type", sd.staffType);
@@ -5134,16 +5413,22 @@ function serializeHarmony(harmony, indent) {
5134
5413
  }
5135
5414
  lines.push(`${indent} </root>`);
5136
5415
  let kindAttrs = "";
5137
- if (harmony.kindText) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5416
+ if (harmony.kindText !== void 0) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5417
+ if (harmony.kindHalign) kindAttrs += ` halign="${escapeXml(harmony.kindHalign)}"`;
5138
5418
  lines.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
5139
5419
  if (harmony.bass) {
5140
- lines.push(`${indent} <bass>`);
5420
+ let bassAttrs = "";
5421
+ if (harmony.bass.arrangement) bassAttrs += ` arrangement="${escapeXml(harmony.bass.arrangement)}"`;
5422
+ lines.push(`${indent} <bass${bassAttrs}>`);
5141
5423
  lines.push(`${indent} <bass-step>${harmony.bass.bassStep}</bass-step>`);
5142
5424
  if (harmony.bass.bassAlter !== void 0) {
5143
5425
  lines.push(`${indent} <bass-alter>${harmony.bass.bassAlter}</bass-alter>`);
5144
5426
  }
5145
5427
  lines.push(`${indent} </bass>`);
5146
5428
  }
5429
+ if (harmony.inversion !== void 0) {
5430
+ lines.push(`${indent} <inversion>${harmony.inversion}</inversion>`);
5431
+ }
5147
5432
  if (harmony.degrees) {
5148
5433
  for (const deg of harmony.degrees) {
5149
5434
  lines.push(`${indent} <degree>`);
@@ -5829,7 +6114,7 @@ function groupByVoice(measure) {
5829
6114
  for (const entry of measure.entries) {
5830
6115
  if (entry.type !== "note") continue;
5831
6116
  const staff = entry.staff ?? 1;
5832
- const voice = entry.voice;
6117
+ const voice = entry.voice ?? 1;
5833
6118
  const key = `${staff}-${voice}`;
5834
6119
  if (!groups.has(key)) {
5835
6120
  groups.set(key, { staff, voice, notes: [] });
@@ -5923,7 +6208,7 @@ function getVoices(measure) {
5923
6208
  const voices = /* @__PURE__ */ new Set();
5924
6209
  for (const entry of measure.entries) {
5925
6210
  if (entry.type === "note") {
5926
- voices.add(entry.voice);
6211
+ voices.add(entry.voice ?? 1);
5927
6212
  }
5928
6213
  }
5929
6214
  return Array.from(voices).sort((a, b) => a - b);
@@ -5974,7 +6259,7 @@ function buildVoiceToStaffMap(measure) {
5974
6259
  const map = /* @__PURE__ */ new Map();
5975
6260
  for (const entry of measure.entries) {
5976
6261
  if (entry.type === "note" && entry.staff !== void 0) {
5977
- const voice = entry.voice;
6262
+ const voice = entry.voice ?? 1;
5978
6263
  const staff = entry.staff;
5979
6264
  if (!map.has(voice)) {
5980
6265
  map.set(voice, staff);
@@ -5993,7 +6278,7 @@ function buildVoiceToStaffMapForPart(part) {
5993
6278
  for (const measure of part.measures) {
5994
6279
  for (const entry of measure.entries) {
5995
6280
  if (entry.type === "note" && entry.staff !== void 0) {
5996
- const voice = entry.voice;
6281
+ const voice = entry.voice ?? 1;
5997
6282
  const staff = entry.staff;
5998
6283
  if (!map.has(voice)) {
5999
6284
  map.set(voice, staff);
@@ -6012,7 +6297,7 @@ function inferStaff(entry, voiceToStaffMap) {
6012
6297
  if (entry.staff !== void 0) {
6013
6298
  return entry.staff;
6014
6299
  }
6015
- const inferredStaff = voiceToStaffMap.get(entry.voice);
6300
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
6016
6301
  if (inferredStaff !== void 0) {
6017
6302
  return inferredStaff;
6018
6303
  }
@@ -6055,7 +6340,7 @@ function getVoicesForStaff(measure, staff) {
6055
6340
  if (entry.type === "note") {
6056
6341
  const entryStaff = entry.staff ?? 1;
6057
6342
  if (entryStaff === staff) {
6058
- voices.add(entry.voice);
6343
+ voices.add(entry.voice ?? 1);
6059
6344
  }
6060
6345
  }
6061
6346
  }
@@ -6391,6 +6676,7 @@ function getDynamics(score, options) {
6391
6676
  if (dirType.kind === "dynamics") {
6392
6677
  results.push({
6393
6678
  dynamic: dirType.value,
6679
+ otherDynamics: dirType.otherDynamics,
6394
6680
  direction: entry,
6395
6681
  part,
6396
6682
  partIndex,
@@ -7465,14 +7751,17 @@ function hasNotesInRange(voiceEntries, startPos, endPos) {
7465
7751
  return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
7466
7752
  }
7467
7753
  function createRest(duration, voice, staff) {
7468
- return {
7754
+ const note = {
7469
7755
  _id: generateId(),
7470
7756
  type: "note",
7471
7757
  rest: { displayStep: void 0, displayOctave: void 0 },
7472
7758
  duration,
7473
- voice,
7474
7759
  staff
7475
7760
  };
7761
+ if (voice !== void 0) {
7762
+ note.voice = voice;
7763
+ }
7764
+ return note;
7476
7765
  }
7477
7766
  function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
7478
7767
  const otherEntries = [];
@@ -9026,7 +9315,7 @@ function autoBeam(score, options) {
9026
9315
  for (const entry of measure.entries) {
9027
9316
  if (entry.type === "note") {
9028
9317
  if (!entry.chord && !entry.rest) {
9029
- const voice = entry.voice;
9318
+ const voice = entry.voice ?? 1;
9030
9319
  if (options.voice === void 0 || voice === options.voice) {
9031
9320
  if (!notesByVoice.has(voice)) {
9032
9321
  notesByVoice.set(voice, []);