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.mjs CHANGED
@@ -241,9 +241,11 @@ function parseDefaults(elements) {
241
241
  const lineWidths = collectElements(appContent, "line-width", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
242
242
  const noteSizes = collectElements(appContent, "note-size", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
243
243
  const distances = collectElements(appContent, "distance", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
244
+ const glyphs = collectElements(appContent, "glyph", (c, a) => ({ type: a["type"] || "", value: extractText(c) }));
244
245
  if (lineWidths.length > 0) appearance["line-widths"] = lineWidths;
245
246
  if (noteSizes.length > 0) appearance["note-sizes"] = noteSizes;
246
247
  if (distances.length > 0) appearance["distances"] = distances;
248
+ if (glyphs.length > 0) appearance["glyphs"] = glyphs;
247
249
  return Object.keys(appearance).length > 0 ? appearance : void 0;
248
250
  });
249
251
  if (appResult) defaults.appearance = appResult;
@@ -265,13 +267,25 @@ function parsePageLayout(elements) {
265
267
  m.type = attrs["type"];
266
268
  }
267
269
  const left = getElementText(content, "left-margin");
268
- if (left) m.leftMargin = parseFloat(left);
270
+ if (left) {
271
+ m.leftMargin = parseFloat(left);
272
+ m.leftMarginRaw = left;
273
+ }
269
274
  const right = getElementText(content, "right-margin");
270
- if (right) m.rightMargin = parseFloat(right);
275
+ if (right) {
276
+ m.rightMargin = parseFloat(right);
277
+ m.rightMarginRaw = right;
278
+ }
271
279
  const top = getElementText(content, "top-margin");
272
- if (top) m.topMargin = parseFloat(top);
280
+ if (top) {
281
+ m.topMargin = parseFloat(top);
282
+ m.topMarginRaw = top;
283
+ }
273
284
  const bottom = getElementText(content, "bottom-margin");
274
- if (bottom) m.bottomMargin = parseFloat(bottom);
285
+ if (bottom) {
286
+ m.bottomMargin = parseFloat(bottom);
287
+ m.bottomMarginRaw = bottom;
288
+ }
275
289
  margins.push(m);
276
290
  }
277
291
  }
@@ -284,14 +298,48 @@ function parseSystemLayout(elements) {
284
298
  if (margins) {
285
299
  layout.systemMargins = {};
286
300
  const left = getElementText(margins, "left-margin");
287
- if (left) layout.systemMargins.leftMargin = parseFloat(left);
301
+ if (left) {
302
+ layout.systemMargins.leftMargin = parseFloat(left);
303
+ layout.systemMargins.leftMarginRaw = left;
304
+ }
288
305
  const right = getElementText(margins, "right-margin");
289
- if (right) layout.systemMargins.rightMargin = parseFloat(right);
306
+ if (right) {
307
+ layout.systemMargins.rightMargin = parseFloat(right);
308
+ layout.systemMargins.rightMarginRaw = right;
309
+ }
290
310
  }
291
311
  const dist = getElementText(elements, "system-distance");
292
- if (dist) layout.systemDistance = parseFloat(dist);
312
+ if (dist) {
313
+ layout.systemDistance = parseFloat(dist);
314
+ layout.systemDistanceRaw = dist;
315
+ }
293
316
  const topDist = getElementText(elements, "top-system-distance");
294
- if (topDist) layout.topSystemDistance = parseFloat(topDist);
317
+ if (topDist) {
318
+ layout.topSystemDistance = parseFloat(topDist);
319
+ layout.topSystemDistanceRaw = topDist;
320
+ }
321
+ const dividers = getElementContent(elements, "system-dividers");
322
+ if (dividers) {
323
+ layout.systemDividers = {};
324
+ for (const el of dividers) {
325
+ if (el["left-divider"]) {
326
+ const attrs = getAttributes(el);
327
+ layout.systemDividers.leftDivider = {
328
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
329
+ halign: attrs["halign"],
330
+ valign: attrs["valign"]
331
+ };
332
+ }
333
+ if (el["right-divider"]) {
334
+ const attrs = getAttributes(el);
335
+ layout.systemDividers.rightDivider = {
336
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
337
+ halign: attrs["halign"],
338
+ valign: attrs["valign"]
339
+ };
340
+ }
341
+ }
342
+ }
295
343
  return layout;
296
344
  }
297
345
  function parseCredits(elements) {
@@ -303,6 +351,7 @@ function parseCredits(elements) {
303
351
  const cw = { text: extractText(c) };
304
352
  if (a["default-x"]) cw.defaultX = parseFloat(a["default-x"]);
305
353
  if (a["default-y"]) cw.defaultY = parseFloat(a["default-y"]);
354
+ if (a["font-family"]) cw.fontFamily = a["font-family"];
306
355
  if (a["font-size"]) cw.fontSize = a["font-size"];
307
356
  if (a["font-weight"]) cw.fontWeight = a["font-weight"];
308
357
  if (a["font-style"]) cw.fontStyle = a["font-style"];
@@ -526,6 +575,15 @@ function parseMeasure(elements, attrs) {
526
575
  measure.entries.push(parseFiguredBass(el["figured-bass"], getAttributes(el)));
527
576
  } else if (el["sound"]) {
528
577
  measure.entries.push(parseSound(el["sound"], getAttributes(el)));
578
+ } else if (el["grouping"] !== void 0) {
579
+ const grpAttrs = getAttributes(el);
580
+ const grouping = {
581
+ _id: generateId(),
582
+ type: "grouping",
583
+ groupingType: grpAttrs["type"] || "start"
584
+ };
585
+ if (grpAttrs["number"]) grouping.number = grpAttrs["number"];
586
+ measure.entries.push(grouping);
529
587
  }
530
588
  }
531
589
  if (barlines.length > 0) measure.barlines = barlines;
@@ -592,6 +650,7 @@ function parseAttributes(elements) {
592
650
  const key = parseKeySignature(keyContent);
593
651
  if (keyAttrs["number"]) key.number = parseInt(keyAttrs["number"], 10);
594
652
  if (keyAttrs["print-object"] === "no") key.printObject = false;
653
+ else if (keyAttrs["print-object"] === "yes") key.printObject = true;
595
654
  keys.push(key);
596
655
  }
597
656
  }
@@ -643,14 +702,15 @@ function parseTimeSignature(elements, parentElements) {
643
702
  return time2;
644
703
  }
645
704
  }
646
- const beatsList = collectElements(elements, "beats", (c) => parseInt(extractText(c), 10));
705
+ const beatsStrList = collectElements(elements, "beats", (c) => extractText(c));
647
706
  const beatTypeList = collectElements(elements, "beat-type", (c) => parseInt(extractText(c), 10));
648
707
  const time = {
649
- beats: beatsList.length > 0 ? String(beatsList[0]) : "4",
708
+ beats: beatsStrList.length > 0 ? beatsStrList[0] : "4",
650
709
  beatType: beatTypeList.length > 0 ? beatTypeList[0] : 4
651
710
  };
652
- if (beatsList.length > 1 || beatTypeList.length > 1) {
653
- time.beatsList = beatsList;
711
+ if (beatsStrList.length > 1 || beatTypeList.length > 1) {
712
+ time.beatsList = beatsStrList.map((b) => parseInt(b, 10));
713
+ time.beatsStrList = beatsStrList;
654
714
  time.beatTypeList = beatTypeList;
655
715
  }
656
716
  for (const el of parentElements) {
@@ -689,6 +749,7 @@ function parseKeySignature(elements) {
689
749
  const keyOctaves = collectElements(elements, "key-octave", (c, a) => {
690
750
  const ko = { number: parseInt(a["number"] || "1", 10), octave: parseInt(extractText(c), 10) };
691
751
  if (a["cancel"] === "yes") ko.cancel = true;
752
+ else if (a["cancel"] === "no") ko.cancel = false;
692
753
  return ko;
693
754
  });
694
755
  if (keySteps.length > 0) key.keySteps = keySteps;
@@ -698,8 +759,11 @@ function parseKeySignature(elements) {
698
759
  }
699
760
  function parseClef(elements, attrs) {
700
761
  const sign = getElementText(elements, "sign") || "G";
701
- const line = parseInt(getElementText(elements, "line") || "2", 10);
702
- const clef = { sign, line };
762
+ const lineText = getElementText(elements, "line");
763
+ const clef = { sign };
764
+ if (lineText) {
765
+ clef.line = parseInt(lineText, 10);
766
+ }
703
767
  if (attrs["number"]) {
704
768
  clef.staff = parseInt(attrs["number"], 10);
705
769
  }
@@ -709,6 +773,8 @@ function parseClef(elements, attrs) {
709
773
  }
710
774
  if (attrs["print-object"] === "no") {
711
775
  clef.printObject = false;
776
+ } else if (attrs["print-object"] === "yes") {
777
+ clef.printObject = true;
712
778
  }
713
779
  if (attrs["after-barline"] === "yes") {
714
780
  clef.afterBarline = true;
@@ -730,15 +796,20 @@ function parseNote(elements, attrs) {
730
796
  const note = {
731
797
  _id: generateId(),
732
798
  type: "note",
733
- duration: getElementTextAsInt(elements, "duration", 0),
734
- voice: getElementTextAsInt(elements, "voice", 1)
799
+ duration: getElementTextAsInt(elements, "duration", 0)
735
800
  };
801
+ const voiceValue = getElementTextAsInt(elements, "voice");
802
+ if (voiceValue !== void 0) {
803
+ note.voice = voiceValue;
804
+ }
736
805
  if (attrs["default-x"]) note.defaultX = parseFloat(attrs["default-x"]);
737
806
  if (attrs["default-y"]) note.defaultY = parseFloat(attrs["default-y"]);
738
807
  if (attrs["relative-x"]) note.relativeX = parseFloat(attrs["relative-x"]);
739
808
  if (attrs["relative-y"]) note.relativeY = parseFloat(attrs["relative-y"]);
740
809
  if (attrs["dynamics"]) note.dynamics = parseFloat(attrs["dynamics"]);
741
810
  if (attrs["print-object"] === "no") note.printObject = false;
811
+ if (attrs["print-dot"] === "no") note.printDot = false;
812
+ if (attrs["print-dot"] === "yes") note.printDot = true;
742
813
  if (attrs["print-spacing"] === "yes") note.printSpacing = true;
743
814
  if (attrs["print-spacing"] === "no") note.printSpacing = false;
744
815
  if (hasElement(elements, "cue")) {
@@ -851,6 +922,7 @@ function parseNote(elements, attrs) {
851
922
  const graceAttrs = getAttributes(el);
852
923
  note.grace = {};
853
924
  if (graceAttrs["slash"] === "yes") note.grace.slash = true;
925
+ else if (graceAttrs["slash"] === "no") note.grace.slash = false;
854
926
  if (graceAttrs["steal-time-previous"]) {
855
927
  note.grace.stealTimePrevious = parseFloat(graceAttrs["steal-time-previous"]);
856
928
  }
@@ -1096,6 +1168,14 @@ function parseNotations(elements, notationsIndex = 0) {
1096
1168
  notations.push(tremNotation);
1097
1169
  }
1098
1170
  }
1171
+ const ornamentNotationsAdded = notations.filter((n) => n.type === "ornament" && n.notationsIndex === notationsIndex);
1172
+ if (ornamentNotationsAdded.length === 0) {
1173
+ notations.push({
1174
+ type: "ornament",
1175
+ ornament: "empty",
1176
+ notationsIndex
1177
+ });
1178
+ }
1099
1179
  } else if (el["technical"]) {
1100
1180
  const techContent = el["technical"];
1101
1181
  const technicalWithText = ["hammer-on", "pull-off", "tap", "pluck", "fingering", "other-technical"];
@@ -1145,8 +1225,7 @@ function parseNotations(elements, notationsIndex = 0) {
1145
1225
  if (bendAlter) techNotation.bendAlter = parseFloat(bendAlter);
1146
1226
  if (hasElement(bendContent, "pre-bend")) techNotation.preBend = true;
1147
1227
  if (hasElement(bendContent, "release")) techNotation.release = true;
1148
- const withBar = getElementText(bendContent, "with-bar");
1149
- if (withBar) techNotation.withBar = parseFloat(withBar);
1228
+ if (hasElement(bendContent, "with-bar")) techNotation.withBar = true;
1150
1229
  notations.push(techNotation);
1151
1230
  }
1152
1231
  for (const techType of technicalTypes) {
@@ -1183,6 +1262,7 @@ function parseNotations(elements, notationsIndex = 0) {
1183
1262
  if (typeAttr === "start" || typeAttr === "stop") {
1184
1263
  notation.startStop = typeAttr;
1185
1264
  }
1265
+ if (techAttrs["number"]) notation.number = parseInt(techAttrs["number"], 10);
1186
1266
  }
1187
1267
  if (techType === "fingering") {
1188
1268
  if (techAttrs["substitution"] === "yes") notation.fingeringSubstitution = true;
@@ -1265,11 +1345,33 @@ function parseNotations(elements, notationsIndex = 0) {
1265
1345
  notations.push(fermataNotation);
1266
1346
  } else if (el["arpeggiate"] !== void 0) {
1267
1347
  const arpAttrs = getAttributes(el);
1268
- notations.push({
1348
+ const arpNotation = {
1269
1349
  type: "arpeggiate",
1270
1350
  direction: arpAttrs["direction"],
1271
1351
  number: arpAttrs["number"] ? parseInt(arpAttrs["number"], 10) : void 0,
1272
1352
  notationsIndex
1353
+ };
1354
+ if (arpAttrs["default-x"]) arpNotation.defaultX = parseFloat(arpAttrs["default-x"]);
1355
+ if (arpAttrs["default-y"]) arpNotation.defaultY = parseFloat(arpAttrs["default-y"]);
1356
+ notations.push(arpNotation);
1357
+ } else if (el["non-arpeggiate"] !== void 0) {
1358
+ const nonArpAttrs = getAttributes(el);
1359
+ notations.push({
1360
+ type: "non-arpeggiate",
1361
+ nonArpeggiateType: nonArpAttrs["type"],
1362
+ number: nonArpAttrs["number"] ? parseInt(nonArpAttrs["number"], 10) : void 0,
1363
+ placement: nonArpAttrs["placement"],
1364
+ notationsIndex
1365
+ });
1366
+ } else if (el["accidental-mark"]) {
1367
+ const amAttrs = getAttributes(el);
1368
+ const amContent = el["accidental-mark"];
1369
+ const value = extractText(amContent);
1370
+ notations.push({
1371
+ type: "accidental-mark",
1372
+ value,
1373
+ placement: amAttrs["placement"],
1374
+ notationsIndex
1273
1375
  });
1274
1376
  } else if (el["glissando"]) {
1275
1377
  const glissAttrs = getAttributes(el);
@@ -1291,13 +1393,17 @@ function parseNotations(elements, notationsIndex = 0) {
1291
1393
  });
1292
1394
  } else if (el["slide"]) {
1293
1395
  const slideAttrs = getAttributes(el);
1294
- notations.push({
1396
+ const slideContent = el["slide"];
1397
+ const slideText = extractText(slideContent);
1398
+ const slideNotation = {
1295
1399
  type: "slide",
1296
1400
  slideType: slideAttrs["type"] === "stop" ? "stop" : "start",
1297
1401
  number: slideAttrs["number"] ? parseInt(slideAttrs["number"], 10) : void 0,
1298
1402
  lineType: slideAttrs["line-type"],
1299
1403
  notationsIndex
1300
- });
1404
+ };
1405
+ if (slideText) slideNotation.text = slideText;
1406
+ notations.push(slideNotation);
1301
1407
  }
1302
1408
  }
1303
1409
  return notations;
@@ -1318,8 +1424,9 @@ function parseLyric(elements, attrs) {
1318
1424
  break;
1319
1425
  }
1320
1426
  }
1321
- } else if (el["text"]) {
1427
+ } else if (el["text"] !== void 0) {
1322
1428
  const content = el["text"];
1429
+ let foundText = false;
1323
1430
  for (const item of content) {
1324
1431
  if (item["#text"] !== void 0) {
1325
1432
  textElements.push({
@@ -1327,9 +1434,17 @@ function parseLyric(elements, attrs) {
1327
1434
  syllabic: currentSyllabic
1328
1435
  });
1329
1436
  currentSyllabic = void 0;
1437
+ foundText = true;
1330
1438
  break;
1331
1439
  }
1332
1440
  }
1441
+ if (!foundText) {
1442
+ textElements.push({
1443
+ text: "",
1444
+ syllabic: currentSyllabic
1445
+ });
1446
+ currentSyllabic = void 0;
1447
+ }
1333
1448
  } else if (el["elision"] !== void 0) {
1334
1449
  hasElision = true;
1335
1450
  }
@@ -1498,6 +1613,7 @@ function parseDirectionTypes(elements) {
1498
1613
  "pf"
1499
1614
  ];
1500
1615
  for (const dyn of dynContent) {
1616
+ let foundStandard = false;
1501
1617
  for (const dv of dynamicsValues) {
1502
1618
  if (dyn[dv] !== void 0) {
1503
1619
  const result = { kind: "dynamics", value: dv };
@@ -1506,9 +1622,22 @@ function parseDirectionTypes(elements) {
1506
1622
  if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1507
1623
  if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1508
1624
  results.push(result);
1625
+ foundStandard = true;
1509
1626
  break;
1510
1627
  }
1511
1628
  }
1629
+ if (!foundStandard && dyn["other-dynamics"] !== void 0) {
1630
+ const otherDynContent = dyn["other-dynamics"];
1631
+ const otherDynText = extractText(otherDynContent);
1632
+ if (otherDynText) {
1633
+ const result = { kind: "dynamics", otherDynamics: otherDynText };
1634
+ if (dynAttrs["default-x"]) result.defaultX = parseFloat(dynAttrs["default-x"]);
1635
+ if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
1636
+ if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1637
+ if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1638
+ results.push(result);
1639
+ }
1640
+ }
1512
1641
  }
1513
1642
  continue;
1514
1643
  }
@@ -1633,11 +1762,14 @@ function parseDirectionTypes(elements) {
1633
1762
  for (const acc of accContent) {
1634
1763
  if (acc["accordion-high"] !== void 0) {
1635
1764
  result.high = true;
1636
- } else if (acc["accordion-middle"]) {
1765
+ } else if (acc["accordion-middle"] !== void 0) {
1766
+ result.middlePresent = true;
1637
1767
  const midContent = acc["accordion-middle"];
1638
1768
  for (const item of midContent) {
1639
1769
  if (item["#text"] !== void 0) {
1640
- result.middle = parseInt(String(item["#text"]), 10);
1770
+ const textValue = String(item["#text"]);
1771
+ const numValue = parseInt(textValue, 10);
1772
+ result.middle = !isNaN(numValue) ? numValue : textValue;
1641
1773
  break;
1642
1774
  }
1643
1775
  }
@@ -1814,6 +1946,9 @@ function parseBarline(elements, attrs) {
1814
1946
  if (repeatAttrs["times"]) {
1815
1947
  barline.repeat.times = parseInt(repeatAttrs["times"], 10);
1816
1948
  }
1949
+ if (repeatAttrs["winged"]) {
1950
+ barline.repeat.winged = repeatAttrs["winged"];
1951
+ }
1817
1952
  }
1818
1953
  } else if (el["ending"]) {
1819
1954
  const endingAttrs = getAttributes(el);
@@ -1821,6 +1956,11 @@ function parseBarline(elements, attrs) {
1821
1956
  const type = endingAttrs["type"];
1822
1957
  if (number && (type === "start" || type === "stop" || type === "discontinue")) {
1823
1958
  barline.ending = { number, type };
1959
+ const endingContent = el["ending"];
1960
+ const endingText = extractText(endingContent);
1961
+ if (endingText) barline.ending.text = endingText;
1962
+ if (endingAttrs["default-y"]) barline.ending.defaultY = parseFloat(endingAttrs["default-y"]);
1963
+ if (endingAttrs["end-length"]) barline.ending.endLength = parseFloat(endingAttrs["end-length"]);
1824
1964
  }
1825
1965
  }
1826
1966
  }
@@ -1946,6 +2086,8 @@ function parseStaffDetails(elements, attrs) {
1946
2086
  if (attrs["number"]) sd.number = parseInt(attrs["number"], 10);
1947
2087
  if (attrs["print-object"] === "no") sd.printObject = false;
1948
2088
  else if (attrs["print-object"] === "yes") sd.printObject = true;
2089
+ if (attrs["print-spacing"] === "yes") sd.printSpacing = true;
2090
+ else if (attrs["print-spacing"] === "no") sd.printSpacing = false;
1949
2091
  const staffType = getElementText(elements, "staff-type");
1950
2092
  if (staffType && ["ossia", "cue", "editorial", "regular", "alternate"].includes(staffType)) {
1951
2093
  sd.staffType = staffType;
@@ -2009,7 +2151,9 @@ function parseMeasureStyle(elements, attrs) {
2009
2151
  const slAttrs = getAttributes(el);
2010
2152
  ms.slash = { type: slAttrs["type"] === "stop" ? "stop" : "start" };
2011
2153
  if (slAttrs["use-dots"] === "yes") ms.slash.useDots = true;
2154
+ else if (slAttrs["use-dots"] === "no") ms.slash.useDots = false;
2012
2155
  if (slAttrs["use-stems"] === "yes") ms.slash.useStems = true;
2156
+ else if (slAttrs["use-stems"] === "no") ms.slash.useStems = false;
2013
2157
  }
2014
2158
  }
2015
2159
  return ms;
@@ -2049,19 +2193,29 @@ function parseHarmony(elements, attrs) {
2049
2193
  break;
2050
2194
  }
2051
2195
  }
2052
- if (kindAttrs["text"]) harmony.kindText = kindAttrs["text"];
2196
+ if (kindAttrs["text"] !== void 0) harmony.kindText = kindAttrs["text"];
2197
+ if (kindAttrs["halign"]) harmony.kindHalign = kindAttrs["halign"];
2053
2198
  break;
2054
2199
  }
2055
2200
  }
2056
- const bass = getElementContent(elements, "bass");
2057
- if (bass) {
2058
- const bassStep = getElementText(bass, "bass-step");
2059
- if (bassStep) {
2060
- harmony.bass = { bassStep };
2061
- const bassAlter = getElementText(bass, "bass-alter");
2062
- if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2201
+ for (const el of elements) {
2202
+ if (el["bass"]) {
2203
+ const bassAttrs = getAttributes(el);
2204
+ const bassContent = el["bass"];
2205
+ const bassStep = getElementText(bassContent, "bass-step");
2206
+ if (bassStep) {
2207
+ harmony.bass = { bassStep };
2208
+ const bassAlter = getElementText(bassContent, "bass-alter");
2209
+ if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2210
+ if (bassAttrs["arrangement"]) harmony.bass.arrangement = bassAttrs["arrangement"];
2211
+ }
2212
+ break;
2063
2213
  }
2064
2214
  }
2215
+ const inversionText = getElementText(elements, "inversion");
2216
+ if (inversionText) {
2217
+ harmony.inversion = parseInt(inversionText, 10);
2218
+ }
2065
2219
  const degrees = [];
2066
2220
  for (const el of elements) {
2067
2221
  if (el["degree"]) {
@@ -2828,7 +2982,7 @@ function validateVoiceStaff(measure, staves, location) {
2828
2982
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
2829
2983
  const entry = measure.entries[entryIndex];
2830
2984
  if (entry.type !== "note") continue;
2831
- if (entry.voice <= 0) {
2985
+ if (entry.voice !== void 0 && entry.voice <= 0) {
2832
2986
  errors.push({
2833
2987
  code: "INVALID_VOICE_NUMBER",
2834
2988
  level: "error",
@@ -3477,6 +3631,11 @@ function serializeDefaults(defaults, indent) {
3477
3631
  lines.push(`${indent}${indent}${indent}<distance type="${escapeXml(d.type)}">${d.value}</distance>`);
3478
3632
  }
3479
3633
  }
3634
+ if (app["glyphs"]) {
3635
+ for (const g of app["glyphs"]) {
3636
+ lines.push(`${indent}${indent}${indent}<glyph type="${escapeXml(g.type)}">${escapeXml(g.value)}</glyph>`);
3637
+ }
3638
+ }
3480
3639
  lines.push(`${indent}${indent}</appearance>`);
3481
3640
  }
3482
3641
  if (defaults.musicFont) {
@@ -3533,16 +3692,16 @@ function serializePageLayout(layout, indent) {
3533
3692
  const typeAttr = m.type ? ` type="${m.type}"` : "";
3534
3693
  lines.push(`${indent} <page-margins${typeAttr}>`);
3535
3694
  if (m.leftMargin !== void 0) {
3536
- lines.push(`${indent} <left-margin>${m.leftMargin}</left-margin>`);
3695
+ lines.push(`${indent} <left-margin>${m.leftMarginRaw ?? m.leftMargin}</left-margin>`);
3537
3696
  }
3538
3697
  if (m.rightMargin !== void 0) {
3539
- lines.push(`${indent} <right-margin>${m.rightMargin}</right-margin>`);
3698
+ lines.push(`${indent} <right-margin>${m.rightMarginRaw ?? m.rightMargin}</right-margin>`);
3540
3699
  }
3541
3700
  if (m.topMargin !== void 0) {
3542
- lines.push(`${indent} <top-margin>${m.topMargin}</top-margin>`);
3701
+ lines.push(`${indent} <top-margin>${m.topMarginRaw ?? m.topMargin}</top-margin>`);
3543
3702
  }
3544
3703
  if (m.bottomMargin !== void 0) {
3545
- lines.push(`${indent} <bottom-margin>${m.bottomMargin}</bottom-margin>`);
3704
+ lines.push(`${indent} <bottom-margin>${m.bottomMarginRaw ?? m.bottomMargin}</bottom-margin>`);
3546
3705
  }
3547
3706
  lines.push(`${indent} </page-margins>`);
3548
3707
  }
@@ -3556,18 +3715,48 @@ function serializeSystemLayout(layout, indent) {
3556
3715
  if (layout.systemMargins) {
3557
3716
  lines.push(`${indent} <system-margins>`);
3558
3717
  if (layout.systemMargins.leftMargin !== void 0) {
3559
- lines.push(`${indent} <left-margin>${layout.systemMargins.leftMargin}</left-margin>`);
3718
+ lines.push(`${indent} <left-margin>${layout.systemMargins.leftMarginRaw ?? layout.systemMargins.leftMargin}</left-margin>`);
3560
3719
  }
3561
3720
  if (layout.systemMargins.rightMargin !== void 0) {
3562
- lines.push(`${indent} <right-margin>${layout.systemMargins.rightMargin}</right-margin>`);
3721
+ lines.push(`${indent} <right-margin>${layout.systemMargins.rightMarginRaw ?? layout.systemMargins.rightMargin}</right-margin>`);
3563
3722
  }
3564
3723
  lines.push(`${indent} </system-margins>`);
3565
3724
  }
3566
3725
  if (layout.systemDistance !== void 0) {
3567
- lines.push(`${indent} <system-distance>${layout.systemDistance}</system-distance>`);
3726
+ lines.push(`${indent} <system-distance>${layout.systemDistanceRaw ?? layout.systemDistance}</system-distance>`);
3568
3727
  }
3569
3728
  if (layout.topSystemDistance !== void 0) {
3570
- lines.push(`${indent} <top-system-distance>${layout.topSystemDistance}</top-system-distance>`);
3729
+ lines.push(`${indent} <top-system-distance>${layout.topSystemDistanceRaw ?? layout.topSystemDistance}</top-system-distance>`);
3730
+ }
3731
+ if (layout.systemDividers) {
3732
+ lines.push(`${indent} <system-dividers>`);
3733
+ if (layout.systemDividers.leftDivider) {
3734
+ let attrs = "";
3735
+ if (layout.systemDividers.leftDivider.printObject !== void 0) {
3736
+ attrs += ` print-object="${layout.systemDividers.leftDivider.printObject ? "yes" : "no"}"`;
3737
+ }
3738
+ if (layout.systemDividers.leftDivider.halign) {
3739
+ attrs += ` halign="${layout.systemDividers.leftDivider.halign}"`;
3740
+ }
3741
+ if (layout.systemDividers.leftDivider.valign) {
3742
+ attrs += ` valign="${layout.systemDividers.leftDivider.valign}"`;
3743
+ }
3744
+ lines.push(`${indent} <left-divider${attrs}/>`);
3745
+ }
3746
+ if (layout.systemDividers.rightDivider) {
3747
+ let attrs = "";
3748
+ if (layout.systemDividers.rightDivider.printObject !== void 0) {
3749
+ attrs += ` print-object="${layout.systemDividers.rightDivider.printObject ? "yes" : "no"}"`;
3750
+ }
3751
+ if (layout.systemDividers.rightDivider.halign) {
3752
+ attrs += ` halign="${layout.systemDividers.rightDivider.halign}"`;
3753
+ }
3754
+ if (layout.systemDividers.rightDivider.valign) {
3755
+ attrs += ` valign="${layout.systemDividers.rightDivider.valign}"`;
3756
+ }
3757
+ lines.push(`${indent} <right-divider${attrs}/>`);
3758
+ }
3759
+ lines.push(`${indent} </system-dividers>`);
3571
3760
  }
3572
3761
  lines.push(`${indent}</system-layout>`);
3573
3762
  return lines;
@@ -3588,6 +3777,7 @@ function serializeCredit(credit, indent) {
3588
3777
  let attrs2 = "";
3589
3778
  if (cw.defaultX !== void 0) attrs2 += ` default-x="${cw.defaultX}"`;
3590
3779
  if (cw.defaultY !== void 0) attrs2 += ` default-y="${cw.defaultY}"`;
3780
+ if (cw.fontFamily) attrs2 += ` font-family="${escapeXml(cw.fontFamily)}"`;
3591
3781
  if (cw.fontSize) attrs2 += ` font-size="${escapeXml(cw.fontSize)}"`;
3592
3782
  if (cw.fontWeight) attrs2 += ` font-weight="${escapeXml(cw.fontWeight)}"`;
3593
3783
  if (cw.fontStyle) attrs2 += ` font-style="${escapeXml(cw.fontStyle)}"`;
@@ -3882,6 +4072,7 @@ function serializeKey(key, indent) {
3882
4072
  let keyAttrs = "";
3883
4073
  if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
3884
4074
  if (key.printObject === false) keyAttrs += ' print-object="no"';
4075
+ else if (key.printObject === true) keyAttrs += ' print-object="yes"';
3885
4076
  lines.push(`${indent}<key${keyAttrs}>`);
3886
4077
  if (key.cancel !== void 0) {
3887
4078
  const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
@@ -3929,7 +4120,8 @@ function serializeTime(time, indent) {
3929
4120
  const maxLen = Math.max(time.beatsList.length, time.beatTypeList.length);
3930
4121
  for (let i = 0; i < maxLen; i++) {
3931
4122
  if (i < time.beatsList.length) {
3932
- lines.push(`${indent} <beats>${time.beatsList[i]}</beats>`);
4123
+ const beatsValue = time.beatsStrList && i < time.beatsStrList.length ? time.beatsStrList[i] : time.beatsList[i];
4124
+ lines.push(`${indent} <beats>${beatsValue}</beats>`);
3933
4125
  }
3934
4126
  if (i < time.beatTypeList.length) {
3935
4127
  lines.push(`${indent} <beat-type>${time.beatTypeList[i]}</beat-type>`);
@@ -3946,10 +4138,13 @@ function serializeClef(clef, indent) {
3946
4138
  const lines = [];
3947
4139
  let attrs = clef.staff ? ` number="${clef.staff}"` : "";
3948
4140
  if (clef.printObject === false) attrs += ' print-object="no"';
4141
+ else if (clef.printObject === true) attrs += ' print-object="yes"';
3949
4142
  if (clef.afterBarline) attrs += ' after-barline="yes"';
3950
4143
  lines.push(`${indent}<clef${attrs}>`);
3951
4144
  lines.push(`${indent} <sign>${clef.sign}</sign>`);
3952
- lines.push(`${indent} <line>${clef.line}</line>`);
4145
+ if (clef.line !== void 0) {
4146
+ lines.push(`${indent} <line>${clef.line}</line>`);
4147
+ }
3953
4148
  if (clef.clefOctaveChange !== void 0) {
3954
4149
  lines.push(`${indent} <clef-octave-change>${clef.clefOctaveChange}</clef-octave-change>`);
3955
4150
  }
@@ -3985,6 +4180,12 @@ function serializeEntry(entry, indent) {
3985
4180
  return serializeSound(entry, indent);
3986
4181
  case "attributes":
3987
4182
  return serializeAttributes(entry.attributes, indent, entry._id);
4183
+ case "grouping": {
4184
+ const grp = entry;
4185
+ let grpAttrs = ` type="${grp.groupingType}"`;
4186
+ if (grp.number) grpAttrs += ` number="${grp.number}"`;
4187
+ return [`${indent}<grouping${grpAttrs}/>`];
4188
+ }
3988
4189
  default:
3989
4190
  return [];
3990
4191
  }
@@ -3999,12 +4200,13 @@ function serializeNote(note, indent) {
3999
4200
  "relative-y": note.relativeY,
4000
4201
  "dynamics": note.dynamics,
4001
4202
  "print-object": note.printObject === false ? false : void 0,
4203
+ "print-dot": note.printDot !== void 0 ? note.printDot : void 0,
4002
4204
  "print-spacing": note.printSpacing
4003
4205
  });
4004
4206
  lines.push(`${indent}<note${noteAttrs}>`);
4005
4207
  if (note.grace) {
4006
4208
  const graceAttrs = buildAttrs({
4007
- "slash": note.grace.slash || void 0,
4209
+ "slash": note.grace.slash !== void 0 ? note.grace.slash : void 0,
4008
4210
  "steal-time-previous": note.grace.stealTimePrevious,
4009
4211
  "steal-time-following": note.grace.stealTimeFollowing
4010
4212
  });
@@ -4059,7 +4261,9 @@ function serializeNote(note, indent) {
4059
4261
  } else if (note.tie) {
4060
4262
  lines.push(`${indent} <tie type="${note.tie.type}"/>`);
4061
4263
  }
4062
- lines.push(`${indent} <voice>${note.voice}</voice>`);
4264
+ if (note.voice !== void 0) {
4265
+ lines.push(`${indent} <voice>${note.voice}</voice>`);
4266
+ }
4063
4267
  if (note.noteType) {
4064
4268
  const typeAttrs = note.noteTypeSize ? ` size="${escapeXml(note.noteTypeSize)}"` : "";
4065
4269
  lines.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
@@ -4276,7 +4480,18 @@ function serializeNotationsGroup(notations, indent) {
4276
4480
  let attrs = "";
4277
4481
  if (notation.direction) attrs += ` direction="${notation.direction}"`;
4278
4482
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4483
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4484
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4279
4485
  lines.push(`${indent} <arpeggiate${attrs}/>`);
4486
+ } else if (notation.type === "non-arpeggiate") {
4487
+ let attrs = ` type="${notation.nonArpeggiateType}"`;
4488
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4489
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4490
+ lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4491
+ } else if (notation.type === "accidental-mark") {
4492
+ let attrs = "";
4493
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4494
+ lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4280
4495
  } else if (notation.type === "glissando") {
4281
4496
  let attrs = ` type="${notation.glissandoType}"`;
4282
4497
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
@@ -4290,7 +4505,11 @@ function serializeNotationsGroup(notations, indent) {
4290
4505
  let attrs = ` type="${notation.slideType}"`;
4291
4506
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4292
4507
  if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4293
- lines.push(`${indent} <slide${attrs}/>`);
4508
+ if (notation.text) {
4509
+ lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4510
+ } else {
4511
+ lines.push(`${indent} <slide${attrs}/>`);
4512
+ }
4294
4513
  }
4295
4514
  }
4296
4515
  const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
@@ -4311,44 +4530,50 @@ function serializeNotationsGroup(notations, indent) {
4311
4530
  lines.push(`${indent} </articulations>`);
4312
4531
  }
4313
4532
  if (ornaments.length > 0) {
4314
- lines.push(`${indent} <ornaments>`);
4315
- const allAccidentalMarks = [];
4316
- for (const orn of ornaments) {
4317
- if (orn.type === "ornament") {
4318
- const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4319
- if (orn.ornament === "wavy-line") {
4320
- let wlAttrs = "";
4321
- if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4322
- if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4323
- wlAttrs += placementAttr;
4324
- if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4325
- lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4326
- } else if (orn.ornament === "tremolo") {
4327
- let tremAttrs = "";
4328
- if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4329
- tremAttrs += placementAttr;
4330
- if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4331
- if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4332
- if (orn.tremoloMarks !== void 0) {
4333
- lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4533
+ const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4534
+ if (hasOnlyEmptyMarker) {
4535
+ lines.push(`${indent} <ornaments/>`);
4536
+ } else {
4537
+ lines.push(`${indent} <ornaments>`);
4538
+ const allAccidentalMarks = [];
4539
+ for (const orn of ornaments) {
4540
+ if (orn.type === "ornament") {
4541
+ if (orn.ornament === "empty") continue;
4542
+ const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4543
+ if (orn.ornament === "wavy-line") {
4544
+ let wlAttrs = "";
4545
+ if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4546
+ if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4547
+ wlAttrs += placementAttr;
4548
+ if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4549
+ lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4550
+ } else if (orn.ornament === "tremolo") {
4551
+ let tremAttrs = "";
4552
+ if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4553
+ tremAttrs += placementAttr;
4554
+ if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4555
+ if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4556
+ if (orn.tremoloMarks !== void 0) {
4557
+ lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4558
+ } else {
4559
+ lines.push(`${indent} <tremolo${tremAttrs}/>`);
4560
+ }
4334
4561
  } else {
4335
- lines.push(`${indent} <tremolo${tremAttrs}/>`);
4562
+ let ornAttrs = placementAttr;
4563
+ if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4564
+ lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4565
+ }
4566
+ if (orn.accidentalMarks) {
4567
+ allAccidentalMarks.push(...orn.accidentalMarks);
4336
4568
  }
4337
- } else {
4338
- let ornAttrs = placementAttr;
4339
- if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4340
- lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4341
- }
4342
- if (orn.accidentalMarks) {
4343
- allAccidentalMarks.push(...orn.accidentalMarks);
4344
4569
  }
4345
4570
  }
4571
+ for (const am of allAccidentalMarks) {
4572
+ const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4573
+ lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4574
+ }
4575
+ lines.push(`${indent} </ornaments>`);
4346
4576
  }
4347
- for (const am of allAccidentalMarks) {
4348
- const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4349
- lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4350
- }
4351
- lines.push(`${indent} </ornaments>`);
4352
4577
  }
4353
4578
  if (technicals.length > 0) {
4354
4579
  lines.push(`${indent} <technical>`);
@@ -4369,8 +4594,8 @@ function serializeNotationsGroup(notations, indent) {
4369
4594
  if (techNotation.release) {
4370
4595
  lines.push(`${indent} <release/>`);
4371
4596
  }
4372
- if (techNotation.withBar !== void 0) {
4373
- lines.push(`${indent} <with-bar>${techNotation.withBar}</with-bar>`);
4597
+ if (techNotation.withBar) {
4598
+ lines.push(`${indent} <with-bar/>`);
4374
4599
  }
4375
4600
  lines.push(`${indent} </bend>`);
4376
4601
  } else if (tech.technical === "harmonic") {
@@ -4387,8 +4612,10 @@ function serializeNotationsGroup(notations, indent) {
4387
4612
  lines.push(`${indent} <harmonic${placementAttr}/>`);
4388
4613
  }
4389
4614
  } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4390
- let attrs = placementAttr;
4615
+ let attrs = "";
4616
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4391
4617
  if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4618
+ attrs += placementAttr;
4392
4619
  if (techNotation.text !== void 0) {
4393
4620
  lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4394
4621
  } else {
@@ -4444,7 +4671,7 @@ function serializeLyric(lyric, indent) {
4444
4671
  lines.push(`${indent} <elision/>`);
4445
4672
  }
4446
4673
  }
4447
- } else {
4674
+ } else if (lyric.syllabic || lyric.text) {
4448
4675
  if (lyric.syllabic) {
4449
4676
  lines.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
4450
4677
  }
@@ -4559,7 +4786,12 @@ function serializeDirectionType(dirType, indent) {
4559
4786
  if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
4560
4787
  if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
4561
4788
  lines.push(`${indent} <dynamics${dynAttrs}>`);
4562
- lines.push(`${indent} <${dirType.value}/>`);
4789
+ if (dirType.value) {
4790
+ lines.push(`${indent} <${dirType.value}/>`);
4791
+ }
4792
+ if (dirType.otherDynamics) {
4793
+ lines.push(`${indent} <other-dynamics>${escapeXml(dirType.otherDynamics)}</other-dynamics>`);
4794
+ }
4563
4795
  lines.push(`${indent} </dynamics>`);
4564
4796
  break;
4565
4797
  }
@@ -4646,8 +4878,12 @@ function serializeDirectionType(dirType, indent) {
4646
4878
  if (dirType.high) {
4647
4879
  lines.push(`${indent} <accordion-high/>`);
4648
4880
  }
4649
- if (dirType.middle !== void 0) {
4650
- lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
4881
+ if (dirType.middlePresent || dirType.middle !== void 0) {
4882
+ if (dirType.middle !== void 0) {
4883
+ lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
4884
+ } else {
4885
+ lines.push(`${indent} <accordion-middle/>`);
4886
+ }
4651
4887
  }
4652
4888
  if (dirType.low) {
4653
4889
  lines.push(`${indent} <accordion-low/>`);
@@ -4760,11 +4996,20 @@ function serializeBarline(barline, indent) {
4760
4996
  lines.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
4761
4997
  }
4762
4998
  if (barline.ending) {
4763
- lines.push(`${indent} <ending number="${barline.ending.number}" type="${barline.ending.type}"/>`);
4999
+ let endingAttrs = ` number="${barline.ending.number}" type="${barline.ending.type}"`;
5000
+ if (barline.ending.defaultY !== void 0) endingAttrs += ` default-y="${barline.ending.defaultY}"`;
5001
+ if (barline.ending.endLength !== void 0) endingAttrs += ` end-length="${barline.ending.endLength}"`;
5002
+ if (barline.ending.text) {
5003
+ lines.push(`${indent} <ending${endingAttrs}>${escapeXml(barline.ending.text)}</ending>`);
5004
+ } else {
5005
+ lines.push(`${indent} <ending${endingAttrs}/>`);
5006
+ }
4764
5007
  }
4765
5008
  if (barline.repeat) {
4766
- const timesAttr = barline.repeat.times !== void 0 ? ` times="${barline.repeat.times}"` : "";
4767
- lines.push(`${indent} <repeat direction="${barline.repeat.direction}"${timesAttr}/>`);
5009
+ let repeatAttrs = ` direction="${barline.repeat.direction}"`;
5010
+ if (barline.repeat.times !== void 0) repeatAttrs += ` times="${barline.repeat.times}"`;
5011
+ if (barline.repeat.winged) repeatAttrs += ` winged="${barline.repeat.winged}"`;
5012
+ lines.push(`${indent} <repeat${repeatAttrs}/>`);
4768
5013
  }
4769
5014
  lines.push(`${indent}</barline>`);
4770
5015
  return lines;
@@ -4797,7 +5042,8 @@ function serializeStaffDetails(sd, indent) {
4797
5042
  const attrs = buildAttrs({
4798
5043
  "number": sd.number,
4799
5044
  "show-frets": sd.showFrets,
4800
- "print-object": sd.printObject
5045
+ "print-object": sd.printObject,
5046
+ "print-spacing": sd.printSpacing
4801
5047
  });
4802
5048
  lines.push(`${indent}<staff-details${attrs}>`);
4803
5049
  pushOptionalElement(lines, `${indent} `, "staff-type", sd.staffType);
@@ -4863,16 +5109,22 @@ function serializeHarmony(harmony, indent) {
4863
5109
  }
4864
5110
  lines.push(`${indent} </root>`);
4865
5111
  let kindAttrs = "";
4866
- if (harmony.kindText) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5112
+ if (harmony.kindText !== void 0) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5113
+ if (harmony.kindHalign) kindAttrs += ` halign="${escapeXml(harmony.kindHalign)}"`;
4867
5114
  lines.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
4868
5115
  if (harmony.bass) {
4869
- lines.push(`${indent} <bass>`);
5116
+ let bassAttrs = "";
5117
+ if (harmony.bass.arrangement) bassAttrs += ` arrangement="${escapeXml(harmony.bass.arrangement)}"`;
5118
+ lines.push(`${indent} <bass${bassAttrs}>`);
4870
5119
  lines.push(`${indent} <bass-step>${harmony.bass.bassStep}</bass-step>`);
4871
5120
  if (harmony.bass.bassAlter !== void 0) {
4872
5121
  lines.push(`${indent} <bass-alter>${harmony.bass.bassAlter}</bass-alter>`);
4873
5122
  }
4874
5123
  lines.push(`${indent} </bass>`);
4875
5124
  }
5125
+ if (harmony.inversion !== void 0) {
5126
+ lines.push(`${indent} <inversion>${harmony.inversion}</inversion>`);
5127
+ }
4876
5128
  if (harmony.degrees) {
4877
5129
  for (const deg of harmony.degrees) {
4878
5130
  lines.push(`${indent} <degree>`);
@@ -5558,7 +5810,7 @@ function groupByVoice(measure) {
5558
5810
  for (const entry of measure.entries) {
5559
5811
  if (entry.type !== "note") continue;
5560
5812
  const staff = entry.staff ?? 1;
5561
- const voice = entry.voice;
5813
+ const voice = entry.voice ?? 1;
5562
5814
  const key = `${staff}-${voice}`;
5563
5815
  if (!groups.has(key)) {
5564
5816
  groups.set(key, { staff, voice, notes: [] });
@@ -5652,7 +5904,7 @@ function getVoices(measure) {
5652
5904
  const voices = /* @__PURE__ */ new Set();
5653
5905
  for (const entry of measure.entries) {
5654
5906
  if (entry.type === "note") {
5655
- voices.add(entry.voice);
5907
+ voices.add(entry.voice ?? 1);
5656
5908
  }
5657
5909
  }
5658
5910
  return Array.from(voices).sort((a, b) => a - b);
@@ -5703,7 +5955,7 @@ function buildVoiceToStaffMap(measure) {
5703
5955
  const map = /* @__PURE__ */ new Map();
5704
5956
  for (const entry of measure.entries) {
5705
5957
  if (entry.type === "note" && entry.staff !== void 0) {
5706
- const voice = entry.voice;
5958
+ const voice = entry.voice ?? 1;
5707
5959
  const staff = entry.staff;
5708
5960
  if (!map.has(voice)) {
5709
5961
  map.set(voice, staff);
@@ -5722,7 +5974,7 @@ function buildVoiceToStaffMapForPart(part) {
5722
5974
  for (const measure of part.measures) {
5723
5975
  for (const entry of measure.entries) {
5724
5976
  if (entry.type === "note" && entry.staff !== void 0) {
5725
- const voice = entry.voice;
5977
+ const voice = entry.voice ?? 1;
5726
5978
  const staff = entry.staff;
5727
5979
  if (!map.has(voice)) {
5728
5980
  map.set(voice, staff);
@@ -5741,7 +5993,7 @@ function inferStaff(entry, voiceToStaffMap) {
5741
5993
  if (entry.staff !== void 0) {
5742
5994
  return entry.staff;
5743
5995
  }
5744
- const inferredStaff = voiceToStaffMap.get(entry.voice);
5996
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
5745
5997
  if (inferredStaff !== void 0) {
5746
5998
  return inferredStaff;
5747
5999
  }
@@ -5784,7 +6036,7 @@ function getVoicesForStaff(measure, staff) {
5784
6036
  if (entry.type === "note") {
5785
6037
  const entryStaff = entry.staff ?? 1;
5786
6038
  if (entryStaff === staff) {
5787
- voices.add(entry.voice);
6039
+ voices.add(entry.voice ?? 1);
5788
6040
  }
5789
6041
  }
5790
6042
  }
@@ -6120,6 +6372,7 @@ function getDynamics(score, options) {
6120
6372
  if (dirType.kind === "dynamics") {
6121
6373
  results.push({
6122
6374
  dynamic: dirType.value,
6375
+ otherDynamics: dirType.otherDynamics,
6123
6376
  direction: entry,
6124
6377
  part,
6125
6378
  partIndex,
@@ -7194,14 +7447,17 @@ function hasNotesInRange(voiceEntries, startPos, endPos) {
7194
7447
  return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
7195
7448
  }
7196
7449
  function createRest(duration, voice, staff) {
7197
- return {
7450
+ const note = {
7198
7451
  _id: generateId(),
7199
7452
  type: "note",
7200
7453
  rest: { displayStep: void 0, displayOctave: void 0 },
7201
7454
  duration,
7202
- voice,
7203
7455
  staff
7204
7456
  };
7457
+ if (voice !== void 0) {
7458
+ note.voice = voice;
7459
+ }
7460
+ return note;
7205
7461
  }
7206
7462
  function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
7207
7463
  const otherEntries = [];
@@ -8755,7 +9011,7 @@ function autoBeam(score, options) {
8755
9011
  for (const entry of measure.entries) {
8756
9012
  if (entry.type === "note") {
8757
9013
  if (!entry.chord && !entry.rest) {
8758
- const voice = entry.voice;
9014
+ const voice = entry.voice ?? 1;
8759
9015
  if (options.voice === void 0 || voice === options.voice) {
8760
9016
  if (!notesByVoice.has(voice)) {
8761
9017
  notesByVoice.set(voice, []);