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.mjs CHANGED
@@ -19,11 +19,22 @@ var xmlParser = new XMLParser({
19
19
  });
20
20
  function parse(xmlString) {
21
21
  const parsed = xmlParser.parse(xmlString);
22
- const scorePartwise = findElement(parsed, "score-partwise");
22
+ let scorePartwiseVersion;
23
+ let scorePartwise;
24
+ for (const el of parsed) {
25
+ if (el["score-partwise"]) {
26
+ scorePartwise = el["score-partwise"];
27
+ const attrs = getAttributes(el);
28
+ if (attrs["version"]) scorePartwiseVersion = attrs["version"];
29
+ break;
30
+ }
31
+ }
23
32
  if (!scorePartwise) {
24
33
  throw new Error("Unsupported MusicXML format: only score-partwise is supported");
25
34
  }
26
- return parseScorePartwise(scorePartwise);
35
+ const score = parseScorePartwise(scorePartwise);
36
+ if (scorePartwiseVersion) score.version = scorePartwiseVersion;
37
+ return score;
27
38
  }
28
39
  function findElement(elements, tagName) {
29
40
  for (const el of elements) {
@@ -241,9 +252,11 @@ function parseDefaults(elements) {
241
252
  const lineWidths = collectElements(appContent, "line-width", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
242
253
  const noteSizes = collectElements(appContent, "note-size", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
243
254
  const distances = collectElements(appContent, "distance", (c, a) => ({ type: a["type"] || "", value: parseFloat(extractText(c)) || 0 }));
255
+ const glyphs = collectElements(appContent, "glyph", (c, a) => ({ type: a["type"] || "", value: extractText(c) }));
244
256
  if (lineWidths.length > 0) appearance["line-widths"] = lineWidths;
245
257
  if (noteSizes.length > 0) appearance["note-sizes"] = noteSizes;
246
258
  if (distances.length > 0) appearance["distances"] = distances;
259
+ if (glyphs.length > 0) appearance["glyphs"] = glyphs;
247
260
  return Object.keys(appearance).length > 0 ? appearance : void 0;
248
261
  });
249
262
  if (appResult) defaults.appearance = appResult;
@@ -265,13 +278,25 @@ function parsePageLayout(elements) {
265
278
  m.type = attrs["type"];
266
279
  }
267
280
  const left = getElementText(content, "left-margin");
268
- if (left) m.leftMargin = parseFloat(left);
281
+ if (left) {
282
+ m.leftMargin = parseFloat(left);
283
+ m.leftMarginRaw = left;
284
+ }
269
285
  const right = getElementText(content, "right-margin");
270
- if (right) m.rightMargin = parseFloat(right);
286
+ if (right) {
287
+ m.rightMargin = parseFloat(right);
288
+ m.rightMarginRaw = right;
289
+ }
271
290
  const top = getElementText(content, "top-margin");
272
- if (top) m.topMargin = parseFloat(top);
291
+ if (top) {
292
+ m.topMargin = parseFloat(top);
293
+ m.topMarginRaw = top;
294
+ }
273
295
  const bottom = getElementText(content, "bottom-margin");
274
- if (bottom) m.bottomMargin = parseFloat(bottom);
296
+ if (bottom) {
297
+ m.bottomMargin = parseFloat(bottom);
298
+ m.bottomMarginRaw = bottom;
299
+ }
275
300
  margins.push(m);
276
301
  }
277
302
  }
@@ -284,14 +309,48 @@ function parseSystemLayout(elements) {
284
309
  if (margins) {
285
310
  layout.systemMargins = {};
286
311
  const left = getElementText(margins, "left-margin");
287
- if (left) layout.systemMargins.leftMargin = parseFloat(left);
312
+ if (left) {
313
+ layout.systemMargins.leftMargin = parseFloat(left);
314
+ layout.systemMargins.leftMarginRaw = left;
315
+ }
288
316
  const right = getElementText(margins, "right-margin");
289
- if (right) layout.systemMargins.rightMargin = parseFloat(right);
317
+ if (right) {
318
+ layout.systemMargins.rightMargin = parseFloat(right);
319
+ layout.systemMargins.rightMarginRaw = right;
320
+ }
290
321
  }
291
322
  const dist = getElementText(elements, "system-distance");
292
- if (dist) layout.systemDistance = parseFloat(dist);
323
+ if (dist) {
324
+ layout.systemDistance = parseFloat(dist);
325
+ layout.systemDistanceRaw = dist;
326
+ }
293
327
  const topDist = getElementText(elements, "top-system-distance");
294
- if (topDist) layout.topSystemDistance = parseFloat(topDist);
328
+ if (topDist) {
329
+ layout.topSystemDistance = parseFloat(topDist);
330
+ layout.topSystemDistanceRaw = topDist;
331
+ }
332
+ const dividers = getElementContent(elements, "system-dividers");
333
+ if (dividers) {
334
+ layout.systemDividers = {};
335
+ for (const el of dividers) {
336
+ if (el["left-divider"]) {
337
+ const attrs = getAttributes(el);
338
+ layout.systemDividers.leftDivider = {
339
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
340
+ halign: attrs["halign"],
341
+ valign: attrs["valign"]
342
+ };
343
+ }
344
+ if (el["right-divider"]) {
345
+ const attrs = getAttributes(el);
346
+ layout.systemDividers.rightDivider = {
347
+ printObject: attrs["print-object"] === "yes" ? true : attrs["print-object"] === "no" ? false : void 0,
348
+ halign: attrs["halign"],
349
+ valign: attrs["valign"]
350
+ };
351
+ }
352
+ }
353
+ }
295
354
  return layout;
296
355
  }
297
356
  function parseCredits(elements) {
@@ -303,6 +362,7 @@ function parseCredits(elements) {
303
362
  const cw = { text: extractText(c) };
304
363
  if (a["default-x"]) cw.defaultX = parseFloat(a["default-x"]);
305
364
  if (a["default-y"]) cw.defaultY = parseFloat(a["default-y"]);
365
+ if (a["font-family"]) cw.fontFamily = a["font-family"];
306
366
  if (a["font-size"]) cw.fontSize = a["font-size"];
307
367
  if (a["font-weight"]) cw.fontWeight = a["font-weight"];
308
368
  if (a["font-style"]) cw.fontStyle = a["font-style"];
@@ -526,6 +586,15 @@ function parseMeasure(elements, attrs) {
526
586
  measure.entries.push(parseFiguredBass(el["figured-bass"], getAttributes(el)));
527
587
  } else if (el["sound"]) {
528
588
  measure.entries.push(parseSound(el["sound"], getAttributes(el)));
589
+ } else if (el["grouping"] !== void 0) {
590
+ const grpAttrs = getAttributes(el);
591
+ const grouping = {
592
+ _id: generateId(),
593
+ type: "grouping",
594
+ groupingType: grpAttrs["type"] || "start"
595
+ };
596
+ if (grpAttrs["number"]) grouping.number = grpAttrs["number"];
597
+ measure.entries.push(grouping);
529
598
  }
530
599
  }
531
600
  if (barlines.length > 0) measure.barlines = barlines;
@@ -592,6 +661,7 @@ function parseAttributes(elements) {
592
661
  const key = parseKeySignature(keyContent);
593
662
  if (keyAttrs["number"]) key.number = parseInt(keyAttrs["number"], 10);
594
663
  if (keyAttrs["print-object"] === "no") key.printObject = false;
664
+ else if (keyAttrs["print-object"] === "yes") key.printObject = true;
595
665
  keys.push(key);
596
666
  }
597
667
  }
@@ -643,14 +713,15 @@ function parseTimeSignature(elements, parentElements) {
643
713
  return time2;
644
714
  }
645
715
  }
646
- const beatsList = collectElements(elements, "beats", (c) => parseInt(extractText(c), 10));
716
+ const beatsStrList = collectElements(elements, "beats", (c) => extractText(c));
647
717
  const beatTypeList = collectElements(elements, "beat-type", (c) => parseInt(extractText(c), 10));
648
718
  const time = {
649
- beats: beatsList.length > 0 ? String(beatsList[0]) : "4",
719
+ beats: beatsStrList.length > 0 ? beatsStrList[0] : "4",
650
720
  beatType: beatTypeList.length > 0 ? beatTypeList[0] : 4
651
721
  };
652
- if (beatsList.length > 1 || beatTypeList.length > 1) {
653
- time.beatsList = beatsList;
722
+ if (beatsStrList.length > 1 || beatTypeList.length > 1) {
723
+ time.beatsList = beatsStrList.map((b) => parseInt(b, 10));
724
+ time.beatsStrList = beatsStrList;
654
725
  time.beatTypeList = beatTypeList;
655
726
  }
656
727
  for (const el of parentElements) {
@@ -689,6 +760,7 @@ function parseKeySignature(elements) {
689
760
  const keyOctaves = collectElements(elements, "key-octave", (c, a) => {
690
761
  const ko = { number: parseInt(a["number"] || "1", 10), octave: parseInt(extractText(c), 10) };
691
762
  if (a["cancel"] === "yes") ko.cancel = true;
763
+ else if (a["cancel"] === "no") ko.cancel = false;
692
764
  return ko;
693
765
  });
694
766
  if (keySteps.length > 0) key.keySteps = keySteps;
@@ -698,8 +770,11 @@ function parseKeySignature(elements) {
698
770
  }
699
771
  function parseClef(elements, attrs) {
700
772
  const sign = getElementText(elements, "sign") || "G";
701
- const line = parseInt(getElementText(elements, "line") || "2", 10);
702
- const clef = { sign, line };
773
+ const lineText = getElementText(elements, "line");
774
+ const clef = { sign };
775
+ if (lineText) {
776
+ clef.line = parseInt(lineText, 10);
777
+ }
703
778
  if (attrs["number"]) {
704
779
  clef.staff = parseInt(attrs["number"], 10);
705
780
  }
@@ -709,6 +784,8 @@ function parseClef(elements, attrs) {
709
784
  }
710
785
  if (attrs["print-object"] === "no") {
711
786
  clef.printObject = false;
787
+ } else if (attrs["print-object"] === "yes") {
788
+ clef.printObject = true;
712
789
  }
713
790
  if (attrs["after-barline"] === "yes") {
714
791
  clef.afterBarline = true;
@@ -730,15 +807,20 @@ function parseNote(elements, attrs) {
730
807
  const note = {
731
808
  _id: generateId(),
732
809
  type: "note",
733
- duration: getElementTextAsInt(elements, "duration", 0),
734
- voice: getElementTextAsInt(elements, "voice", 1)
810
+ duration: getElementTextAsInt(elements, "duration", 0)
735
811
  };
812
+ const voiceValue = getElementTextAsInt(elements, "voice");
813
+ if (voiceValue !== void 0) {
814
+ note.voice = voiceValue;
815
+ }
736
816
  if (attrs["default-x"]) note.defaultX = parseFloat(attrs["default-x"]);
737
817
  if (attrs["default-y"]) note.defaultY = parseFloat(attrs["default-y"]);
738
818
  if (attrs["relative-x"]) note.relativeX = parseFloat(attrs["relative-x"]);
739
819
  if (attrs["relative-y"]) note.relativeY = parseFloat(attrs["relative-y"]);
740
820
  if (attrs["dynamics"]) note.dynamics = parseFloat(attrs["dynamics"]);
741
821
  if (attrs["print-object"] === "no") note.printObject = false;
822
+ if (attrs["print-dot"] === "no") note.printDot = false;
823
+ if (attrs["print-dot"] === "yes") note.printDot = true;
742
824
  if (attrs["print-spacing"] === "yes") note.printSpacing = true;
743
825
  if (attrs["print-spacing"] === "no") note.printSpacing = false;
744
826
  if (hasElement(elements, "cue")) {
@@ -851,6 +933,7 @@ function parseNote(elements, attrs) {
851
933
  const graceAttrs = getAttributes(el);
852
934
  note.grace = {};
853
935
  if (graceAttrs["slash"] === "yes") note.grace.slash = true;
936
+ else if (graceAttrs["slash"] === "no") note.grace.slash = false;
854
937
  if (graceAttrs["steal-time-previous"]) {
855
938
  note.grace.stealTimePrevious = parseFloat(graceAttrs["steal-time-previous"]);
856
939
  }
@@ -1096,6 +1179,14 @@ function parseNotations(elements, notationsIndex = 0) {
1096
1179
  notations.push(tremNotation);
1097
1180
  }
1098
1181
  }
1182
+ const ornamentNotationsAdded = notations.filter((n) => n.type === "ornament" && n.notationsIndex === notationsIndex);
1183
+ if (ornamentNotationsAdded.length === 0) {
1184
+ notations.push({
1185
+ type: "ornament",
1186
+ ornament: "empty",
1187
+ notationsIndex
1188
+ });
1189
+ }
1099
1190
  } else if (el["technical"]) {
1100
1191
  const techContent = el["technical"];
1101
1192
  const technicalWithText = ["hammer-on", "pull-off", "tap", "pluck", "fingering", "other-technical"];
@@ -1145,8 +1236,7 @@ function parseNotations(elements, notationsIndex = 0) {
1145
1236
  if (bendAlter) techNotation.bendAlter = parseFloat(bendAlter);
1146
1237
  if (hasElement(bendContent, "pre-bend")) techNotation.preBend = true;
1147
1238
  if (hasElement(bendContent, "release")) techNotation.release = true;
1148
- const withBar = getElementText(bendContent, "with-bar");
1149
- if (withBar) techNotation.withBar = parseFloat(withBar);
1239
+ if (hasElement(bendContent, "with-bar")) techNotation.withBar = true;
1150
1240
  notations.push(techNotation);
1151
1241
  }
1152
1242
  for (const techType of technicalTypes) {
@@ -1183,6 +1273,7 @@ function parseNotations(elements, notationsIndex = 0) {
1183
1273
  if (typeAttr === "start" || typeAttr === "stop") {
1184
1274
  notation.startStop = typeAttr;
1185
1275
  }
1276
+ if (techAttrs["number"]) notation.number = parseInt(techAttrs["number"], 10);
1186
1277
  }
1187
1278
  if (techType === "fingering") {
1188
1279
  if (techAttrs["substitution"] === "yes") notation.fingeringSubstitution = true;
@@ -1265,11 +1356,33 @@ function parseNotations(elements, notationsIndex = 0) {
1265
1356
  notations.push(fermataNotation);
1266
1357
  } else if (el["arpeggiate"] !== void 0) {
1267
1358
  const arpAttrs = getAttributes(el);
1268
- notations.push({
1359
+ const arpNotation = {
1269
1360
  type: "arpeggiate",
1270
1361
  direction: arpAttrs["direction"],
1271
1362
  number: arpAttrs["number"] ? parseInt(arpAttrs["number"], 10) : void 0,
1272
1363
  notationsIndex
1364
+ };
1365
+ if (arpAttrs["default-x"]) arpNotation.defaultX = parseFloat(arpAttrs["default-x"]);
1366
+ if (arpAttrs["default-y"]) arpNotation.defaultY = parseFloat(arpAttrs["default-y"]);
1367
+ notations.push(arpNotation);
1368
+ } else if (el["non-arpeggiate"] !== void 0) {
1369
+ const nonArpAttrs = getAttributes(el);
1370
+ notations.push({
1371
+ type: "non-arpeggiate",
1372
+ nonArpeggiateType: nonArpAttrs["type"],
1373
+ number: nonArpAttrs["number"] ? parseInt(nonArpAttrs["number"], 10) : void 0,
1374
+ placement: nonArpAttrs["placement"],
1375
+ notationsIndex
1376
+ });
1377
+ } else if (el["accidental-mark"]) {
1378
+ const amAttrs = getAttributes(el);
1379
+ const amContent = el["accidental-mark"];
1380
+ const value = extractText(amContent);
1381
+ notations.push({
1382
+ type: "accidental-mark",
1383
+ value,
1384
+ placement: amAttrs["placement"],
1385
+ notationsIndex
1273
1386
  });
1274
1387
  } else if (el["glissando"]) {
1275
1388
  const glissAttrs = getAttributes(el);
@@ -1291,13 +1404,17 @@ function parseNotations(elements, notationsIndex = 0) {
1291
1404
  });
1292
1405
  } else if (el["slide"]) {
1293
1406
  const slideAttrs = getAttributes(el);
1294
- notations.push({
1407
+ const slideContent = el["slide"];
1408
+ const slideText = extractText(slideContent);
1409
+ const slideNotation = {
1295
1410
  type: "slide",
1296
1411
  slideType: slideAttrs["type"] === "stop" ? "stop" : "start",
1297
1412
  number: slideAttrs["number"] ? parseInt(slideAttrs["number"], 10) : void 0,
1298
1413
  lineType: slideAttrs["line-type"],
1299
1414
  notationsIndex
1300
- });
1415
+ };
1416
+ if (slideText) slideNotation.text = slideText;
1417
+ notations.push(slideNotation);
1301
1418
  }
1302
1419
  }
1303
1420
  return notations;
@@ -1318,8 +1435,9 @@ function parseLyric(elements, attrs) {
1318
1435
  break;
1319
1436
  }
1320
1437
  }
1321
- } else if (el["text"]) {
1438
+ } else if (el["text"] !== void 0) {
1322
1439
  const content = el["text"];
1440
+ let foundText = false;
1323
1441
  for (const item of content) {
1324
1442
  if (item["#text"] !== void 0) {
1325
1443
  textElements.push({
@@ -1327,9 +1445,17 @@ function parseLyric(elements, attrs) {
1327
1445
  syllabic: currentSyllabic
1328
1446
  });
1329
1447
  currentSyllabic = void 0;
1448
+ foundText = true;
1330
1449
  break;
1331
1450
  }
1332
1451
  }
1452
+ if (!foundText) {
1453
+ textElements.push({
1454
+ text: "",
1455
+ syllabic: currentSyllabic
1456
+ });
1457
+ currentSyllabic = void 0;
1458
+ }
1333
1459
  } else if (el["elision"] !== void 0) {
1334
1460
  hasElision = true;
1335
1461
  }
@@ -1498,6 +1624,7 @@ function parseDirectionTypes(elements) {
1498
1624
  "pf"
1499
1625
  ];
1500
1626
  for (const dyn of dynContent) {
1627
+ let foundStandard = false;
1501
1628
  for (const dv of dynamicsValues) {
1502
1629
  if (dyn[dv] !== void 0) {
1503
1630
  const result = { kind: "dynamics", value: dv };
@@ -1506,9 +1633,22 @@ function parseDirectionTypes(elements) {
1506
1633
  if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1507
1634
  if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1508
1635
  results.push(result);
1636
+ foundStandard = true;
1509
1637
  break;
1510
1638
  }
1511
1639
  }
1640
+ if (!foundStandard && dyn["other-dynamics"] !== void 0) {
1641
+ const otherDynContent = dyn["other-dynamics"];
1642
+ const otherDynText = extractText(otherDynContent);
1643
+ if (otherDynText) {
1644
+ const result = { kind: "dynamics", otherDynamics: otherDynText };
1645
+ if (dynAttrs["default-x"]) result.defaultX = parseFloat(dynAttrs["default-x"]);
1646
+ if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
1647
+ if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1648
+ if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1649
+ results.push(result);
1650
+ }
1651
+ }
1512
1652
  }
1513
1653
  continue;
1514
1654
  }
@@ -1633,11 +1773,14 @@ function parseDirectionTypes(elements) {
1633
1773
  for (const acc of accContent) {
1634
1774
  if (acc["accordion-high"] !== void 0) {
1635
1775
  result.high = true;
1636
- } else if (acc["accordion-middle"]) {
1776
+ } else if (acc["accordion-middle"] !== void 0) {
1777
+ result.middlePresent = true;
1637
1778
  const midContent = acc["accordion-middle"];
1638
1779
  for (const item of midContent) {
1639
1780
  if (item["#text"] !== void 0) {
1640
- result.middle = parseInt(String(item["#text"]), 10);
1781
+ const textValue = String(item["#text"]);
1782
+ const numValue = parseInt(textValue, 10);
1783
+ result.middle = !isNaN(numValue) ? numValue : textValue;
1641
1784
  break;
1642
1785
  }
1643
1786
  }
@@ -1814,6 +1957,9 @@ function parseBarline(elements, attrs) {
1814
1957
  if (repeatAttrs["times"]) {
1815
1958
  barline.repeat.times = parseInt(repeatAttrs["times"], 10);
1816
1959
  }
1960
+ if (repeatAttrs["winged"]) {
1961
+ barline.repeat.winged = repeatAttrs["winged"];
1962
+ }
1817
1963
  }
1818
1964
  } else if (el["ending"]) {
1819
1965
  const endingAttrs = getAttributes(el);
@@ -1821,6 +1967,11 @@ function parseBarline(elements, attrs) {
1821
1967
  const type = endingAttrs["type"];
1822
1968
  if (number && (type === "start" || type === "stop" || type === "discontinue")) {
1823
1969
  barline.ending = { number, type };
1970
+ const endingContent = el["ending"];
1971
+ const endingText = extractText(endingContent);
1972
+ if (endingText) barline.ending.text = endingText;
1973
+ if (endingAttrs["default-y"]) barline.ending.defaultY = parseFloat(endingAttrs["default-y"]);
1974
+ if (endingAttrs["end-length"]) barline.ending.endLength = parseFloat(endingAttrs["end-length"]);
1824
1975
  }
1825
1976
  }
1826
1977
  }
@@ -1946,6 +2097,8 @@ function parseStaffDetails(elements, attrs) {
1946
2097
  if (attrs["number"]) sd.number = parseInt(attrs["number"], 10);
1947
2098
  if (attrs["print-object"] === "no") sd.printObject = false;
1948
2099
  else if (attrs["print-object"] === "yes") sd.printObject = true;
2100
+ if (attrs["print-spacing"] === "yes") sd.printSpacing = true;
2101
+ else if (attrs["print-spacing"] === "no") sd.printSpacing = false;
1949
2102
  const staffType = getElementText(elements, "staff-type");
1950
2103
  if (staffType && ["ossia", "cue", "editorial", "regular", "alternate"].includes(staffType)) {
1951
2104
  sd.staffType = staffType;
@@ -2009,7 +2162,9 @@ function parseMeasureStyle(elements, attrs) {
2009
2162
  const slAttrs = getAttributes(el);
2010
2163
  ms.slash = { type: slAttrs["type"] === "stop" ? "stop" : "start" };
2011
2164
  if (slAttrs["use-dots"] === "yes") ms.slash.useDots = true;
2165
+ else if (slAttrs["use-dots"] === "no") ms.slash.useDots = false;
2012
2166
  if (slAttrs["use-stems"] === "yes") ms.slash.useStems = true;
2167
+ else if (slAttrs["use-stems"] === "no") ms.slash.useStems = false;
2013
2168
  }
2014
2169
  }
2015
2170
  return ms;
@@ -2049,19 +2204,29 @@ function parseHarmony(elements, attrs) {
2049
2204
  break;
2050
2205
  }
2051
2206
  }
2052
- if (kindAttrs["text"]) harmony.kindText = kindAttrs["text"];
2207
+ if (kindAttrs["text"] !== void 0) harmony.kindText = kindAttrs["text"];
2208
+ if (kindAttrs["halign"]) harmony.kindHalign = kindAttrs["halign"];
2053
2209
  break;
2054
2210
  }
2055
2211
  }
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);
2212
+ for (const el of elements) {
2213
+ if (el["bass"]) {
2214
+ const bassAttrs = getAttributes(el);
2215
+ const bassContent = el["bass"];
2216
+ const bassStep = getElementText(bassContent, "bass-step");
2217
+ if (bassStep) {
2218
+ harmony.bass = { bassStep };
2219
+ const bassAlter = getElementText(bassContent, "bass-alter");
2220
+ if (bassAlter) harmony.bass.bassAlter = parseFloat(bassAlter);
2221
+ if (bassAttrs["arrangement"]) harmony.bass.arrangement = bassAttrs["arrangement"];
2222
+ }
2223
+ break;
2063
2224
  }
2064
2225
  }
2226
+ const inversionText = getElementText(elements, "inversion");
2227
+ if (inversionText) {
2228
+ harmony.inversion = parseInt(inversionText, 10);
2229
+ }
2065
2230
  const degrees = [];
2066
2231
  for (const el of elements) {
2067
2232
  if (el["degree"]) {
@@ -2828,7 +2993,7 @@ function validateVoiceStaff(measure, staves, location) {
2828
2993
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
2829
2994
  const entry = measure.entries[entryIndex];
2830
2995
  if (entry.type !== "note") continue;
2831
- if (entry.voice <= 0) {
2996
+ if (entry.voice !== void 0 && entry.voice <= 0) {
2832
2997
  errors.push({
2833
2998
  code: "INVALID_VOICE_NUMBER",
2834
2999
  level: "error",
@@ -3324,7 +3489,7 @@ function validateSlursAcrossMeasures(part) {
3324
3489
 
3325
3490
  // src/exporters/musicxml.ts
3326
3491
  function serialize(score, options = {}) {
3327
- const version = options.version || "4.0";
3492
+ const version = options.version || score.version || "4.0";
3328
3493
  const indent = options.indent ?? " ";
3329
3494
  if (options.validate) {
3330
3495
  const result = validate(score, options.validateOptions);
@@ -3339,11 +3504,7 @@ ${errorMessages}`);
3339
3504
  }
3340
3505
  const lines = [];
3341
3506
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
3342
- if (version === "4.0") {
3343
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3344
- } else {
3345
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3346
- }
3507
+ lines.push(`<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML ${version} Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">`);
3347
3508
  lines.push(`<score-partwise version="${version}">`);
3348
3509
  lines.push(...serializeMetadata(score.metadata, indent));
3349
3510
  if (score.defaults) {
@@ -3477,6 +3638,11 @@ function serializeDefaults(defaults, indent) {
3477
3638
  lines.push(`${indent}${indent}${indent}<distance type="${escapeXml(d.type)}">${d.value}</distance>`);
3478
3639
  }
3479
3640
  }
3641
+ if (app["glyphs"]) {
3642
+ for (const g of app["glyphs"]) {
3643
+ lines.push(`${indent}${indent}${indent}<glyph type="${escapeXml(g.type)}">${escapeXml(g.value)}</glyph>`);
3644
+ }
3645
+ }
3480
3646
  lines.push(`${indent}${indent}</appearance>`);
3481
3647
  }
3482
3648
  if (defaults.musicFont) {
@@ -3533,16 +3699,16 @@ function serializePageLayout(layout, indent) {
3533
3699
  const typeAttr = m.type ? ` type="${m.type}"` : "";
3534
3700
  lines.push(`${indent} <page-margins${typeAttr}>`);
3535
3701
  if (m.leftMargin !== void 0) {
3536
- lines.push(`${indent} <left-margin>${m.leftMargin}</left-margin>`);
3702
+ lines.push(`${indent} <left-margin>${m.leftMarginRaw ?? m.leftMargin}</left-margin>`);
3537
3703
  }
3538
3704
  if (m.rightMargin !== void 0) {
3539
- lines.push(`${indent} <right-margin>${m.rightMargin}</right-margin>`);
3705
+ lines.push(`${indent} <right-margin>${m.rightMarginRaw ?? m.rightMargin}</right-margin>`);
3540
3706
  }
3541
3707
  if (m.topMargin !== void 0) {
3542
- lines.push(`${indent} <top-margin>${m.topMargin}</top-margin>`);
3708
+ lines.push(`${indent} <top-margin>${m.topMarginRaw ?? m.topMargin}</top-margin>`);
3543
3709
  }
3544
3710
  if (m.bottomMargin !== void 0) {
3545
- lines.push(`${indent} <bottom-margin>${m.bottomMargin}</bottom-margin>`);
3711
+ lines.push(`${indent} <bottom-margin>${m.bottomMarginRaw ?? m.bottomMargin}</bottom-margin>`);
3546
3712
  }
3547
3713
  lines.push(`${indent} </page-margins>`);
3548
3714
  }
@@ -3556,18 +3722,48 @@ function serializeSystemLayout(layout, indent) {
3556
3722
  if (layout.systemMargins) {
3557
3723
  lines.push(`${indent} <system-margins>`);
3558
3724
  if (layout.systemMargins.leftMargin !== void 0) {
3559
- lines.push(`${indent} <left-margin>${layout.systemMargins.leftMargin}</left-margin>`);
3725
+ lines.push(`${indent} <left-margin>${layout.systemMargins.leftMarginRaw ?? layout.systemMargins.leftMargin}</left-margin>`);
3560
3726
  }
3561
3727
  if (layout.systemMargins.rightMargin !== void 0) {
3562
- lines.push(`${indent} <right-margin>${layout.systemMargins.rightMargin}</right-margin>`);
3728
+ lines.push(`${indent} <right-margin>${layout.systemMargins.rightMarginRaw ?? layout.systemMargins.rightMargin}</right-margin>`);
3563
3729
  }
3564
3730
  lines.push(`${indent} </system-margins>`);
3565
3731
  }
3566
3732
  if (layout.systemDistance !== void 0) {
3567
- lines.push(`${indent} <system-distance>${layout.systemDistance}</system-distance>`);
3733
+ lines.push(`${indent} <system-distance>${layout.systemDistanceRaw ?? layout.systemDistance}</system-distance>`);
3568
3734
  }
3569
3735
  if (layout.topSystemDistance !== void 0) {
3570
- lines.push(`${indent} <top-system-distance>${layout.topSystemDistance}</top-system-distance>`);
3736
+ lines.push(`${indent} <top-system-distance>${layout.topSystemDistanceRaw ?? layout.topSystemDistance}</top-system-distance>`);
3737
+ }
3738
+ if (layout.systemDividers) {
3739
+ lines.push(`${indent} <system-dividers>`);
3740
+ if (layout.systemDividers.leftDivider) {
3741
+ let attrs = "";
3742
+ if (layout.systemDividers.leftDivider.printObject !== void 0) {
3743
+ attrs += ` print-object="${layout.systemDividers.leftDivider.printObject ? "yes" : "no"}"`;
3744
+ }
3745
+ if (layout.systemDividers.leftDivider.halign) {
3746
+ attrs += ` halign="${layout.systemDividers.leftDivider.halign}"`;
3747
+ }
3748
+ if (layout.systemDividers.leftDivider.valign) {
3749
+ attrs += ` valign="${layout.systemDividers.leftDivider.valign}"`;
3750
+ }
3751
+ lines.push(`${indent} <left-divider${attrs}/>`);
3752
+ }
3753
+ if (layout.systemDividers.rightDivider) {
3754
+ let attrs = "";
3755
+ if (layout.systemDividers.rightDivider.printObject !== void 0) {
3756
+ attrs += ` print-object="${layout.systemDividers.rightDivider.printObject ? "yes" : "no"}"`;
3757
+ }
3758
+ if (layout.systemDividers.rightDivider.halign) {
3759
+ attrs += ` halign="${layout.systemDividers.rightDivider.halign}"`;
3760
+ }
3761
+ if (layout.systemDividers.rightDivider.valign) {
3762
+ attrs += ` valign="${layout.systemDividers.rightDivider.valign}"`;
3763
+ }
3764
+ lines.push(`${indent} <right-divider${attrs}/>`);
3765
+ }
3766
+ lines.push(`${indent} </system-dividers>`);
3571
3767
  }
3572
3768
  lines.push(`${indent}</system-layout>`);
3573
3769
  return lines;
@@ -3588,6 +3784,7 @@ function serializeCredit(credit, indent) {
3588
3784
  let attrs2 = "";
3589
3785
  if (cw.defaultX !== void 0) attrs2 += ` default-x="${cw.defaultX}"`;
3590
3786
  if (cw.defaultY !== void 0) attrs2 += ` default-y="${cw.defaultY}"`;
3787
+ if (cw.fontFamily) attrs2 += ` font-family="${escapeXml(cw.fontFamily)}"`;
3591
3788
  if (cw.fontSize) attrs2 += ` font-size="${escapeXml(cw.fontSize)}"`;
3592
3789
  if (cw.fontWeight) attrs2 += ` font-weight="${escapeXml(cw.fontWeight)}"`;
3593
3790
  if (cw.fontStyle) attrs2 += ` font-style="${escapeXml(cw.fontStyle)}"`;
@@ -3882,6 +4079,7 @@ function serializeKey(key, indent) {
3882
4079
  let keyAttrs = "";
3883
4080
  if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
3884
4081
  if (key.printObject === false) keyAttrs += ' print-object="no"';
4082
+ else if (key.printObject === true) keyAttrs += ' print-object="yes"';
3885
4083
  lines.push(`${indent}<key${keyAttrs}>`);
3886
4084
  if (key.cancel !== void 0) {
3887
4085
  const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
@@ -3929,7 +4127,8 @@ function serializeTime(time, indent) {
3929
4127
  const maxLen = Math.max(time.beatsList.length, time.beatTypeList.length);
3930
4128
  for (let i = 0; i < maxLen; i++) {
3931
4129
  if (i < time.beatsList.length) {
3932
- lines.push(`${indent} <beats>${time.beatsList[i]}</beats>`);
4130
+ const beatsValue = time.beatsStrList && i < time.beatsStrList.length ? time.beatsStrList[i] : time.beatsList[i];
4131
+ lines.push(`${indent} <beats>${beatsValue}</beats>`);
3933
4132
  }
3934
4133
  if (i < time.beatTypeList.length) {
3935
4134
  lines.push(`${indent} <beat-type>${time.beatTypeList[i]}</beat-type>`);
@@ -3946,10 +4145,13 @@ function serializeClef(clef, indent) {
3946
4145
  const lines = [];
3947
4146
  let attrs = clef.staff ? ` number="${clef.staff}"` : "";
3948
4147
  if (clef.printObject === false) attrs += ' print-object="no"';
4148
+ else if (clef.printObject === true) attrs += ' print-object="yes"';
3949
4149
  if (clef.afterBarline) attrs += ' after-barline="yes"';
3950
4150
  lines.push(`${indent}<clef${attrs}>`);
3951
4151
  lines.push(`${indent} <sign>${clef.sign}</sign>`);
3952
- lines.push(`${indent} <line>${clef.line}</line>`);
4152
+ if (clef.line !== void 0) {
4153
+ lines.push(`${indent} <line>${clef.line}</line>`);
4154
+ }
3953
4155
  if (clef.clefOctaveChange !== void 0) {
3954
4156
  lines.push(`${indent} <clef-octave-change>${clef.clefOctaveChange}</clef-octave-change>`);
3955
4157
  }
@@ -3985,6 +4187,12 @@ function serializeEntry(entry, indent) {
3985
4187
  return serializeSound(entry, indent);
3986
4188
  case "attributes":
3987
4189
  return serializeAttributes(entry.attributes, indent, entry._id);
4190
+ case "grouping": {
4191
+ const grp = entry;
4192
+ let grpAttrs = ` type="${grp.groupingType}"`;
4193
+ if (grp.number) grpAttrs += ` number="${grp.number}"`;
4194
+ return [`${indent}<grouping${grpAttrs}/>`];
4195
+ }
3988
4196
  default:
3989
4197
  return [];
3990
4198
  }
@@ -3999,12 +4207,13 @@ function serializeNote(note, indent) {
3999
4207
  "relative-y": note.relativeY,
4000
4208
  "dynamics": note.dynamics,
4001
4209
  "print-object": note.printObject === false ? false : void 0,
4210
+ "print-dot": note.printDot !== void 0 ? note.printDot : void 0,
4002
4211
  "print-spacing": note.printSpacing
4003
4212
  });
4004
4213
  lines.push(`${indent}<note${noteAttrs}>`);
4005
4214
  if (note.grace) {
4006
4215
  const graceAttrs = buildAttrs({
4007
- "slash": note.grace.slash || void 0,
4216
+ "slash": note.grace.slash !== void 0 ? note.grace.slash : void 0,
4008
4217
  "steal-time-previous": note.grace.stealTimePrevious,
4009
4218
  "steal-time-following": note.grace.stealTimeFollowing
4010
4219
  });
@@ -4059,7 +4268,9 @@ function serializeNote(note, indent) {
4059
4268
  } else if (note.tie) {
4060
4269
  lines.push(`${indent} <tie type="${note.tie.type}"/>`);
4061
4270
  }
4062
- lines.push(`${indent} <voice>${note.voice}</voice>`);
4271
+ if (note.voice !== void 0) {
4272
+ lines.push(`${indent} <voice>${note.voice}</voice>`);
4273
+ }
4063
4274
  if (note.noteType) {
4064
4275
  const typeAttrs = note.noteTypeSize ? ` size="${escapeXml(note.noteTypeSize)}"` : "";
4065
4276
  lines.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
@@ -4167,154 +4378,200 @@ function serializeNotations(notations, indent) {
4167
4378
  function serializeNotationsGroup(notations, indent) {
4168
4379
  const lines = [];
4169
4380
  lines.push(`${indent}<notations>`);
4170
- const articulationsGroups = /* @__PURE__ */ new Map();
4171
- const ornaments = [];
4172
- const technicals = [];
4173
- const others = [];
4381
+ const chunks = [];
4174
4382
  for (const notation of notations) {
4175
4383
  if (notation.type === "articulation") {
4176
4384
  const artIdx = notation.articulationsIndex ?? 0;
4177
- if (!articulationsGroups.has(artIdx)) {
4178
- articulationsGroups.set(artIdx, []);
4385
+ const last = chunks[chunks.length - 1];
4386
+ if (last && last.kind === "articulations" && last.articulationsIndex === artIdx) {
4387
+ last.items.push(notation);
4388
+ } else {
4389
+ chunks.push({ kind: "articulations", items: [notation], articulationsIndex: artIdx });
4179
4390
  }
4180
- articulationsGroups.get(artIdx).push(notation);
4181
4391
  } else if (notation.type === "ornament") {
4182
- ornaments.push(notation);
4392
+ const last = chunks[chunks.length - 1];
4393
+ if (last && last.kind === "ornaments") {
4394
+ last.items.push(notation);
4395
+ } else {
4396
+ chunks.push({ kind: "ornaments", items: [notation] });
4397
+ }
4183
4398
  } else if (notation.type === "technical") {
4184
- technicals.push(notation);
4399
+ const last = chunks[chunks.length - 1];
4400
+ if (last && last.kind === "technical") {
4401
+ last.items.push(notation);
4402
+ } else {
4403
+ chunks.push({ kind: "technical", items: [notation] });
4404
+ }
4185
4405
  } else {
4186
- others.push(notation);
4406
+ chunks.push({ kind: "standalone", notation });
4187
4407
  }
4188
4408
  }
4189
- for (const notation of others) {
4190
- if (notation.type === "tied") {
4191
- let attrs = ` type="${notation.tiedType}"`;
4192
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4193
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4194
- lines.push(`${indent} <tied${attrs}/>`);
4195
- } else if (notation.type === "slur") {
4196
- let attrs = "";
4197
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4198
- attrs += ` type="${notation.slurType}"`;
4199
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4200
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4201
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4202
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4203
- if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4204
- if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4205
- if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4206
- if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4207
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4208
- lines.push(`${indent} <slur${attrs}/>`);
4209
- } else if (notation.type === "tuplet") {
4210
- let attrs = ` type="${notation.tupletType}"`;
4211
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4212
- if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4213
- if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4214
- if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4215
- if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4216
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4217
- const tup = notation;
4218
- if (tup.tupletActual || tup.tupletNormal) {
4219
- lines.push(`${indent} <tuplet${attrs}>`);
4220
- if (tup.tupletActual) {
4221
- lines.push(`${indent} <tuplet-actual>`);
4222
- if (tup.tupletActual.tupletNumber !== void 0) {
4223
- lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4224
- }
4225
- if (tup.tupletActual.tupletType) {
4226
- lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4227
- }
4228
- if (tup.tupletActual.tupletDots) {
4229
- for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4230
- lines.push(`${indent} <tuplet-dot/>`);
4231
- }
4409
+ for (const chunk of chunks) {
4410
+ if (chunk.kind === "standalone") {
4411
+ lines.push(...serializeStandaloneNotation(chunk.notation, indent));
4412
+ } else if (chunk.kind === "articulations") {
4413
+ lines.push(...serializeArticulationsGroup(chunk.items, indent));
4414
+ } else if (chunk.kind === "ornaments") {
4415
+ lines.push(...serializeOrnamentsGroup(chunk.items, indent));
4416
+ } else if (chunk.kind === "technical") {
4417
+ lines.push(...serializeTechnicalGroup(chunk.items, indent));
4418
+ }
4419
+ }
4420
+ lines.push(`${indent}</notations>`);
4421
+ return lines;
4422
+ }
4423
+ function serializeStandaloneNotation(notation, indent) {
4424
+ const lines = [];
4425
+ if (notation.type === "tied") {
4426
+ let attrs = ` type="${notation.tiedType}"`;
4427
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4428
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4429
+ lines.push(`${indent} <tied${attrs}/>`);
4430
+ } else if (notation.type === "slur") {
4431
+ let attrs = "";
4432
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4433
+ attrs += ` type="${notation.slurType}"`;
4434
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4435
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4436
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4437
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4438
+ if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4439
+ if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4440
+ if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4441
+ if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4442
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4443
+ lines.push(`${indent} <slur${attrs}/>`);
4444
+ } else if (notation.type === "tuplet") {
4445
+ let attrs = ` type="${notation.tupletType}"`;
4446
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4447
+ if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4448
+ if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4449
+ if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4450
+ if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4451
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4452
+ const tup = notation;
4453
+ if (tup.tupletActual || tup.tupletNormal) {
4454
+ lines.push(`${indent} <tuplet${attrs}>`);
4455
+ if (tup.tupletActual) {
4456
+ lines.push(`${indent} <tuplet-actual>`);
4457
+ if (tup.tupletActual.tupletNumber !== void 0) {
4458
+ lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4459
+ }
4460
+ if (tup.tupletActual.tupletType) {
4461
+ lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4462
+ }
4463
+ if (tup.tupletActual.tupletDots) {
4464
+ for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4465
+ lines.push(`${indent} <tuplet-dot/>`);
4232
4466
  }
4233
- lines.push(`${indent} </tuplet-actual>`);
4234
4467
  }
4235
- if (tup.tupletNormal) {
4236
- lines.push(`${indent} <tuplet-normal>`);
4237
- if (tup.tupletNormal.tupletNumber !== void 0) {
4238
- lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4239
- }
4240
- if (tup.tupletNormal.tupletType) {
4241
- lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4242
- }
4243
- if (tup.tupletNormal.tupletDots) {
4244
- for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4245
- lines.push(`${indent} <tuplet-dot/>`);
4246
- }
4468
+ lines.push(`${indent} </tuplet-actual>`);
4469
+ }
4470
+ if (tup.tupletNormal) {
4471
+ lines.push(`${indent} <tuplet-normal>`);
4472
+ if (tup.tupletNormal.tupletNumber !== void 0) {
4473
+ lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4474
+ }
4475
+ if (tup.tupletNormal.tupletType) {
4476
+ lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4477
+ }
4478
+ if (tup.tupletNormal.tupletDots) {
4479
+ for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4480
+ lines.push(`${indent} <tuplet-dot/>`);
4247
4481
  }
4248
- lines.push(`${indent} </tuplet-normal>`);
4249
4482
  }
4250
- lines.push(`${indent} </tuplet>`);
4251
- } else {
4252
- lines.push(`${indent} <tuplet${attrs}/>`);
4253
- }
4254
- } else if (notation.type === "dynamics") {
4255
- const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4256
- lines.push(`${indent} <dynamics${placementAttr}>`);
4257
- for (const dyn of notation.dynamics) {
4258
- lines.push(`${indent} <${dyn}/>`);
4259
- }
4260
- if (notation.otherDynamics) {
4261
- lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4262
- }
4263
- lines.push(`${indent} </dynamics>`);
4264
- } else if (notation.type === "fermata") {
4265
- let attrs = "";
4266
- if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4267
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4268
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4269
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4270
- if (notation.shape) {
4271
- lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4272
- } else {
4273
- lines.push(`${indent} <fermata${attrs}/>`);
4274
- }
4275
- } else if (notation.type === "arpeggiate") {
4276
- let attrs = "";
4277
- if (notation.direction) attrs += ` direction="${notation.direction}"`;
4278
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4279
- lines.push(`${indent} <arpeggiate${attrs}/>`);
4280
- } else if (notation.type === "glissando") {
4281
- let attrs = ` type="${notation.glissandoType}"`;
4282
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4283
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4284
- if (notation.text) {
4285
- lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4286
- } else {
4287
- lines.push(`${indent} <glissando${attrs}/>`);
4483
+ lines.push(`${indent} </tuplet-normal>`);
4288
4484
  }
4289
- } else if (notation.type === "slide") {
4290
- let attrs = ` type="${notation.slideType}"`;
4291
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4292
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4485
+ lines.push(`${indent} </tuplet>`);
4486
+ } else {
4487
+ lines.push(`${indent} <tuplet${attrs}/>`);
4488
+ }
4489
+ } else if (notation.type === "dynamics") {
4490
+ const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4491
+ lines.push(`${indent} <dynamics${placementAttr}>`);
4492
+ for (const dyn of notation.dynamics) {
4493
+ lines.push(`${indent} <${dyn}/>`);
4494
+ }
4495
+ if (notation.otherDynamics) {
4496
+ lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4497
+ }
4498
+ lines.push(`${indent} </dynamics>`);
4499
+ } else if (notation.type === "fermata") {
4500
+ let attrs = "";
4501
+ if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4502
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4503
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4504
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4505
+ if (notation.shape) {
4506
+ lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4507
+ } else {
4508
+ lines.push(`${indent} <fermata${attrs}/>`);
4509
+ }
4510
+ } else if (notation.type === "arpeggiate") {
4511
+ let attrs = "";
4512
+ if (notation.direction) attrs += ` direction="${notation.direction}"`;
4513
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4514
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4515
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4516
+ lines.push(`${indent} <arpeggiate${attrs}/>`);
4517
+ } else if (notation.type === "non-arpeggiate") {
4518
+ let attrs = ` type="${notation.nonArpeggiateType}"`;
4519
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4520
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4521
+ lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4522
+ } else if (notation.type === "accidental-mark") {
4523
+ let attrs = "";
4524
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4525
+ lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4526
+ } else if (notation.type === "glissando") {
4527
+ let attrs = ` type="${notation.glissandoType}"`;
4528
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4529
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4530
+ if (notation.text) {
4531
+ lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4532
+ } else {
4533
+ lines.push(`${indent} <glissando${attrs}/>`);
4534
+ }
4535
+ } else if (notation.type === "slide") {
4536
+ let attrs = ` type="${notation.slideType}"`;
4537
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4538
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4539
+ if (notation.text) {
4540
+ lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4541
+ } else {
4293
4542
  lines.push(`${indent} <slide${attrs}/>`);
4294
4543
  }
4295
4544
  }
4296
- const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
4297
- for (const artIdx of sortedArtIndices) {
4298
- const artGroup = articulationsGroups.get(artIdx);
4299
- lines.push(`${indent} <articulations>`);
4300
- for (const art of artGroup) {
4301
- if (art.type === "articulation") {
4302
- let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4303
- if (art.articulation === "strong-accent" && art.strongAccentType) {
4304
- artAttrs += ` type="${art.strongAccentType}"`;
4305
- }
4306
- if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4307
- if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4308
- lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4545
+ return lines;
4546
+ }
4547
+ function serializeArticulationsGroup(artGroup, indent) {
4548
+ const lines = [];
4549
+ lines.push(`${indent} <articulations>`);
4550
+ for (const art of artGroup) {
4551
+ if (art.type === "articulation") {
4552
+ let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4553
+ if (art.articulation === "strong-accent" && art.strongAccentType) {
4554
+ artAttrs += ` type="${art.strongAccentType}"`;
4309
4555
  }
4556
+ if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4557
+ if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4558
+ lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4310
4559
  }
4311
- lines.push(`${indent} </articulations>`);
4312
4560
  }
4313
- if (ornaments.length > 0) {
4561
+ lines.push(`${indent} </articulations>`);
4562
+ return lines;
4563
+ }
4564
+ function serializeOrnamentsGroup(ornaments, indent) {
4565
+ const lines = [];
4566
+ const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4567
+ if (hasOnlyEmptyMarker) {
4568
+ lines.push(`${indent} <ornaments/>`);
4569
+ } else {
4314
4570
  lines.push(`${indent} <ornaments>`);
4315
4571
  const allAccidentalMarks = [];
4316
4572
  for (const orn of ornaments) {
4317
4573
  if (orn.type === "ornament") {
4574
+ if (orn.ornament === "empty") continue;
4318
4575
  const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4319
4576
  if (orn.ornament === "wavy-line") {
4320
4577
  let wlAttrs = "";
@@ -4350,77 +4607,80 @@ function serializeNotationsGroup(notations, indent) {
4350
4607
  }
4351
4608
  lines.push(`${indent} </ornaments>`);
4352
4609
  }
4353
- if (technicals.length > 0) {
4354
- lines.push(`${indent} <technical>`);
4355
- for (const tech of technicals) {
4356
- if (tech.type === "technical") {
4357
- let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4358
- const techNotation = tech;
4359
- if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4360
- if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4361
- if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4362
- lines.push(`${indent} <bend${placementAttr}>`);
4363
- if (techNotation.bendAlter !== void 0) {
4364
- lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4365
- }
4366
- if (techNotation.preBend) {
4367
- lines.push(`${indent} <pre-bend/>`);
4368
- }
4369
- if (techNotation.release) {
4370
- lines.push(`${indent} <release/>`);
4371
- }
4372
- if (techNotation.withBar !== void 0) {
4373
- lines.push(`${indent} <with-bar>${techNotation.withBar}</with-bar>`);
4374
- }
4375
- lines.push(`${indent} </bend>`);
4376
- } else if (tech.technical === "harmonic") {
4377
- const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4378
- if (hasChildren) {
4379
- lines.push(`${indent} <harmonic${placementAttr}>`);
4380
- if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4381
- if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4382
- if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4383
- if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4384
- if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4385
- lines.push(`${indent} </harmonic>`);
4386
- } else {
4387
- lines.push(`${indent} <harmonic${placementAttr}/>`);
4388
- }
4389
- } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4390
- let attrs = placementAttr;
4391
- if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4392
- if (techNotation.text !== void 0) {
4393
- lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4394
- } else {
4395
- lines.push(`${indent} <${tech.technical}${attrs}/>`);
4396
- }
4397
- } else if (tech.technical === "string" && techNotation.string !== void 0) {
4398
- lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4399
- } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4400
- lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4401
- } else if (tech.technical === "fingering") {
4402
- let fAttrs = placementAttr;
4403
- if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4404
- if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4405
- if (techNotation.text !== void 0) {
4406
- lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4407
- } else {
4408
- lines.push(`${indent} <fingering${fAttrs}/>`);
4409
- }
4410
- } else if (tech.technical === "heel" || tech.technical === "toe") {
4411
- let htAttrs = placementAttr;
4412
- if (techNotation.substitution) htAttrs += ' substitution="yes"';
4413
- lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4414
- } else if (techNotation.text !== void 0) {
4415
- lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4610
+ return lines;
4611
+ }
4612
+ function serializeTechnicalGroup(technicals, indent) {
4613
+ const lines = [];
4614
+ lines.push(`${indent} <technical>`);
4615
+ for (const tech of technicals) {
4616
+ if (tech.type === "technical") {
4617
+ let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4618
+ const techNotation = tech;
4619
+ if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4620
+ if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4621
+ if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4622
+ lines.push(`${indent} <bend${placementAttr}>`);
4623
+ if (techNotation.bendAlter !== void 0) {
4624
+ lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4625
+ }
4626
+ if (techNotation.preBend) {
4627
+ lines.push(`${indent} <pre-bend/>`);
4628
+ }
4629
+ if (techNotation.release) {
4630
+ lines.push(`${indent} <release/>`);
4631
+ }
4632
+ if (techNotation.withBar) {
4633
+ lines.push(`${indent} <with-bar/>`);
4634
+ }
4635
+ lines.push(`${indent} </bend>`);
4636
+ } else if (tech.technical === "harmonic") {
4637
+ const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4638
+ if (hasChildren) {
4639
+ lines.push(`${indent} <harmonic${placementAttr}>`);
4640
+ if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4641
+ if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4642
+ if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4643
+ if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4644
+ if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4645
+ lines.push(`${indent} </harmonic>`);
4416
4646
  } else {
4417
- lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4418
- }
4647
+ lines.push(`${indent} <harmonic${placementAttr}/>`);
4648
+ }
4649
+ } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4650
+ let attrs = "";
4651
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4652
+ if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4653
+ attrs += placementAttr;
4654
+ if (techNotation.text !== void 0) {
4655
+ lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4656
+ } else {
4657
+ lines.push(`${indent} <${tech.technical}${attrs}/>`);
4658
+ }
4659
+ } else if (tech.technical === "string" && techNotation.string !== void 0) {
4660
+ lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4661
+ } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4662
+ lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4663
+ } else if (tech.technical === "fingering") {
4664
+ let fAttrs = placementAttr;
4665
+ if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4666
+ if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4667
+ if (techNotation.text !== void 0) {
4668
+ lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4669
+ } else {
4670
+ lines.push(`${indent} <fingering${fAttrs}/>`);
4671
+ }
4672
+ } else if (tech.technical === "heel" || tech.technical === "toe") {
4673
+ let htAttrs = placementAttr;
4674
+ if (techNotation.substitution) htAttrs += ' substitution="yes"';
4675
+ lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4676
+ } else if (techNotation.text !== void 0) {
4677
+ lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4678
+ } else {
4679
+ lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4419
4680
  }
4420
4681
  }
4421
- lines.push(`${indent} </technical>`);
4422
4682
  }
4423
- lines.push(`${indent}</notations>`);
4683
+ lines.push(`${indent} </technical>`);
4424
4684
  return lines;
4425
4685
  }
4426
4686
  function serializeLyric(lyric, indent) {
@@ -4444,7 +4704,7 @@ function serializeLyric(lyric, indent) {
4444
4704
  lines.push(`${indent} <elision/>`);
4445
4705
  }
4446
4706
  }
4447
- } else {
4707
+ } else if (lyric.syllabic || lyric.text) {
4448
4708
  if (lyric.syllabic) {
4449
4709
  lines.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
4450
4710
  }
@@ -4559,7 +4819,12 @@ function serializeDirectionType(dirType, indent) {
4559
4819
  if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
4560
4820
  if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
4561
4821
  lines.push(`${indent} <dynamics${dynAttrs}>`);
4562
- lines.push(`${indent} <${dirType.value}/>`);
4822
+ if (dirType.value) {
4823
+ lines.push(`${indent} <${dirType.value}/>`);
4824
+ }
4825
+ if (dirType.otherDynamics) {
4826
+ lines.push(`${indent} <other-dynamics>${escapeXml(dirType.otherDynamics)}</other-dynamics>`);
4827
+ }
4563
4828
  lines.push(`${indent} </dynamics>`);
4564
4829
  break;
4565
4830
  }
@@ -4646,8 +4911,12 @@ function serializeDirectionType(dirType, indent) {
4646
4911
  if (dirType.high) {
4647
4912
  lines.push(`${indent} <accordion-high/>`);
4648
4913
  }
4649
- if (dirType.middle !== void 0) {
4650
- lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
4914
+ if (dirType.middlePresent || dirType.middle !== void 0) {
4915
+ if (dirType.middle !== void 0) {
4916
+ lines.push(`${indent} <accordion-middle>${dirType.middle}</accordion-middle>`);
4917
+ } else {
4918
+ lines.push(`${indent} <accordion-middle/>`);
4919
+ }
4651
4920
  }
4652
4921
  if (dirType.low) {
4653
4922
  lines.push(`${indent} <accordion-low/>`);
@@ -4760,11 +5029,20 @@ function serializeBarline(barline, indent) {
4760
5029
  lines.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
4761
5030
  }
4762
5031
  if (barline.ending) {
4763
- lines.push(`${indent} <ending number="${barline.ending.number}" type="${barline.ending.type}"/>`);
5032
+ let endingAttrs = ` number="${barline.ending.number}" type="${barline.ending.type}"`;
5033
+ if (barline.ending.defaultY !== void 0) endingAttrs += ` default-y="${barline.ending.defaultY}"`;
5034
+ if (barline.ending.endLength !== void 0) endingAttrs += ` end-length="${barline.ending.endLength}"`;
5035
+ if (barline.ending.text) {
5036
+ lines.push(`${indent} <ending${endingAttrs}>${escapeXml(barline.ending.text)}</ending>`);
5037
+ } else {
5038
+ lines.push(`${indent} <ending${endingAttrs}/>`);
5039
+ }
4764
5040
  }
4765
5041
  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}/>`);
5042
+ let repeatAttrs = ` direction="${barline.repeat.direction}"`;
5043
+ if (barline.repeat.times !== void 0) repeatAttrs += ` times="${barline.repeat.times}"`;
5044
+ if (barline.repeat.winged) repeatAttrs += ` winged="${barline.repeat.winged}"`;
5045
+ lines.push(`${indent} <repeat${repeatAttrs}/>`);
4768
5046
  }
4769
5047
  lines.push(`${indent}</barline>`);
4770
5048
  return lines;
@@ -4797,7 +5075,8 @@ function serializeStaffDetails(sd, indent) {
4797
5075
  const attrs = buildAttrs({
4798
5076
  "number": sd.number,
4799
5077
  "show-frets": sd.showFrets,
4800
- "print-object": sd.printObject
5078
+ "print-object": sd.printObject,
5079
+ "print-spacing": sd.printSpacing
4801
5080
  });
4802
5081
  lines.push(`${indent}<staff-details${attrs}>`);
4803
5082
  pushOptionalElement(lines, `${indent} `, "staff-type", sd.staffType);
@@ -4863,16 +5142,22 @@ function serializeHarmony(harmony, indent) {
4863
5142
  }
4864
5143
  lines.push(`${indent} </root>`);
4865
5144
  let kindAttrs = "";
4866
- if (harmony.kindText) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5145
+ if (harmony.kindText !== void 0) kindAttrs += ` text="${escapeXml(harmony.kindText)}"`;
5146
+ if (harmony.kindHalign) kindAttrs += ` halign="${escapeXml(harmony.kindHalign)}"`;
4867
5147
  lines.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
4868
5148
  if (harmony.bass) {
4869
- lines.push(`${indent} <bass>`);
5149
+ let bassAttrs = "";
5150
+ if (harmony.bass.arrangement) bassAttrs += ` arrangement="${escapeXml(harmony.bass.arrangement)}"`;
5151
+ lines.push(`${indent} <bass${bassAttrs}>`);
4870
5152
  lines.push(`${indent} <bass-step>${harmony.bass.bassStep}</bass-step>`);
4871
5153
  if (harmony.bass.bassAlter !== void 0) {
4872
5154
  lines.push(`${indent} <bass-alter>${harmony.bass.bassAlter}</bass-alter>`);
4873
5155
  }
4874
5156
  lines.push(`${indent} </bass>`);
4875
5157
  }
5158
+ if (harmony.inversion !== void 0) {
5159
+ lines.push(`${indent} <inversion>${harmony.inversion}</inversion>`);
5160
+ }
4876
5161
  if (harmony.degrees) {
4877
5162
  for (const deg of harmony.degrees) {
4878
5163
  lines.push(`${indent} <degree>`);
@@ -5558,7 +5843,7 @@ function groupByVoice(measure) {
5558
5843
  for (const entry of measure.entries) {
5559
5844
  if (entry.type !== "note") continue;
5560
5845
  const staff = entry.staff ?? 1;
5561
- const voice = entry.voice;
5846
+ const voice = entry.voice ?? 1;
5562
5847
  const key = `${staff}-${voice}`;
5563
5848
  if (!groups.has(key)) {
5564
5849
  groups.set(key, { staff, voice, notes: [] });
@@ -5652,7 +5937,7 @@ function getVoices(measure) {
5652
5937
  const voices = /* @__PURE__ */ new Set();
5653
5938
  for (const entry of measure.entries) {
5654
5939
  if (entry.type === "note") {
5655
- voices.add(entry.voice);
5940
+ voices.add(entry.voice ?? 1);
5656
5941
  }
5657
5942
  }
5658
5943
  return Array.from(voices).sort((a, b) => a - b);
@@ -5703,7 +5988,7 @@ function buildVoiceToStaffMap(measure) {
5703
5988
  const map = /* @__PURE__ */ new Map();
5704
5989
  for (const entry of measure.entries) {
5705
5990
  if (entry.type === "note" && entry.staff !== void 0) {
5706
- const voice = entry.voice;
5991
+ const voice = entry.voice ?? 1;
5707
5992
  const staff = entry.staff;
5708
5993
  if (!map.has(voice)) {
5709
5994
  map.set(voice, staff);
@@ -5722,7 +6007,7 @@ function buildVoiceToStaffMapForPart(part) {
5722
6007
  for (const measure of part.measures) {
5723
6008
  for (const entry of measure.entries) {
5724
6009
  if (entry.type === "note" && entry.staff !== void 0) {
5725
- const voice = entry.voice;
6010
+ const voice = entry.voice ?? 1;
5726
6011
  const staff = entry.staff;
5727
6012
  if (!map.has(voice)) {
5728
6013
  map.set(voice, staff);
@@ -5741,7 +6026,7 @@ function inferStaff(entry, voiceToStaffMap) {
5741
6026
  if (entry.staff !== void 0) {
5742
6027
  return entry.staff;
5743
6028
  }
5744
- const inferredStaff = voiceToStaffMap.get(entry.voice);
6029
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
5745
6030
  if (inferredStaff !== void 0) {
5746
6031
  return inferredStaff;
5747
6032
  }
@@ -5784,7 +6069,7 @@ function getVoicesForStaff(measure, staff) {
5784
6069
  if (entry.type === "note") {
5785
6070
  const entryStaff = entry.staff ?? 1;
5786
6071
  if (entryStaff === staff) {
5787
- voices.add(entry.voice);
6072
+ voices.add(entry.voice ?? 1);
5788
6073
  }
5789
6074
  }
5790
6075
  }
@@ -6120,6 +6405,7 @@ function getDynamics(score, options) {
6120
6405
  if (dirType.kind === "dynamics") {
6121
6406
  results.push({
6122
6407
  dynamic: dirType.value,
6408
+ otherDynamics: dirType.otherDynamics,
6123
6409
  direction: entry,
6124
6410
  part,
6125
6411
  partIndex,
@@ -7194,14 +7480,17 @@ function hasNotesInRange(voiceEntries, startPos, endPos) {
7194
7480
  return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
7195
7481
  }
7196
7482
  function createRest(duration, voice, staff) {
7197
- return {
7483
+ const note = {
7198
7484
  _id: generateId(),
7199
7485
  type: "note",
7200
7486
  rest: { displayStep: void 0, displayOctave: void 0 },
7201
7487
  duration,
7202
- voice,
7203
7488
  staff
7204
7489
  };
7490
+ if (voice !== void 0) {
7491
+ note.voice = voice;
7492
+ }
7493
+ return note;
7205
7494
  }
7206
7495
  function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
7207
7496
  const otherEntries = [];
@@ -8755,7 +9044,7 @@ function autoBeam(score, options) {
8755
9044
  for (const entry of measure.entries) {
8756
9045
  if (entry.type === "note") {
8757
9046
  if (!entry.chord && !entry.rest) {
8758
- const voice = entry.voice;
9047
+ const voice = entry.voice ?? 1;
8759
9048
  if (options.voice === void 0 || voice === options.voice) {
8760
9049
  if (!notesByVoice.has(voice)) {
8761
9050
  notesByVoice.set(voice, []);