musicxml-io 0.2.12 → 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
  }
@@ -1769,6 +1884,7 @@ function parseDirectionTypes(elements) {
1769
1884
  "pf"
1770
1885
  ];
1771
1886
  for (const dyn of dynContent) {
1887
+ let foundStandard = false;
1772
1888
  for (const dv of dynamicsValues) {
1773
1889
  if (dyn[dv] !== void 0) {
1774
1890
  const result = { kind: "dynamics", value: dv };
@@ -1777,9 +1893,22 @@ function parseDirectionTypes(elements) {
1777
1893
  if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1778
1894
  if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1779
1895
  results.push(result);
1896
+ foundStandard = true;
1780
1897
  break;
1781
1898
  }
1782
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);
1910
+ }
1911
+ }
1783
1912
  }
1784
1913
  continue;
1785
1914
  }
@@ -1904,11 +2033,14 @@ function parseDirectionTypes(elements) {
1904
2033
  for (const acc of accContent) {
1905
2034
  if (acc["accordion-high"] !== void 0) {
1906
2035
  result.high = true;
1907
- } else if (acc["accordion-middle"]) {
2036
+ } else if (acc["accordion-middle"] !== void 0) {
2037
+ result.middlePresent = true;
1908
2038
  const midContent = acc["accordion-middle"];
1909
2039
  for (const item of midContent) {
1910
2040
  if (item["#text"] !== void 0) {
1911
- 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;
1912
2044
  break;
1913
2045
  }
1914
2046
  }
@@ -2085,6 +2217,9 @@ function parseBarline(elements, attrs) {
2085
2217
  if (repeatAttrs["times"]) {
2086
2218
  barline.repeat.times = parseInt(repeatAttrs["times"], 10);
2087
2219
  }
2220
+ if (repeatAttrs["winged"]) {
2221
+ barline.repeat.winged = repeatAttrs["winged"];
2222
+ }
2088
2223
  }
2089
2224
  } else if (el["ending"]) {
2090
2225
  const endingAttrs = getAttributes(el);
@@ -2092,6 +2227,11 @@ function parseBarline(elements, attrs) {
2092
2227
  const type = endingAttrs["type"];
2093
2228
  if (number && (type === "start" || type === "stop" || type === "discontinue")) {
2094
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"]);
2095
2235
  }
2096
2236
  }
2097
2237
  }
@@ -2217,6 +2357,8 @@ function parseStaffDetails(elements, attrs) {
2217
2357
  if (attrs["number"]) sd.number = parseInt(attrs["number"], 10);
2218
2358
  if (attrs["print-object"] === "no") sd.printObject = false;
2219
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;
2220
2362
  const staffType = getElementText(elements, "staff-type");
2221
2363
  if (staffType && ["ossia", "cue", "editorial", "regular", "alternate"].includes(staffType)) {
2222
2364
  sd.staffType = staffType;
@@ -2280,7 +2422,9 @@ function parseMeasureStyle(elements, attrs) {
2280
2422
  const slAttrs = getAttributes(el);
2281
2423
  ms.slash = { type: slAttrs["type"] === "stop" ? "stop" : "start" };
2282
2424
  if (slAttrs["use-dots"] === "yes") ms.slash.useDots = true;
2425
+ else if (slAttrs["use-dots"] === "no") ms.slash.useDots = false;
2283
2426
  if (slAttrs["use-stems"] === "yes") ms.slash.useStems = true;
2427
+ else if (slAttrs["use-stems"] === "no") ms.slash.useStems = false;
2284
2428
  }
2285
2429
  }
2286
2430
  return ms;
@@ -2320,19 +2464,29 @@ function parseHarmony(elements, attrs) {
2320
2464
  break;
2321
2465
  }
2322
2466
  }
2323
- 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"];
2324
2469
  break;
2325
2470
  }
2326
2471
  }
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);
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;
2334
2484
  }
2335
2485
  }
2486
+ const inversionText = getElementText(elements, "inversion");
2487
+ if (inversionText) {
2488
+ harmony.inversion = parseInt(inversionText, 10);
2489
+ }
2336
2490
  const degrees = [];
2337
2491
  for (const el of elements) {
2338
2492
  if (el["degree"]) {
@@ -3099,7 +3253,7 @@ function validateVoiceStaff(measure, staves, location) {
3099
3253
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
3100
3254
  const entry = measure.entries[entryIndex];
3101
3255
  if (entry.type !== "note") continue;
3102
- if (entry.voice <= 0) {
3256
+ if (entry.voice !== void 0 && entry.voice <= 0) {
3103
3257
  errors.push({
3104
3258
  code: "INVALID_VOICE_NUMBER",
3105
3259
  level: "error",
@@ -3748,6 +3902,11 @@ function serializeDefaults(defaults, indent) {
3748
3902
  lines.push(`${indent}${indent}${indent}<distance type="${escapeXml(d.type)}">${d.value}</distance>`);
3749
3903
  }
3750
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
+ }
3751
3910
  lines.push(`${indent}${indent}</appearance>`);
3752
3911
  }
3753
3912
  if (defaults.musicFont) {
@@ -3804,16 +3963,16 @@ function serializePageLayout(layout, indent) {
3804
3963
  const typeAttr = m.type ? ` type="${m.type}"` : "";
3805
3964
  lines.push(`${indent} <page-margins${typeAttr}>`);
3806
3965
  if (m.leftMargin !== void 0) {
3807
- lines.push(`${indent} <left-margin>${m.leftMargin}</left-margin>`);
3966
+ lines.push(`${indent} <left-margin>${m.leftMarginRaw ?? m.leftMargin}</left-margin>`);
3808
3967
  }
3809
3968
  if (m.rightMargin !== void 0) {
3810
- lines.push(`${indent} <right-margin>${m.rightMargin}</right-margin>`);
3969
+ lines.push(`${indent} <right-margin>${m.rightMarginRaw ?? m.rightMargin}</right-margin>`);
3811
3970
  }
3812
3971
  if (m.topMargin !== void 0) {
3813
- lines.push(`${indent} <top-margin>${m.topMargin}</top-margin>`);
3972
+ lines.push(`${indent} <top-margin>${m.topMarginRaw ?? m.topMargin}</top-margin>`);
3814
3973
  }
3815
3974
  if (m.bottomMargin !== void 0) {
3816
- lines.push(`${indent} <bottom-margin>${m.bottomMargin}</bottom-margin>`);
3975
+ lines.push(`${indent} <bottom-margin>${m.bottomMarginRaw ?? m.bottomMargin}</bottom-margin>`);
3817
3976
  }
3818
3977
  lines.push(`${indent} </page-margins>`);
3819
3978
  }
@@ -3827,18 +3986,48 @@ function serializeSystemLayout(layout, indent) {
3827
3986
  if (layout.systemMargins) {
3828
3987
  lines.push(`${indent} <system-margins>`);
3829
3988
  if (layout.systemMargins.leftMargin !== void 0) {
3830
- lines.push(`${indent} <left-margin>${layout.systemMargins.leftMargin}</left-margin>`);
3989
+ lines.push(`${indent} <left-margin>${layout.systemMargins.leftMarginRaw ?? layout.systemMargins.leftMargin}</left-margin>`);
3831
3990
  }
3832
3991
  if (layout.systemMargins.rightMargin !== void 0) {
3833
- lines.push(`${indent} <right-margin>${layout.systemMargins.rightMargin}</right-margin>`);
3992
+ lines.push(`${indent} <right-margin>${layout.systemMargins.rightMarginRaw ?? layout.systemMargins.rightMargin}</right-margin>`);
3834
3993
  }
3835
3994
  lines.push(`${indent} </system-margins>`);
3836
3995
  }
3837
3996
  if (layout.systemDistance !== void 0) {
3838
- lines.push(`${indent} <system-distance>${layout.systemDistance}</system-distance>`);
3997
+ lines.push(`${indent} <system-distance>${layout.systemDistanceRaw ?? layout.systemDistance}</system-distance>`);
3839
3998
  }
3840
3999
  if (layout.topSystemDistance !== void 0) {
3841
- 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>`);
3842
4031
  }
3843
4032
  lines.push(`${indent}</system-layout>`);
3844
4033
  return lines;
@@ -3859,6 +4048,7 @@ function serializeCredit(credit, indent) {
3859
4048
  let attrs2 = "";
3860
4049
  if (cw.defaultX !== void 0) attrs2 += ` default-x="${cw.defaultX}"`;
3861
4050
  if (cw.defaultY !== void 0) attrs2 += ` default-y="${cw.defaultY}"`;
4051
+ if (cw.fontFamily) attrs2 += ` font-family="${escapeXml(cw.fontFamily)}"`;
3862
4052
  if (cw.fontSize) attrs2 += ` font-size="${escapeXml(cw.fontSize)}"`;
3863
4053
  if (cw.fontWeight) attrs2 += ` font-weight="${escapeXml(cw.fontWeight)}"`;
3864
4054
  if (cw.fontStyle) attrs2 += ` font-style="${escapeXml(cw.fontStyle)}"`;
@@ -4153,6 +4343,7 @@ function serializeKey(key, indent) {
4153
4343
  let keyAttrs = "";
4154
4344
  if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
4155
4345
  if (key.printObject === false) keyAttrs += ' print-object="no"';
4346
+ else if (key.printObject === true) keyAttrs += ' print-object="yes"';
4156
4347
  lines.push(`${indent}<key${keyAttrs}>`);
4157
4348
  if (key.cancel !== void 0) {
4158
4349
  const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
@@ -4200,7 +4391,8 @@ function serializeTime(time, indent) {
4200
4391
  const maxLen = Math.max(time.beatsList.length, time.beatTypeList.length);
4201
4392
  for (let i = 0; i < maxLen; i++) {
4202
4393
  if (i < time.beatsList.length) {
4203
- 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>`);
4204
4396
  }
4205
4397
  if (i < time.beatTypeList.length) {
4206
4398
  lines.push(`${indent} <beat-type>${time.beatTypeList[i]}</beat-type>`);
@@ -4217,10 +4409,13 @@ function serializeClef(clef, indent) {
4217
4409
  const lines = [];
4218
4410
  let attrs = clef.staff ? ` number="${clef.staff}"` : "";
4219
4411
  if (clef.printObject === false) attrs += ' print-object="no"';
4412
+ else if (clef.printObject === true) attrs += ' print-object="yes"';
4220
4413
  if (clef.afterBarline) attrs += ' after-barline="yes"';
4221
4414
  lines.push(`${indent}<clef${attrs}>`);
4222
4415
  lines.push(`${indent} <sign>${clef.sign}</sign>`);
4223
- lines.push(`${indent} <line>${clef.line}</line>`);
4416
+ if (clef.line !== void 0) {
4417
+ lines.push(`${indent} <line>${clef.line}</line>`);
4418
+ }
4224
4419
  if (clef.clefOctaveChange !== void 0) {
4225
4420
  lines.push(`${indent} <clef-octave-change>${clef.clefOctaveChange}</clef-octave-change>`);
4226
4421
  }
@@ -4256,6 +4451,12 @@ function serializeEntry(entry, indent) {
4256
4451
  return serializeSound(entry, indent);
4257
4452
  case "attributes":
4258
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
+ }
4259
4460
  default:
4260
4461
  return [];
4261
4462
  }
@@ -4270,12 +4471,13 @@ function serializeNote(note, indent) {
4270
4471
  "relative-y": note.relativeY,
4271
4472
  "dynamics": note.dynamics,
4272
4473
  "print-object": note.printObject === false ? false : void 0,
4474
+ "print-dot": note.printDot !== void 0 ? note.printDot : void 0,
4273
4475
  "print-spacing": note.printSpacing
4274
4476
  });
4275
4477
  lines.push(`${indent}<note${noteAttrs}>`);
4276
4478
  if (note.grace) {
4277
4479
  const graceAttrs = buildAttrs({
4278
- "slash": note.grace.slash || void 0,
4480
+ "slash": note.grace.slash !== void 0 ? note.grace.slash : void 0,
4279
4481
  "steal-time-previous": note.grace.stealTimePrevious,
4280
4482
  "steal-time-following": note.grace.stealTimeFollowing
4281
4483
  });
@@ -4330,7 +4532,9 @@ function serializeNote(note, indent) {
4330
4532
  } else if (note.tie) {
4331
4533
  lines.push(`${indent} <tie type="${note.tie.type}"/>`);
4332
4534
  }
4333
- lines.push(`${indent} <voice>${note.voice}</voice>`);
4535
+ if (note.voice !== void 0) {
4536
+ lines.push(`${indent} <voice>${note.voice}</voice>`);
4537
+ }
4334
4538
  if (note.noteType) {
4335
4539
  const typeAttrs = note.noteTypeSize ? ` size="${escapeXml(note.noteTypeSize)}"` : "";
4336
4540
  lines.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
@@ -4547,7 +4751,18 @@ function serializeNotationsGroup(notations, indent) {
4547
4751
  let attrs = "";
4548
4752
  if (notation.direction) attrs += ` direction="${notation.direction}"`;
4549
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}"`;
4550
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>`);
4551
4766
  } else if (notation.type === "glissando") {
4552
4767
  let attrs = ` type="${notation.glissandoType}"`;
4553
4768
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
@@ -4561,7 +4776,11 @@ function serializeNotationsGroup(notations, indent) {
4561
4776
  let attrs = ` type="${notation.slideType}"`;
4562
4777
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4563
4778
  if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4564
- 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
+ }
4565
4784
  }
4566
4785
  }
4567
4786
  const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
@@ -4582,44 +4801,50 @@ function serializeNotationsGroup(notations, indent) {
4582
4801
  lines.push(`${indent} </articulations>`);
4583
4802
  }
4584
4803
  if (ornaments.length > 0) {
4585
- lines.push(`${indent} <ornaments>`);
4586
- const allAccidentalMarks = [];
4587
- for (const orn of ornaments) {
4588
- if (orn.type === "ornament") {
4589
- const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4590
- if (orn.ornament === "wavy-line") {
4591
- let wlAttrs = "";
4592
- if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4593
- if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4594
- wlAttrs += placementAttr;
4595
- if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4596
- lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4597
- } else if (orn.ornament === "tremolo") {
4598
- let tremAttrs = "";
4599
- if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4600
- tremAttrs += placementAttr;
4601
- if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4602
- if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4603
- if (orn.tremoloMarks !== void 0) {
4604
- 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
+ }
4605
4832
  } else {
4606
- 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);
4607
4839
  }
4608
- } else {
4609
- let ornAttrs = placementAttr;
4610
- if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4611
- lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4612
- }
4613
- if (orn.accidentalMarks) {
4614
- allAccidentalMarks.push(...orn.accidentalMarks);
4615
4840
  }
4616
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>`);
4617
4847
  }
4618
- for (const am of allAccidentalMarks) {
4619
- const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4620
- lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4621
- }
4622
- lines.push(`${indent} </ornaments>`);
4623
4848
  }
4624
4849
  if (technicals.length > 0) {
4625
4850
  lines.push(`${indent} <technical>`);
@@ -4640,8 +4865,8 @@ function serializeNotationsGroup(notations, indent) {
4640
4865
  if (techNotation.release) {
4641
4866
  lines.push(`${indent} <release/>`);
4642
4867
  }
4643
- if (techNotation.withBar !== void 0) {
4644
- lines.push(`${indent} <with-bar>${techNotation.withBar}</with-bar>`);
4868
+ if (techNotation.withBar) {
4869
+ lines.push(`${indent} <with-bar/>`);
4645
4870
  }
4646
4871
  lines.push(`${indent} </bend>`);
4647
4872
  } else if (tech.technical === "harmonic") {
@@ -4658,8 +4883,10 @@ function serializeNotationsGroup(notations, indent) {
4658
4883
  lines.push(`${indent} <harmonic${placementAttr}/>`);
4659
4884
  }
4660
4885
  } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4661
- let attrs = placementAttr;
4886
+ let attrs = "";
4887
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4662
4888
  if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4889
+ attrs += placementAttr;
4663
4890
  if (techNotation.text !== void 0) {
4664
4891
  lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4665
4892
  } else {
@@ -4715,7 +4942,7 @@ function serializeLyric(lyric, indent) {
4715
4942
  lines.push(`${indent} <elision/>`);
4716
4943
  }
4717
4944
  }
4718
- } else {
4945
+ } else if (lyric.syllabic || lyric.text) {
4719
4946
  if (lyric.syllabic) {
4720
4947
  lines.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
4721
4948
  }
@@ -4830,7 +5057,12 @@ function serializeDirectionType(dirType, indent) {
4830
5057
  if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
4831
5058
  if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
4832
5059
  lines.push(`${indent} <dynamics${dynAttrs}>`);
4833
- 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
+ }
4834
5066
  lines.push(`${indent} </dynamics>`);
4835
5067
  break;
4836
5068
  }
@@ -4917,8 +5149,12 @@ function serializeDirectionType(dirType, indent) {
4917
5149
  if (dirType.high) {
4918
5150
  lines.push(`${indent} <accordion-high/>`);
4919
5151
  }
4920
- if (dirType.middle !== void 0) {
4921
- 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
+ }
4922
5158
  }
4923
5159
  if (dirType.low) {
4924
5160
  lines.push(`${indent} <accordion-low/>`);
@@ -5031,11 +5267,20 @@ function serializeBarline(barline, indent) {
5031
5267
  lines.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
5032
5268
  }
5033
5269
  if (barline.ending) {
5034
- 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
+ }
5035
5278
  }
5036
5279
  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}/>`);
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}/>`);
5039
5284
  }
5040
5285
  lines.push(`${indent}</barline>`);
5041
5286
  return lines;
@@ -5068,7 +5313,8 @@ function serializeStaffDetails(sd, indent) {
5068
5313
  const attrs = buildAttrs({
5069
5314
  "number": sd.number,
5070
5315
  "show-frets": sd.showFrets,
5071
- "print-object": sd.printObject
5316
+ "print-object": sd.printObject,
5317
+ "print-spacing": sd.printSpacing
5072
5318
  });
5073
5319
  lines.push(`${indent}<staff-details${attrs}>`);
5074
5320
  pushOptionalElement(lines, `${indent} `, "staff-type", sd.staffType);
@@ -5134,16 +5380,22 @@ function serializeHarmony(harmony, indent) {
5134
5380
  }
5135
5381
  lines.push(`${indent} </root>`);
5136
5382
  let kindAttrs = "";
5137
- 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)}"`;
5138
5385
  lines.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
5139
5386
  if (harmony.bass) {
5140
- lines.push(`${indent} <bass>`);
5387
+ let bassAttrs = "";
5388
+ if (harmony.bass.arrangement) bassAttrs += ` arrangement="${escapeXml(harmony.bass.arrangement)}"`;
5389
+ lines.push(`${indent} <bass${bassAttrs}>`);
5141
5390
  lines.push(`${indent} <bass-step>${harmony.bass.bassStep}</bass-step>`);
5142
5391
  if (harmony.bass.bassAlter !== void 0) {
5143
5392
  lines.push(`${indent} <bass-alter>${harmony.bass.bassAlter}</bass-alter>`);
5144
5393
  }
5145
5394
  lines.push(`${indent} </bass>`);
5146
5395
  }
5396
+ if (harmony.inversion !== void 0) {
5397
+ lines.push(`${indent} <inversion>${harmony.inversion}</inversion>`);
5398
+ }
5147
5399
  if (harmony.degrees) {
5148
5400
  for (const deg of harmony.degrees) {
5149
5401
  lines.push(`${indent} <degree>`);
@@ -5829,7 +6081,7 @@ function groupByVoice(measure) {
5829
6081
  for (const entry of measure.entries) {
5830
6082
  if (entry.type !== "note") continue;
5831
6083
  const staff = entry.staff ?? 1;
5832
- const voice = entry.voice;
6084
+ const voice = entry.voice ?? 1;
5833
6085
  const key = `${staff}-${voice}`;
5834
6086
  if (!groups.has(key)) {
5835
6087
  groups.set(key, { staff, voice, notes: [] });
@@ -5923,7 +6175,7 @@ function getVoices(measure) {
5923
6175
  const voices = /* @__PURE__ */ new Set();
5924
6176
  for (const entry of measure.entries) {
5925
6177
  if (entry.type === "note") {
5926
- voices.add(entry.voice);
6178
+ voices.add(entry.voice ?? 1);
5927
6179
  }
5928
6180
  }
5929
6181
  return Array.from(voices).sort((a, b) => a - b);
@@ -5974,7 +6226,7 @@ function buildVoiceToStaffMap(measure) {
5974
6226
  const map = /* @__PURE__ */ new Map();
5975
6227
  for (const entry of measure.entries) {
5976
6228
  if (entry.type === "note" && entry.staff !== void 0) {
5977
- const voice = entry.voice;
6229
+ const voice = entry.voice ?? 1;
5978
6230
  const staff = entry.staff;
5979
6231
  if (!map.has(voice)) {
5980
6232
  map.set(voice, staff);
@@ -5993,7 +6245,7 @@ function buildVoiceToStaffMapForPart(part) {
5993
6245
  for (const measure of part.measures) {
5994
6246
  for (const entry of measure.entries) {
5995
6247
  if (entry.type === "note" && entry.staff !== void 0) {
5996
- const voice = entry.voice;
6248
+ const voice = entry.voice ?? 1;
5997
6249
  const staff = entry.staff;
5998
6250
  if (!map.has(voice)) {
5999
6251
  map.set(voice, staff);
@@ -6012,7 +6264,7 @@ function inferStaff(entry, voiceToStaffMap) {
6012
6264
  if (entry.staff !== void 0) {
6013
6265
  return entry.staff;
6014
6266
  }
6015
- const inferredStaff = voiceToStaffMap.get(entry.voice);
6267
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
6016
6268
  if (inferredStaff !== void 0) {
6017
6269
  return inferredStaff;
6018
6270
  }
@@ -6055,7 +6307,7 @@ function getVoicesForStaff(measure, staff) {
6055
6307
  if (entry.type === "note") {
6056
6308
  const entryStaff = entry.staff ?? 1;
6057
6309
  if (entryStaff === staff) {
6058
- voices.add(entry.voice);
6310
+ voices.add(entry.voice ?? 1);
6059
6311
  }
6060
6312
  }
6061
6313
  }
@@ -6391,6 +6643,7 @@ function getDynamics(score, options) {
6391
6643
  if (dirType.kind === "dynamics") {
6392
6644
  results.push({
6393
6645
  dynamic: dirType.value,
6646
+ otherDynamics: dirType.otherDynamics,
6394
6647
  direction: entry,
6395
6648
  part,
6396
6649
  partIndex,
@@ -7465,14 +7718,17 @@ function hasNotesInRange(voiceEntries, startPos, endPos) {
7465
7718
  return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
7466
7719
  }
7467
7720
  function createRest(duration, voice, staff) {
7468
- return {
7721
+ const note = {
7469
7722
  _id: generateId(),
7470
7723
  type: "note",
7471
7724
  rest: { displayStep: void 0, displayOctave: void 0 },
7472
7725
  duration,
7473
- voice,
7474
7726
  staff
7475
7727
  };
7728
+ if (voice !== void 0) {
7729
+ note.voice = voice;
7730
+ }
7731
+ return note;
7476
7732
  }
7477
7733
  function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
7478
7734
  const otherEntries = [];
@@ -9026,7 +9282,7 @@ function autoBeam(score, options) {
9026
9282
  for (const entry of measure.entries) {
9027
9283
  if (entry.type === "note") {
9028
9284
  if (!entry.chord && !entry.rest) {
9029
- const voice = entry.voice;
9285
+ const voice = entry.voice ?? 1;
9030
9286
  if (options.voice === void 0 || voice === options.voice) {
9031
9287
  if (!notesByVoice.has(voice)) {
9032
9288
  notesByVoice.set(voice, []);