musicxml-io 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
  }
@@ -1426,8 +1541,8 @@ function parseDirection(elements, attrs) {
1426
1541
  });
1427
1542
  for (const el of elements) {
1428
1543
  if (el["direction-type"]) {
1429
- const parsed = parseDirectionType(el["direction-type"]);
1430
- if (parsed) {
1544
+ const parsedTypes = parseDirectionTypes(el["direction-type"]);
1545
+ for (const parsed of parsedTypes) {
1431
1546
  direction.directionTypes.push(parsed);
1432
1547
  }
1433
1548
  }
@@ -1464,7 +1579,8 @@ function parseDirection(elements, attrs) {
1464
1579
  }
1465
1580
  return direction;
1466
1581
  }
1467
- function parseDirectionType(elements) {
1582
+ function parseDirectionTypes(elements) {
1583
+ const results = [];
1468
1584
  for (const el of elements) {
1469
1585
  if (el["dynamics"]) {
1470
1586
  const dynAttrs = getAttributes(el);
@@ -1497,6 +1613,7 @@ function parseDirectionType(elements) {
1497
1613
  "pf"
1498
1614
  ];
1499
1615
  for (const dyn of dynContent) {
1616
+ let foundStandard = false;
1500
1617
  for (const dv of dynamicsValues) {
1501
1618
  if (dyn[dv] !== void 0) {
1502
1619
  const result = { kind: "dynamics", value: dv };
@@ -1504,10 +1621,25 @@ function parseDirectionType(elements) {
1504
1621
  if (dynAttrs["default-y"]) result.defaultY = parseFloat(dynAttrs["default-y"]);
1505
1622
  if (dynAttrs["relative-x"]) result.relativeX = parseFloat(dynAttrs["relative-x"]);
1506
1623
  if (dynAttrs["halign"]) result.halign = dynAttrs["halign"];
1507
- return result;
1624
+ results.push(result);
1625
+ foundStandard = true;
1626
+ break;
1627
+ }
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);
1508
1639
  }
1509
1640
  }
1510
1641
  }
1642
+ continue;
1511
1643
  }
1512
1644
  if (el["wedge"]) {
1513
1645
  const wedgeAttrs = getAttributes(el);
@@ -1517,8 +1649,9 @@ function parseDirectionType(elements) {
1517
1649
  if (wedgeAttrs["spread"]) result.spread = parseFloat(wedgeAttrs["spread"]);
1518
1650
  if (wedgeAttrs["default-y"]) result.defaultY = parseFloat(wedgeAttrs["default-y"]);
1519
1651
  if (wedgeAttrs["relative-x"]) result.relativeX = parseFloat(wedgeAttrs["relative-x"]);
1520
- return result;
1652
+ results.push(result);
1521
1653
  }
1654
+ continue;
1522
1655
  }
1523
1656
  if (el["metronome"]) {
1524
1657
  const metAttrs = getAttributes(el);
@@ -1558,28 +1691,29 @@ function parseDirectionType(elements) {
1558
1691
  if (metAttrs["default-y"]) result.defaultY = parseFloat(metAttrs["default-y"]);
1559
1692
  if (metAttrs["font-family"]) result.fontFamily = metAttrs["font-family"];
1560
1693
  if (metAttrs["font-size"]) result.fontSize = metAttrs["font-size"];
1561
- return result;
1694
+ results.push(result);
1562
1695
  }
1696
+ continue;
1563
1697
  }
1564
1698
  if (el["words"]) {
1565
1699
  const a = getAttributes(el);
1566
1700
  const text = extractText(el["words"]);
1567
- if (text) {
1568
- const result = { kind: "words", text };
1569
- if (a["default-x"]) result.defaultX = parseFloat(a["default-x"]);
1570
- if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
1571
- if (a["relative-x"]) result.relativeX = parseFloat(a["relative-x"]);
1572
- if (a["font-family"]) result.fontFamily = a["font-family"];
1573
- if (a["font-size"]) result.fontSize = a["font-size"];
1574
- if (a["font-style"]) result.fontStyle = a["font-style"];
1575
- if (a["font-weight"]) result.fontWeight = a["font-weight"];
1576
- if (a["xml:lang"]) result.xmlLang = a["xml:lang"];
1577
- if (a["justify"]) result.justify = a["justify"];
1578
- if (a["color"]) result.color = a["color"];
1579
- if (a["xml:space"]) result.xmlSpace = a["xml:space"];
1580
- if (a["halign"]) result.halign = a["halign"];
1581
- return result;
1582
- }
1701
+ const result = { kind: "words", text: text || "" };
1702
+ if (a["default-x"]) result.defaultX = parseFloat(a["default-x"]);
1703
+ if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
1704
+ if (a["relative-x"]) result.relativeX = parseFloat(a["relative-x"]);
1705
+ if (a["relative-y"]) result.relativeY = parseFloat(a["relative-y"]);
1706
+ if (a["font-family"]) result.fontFamily = a["font-family"];
1707
+ if (a["font-size"]) result.fontSize = a["font-size"];
1708
+ if (a["font-style"]) result.fontStyle = a["font-style"];
1709
+ if (a["font-weight"]) result.fontWeight = a["font-weight"];
1710
+ if (a["xml:lang"]) result.xmlLang = a["xml:lang"];
1711
+ if (a["justify"]) result.justify = a["justify"];
1712
+ if (a["color"]) result.color = a["color"];
1713
+ if (a["xml:space"]) result.xmlSpace = a["xml:space"];
1714
+ if (a["halign"]) result.halign = a["halign"];
1715
+ results.push(result);
1716
+ continue;
1583
1717
  }
1584
1718
  if (el["rehearsal"]) {
1585
1719
  const a = getAttributes(el);
@@ -1591,8 +1725,9 @@ function parseDirectionType(elements) {
1591
1725
  if (a["default-y"]) result.defaultY = parseFloat(a["default-y"]);
1592
1726
  if (a["font-size"]) result.fontSize = a["font-size"];
1593
1727
  if (a["font-weight"]) result.fontWeight = a["font-weight"];
1594
- return result;
1728
+ results.push(result);
1595
1729
  }
1730
+ continue;
1596
1731
  }
1597
1732
  if (el["bracket"]) {
1598
1733
  const bracketAttrs = getAttributes(el);
@@ -1604,8 +1739,9 @@ function parseDirectionType(elements) {
1604
1739
  if (bracketAttrs["line-type"]) result.lineType = bracketAttrs["line-type"];
1605
1740
  if (bracketAttrs["default-y"]) result.defaultY = parseFloat(bracketAttrs["default-y"]);
1606
1741
  if (bracketAttrs["relative-x"]) result.relativeX = parseFloat(bracketAttrs["relative-x"]);
1607
- return result;
1742
+ results.push(result);
1608
1743
  }
1744
+ continue;
1609
1745
  }
1610
1746
  if (el["dashes"]) {
1611
1747
  const dashAttrs = getAttributes(el);
@@ -1616,8 +1752,9 @@ function parseDirectionType(elements) {
1616
1752
  if (dashAttrs["dash-length"]) result.dashLength = parseFloat(dashAttrs["dash-length"]);
1617
1753
  if (dashAttrs["default-y"]) result.defaultY = parseFloat(dashAttrs["default-y"]);
1618
1754
  if (dashAttrs["space-length"]) result.spaceLength = parseFloat(dashAttrs["space-length"]);
1619
- return result;
1755
+ results.push(result);
1620
1756
  }
1757
+ continue;
1621
1758
  }
1622
1759
  if (el["accordion-registration"]) {
1623
1760
  const accContent = el["accordion-registration"];
@@ -1625,11 +1762,14 @@ function parseDirectionType(elements) {
1625
1762
  for (const acc of accContent) {
1626
1763
  if (acc["accordion-high"] !== void 0) {
1627
1764
  result.high = true;
1628
- } else if (acc["accordion-middle"]) {
1765
+ } else if (acc["accordion-middle"] !== void 0) {
1766
+ result.middlePresent = true;
1629
1767
  const midContent = acc["accordion-middle"];
1630
1768
  for (const item of midContent) {
1631
1769
  if (item["#text"] !== void 0) {
1632
- 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;
1633
1773
  break;
1634
1774
  }
1635
1775
  }
@@ -1637,7 +1777,8 @@ function parseDirectionType(elements) {
1637
1777
  result.low = true;
1638
1778
  }
1639
1779
  }
1640
- return result;
1780
+ results.push(result);
1781
+ continue;
1641
1782
  }
1642
1783
  if (el["other-direction"]) {
1643
1784
  const otherAttrs = getAttributes(el);
@@ -1649,24 +1790,31 @@ function parseDirectionType(elements) {
1649
1790
  if (otherAttrs["default-y"]) result.defaultY = parseFloat(otherAttrs["default-y"]);
1650
1791
  if (otherAttrs["halign"]) result.halign = otherAttrs["halign"];
1651
1792
  if (otherAttrs["print-object"] === "no") result.printObject = false;
1652
- return result;
1793
+ results.push(result);
1794
+ break;
1653
1795
  }
1654
1796
  }
1797
+ continue;
1655
1798
  }
1656
1799
  if (el["segno"] !== void 0) {
1657
- return { kind: "segno" };
1800
+ results.push({ kind: "segno" });
1801
+ continue;
1658
1802
  }
1659
1803
  if (el["coda"] !== void 0) {
1660
- return { kind: "coda" };
1804
+ results.push({ kind: "coda" });
1805
+ continue;
1661
1806
  }
1662
1807
  if (el["eyeglasses"] !== void 0) {
1663
- return { kind: "eyeglasses" };
1808
+ results.push({ kind: "eyeglasses" });
1809
+ continue;
1664
1810
  }
1665
1811
  if (el["damp"] !== void 0) {
1666
- return { kind: "damp" };
1812
+ results.push({ kind: "damp" });
1813
+ continue;
1667
1814
  }
1668
1815
  if (el["damp-all"] !== void 0) {
1669
- return { kind: "damp-all" };
1816
+ results.push({ kind: "damp-all" });
1817
+ continue;
1670
1818
  }
1671
1819
  if (el["scordatura"] !== void 0) {
1672
1820
  const scordContent = el["scordatura"];
@@ -1689,7 +1837,8 @@ function parseDirectionType(elements) {
1689
1837
  }
1690
1838
  }
1691
1839
  }
1692
- return { kind: "scordatura", accords: accords.length > 0 ? accords : void 0 };
1840
+ results.push({ kind: "scordatura", accords: accords.length > 0 ? accords : void 0 });
1841
+ continue;
1693
1842
  }
1694
1843
  if (el["harp-pedals"] !== void 0) {
1695
1844
  const harpContent = el["harp-pedals"];
@@ -1707,15 +1856,17 @@ function parseDirectionType(elements) {
1707
1856
  }
1708
1857
  }
1709
1858
  }
1710
- return { kind: "harp-pedals", pedalTunings: pedalTunings.length > 0 ? pedalTunings : void 0 };
1859
+ results.push({ kind: "harp-pedals", pedalTunings: pedalTunings.length > 0 ? pedalTunings : void 0 });
1860
+ continue;
1711
1861
  }
1712
1862
  if (el["image"] !== void 0) {
1713
1863
  const imgAttrs = getAttributes(el);
1714
- return {
1864
+ results.push({
1715
1865
  kind: "image",
1716
1866
  source: imgAttrs["source"],
1717
1867
  type: imgAttrs["type"]
1718
- };
1868
+ });
1869
+ continue;
1719
1870
  }
1720
1871
  if (el["pedal"]) {
1721
1872
  const pedalAttrs = getAttributes(el);
@@ -1727,8 +1878,9 @@ function parseDirectionType(elements) {
1727
1878
  if (pedalAttrs["default-y"]) result.defaultY = parseFloat(pedalAttrs["default-y"]);
1728
1879
  if (pedalAttrs["relative-x"]) result.relativeX = parseFloat(pedalAttrs["relative-x"]);
1729
1880
  if (pedalAttrs["halign"]) result.halign = pedalAttrs["halign"];
1730
- return result;
1881
+ results.push(result);
1731
1882
  }
1883
+ continue;
1732
1884
  }
1733
1885
  if (el["octave-shift"]) {
1734
1886
  const shiftAttrs = getAttributes(el);
@@ -1736,8 +1888,9 @@ function parseDirectionType(elements) {
1736
1888
  if (shiftType === "up" || shiftType === "down" || shiftType === "stop") {
1737
1889
  const result = { kind: "octave-shift", type: shiftType };
1738
1890
  if (shiftAttrs["size"]) result.size = parseInt(shiftAttrs["size"], 10);
1739
- return result;
1891
+ results.push(result);
1740
1892
  }
1893
+ continue;
1741
1894
  }
1742
1895
  if (el["swing"]) {
1743
1896
  const swingContent = el["swing"];
@@ -1771,10 +1924,11 @@ function parseDirectionType(elements) {
1771
1924
  }
1772
1925
  }
1773
1926
  }
1774
- return result;
1927
+ results.push(result);
1928
+ continue;
1775
1929
  }
1776
1930
  }
1777
- return null;
1931
+ return results;
1778
1932
  }
1779
1933
  function parseBarline(elements, attrs) {
1780
1934
  const location = attrs["location"] || "right";
@@ -1792,6 +1946,9 @@ function parseBarline(elements, attrs) {
1792
1946
  if (repeatAttrs["times"]) {
1793
1947
  barline.repeat.times = parseInt(repeatAttrs["times"], 10);
1794
1948
  }
1949
+ if (repeatAttrs["winged"]) {
1950
+ barline.repeat.winged = repeatAttrs["winged"];
1951
+ }
1795
1952
  }
1796
1953
  } else if (el["ending"]) {
1797
1954
  const endingAttrs = getAttributes(el);
@@ -1799,6 +1956,11 @@ function parseBarline(elements, attrs) {
1799
1956
  const type = endingAttrs["type"];
1800
1957
  if (number && (type === "start" || type === "stop" || type === "discontinue")) {
1801
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"]);
1802
1964
  }
1803
1965
  }
1804
1966
  }
@@ -1924,6 +2086,8 @@ function parseStaffDetails(elements, attrs) {
1924
2086
  if (attrs["number"]) sd.number = parseInt(attrs["number"], 10);
1925
2087
  if (attrs["print-object"] === "no") sd.printObject = false;
1926
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;
1927
2091
  const staffType = getElementText(elements, "staff-type");
1928
2092
  if (staffType && ["ossia", "cue", "editorial", "regular", "alternate"].includes(staffType)) {
1929
2093
  sd.staffType = staffType;
@@ -1987,7 +2151,9 @@ function parseMeasureStyle(elements, attrs) {
1987
2151
  const slAttrs = getAttributes(el);
1988
2152
  ms.slash = { type: slAttrs["type"] === "stop" ? "stop" : "start" };
1989
2153
  if (slAttrs["use-dots"] === "yes") ms.slash.useDots = true;
2154
+ else if (slAttrs["use-dots"] === "no") ms.slash.useDots = false;
1990
2155
  if (slAttrs["use-stems"] === "yes") ms.slash.useStems = true;
2156
+ else if (slAttrs["use-stems"] === "no") ms.slash.useStems = false;
1991
2157
  }
1992
2158
  }
1993
2159
  return ms;
@@ -2027,19 +2193,29 @@ function parseHarmony(elements, attrs) {
2027
2193
  break;
2028
2194
  }
2029
2195
  }
2030
- 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"];
2031
2198
  break;
2032
2199
  }
2033
2200
  }
2034
- const bass = getElementContent(elements, "bass");
2035
- if (bass) {
2036
- const bassStep = getElementText(bass, "bass-step");
2037
- if (bassStep) {
2038
- harmony.bass = { bassStep };
2039
- const bassAlter = getElementText(bass, "bass-alter");
2040
- 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;
2041
2213
  }
2042
2214
  }
2215
+ const inversionText = getElementText(elements, "inversion");
2216
+ if (inversionText) {
2217
+ harmony.inversion = parseInt(inversionText, 10);
2218
+ }
2043
2219
  const degrees = [];
2044
2220
  for (const el of elements) {
2045
2221
  if (el["degree"]) {
@@ -2655,7 +2831,7 @@ function validateBeams(measure, location) {
2655
2831
  const entry = measure.entries[entryIndex];
2656
2832
  if (entry.type !== "note" || !entry.beam) continue;
2657
2833
  for (const beam of entry.beam) {
2658
- const beamKey = `${beam.number}-${entry.voice}-${entry.staff ?? 1}`;
2834
+ const beamKey = `${beam.number}-${entry.voice}`;
2659
2835
  if (beam.type === "begin") {
2660
2836
  if (openBeams.has(beamKey)) {
2661
2837
  errors.push({
@@ -2666,7 +2842,7 @@ function validateBeams(measure, location) {
2666
2842
  details: { beamNumber: beam.number }
2667
2843
  });
2668
2844
  }
2669
- openBeams.set(beamKey, entryIndex);
2845
+ openBeams.set(beamKey, { entryIndex, staff: entry.staff ?? 1 });
2670
2846
  } else if (beam.type === "end") {
2671
2847
  if (!openBeams.has(beamKey)) {
2672
2848
  errors.push({
@@ -2682,8 +2858,8 @@ function validateBeams(measure, location) {
2682
2858
  }
2683
2859
  }
2684
2860
  }
2685
- for (const [beamKey, startIndex] of openBeams.entries()) {
2686
- const [beamNumber, voice, staff] = beamKey.split("-").map(Number);
2861
+ for (const [beamKey, { entryIndex: startIndex, staff }] of openBeams.entries()) {
2862
+ const [beamNumber, voice] = beamKey.split("-").map(Number);
2687
2863
  errors.push({
2688
2864
  code: "BEAM_BEGIN_WITHOUT_END",
2689
2865
  level: "error",
@@ -2806,7 +2982,7 @@ function validateVoiceStaff(measure, staves, location) {
2806
2982
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
2807
2983
  const entry = measure.entries[entryIndex];
2808
2984
  if (entry.type !== "note") continue;
2809
- if (entry.voice <= 0) {
2985
+ if (entry.voice !== void 0 && entry.voice <= 0) {
2810
2986
  errors.push({
2811
2987
  code: "INVALID_VOICE_NUMBER",
2812
2988
  level: "error",
@@ -3455,6 +3631,11 @@ function serializeDefaults(defaults, indent) {
3455
3631
  lines.push(`${indent}${indent}${indent}<distance type="${escapeXml(d.type)}">${d.value}</distance>`);
3456
3632
  }
3457
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
+ }
3458
3639
  lines.push(`${indent}${indent}</appearance>`);
3459
3640
  }
3460
3641
  if (defaults.musicFont) {
@@ -3511,16 +3692,16 @@ function serializePageLayout(layout, indent) {
3511
3692
  const typeAttr = m.type ? ` type="${m.type}"` : "";
3512
3693
  lines.push(`${indent} <page-margins${typeAttr}>`);
3513
3694
  if (m.leftMargin !== void 0) {
3514
- lines.push(`${indent} <left-margin>${m.leftMargin}</left-margin>`);
3695
+ lines.push(`${indent} <left-margin>${m.leftMarginRaw ?? m.leftMargin}</left-margin>`);
3515
3696
  }
3516
3697
  if (m.rightMargin !== void 0) {
3517
- lines.push(`${indent} <right-margin>${m.rightMargin}</right-margin>`);
3698
+ lines.push(`${indent} <right-margin>${m.rightMarginRaw ?? m.rightMargin}</right-margin>`);
3518
3699
  }
3519
3700
  if (m.topMargin !== void 0) {
3520
- lines.push(`${indent} <top-margin>${m.topMargin}</top-margin>`);
3701
+ lines.push(`${indent} <top-margin>${m.topMarginRaw ?? m.topMargin}</top-margin>`);
3521
3702
  }
3522
3703
  if (m.bottomMargin !== void 0) {
3523
- lines.push(`${indent} <bottom-margin>${m.bottomMargin}</bottom-margin>`);
3704
+ lines.push(`${indent} <bottom-margin>${m.bottomMarginRaw ?? m.bottomMargin}</bottom-margin>`);
3524
3705
  }
3525
3706
  lines.push(`${indent} </page-margins>`);
3526
3707
  }
@@ -3534,18 +3715,48 @@ function serializeSystemLayout(layout, indent) {
3534
3715
  if (layout.systemMargins) {
3535
3716
  lines.push(`${indent} <system-margins>`);
3536
3717
  if (layout.systemMargins.leftMargin !== void 0) {
3537
- lines.push(`${indent} <left-margin>${layout.systemMargins.leftMargin}</left-margin>`);
3718
+ lines.push(`${indent} <left-margin>${layout.systemMargins.leftMarginRaw ?? layout.systemMargins.leftMargin}</left-margin>`);
3538
3719
  }
3539
3720
  if (layout.systemMargins.rightMargin !== void 0) {
3540
- lines.push(`${indent} <right-margin>${layout.systemMargins.rightMargin}</right-margin>`);
3721
+ lines.push(`${indent} <right-margin>${layout.systemMargins.rightMarginRaw ?? layout.systemMargins.rightMargin}</right-margin>`);
3541
3722
  }
3542
3723
  lines.push(`${indent} </system-margins>`);
3543
3724
  }
3544
3725
  if (layout.systemDistance !== void 0) {
3545
- lines.push(`${indent} <system-distance>${layout.systemDistance}</system-distance>`);
3726
+ lines.push(`${indent} <system-distance>${layout.systemDistanceRaw ?? layout.systemDistance}</system-distance>`);
3546
3727
  }
3547
3728
  if (layout.topSystemDistance !== void 0) {
3548
- 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>`);
3549
3760
  }
3550
3761
  lines.push(`${indent}</system-layout>`);
3551
3762
  return lines;
@@ -3566,6 +3777,7 @@ function serializeCredit(credit, indent) {
3566
3777
  let attrs2 = "";
3567
3778
  if (cw.defaultX !== void 0) attrs2 += ` default-x="${cw.defaultX}"`;
3568
3779
  if (cw.defaultY !== void 0) attrs2 += ` default-y="${cw.defaultY}"`;
3780
+ if (cw.fontFamily) attrs2 += ` font-family="${escapeXml(cw.fontFamily)}"`;
3569
3781
  if (cw.fontSize) attrs2 += ` font-size="${escapeXml(cw.fontSize)}"`;
3570
3782
  if (cw.fontWeight) attrs2 += ` font-weight="${escapeXml(cw.fontWeight)}"`;
3571
3783
  if (cw.fontStyle) attrs2 += ` font-style="${escapeXml(cw.fontStyle)}"`;
@@ -3860,6 +4072,7 @@ function serializeKey(key, indent) {
3860
4072
  let keyAttrs = "";
3861
4073
  if (key.number !== void 0) keyAttrs += ` number="${key.number}"`;
3862
4074
  if (key.printObject === false) keyAttrs += ' print-object="no"';
4075
+ else if (key.printObject === true) keyAttrs += ' print-object="yes"';
3863
4076
  lines.push(`${indent}<key${keyAttrs}>`);
3864
4077
  if (key.cancel !== void 0) {
3865
4078
  const locationAttr = key.cancelLocation ? ` location="${key.cancelLocation}"` : "";
@@ -3907,7 +4120,8 @@ function serializeTime(time, indent) {
3907
4120
  const maxLen = Math.max(time.beatsList.length, time.beatTypeList.length);
3908
4121
  for (let i = 0; i < maxLen; i++) {
3909
4122
  if (i < time.beatsList.length) {
3910
- 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>`);
3911
4125
  }
3912
4126
  if (i < time.beatTypeList.length) {
3913
4127
  lines.push(`${indent} <beat-type>${time.beatTypeList[i]}</beat-type>`);
@@ -3924,10 +4138,13 @@ function serializeClef(clef, indent) {
3924
4138
  const lines = [];
3925
4139
  let attrs = clef.staff ? ` number="${clef.staff}"` : "";
3926
4140
  if (clef.printObject === false) attrs += ' print-object="no"';
4141
+ else if (clef.printObject === true) attrs += ' print-object="yes"';
3927
4142
  if (clef.afterBarline) attrs += ' after-barline="yes"';
3928
4143
  lines.push(`${indent}<clef${attrs}>`);
3929
4144
  lines.push(`${indent} <sign>${clef.sign}</sign>`);
3930
- lines.push(`${indent} <line>${clef.line}</line>`);
4145
+ if (clef.line !== void 0) {
4146
+ lines.push(`${indent} <line>${clef.line}</line>`);
4147
+ }
3931
4148
  if (clef.clefOctaveChange !== void 0) {
3932
4149
  lines.push(`${indent} <clef-octave-change>${clef.clefOctaveChange}</clef-octave-change>`);
3933
4150
  }
@@ -3963,6 +4180,12 @@ function serializeEntry(entry, indent) {
3963
4180
  return serializeSound(entry, indent);
3964
4181
  case "attributes":
3965
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
+ }
3966
4189
  default:
3967
4190
  return [];
3968
4191
  }
@@ -3977,12 +4200,13 @@ function serializeNote(note, indent) {
3977
4200
  "relative-y": note.relativeY,
3978
4201
  "dynamics": note.dynamics,
3979
4202
  "print-object": note.printObject === false ? false : void 0,
4203
+ "print-dot": note.printDot !== void 0 ? note.printDot : void 0,
3980
4204
  "print-spacing": note.printSpacing
3981
4205
  });
3982
4206
  lines.push(`${indent}<note${noteAttrs}>`);
3983
4207
  if (note.grace) {
3984
4208
  const graceAttrs = buildAttrs({
3985
- "slash": note.grace.slash || void 0,
4209
+ "slash": note.grace.slash !== void 0 ? note.grace.slash : void 0,
3986
4210
  "steal-time-previous": note.grace.stealTimePrevious,
3987
4211
  "steal-time-following": note.grace.stealTimeFollowing
3988
4212
  });
@@ -4037,7 +4261,9 @@ function serializeNote(note, indent) {
4037
4261
  } else if (note.tie) {
4038
4262
  lines.push(`${indent} <tie type="${note.tie.type}"/>`);
4039
4263
  }
4040
- lines.push(`${indent} <voice>${note.voice}</voice>`);
4264
+ if (note.voice !== void 0) {
4265
+ lines.push(`${indent} <voice>${note.voice}</voice>`);
4266
+ }
4041
4267
  if (note.noteType) {
4042
4268
  const typeAttrs = note.noteTypeSize ? ` size="${escapeXml(note.noteTypeSize)}"` : "";
4043
4269
  lines.push(`${indent} <type${typeAttrs}>${note.noteType}</type>`);
@@ -4254,7 +4480,18 @@ function serializeNotationsGroup(notations, indent) {
4254
4480
  let attrs = "";
4255
4481
  if (notation.direction) attrs += ` direction="${notation.direction}"`;
4256
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}"`;
4257
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>`);
4258
4495
  } else if (notation.type === "glissando") {
4259
4496
  let attrs = ` type="${notation.glissandoType}"`;
4260
4497
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
@@ -4268,7 +4505,11 @@ function serializeNotationsGroup(notations, indent) {
4268
4505
  let attrs = ` type="${notation.slideType}"`;
4269
4506
  if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4270
4507
  if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4271
- 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
+ }
4272
4513
  }
4273
4514
  }
4274
4515
  const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
@@ -4289,44 +4530,50 @@ function serializeNotationsGroup(notations, indent) {
4289
4530
  lines.push(`${indent} </articulations>`);
4290
4531
  }
4291
4532
  if (ornaments.length > 0) {
4292
- lines.push(`${indent} <ornaments>`);
4293
- const allAccidentalMarks = [];
4294
- for (const orn of ornaments) {
4295
- if (orn.type === "ornament") {
4296
- const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4297
- if (orn.ornament === "wavy-line") {
4298
- let wlAttrs = "";
4299
- if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4300
- if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4301
- wlAttrs += placementAttr;
4302
- if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4303
- lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4304
- } else if (orn.ornament === "tremolo") {
4305
- let tremAttrs = "";
4306
- if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4307
- tremAttrs += placementAttr;
4308
- if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4309
- if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4310
- if (orn.tremoloMarks !== void 0) {
4311
- 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
+ }
4312
4561
  } else {
4313
- 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);
4314
4568
  }
4315
- } else {
4316
- let ornAttrs = placementAttr;
4317
- if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4318
- lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4319
- }
4320
- if (orn.accidentalMarks) {
4321
- allAccidentalMarks.push(...orn.accidentalMarks);
4322
4569
  }
4323
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>`);
4324
4576
  }
4325
- for (const am of allAccidentalMarks) {
4326
- const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4327
- lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4328
- }
4329
- lines.push(`${indent} </ornaments>`);
4330
4577
  }
4331
4578
  if (technicals.length > 0) {
4332
4579
  lines.push(`${indent} <technical>`);
@@ -4347,8 +4594,8 @@ function serializeNotationsGroup(notations, indent) {
4347
4594
  if (techNotation.release) {
4348
4595
  lines.push(`${indent} <release/>`);
4349
4596
  }
4350
- if (techNotation.withBar !== void 0) {
4351
- lines.push(`${indent} <with-bar>${techNotation.withBar}</with-bar>`);
4597
+ if (techNotation.withBar) {
4598
+ lines.push(`${indent} <with-bar/>`);
4352
4599
  }
4353
4600
  lines.push(`${indent} </bend>`);
4354
4601
  } else if (tech.technical === "harmonic") {
@@ -4365,8 +4612,10 @@ function serializeNotationsGroup(notations, indent) {
4365
4612
  lines.push(`${indent} <harmonic${placementAttr}/>`);
4366
4613
  }
4367
4614
  } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4368
- let attrs = placementAttr;
4615
+ let attrs = "";
4616
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4369
4617
  if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4618
+ attrs += placementAttr;
4370
4619
  if (techNotation.text !== void 0) {
4371
4620
  lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4372
4621
  } else {
@@ -4422,7 +4671,7 @@ function serializeLyric(lyric, indent) {
4422
4671
  lines.push(`${indent} <elision/>`);
4423
4672
  }
4424
4673
  }
4425
- } else {
4674
+ } else if (lyric.syllabic || lyric.text) {
4426
4675
  if (lyric.syllabic) {
4427
4676
  lines.push(`${indent} <syllabic>${lyric.syllabic}</syllabic>`);
4428
4677
  }
@@ -4537,7 +4786,12 @@ function serializeDirectionType(dirType, indent) {
4537
4786
  if (dirType.relativeX !== void 0) dynAttrs += ` relative-x="${dirType.relativeX}"`;
4538
4787
  if (dirType.halign) dynAttrs += ` halign="${dirType.halign}"`;
4539
4788
  lines.push(`${indent} <dynamics${dynAttrs}>`);
4540
- 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
+ }
4541
4795
  lines.push(`${indent} </dynamics>`);
4542
4796
  break;
4543
4797
  }
@@ -4577,6 +4831,7 @@ function serializeDirectionType(dirType, indent) {
4577
4831
  if (dirType.defaultX !== void 0) wordAttrs += ` default-x="${dirType.defaultX}"`;
4578
4832
  if (dirType.defaultY !== void 0) wordAttrs += ` default-y="${dirType.defaultY}"`;
4579
4833
  if (dirType.relativeX !== void 0) wordAttrs += ` relative-x="${dirType.relativeX}"`;
4834
+ if (dirType.relativeY !== void 0) wordAttrs += ` relative-y="${dirType.relativeY}"`;
4580
4835
  if (dirType.fontFamily) wordAttrs += ` font-family="${escapeXml(dirType.fontFamily)}"`;
4581
4836
  if (dirType.fontSize) wordAttrs += ` font-size="${escapeXml(dirType.fontSize)}"`;
4582
4837
  if (dirType.fontStyle) wordAttrs += ` font-style="${escapeXml(dirType.fontStyle)}"`;
@@ -4623,8 +4878,12 @@ function serializeDirectionType(dirType, indent) {
4623
4878
  if (dirType.high) {
4624
4879
  lines.push(`${indent} <accordion-high/>`);
4625
4880
  }
4626
- if (dirType.middle !== void 0) {
4627
- 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
+ }
4628
4887
  }
4629
4888
  if (dirType.low) {
4630
4889
  lines.push(`${indent} <accordion-low/>`);
@@ -4737,11 +4996,20 @@ function serializeBarline(barline, indent) {
4737
4996
  lines.push(`${indent} <bar-style>${barline.barStyle}</bar-style>`);
4738
4997
  }
4739
4998
  if (barline.ending) {
4740
- 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
+ }
4741
5007
  }
4742
5008
  if (barline.repeat) {
4743
- const timesAttr = barline.repeat.times !== void 0 ? ` times="${barline.repeat.times}"` : "";
4744
- 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}/>`);
4745
5013
  }
4746
5014
  lines.push(`${indent}</barline>`);
4747
5015
  return lines;
@@ -4774,7 +5042,8 @@ function serializeStaffDetails(sd, indent) {
4774
5042
  const attrs = buildAttrs({
4775
5043
  "number": sd.number,
4776
5044
  "show-frets": sd.showFrets,
4777
- "print-object": sd.printObject
5045
+ "print-object": sd.printObject,
5046
+ "print-spacing": sd.printSpacing
4778
5047
  });
4779
5048
  lines.push(`${indent}<staff-details${attrs}>`);
4780
5049
  pushOptionalElement(lines, `${indent} `, "staff-type", sd.staffType);
@@ -4840,16 +5109,22 @@ function serializeHarmony(harmony, indent) {
4840
5109
  }
4841
5110
  lines.push(`${indent} </root>`);
4842
5111
  let kindAttrs = "";
4843
- 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)}"`;
4844
5114
  lines.push(`${indent} <kind${kindAttrs}>${escapeXml(harmony.kind)}</kind>`);
4845
5115
  if (harmony.bass) {
4846
- lines.push(`${indent} <bass>`);
5116
+ let bassAttrs = "";
5117
+ if (harmony.bass.arrangement) bassAttrs += ` arrangement="${escapeXml(harmony.bass.arrangement)}"`;
5118
+ lines.push(`${indent} <bass${bassAttrs}>`);
4847
5119
  lines.push(`${indent} <bass-step>${harmony.bass.bassStep}</bass-step>`);
4848
5120
  if (harmony.bass.bassAlter !== void 0) {
4849
5121
  lines.push(`${indent} <bass-alter>${harmony.bass.bassAlter}</bass-alter>`);
4850
5122
  }
4851
5123
  lines.push(`${indent} </bass>`);
4852
5124
  }
5125
+ if (harmony.inversion !== void 0) {
5126
+ lines.push(`${indent} <inversion>${harmony.inversion}</inversion>`);
5127
+ }
4853
5128
  if (harmony.degrees) {
4854
5129
  for (const deg of harmony.degrees) {
4855
5130
  lines.push(`${indent} <degree>`);
@@ -5535,7 +5810,7 @@ function groupByVoice(measure) {
5535
5810
  for (const entry of measure.entries) {
5536
5811
  if (entry.type !== "note") continue;
5537
5812
  const staff = entry.staff ?? 1;
5538
- const voice = entry.voice;
5813
+ const voice = entry.voice ?? 1;
5539
5814
  const key = `${staff}-${voice}`;
5540
5815
  if (!groups.has(key)) {
5541
5816
  groups.set(key, { staff, voice, notes: [] });
@@ -5629,7 +5904,7 @@ function getVoices(measure) {
5629
5904
  const voices = /* @__PURE__ */ new Set();
5630
5905
  for (const entry of measure.entries) {
5631
5906
  if (entry.type === "note") {
5632
- voices.add(entry.voice);
5907
+ voices.add(entry.voice ?? 1);
5633
5908
  }
5634
5909
  }
5635
5910
  return Array.from(voices).sort((a, b) => a - b);
@@ -5680,7 +5955,7 @@ function buildVoiceToStaffMap(measure) {
5680
5955
  const map = /* @__PURE__ */ new Map();
5681
5956
  for (const entry of measure.entries) {
5682
5957
  if (entry.type === "note" && entry.staff !== void 0) {
5683
- const voice = entry.voice;
5958
+ const voice = entry.voice ?? 1;
5684
5959
  const staff = entry.staff;
5685
5960
  if (!map.has(voice)) {
5686
5961
  map.set(voice, staff);
@@ -5699,7 +5974,7 @@ function buildVoiceToStaffMapForPart(part) {
5699
5974
  for (const measure of part.measures) {
5700
5975
  for (const entry of measure.entries) {
5701
5976
  if (entry.type === "note" && entry.staff !== void 0) {
5702
- const voice = entry.voice;
5977
+ const voice = entry.voice ?? 1;
5703
5978
  const staff = entry.staff;
5704
5979
  if (!map.has(voice)) {
5705
5980
  map.set(voice, staff);
@@ -5718,7 +5993,7 @@ function inferStaff(entry, voiceToStaffMap) {
5718
5993
  if (entry.staff !== void 0) {
5719
5994
  return entry.staff;
5720
5995
  }
5721
- const inferredStaff = voiceToStaffMap.get(entry.voice);
5996
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
5722
5997
  if (inferredStaff !== void 0) {
5723
5998
  return inferredStaff;
5724
5999
  }
@@ -5761,7 +6036,7 @@ function getVoicesForStaff(measure, staff) {
5761
6036
  if (entry.type === "note") {
5762
6037
  const entryStaff = entry.staff ?? 1;
5763
6038
  if (entryStaff === staff) {
5764
- voices.add(entry.voice);
6039
+ voices.add(entry.voice ?? 1);
5765
6040
  }
5766
6041
  }
5767
6042
  }
@@ -6097,6 +6372,7 @@ function getDynamics(score, options) {
6097
6372
  if (dirType.kind === "dynamics") {
6098
6373
  results.push({
6099
6374
  dynamic: dirType.value,
6375
+ otherDynamics: dirType.otherDynamics,
6100
6376
  direction: entry,
6101
6377
  part,
6102
6378
  partIndex,
@@ -7171,14 +7447,17 @@ function hasNotesInRange(voiceEntries, startPos, endPos) {
7171
7447
  return { hasNotes: conflicting.length > 0, conflictingNotes: conflicting };
7172
7448
  }
7173
7449
  function createRest(duration, voice, staff) {
7174
- return {
7450
+ const note = {
7175
7451
  _id: generateId(),
7176
7452
  type: "note",
7177
7453
  rest: { displayStep: void 0, displayOctave: void 0 },
7178
7454
  duration,
7179
- voice,
7180
7455
  staff
7181
7456
  };
7457
+ if (voice !== void 0) {
7458
+ note.voice = voice;
7459
+ }
7460
+ return note;
7182
7461
  }
7183
7462
  function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, staff) {
7184
7463
  const otherEntries = [];
@@ -8732,7 +9011,7 @@ function autoBeam(score, options) {
8732
9011
  for (const entry of measure.entries) {
8733
9012
  if (entry.type === "note") {
8734
9013
  if (!entry.chord && !entry.rest) {
8735
- const voice = entry.voice;
9014
+ const voice = entry.voice ?? 1;
8736
9015
  if (options.voice === void 0 || voice === options.voice) {
8737
9016
  if (!notesByVoice.has(voice)) {
8738
9017
  notesByVoice.set(voice, []);