musicxml-io 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -290,11 +290,22 @@ var xmlParser = new import_fast_xml_parser.XMLParser({
290
290
  });
291
291
  function parse(xmlString) {
292
292
  const parsed = xmlParser.parse(xmlString);
293
- const scorePartwise = findElement(parsed, "score-partwise");
293
+ let scorePartwiseVersion;
294
+ let scorePartwise;
295
+ for (const el of parsed) {
296
+ if (el["score-partwise"]) {
297
+ scorePartwise = el["score-partwise"];
298
+ const attrs = getAttributes(el);
299
+ if (attrs["version"]) scorePartwiseVersion = attrs["version"];
300
+ break;
301
+ }
302
+ }
294
303
  if (!scorePartwise) {
295
304
  throw new Error("Unsupported MusicXML format: only score-partwise is supported");
296
305
  }
297
- return parseScorePartwise(scorePartwise);
306
+ const score = parseScorePartwise(scorePartwise);
307
+ if (scorePartwiseVersion) score.version = scorePartwiseVersion;
308
+ return score;
298
309
  }
299
310
  function findElement(elements, tagName) {
300
311
  for (const el of elements) {
@@ -3749,7 +3760,7 @@ function validateSlursAcrossMeasures(part) {
3749
3760
 
3750
3761
  // src/exporters/musicxml.ts
3751
3762
  function serialize(score, options = {}) {
3752
- const version = options.version || "4.0";
3763
+ const version = options.version || score.version || "4.0";
3753
3764
  const indent = options.indent ?? " ";
3754
3765
  if (options.validate) {
3755
3766
  const result = validate(score, options.validateOptions);
@@ -3764,11 +3775,7 @@ ${errorMessages}`);
3764
3775
  }
3765
3776
  const lines = [];
3766
3777
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
3767
- if (version === "4.0") {
3768
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3769
- } else {
3770
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3771
- }
3778
+ lines.push(`<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML ${version} Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">`);
3772
3779
  lines.push(`<score-partwise version="${version}">`);
3773
3780
  lines.push(...serializeMetadata(score.metadata, indent));
3774
3781
  if (score.defaults) {
@@ -4642,283 +4649,309 @@ function serializeNotations(notations, indent) {
4642
4649
  function serializeNotationsGroup(notations, indent) {
4643
4650
  const lines = [];
4644
4651
  lines.push(`${indent}<notations>`);
4645
- const articulationsGroups = /* @__PURE__ */ new Map();
4646
- const ornaments = [];
4647
- const technicals = [];
4648
- const others = [];
4652
+ const chunks = [];
4649
4653
  for (const notation of notations) {
4650
4654
  if (notation.type === "articulation") {
4651
4655
  const artIdx = notation.articulationsIndex ?? 0;
4652
- if (!articulationsGroups.has(artIdx)) {
4653
- articulationsGroups.set(artIdx, []);
4656
+ const last = chunks[chunks.length - 1];
4657
+ if (last && last.kind === "articulations" && last.articulationsIndex === artIdx) {
4658
+ last.items.push(notation);
4659
+ } else {
4660
+ chunks.push({ kind: "articulations", items: [notation], articulationsIndex: artIdx });
4654
4661
  }
4655
- articulationsGroups.get(artIdx).push(notation);
4656
4662
  } else if (notation.type === "ornament") {
4657
- ornaments.push(notation);
4663
+ const last = chunks[chunks.length - 1];
4664
+ if (last && last.kind === "ornaments") {
4665
+ last.items.push(notation);
4666
+ } else {
4667
+ chunks.push({ kind: "ornaments", items: [notation] });
4668
+ }
4658
4669
  } else if (notation.type === "technical") {
4659
- technicals.push(notation);
4670
+ const last = chunks[chunks.length - 1];
4671
+ if (last && last.kind === "technical") {
4672
+ last.items.push(notation);
4673
+ } else {
4674
+ chunks.push({ kind: "technical", items: [notation] });
4675
+ }
4660
4676
  } else {
4661
- others.push(notation);
4677
+ chunks.push({ kind: "standalone", notation });
4662
4678
  }
4663
4679
  }
4664
- for (const notation of others) {
4665
- if (notation.type === "tied") {
4666
- let attrs = ` type="${notation.tiedType}"`;
4667
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4668
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4669
- lines.push(`${indent} <tied${attrs}/>`);
4670
- } else if (notation.type === "slur") {
4671
- let attrs = "";
4672
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4673
- attrs += ` type="${notation.slurType}"`;
4674
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4675
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4676
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4677
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4678
- if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4679
- if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4680
- if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4681
- if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4682
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4683
- lines.push(`${indent} <slur${attrs}/>`);
4684
- } else if (notation.type === "tuplet") {
4685
- let attrs = ` type="${notation.tupletType}"`;
4686
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4687
- if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4688
- if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4689
- if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4690
- if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4691
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4692
- const tup = notation;
4693
- if (tup.tupletActual || tup.tupletNormal) {
4694
- lines.push(`${indent} <tuplet${attrs}>`);
4695
- if (tup.tupletActual) {
4696
- lines.push(`${indent} <tuplet-actual>`);
4697
- if (tup.tupletActual.tupletNumber !== void 0) {
4698
- lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4699
- }
4700
- if (tup.tupletActual.tupletType) {
4701
- lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4702
- }
4703
- if (tup.tupletActual.tupletDots) {
4704
- for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4705
- lines.push(`${indent} <tuplet-dot/>`);
4706
- }
4680
+ for (const chunk of chunks) {
4681
+ if (chunk.kind === "standalone") {
4682
+ lines.push(...serializeStandaloneNotation(chunk.notation, indent));
4683
+ } else if (chunk.kind === "articulations") {
4684
+ lines.push(...serializeArticulationsGroup(chunk.items, indent));
4685
+ } else if (chunk.kind === "ornaments") {
4686
+ lines.push(...serializeOrnamentsGroup(chunk.items, indent));
4687
+ } else if (chunk.kind === "technical") {
4688
+ lines.push(...serializeTechnicalGroup(chunk.items, indent));
4689
+ }
4690
+ }
4691
+ lines.push(`${indent}</notations>`);
4692
+ return lines;
4693
+ }
4694
+ function serializeStandaloneNotation(notation, indent) {
4695
+ const lines = [];
4696
+ if (notation.type === "tied") {
4697
+ let attrs = ` type="${notation.tiedType}"`;
4698
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4699
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4700
+ lines.push(`${indent} <tied${attrs}/>`);
4701
+ } else if (notation.type === "slur") {
4702
+ let attrs = "";
4703
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4704
+ attrs += ` type="${notation.slurType}"`;
4705
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4706
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4707
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4708
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4709
+ if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4710
+ if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4711
+ if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4712
+ if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4713
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4714
+ lines.push(`${indent} <slur${attrs}/>`);
4715
+ } else if (notation.type === "tuplet") {
4716
+ let attrs = ` type="${notation.tupletType}"`;
4717
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4718
+ if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4719
+ if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4720
+ if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4721
+ if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4722
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4723
+ const tup = notation;
4724
+ if (tup.tupletActual || tup.tupletNormal) {
4725
+ lines.push(`${indent} <tuplet${attrs}>`);
4726
+ if (tup.tupletActual) {
4727
+ lines.push(`${indent} <tuplet-actual>`);
4728
+ if (tup.tupletActual.tupletNumber !== void 0) {
4729
+ lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4730
+ }
4731
+ if (tup.tupletActual.tupletType) {
4732
+ lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4733
+ }
4734
+ if (tup.tupletActual.tupletDots) {
4735
+ for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4736
+ lines.push(`${indent} <tuplet-dot/>`);
4707
4737
  }
4708
- lines.push(`${indent} </tuplet-actual>`);
4709
4738
  }
4710
- if (tup.tupletNormal) {
4711
- lines.push(`${indent} <tuplet-normal>`);
4712
- if (tup.tupletNormal.tupletNumber !== void 0) {
4713
- lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4714
- }
4715
- if (tup.tupletNormal.tupletType) {
4716
- lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4717
- }
4718
- if (tup.tupletNormal.tupletDots) {
4719
- for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4720
- lines.push(`${indent} <tuplet-dot/>`);
4721
- }
4739
+ lines.push(`${indent} </tuplet-actual>`);
4740
+ }
4741
+ if (tup.tupletNormal) {
4742
+ lines.push(`${indent} <tuplet-normal>`);
4743
+ if (tup.tupletNormal.tupletNumber !== void 0) {
4744
+ lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4745
+ }
4746
+ if (tup.tupletNormal.tupletType) {
4747
+ lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4748
+ }
4749
+ if (tup.tupletNormal.tupletDots) {
4750
+ for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4751
+ lines.push(`${indent} <tuplet-dot/>`);
4722
4752
  }
4723
- lines.push(`${indent} </tuplet-normal>`);
4724
4753
  }
4725
- lines.push(`${indent} </tuplet>`);
4726
- } else {
4727
- lines.push(`${indent} <tuplet${attrs}/>`);
4728
- }
4729
- } else if (notation.type === "dynamics") {
4730
- const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4731
- lines.push(`${indent} <dynamics${placementAttr}>`);
4732
- for (const dyn of notation.dynamics) {
4733
- lines.push(`${indent} <${dyn}/>`);
4734
- }
4735
- if (notation.otherDynamics) {
4736
- lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4737
- }
4738
- lines.push(`${indent} </dynamics>`);
4739
- } else if (notation.type === "fermata") {
4740
- let attrs = "";
4741
- if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4742
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4743
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4744
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4745
- if (notation.shape) {
4746
- lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4747
- } else {
4748
- lines.push(`${indent} <fermata${attrs}/>`);
4749
- }
4750
- } else if (notation.type === "arpeggiate") {
4751
- let attrs = "";
4752
- if (notation.direction) attrs += ` direction="${notation.direction}"`;
4753
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4754
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4755
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4756
- lines.push(`${indent} <arpeggiate${attrs}/>`);
4757
- } else if (notation.type === "non-arpeggiate") {
4758
- let attrs = ` type="${notation.nonArpeggiateType}"`;
4759
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4760
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4761
- lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4762
- } else if (notation.type === "accidental-mark") {
4763
- let attrs = "";
4764
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4765
- lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4766
- } else if (notation.type === "glissando") {
4767
- let attrs = ` type="${notation.glissandoType}"`;
4768
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4769
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4770
- if (notation.text) {
4771
- lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4772
- } else {
4773
- lines.push(`${indent} <glissando${attrs}/>`);
4774
- }
4775
- } else if (notation.type === "slide") {
4776
- let attrs = ` type="${notation.slideType}"`;
4777
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4778
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4779
- if (notation.text) {
4780
- lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4781
- } else {
4782
- lines.push(`${indent} <slide${attrs}/>`);
4754
+ lines.push(`${indent} </tuplet-normal>`);
4783
4755
  }
4756
+ lines.push(`${indent} </tuplet>`);
4757
+ } else {
4758
+ lines.push(`${indent} <tuplet${attrs}/>`);
4759
+ }
4760
+ } else if (notation.type === "dynamics") {
4761
+ const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4762
+ lines.push(`${indent} <dynamics${placementAttr}>`);
4763
+ for (const dyn of notation.dynamics) {
4764
+ lines.push(`${indent} <${dyn}/>`);
4765
+ }
4766
+ if (notation.otherDynamics) {
4767
+ lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4768
+ }
4769
+ lines.push(`${indent} </dynamics>`);
4770
+ } else if (notation.type === "fermata") {
4771
+ let attrs = "";
4772
+ if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4773
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4774
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4775
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4776
+ if (notation.shape) {
4777
+ lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4778
+ } else {
4779
+ lines.push(`${indent} <fermata${attrs}/>`);
4780
+ }
4781
+ } else if (notation.type === "arpeggiate") {
4782
+ let attrs = "";
4783
+ if (notation.direction) attrs += ` direction="${notation.direction}"`;
4784
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4785
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4786
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4787
+ lines.push(`${indent} <arpeggiate${attrs}/>`);
4788
+ } else if (notation.type === "non-arpeggiate") {
4789
+ let attrs = ` type="${notation.nonArpeggiateType}"`;
4790
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4791
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4792
+ lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4793
+ } else if (notation.type === "accidental-mark") {
4794
+ let attrs = "";
4795
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4796
+ lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4797
+ } else if (notation.type === "glissando") {
4798
+ let attrs = ` type="${notation.glissandoType}"`;
4799
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4800
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4801
+ if (notation.text) {
4802
+ lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4803
+ } else {
4804
+ lines.push(`${indent} <glissando${attrs}/>`);
4805
+ }
4806
+ } else if (notation.type === "slide") {
4807
+ let attrs = ` type="${notation.slideType}"`;
4808
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4809
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4810
+ if (notation.text) {
4811
+ lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4812
+ } else {
4813
+ lines.push(`${indent} <slide${attrs}/>`);
4784
4814
  }
4785
4815
  }
4786
- const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
4787
- for (const artIdx of sortedArtIndices) {
4788
- const artGroup = articulationsGroups.get(artIdx);
4789
- lines.push(`${indent} <articulations>`);
4790
- for (const art of artGroup) {
4791
- if (art.type === "articulation") {
4792
- let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4793
- if (art.articulation === "strong-accent" && art.strongAccentType) {
4794
- artAttrs += ` type="${art.strongAccentType}"`;
4795
- }
4796
- if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4797
- if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4798
- lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4816
+ return lines;
4817
+ }
4818
+ function serializeArticulationsGroup(artGroup, indent) {
4819
+ const lines = [];
4820
+ lines.push(`${indent} <articulations>`);
4821
+ for (const art of artGroup) {
4822
+ if (art.type === "articulation") {
4823
+ let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4824
+ if (art.articulation === "strong-accent" && art.strongAccentType) {
4825
+ artAttrs += ` type="${art.strongAccentType}"`;
4799
4826
  }
4827
+ if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4828
+ if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4829
+ lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4800
4830
  }
4801
- lines.push(`${indent} </articulations>`);
4802
4831
  }
4803
- if (ornaments.length > 0) {
4804
- const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4805
- if (hasOnlyEmptyMarker) {
4806
- lines.push(`${indent} <ornaments/>`);
4807
- } else {
4808
- lines.push(`${indent} <ornaments>`);
4809
- const allAccidentalMarks = [];
4810
- for (const orn of ornaments) {
4811
- if (orn.type === "ornament") {
4812
- if (orn.ornament === "empty") continue;
4813
- const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4814
- if (orn.ornament === "wavy-line") {
4815
- let wlAttrs = "";
4816
- if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4817
- if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4818
- wlAttrs += placementAttr;
4819
- if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4820
- lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4821
- } else if (orn.ornament === "tremolo") {
4822
- let tremAttrs = "";
4823
- if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4824
- tremAttrs += placementAttr;
4825
- if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4826
- if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4827
- if (orn.tremoloMarks !== void 0) {
4828
- lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4829
- } else {
4830
- lines.push(`${indent} <tremolo${tremAttrs}/>`);
4831
- }
4832
+ lines.push(`${indent} </articulations>`);
4833
+ return lines;
4834
+ }
4835
+ function serializeOrnamentsGroup(ornaments, indent) {
4836
+ const lines = [];
4837
+ const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4838
+ if (hasOnlyEmptyMarker) {
4839
+ lines.push(`${indent} <ornaments/>`);
4840
+ } else {
4841
+ lines.push(`${indent} <ornaments>`);
4842
+ const allAccidentalMarks = [];
4843
+ for (const orn of ornaments) {
4844
+ if (orn.type === "ornament") {
4845
+ if (orn.ornament === "empty") continue;
4846
+ const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4847
+ if (orn.ornament === "wavy-line") {
4848
+ let wlAttrs = "";
4849
+ if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4850
+ if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4851
+ wlAttrs += placementAttr;
4852
+ if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4853
+ lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4854
+ } else if (orn.ornament === "tremolo") {
4855
+ let tremAttrs = "";
4856
+ if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4857
+ tremAttrs += placementAttr;
4858
+ if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4859
+ if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4860
+ if (orn.tremoloMarks !== void 0) {
4861
+ lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4832
4862
  } else {
4833
- let ornAttrs = placementAttr;
4834
- if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4835
- lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4836
- }
4837
- if (orn.accidentalMarks) {
4838
- allAccidentalMarks.push(...orn.accidentalMarks);
4863
+ lines.push(`${indent} <tremolo${tremAttrs}/>`);
4839
4864
  }
4865
+ } else {
4866
+ let ornAttrs = placementAttr;
4867
+ if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4868
+ lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4869
+ }
4870
+ if (orn.accidentalMarks) {
4871
+ allAccidentalMarks.push(...orn.accidentalMarks);
4840
4872
  }
4841
4873
  }
4842
- for (const am of allAccidentalMarks) {
4843
- const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4844
- lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4845
- }
4846
- lines.push(`${indent} </ornaments>`);
4847
4874
  }
4875
+ for (const am of allAccidentalMarks) {
4876
+ const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4877
+ lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4878
+ }
4879
+ lines.push(`${indent} </ornaments>`);
4848
4880
  }
4849
- if (technicals.length > 0) {
4850
- lines.push(`${indent} <technical>`);
4851
- for (const tech of technicals) {
4852
- if (tech.type === "technical") {
4853
- let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4854
- const techNotation = tech;
4855
- if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4856
- if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4857
- if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4858
- lines.push(`${indent} <bend${placementAttr}>`);
4859
- if (techNotation.bendAlter !== void 0) {
4860
- lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4861
- }
4862
- if (techNotation.preBend) {
4863
- lines.push(`${indent} <pre-bend/>`);
4864
- }
4865
- if (techNotation.release) {
4866
- lines.push(`${indent} <release/>`);
4867
- }
4868
- if (techNotation.withBar) {
4869
- lines.push(`${indent} <with-bar/>`);
4870
- }
4871
- lines.push(`${indent} </bend>`);
4872
- } else if (tech.technical === "harmonic") {
4873
- const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4874
- if (hasChildren) {
4875
- lines.push(`${indent} <harmonic${placementAttr}>`);
4876
- if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4877
- if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4878
- if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4879
- if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4880
- if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4881
- lines.push(`${indent} </harmonic>`);
4882
- } else {
4883
- lines.push(`${indent} <harmonic${placementAttr}/>`);
4884
- }
4885
- } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4886
- let attrs = "";
4887
- if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4888
- if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4889
- attrs += placementAttr;
4890
- if (techNotation.text !== void 0) {
4891
- lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4892
- } else {
4893
- lines.push(`${indent} <${tech.technical}${attrs}/>`);
4894
- }
4895
- } else if (tech.technical === "string" && techNotation.string !== void 0) {
4896
- lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4897
- } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4898
- lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4899
- } else if (tech.technical === "fingering") {
4900
- let fAttrs = placementAttr;
4901
- if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4902
- if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4903
- if (techNotation.text !== void 0) {
4904
- lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4905
- } else {
4906
- lines.push(`${indent} <fingering${fAttrs}/>`);
4907
- }
4908
- } else if (tech.technical === "heel" || tech.technical === "toe") {
4909
- let htAttrs = placementAttr;
4910
- if (techNotation.substitution) htAttrs += ' substitution="yes"';
4911
- lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4912
- } else if (techNotation.text !== void 0) {
4913
- lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4881
+ return lines;
4882
+ }
4883
+ function serializeTechnicalGroup(technicals, indent) {
4884
+ const lines = [];
4885
+ lines.push(`${indent} <technical>`);
4886
+ for (const tech of technicals) {
4887
+ if (tech.type === "technical") {
4888
+ let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4889
+ const techNotation = tech;
4890
+ if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4891
+ if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4892
+ if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4893
+ lines.push(`${indent} <bend${placementAttr}>`);
4894
+ if (techNotation.bendAlter !== void 0) {
4895
+ lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4896
+ }
4897
+ if (techNotation.preBend) {
4898
+ lines.push(`${indent} <pre-bend/>`);
4899
+ }
4900
+ if (techNotation.release) {
4901
+ lines.push(`${indent} <release/>`);
4902
+ }
4903
+ if (techNotation.withBar) {
4904
+ lines.push(`${indent} <with-bar/>`);
4905
+ }
4906
+ lines.push(`${indent} </bend>`);
4907
+ } else if (tech.technical === "harmonic") {
4908
+ const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4909
+ if (hasChildren) {
4910
+ lines.push(`${indent} <harmonic${placementAttr}>`);
4911
+ if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4912
+ if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4913
+ if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4914
+ if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4915
+ if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4916
+ lines.push(`${indent} </harmonic>`);
4914
4917
  } else {
4915
- lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4916
- }
4918
+ lines.push(`${indent} <harmonic${placementAttr}/>`);
4919
+ }
4920
+ } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4921
+ let attrs = "";
4922
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4923
+ if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4924
+ attrs += placementAttr;
4925
+ if (techNotation.text !== void 0) {
4926
+ lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4927
+ } else {
4928
+ lines.push(`${indent} <${tech.technical}${attrs}/>`);
4929
+ }
4930
+ } else if (tech.technical === "string" && techNotation.string !== void 0) {
4931
+ lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4932
+ } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4933
+ lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4934
+ } else if (tech.technical === "fingering") {
4935
+ let fAttrs = placementAttr;
4936
+ if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4937
+ if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4938
+ if (techNotation.text !== void 0) {
4939
+ lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4940
+ } else {
4941
+ lines.push(`${indent} <fingering${fAttrs}/>`);
4942
+ }
4943
+ } else if (tech.technical === "heel" || tech.technical === "toe") {
4944
+ let htAttrs = placementAttr;
4945
+ if (techNotation.substitution) htAttrs += ' substitution="yes"';
4946
+ lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4947
+ } else if (techNotation.text !== void 0) {
4948
+ lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4949
+ } else {
4950
+ lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4917
4951
  }
4918
4952
  }
4919
- lines.push(`${indent} </technical>`);
4920
4953
  }
4921
- lines.push(`${indent}</notations>`);
4954
+ lines.push(`${indent} </technical>`);
4922
4955
  return lines;
4923
4956
  }
4924
4957
  function serializeLyric(lyric, indent) {