musicxml-io 0.3.0 → 0.3.2

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) {
@@ -5539,6 +5572,97 @@ function serializeCompressed(score, options = {}) {
5539
5572
  return (0, import_fflate2.zipSync)(files, { level: 6 });
5540
5573
  }
5541
5574
 
5575
+ // src/entry-accessors.ts
5576
+ function getDirectionOfKind(entry, kind) {
5577
+ return entry.directionTypes.find((d) => d.kind === kind);
5578
+ }
5579
+ function getDirectionsOfKind(entry, kind) {
5580
+ return entry.directionTypes.filter((d) => d.kind === kind);
5581
+ }
5582
+ function hasDirectionOfKind(entry, kind) {
5583
+ return entry.directionTypes.some((d) => d.kind === kind);
5584
+ }
5585
+ function getSoundTempo(entry) {
5586
+ return entry.sound?.tempo;
5587
+ }
5588
+ function getSoundDynamics(entry) {
5589
+ return entry.sound?.dynamics;
5590
+ }
5591
+ function getSoundDamperPedal(entry) {
5592
+ return entry.sound?.damperPedal;
5593
+ }
5594
+ function getSoundSoftPedal(entry) {
5595
+ return entry.sound?.softPedal;
5596
+ }
5597
+ function getSoundSostenutoPedal(entry) {
5598
+ return entry.sound?.sostenutoPedal;
5599
+ }
5600
+ function isRest(entry) {
5601
+ return entry.rest !== void 0 || !entry.pitch && !entry.unpitched;
5602
+ }
5603
+ function isPitchedNote(entry) {
5604
+ return entry.pitch !== void 0;
5605
+ }
5606
+ function isUnpitchedNote(entry) {
5607
+ return entry.unpitched !== void 0;
5608
+ }
5609
+ function isChordNote(entry) {
5610
+ return entry.chord === true;
5611
+ }
5612
+ function isGraceNote(entry) {
5613
+ return entry.grace !== void 0;
5614
+ }
5615
+ function hasTie(entry) {
5616
+ return entry.tie !== void 0 || entry.ties !== void 0 && entry.ties.length > 0;
5617
+ }
5618
+ function hasTieStart(entry) {
5619
+ if (entry.tie?.type === "start") return true;
5620
+ return entry.ties?.some((t) => t.type === "start") ?? false;
5621
+ }
5622
+ function hasTieStop(entry) {
5623
+ if (entry.tie?.type === "stop") return true;
5624
+ return entry.ties?.some((t) => t.type === "stop") ?? false;
5625
+ }
5626
+ function isCueNote(entry) {
5627
+ return entry.cue === true;
5628
+ }
5629
+ function hasBeam(entry) {
5630
+ return entry.beam !== void 0 && entry.beam.length > 0;
5631
+ }
5632
+ function hasLyrics(entry) {
5633
+ return entry.lyrics !== void 0 && entry.lyrics.length > 0;
5634
+ }
5635
+ function hasNotations(entry) {
5636
+ return entry.notations !== void 0 && entry.notations.length > 0;
5637
+ }
5638
+ function hasTuplet(entry) {
5639
+ return entry.timeModification !== void 0;
5640
+ }
5641
+ function isPartInfo(entry) {
5642
+ return entry.type === "score-part";
5643
+ }
5644
+ function getPartInfo(score, partId) {
5645
+ return score.partList.find((entry) => {
5646
+ return entry.type === "score-part" && entry.id === partId;
5647
+ });
5648
+ }
5649
+ function getPartName(score, partId) {
5650
+ return getPartInfo(score, partId)?.name;
5651
+ }
5652
+ function getPartAbbreviation(score, partId) {
5653
+ return getPartInfo(score, partId)?.abbreviation;
5654
+ }
5655
+ function getAllPartInfos(score) {
5656
+ return score.partList.filter(isPartInfo);
5657
+ }
5658
+ function getPartNameMap(score) {
5659
+ const map = {};
5660
+ for (const part of getAllPartInfos(score)) {
5661
+ map[part.id] = part.name;
5662
+ }
5663
+ return map;
5664
+ }
5665
+
5542
5666
  // src/exporters/midi.ts
5543
5667
  function exportMidi(score, options = {}) {
5544
5668
  const ticksPerQuarterNote = options.ticksPerQuarterNote ?? 480;
@@ -5696,18 +5820,24 @@ function createPartTrack(part, _score, channel, program, ticksPerQuarterNote, de
5696
5820
  const notePosition = note.chord ? chordBasePosition : position;
5697
5821
  const startTick = measureStartTick + Math.round(notePosition * ticksPerQuarterNote / divisions);
5698
5822
  const durationTicks = Math.round(note.duration * ticksPerQuarterNote / divisions);
5699
- noteEvents.push({
5700
- tick: startTick,
5701
- type: "on",
5702
- note: midiNote,
5703
- velocity: defaultVelocity
5704
- });
5705
- noteEvents.push({
5706
- tick: startTick + durationTicks,
5707
- type: "off",
5708
- note: midiNote,
5709
- velocity: 0
5710
- });
5823
+ const isTieStop = hasTieStop(note);
5824
+ const isTieStart = hasTieStart(note);
5825
+ if (!isTieStop) {
5826
+ noteEvents.push({
5827
+ tick: startTick,
5828
+ type: "on",
5829
+ note: midiNote,
5830
+ velocity: defaultVelocity
5831
+ });
5832
+ }
5833
+ if (!isTieStart) {
5834
+ noteEvents.push({
5835
+ tick: startTick + durationTicks,
5836
+ type: "off",
5837
+ note: midiNote,
5838
+ velocity: 0
5839
+ });
5840
+ }
5711
5841
  }
5712
5842
  if (!note.chord) {
5713
5843
  chordBasePosition = position;
@@ -5740,6 +5870,18 @@ function createPartTrack(part, _score, channel, program, ticksPerQuarterNote, de
5740
5870
  }
5741
5871
  }
5742
5872
  }
5873
+ const onCounts = /* @__PURE__ */ new Map();
5874
+ const offCounts = /* @__PURE__ */ new Map();
5875
+ for (const e of noteEvents) {
5876
+ const map = e.type === "on" ? onCounts : offCounts;
5877
+ map.set(e.note, (map.get(e.note) ?? 0) + 1);
5878
+ }
5879
+ for (const [note, onCount] of onCounts) {
5880
+ const offCount = offCounts.get(note) ?? 0;
5881
+ for (let i = 0; i < onCount - offCount; i++) {
5882
+ noteEvents.push({ tick: currentTick, type: "off", note, velocity: 0 });
5883
+ }
5884
+ }
5743
5885
  noteEvents.sort((a, b) => {
5744
5886
  if (a.tick !== b.tick) return a.tick - b.tick;
5745
5887
  if (a.type !== b.type) return a.type === "off" ? -1 : 1;
@@ -11413,97 +11555,6 @@ async function serializeToFile(score, filePath, options = {}) {
11413
11555
  await (0, import_promises.writeFile)(filePath, xmlString, "utf-8");
11414
11556
  }
11415
11557
  }
11416
-
11417
- // src/entry-accessors.ts
11418
- function getDirectionOfKind(entry, kind) {
11419
- return entry.directionTypes.find((d) => d.kind === kind);
11420
- }
11421
- function getDirectionsOfKind(entry, kind) {
11422
- return entry.directionTypes.filter((d) => d.kind === kind);
11423
- }
11424
- function hasDirectionOfKind(entry, kind) {
11425
- return entry.directionTypes.some((d) => d.kind === kind);
11426
- }
11427
- function getSoundTempo(entry) {
11428
- return entry.sound?.tempo;
11429
- }
11430
- function getSoundDynamics(entry) {
11431
- return entry.sound?.dynamics;
11432
- }
11433
- function getSoundDamperPedal(entry) {
11434
- return entry.sound?.damperPedal;
11435
- }
11436
- function getSoundSoftPedal(entry) {
11437
- return entry.sound?.softPedal;
11438
- }
11439
- function getSoundSostenutoPedal(entry) {
11440
- return entry.sound?.sostenutoPedal;
11441
- }
11442
- function isRest(entry) {
11443
- return entry.rest !== void 0 || !entry.pitch && !entry.unpitched;
11444
- }
11445
- function isPitchedNote(entry) {
11446
- return entry.pitch !== void 0;
11447
- }
11448
- function isUnpitchedNote(entry) {
11449
- return entry.unpitched !== void 0;
11450
- }
11451
- function isChordNote(entry) {
11452
- return entry.chord === true;
11453
- }
11454
- function isGraceNote(entry) {
11455
- return entry.grace !== void 0;
11456
- }
11457
- function hasTie(entry) {
11458
- return entry.tie !== void 0 || entry.ties !== void 0 && entry.ties.length > 0;
11459
- }
11460
- function hasTieStart(entry) {
11461
- if (entry.tie?.type === "start") return true;
11462
- return entry.ties?.some((t) => t.type === "start") ?? false;
11463
- }
11464
- function hasTieStop(entry) {
11465
- if (entry.tie?.type === "stop") return true;
11466
- return entry.ties?.some((t) => t.type === "stop") ?? false;
11467
- }
11468
- function isCueNote(entry) {
11469
- return entry.cue === true;
11470
- }
11471
- function hasBeam(entry) {
11472
- return entry.beam !== void 0 && entry.beam.length > 0;
11473
- }
11474
- function hasLyrics(entry) {
11475
- return entry.lyrics !== void 0 && entry.lyrics.length > 0;
11476
- }
11477
- function hasNotations(entry) {
11478
- return entry.notations !== void 0 && entry.notations.length > 0;
11479
- }
11480
- function hasTuplet(entry) {
11481
- return entry.timeModification !== void 0;
11482
- }
11483
- function isPartInfo(entry) {
11484
- return entry.type === "score-part";
11485
- }
11486
- function getPartInfo(score, partId) {
11487
- return score.partList.find((entry) => {
11488
- return entry.type === "score-part" && entry.id === partId;
11489
- });
11490
- }
11491
- function getPartName(score, partId) {
11492
- return getPartInfo(score, partId)?.name;
11493
- }
11494
- function getPartAbbreviation(score, partId) {
11495
- return getPartInfo(score, partId)?.abbreviation;
11496
- }
11497
- function getAllPartInfos(score) {
11498
- return score.partList.filter(isPartInfo);
11499
- }
11500
- function getPartNameMap(score) {
11501
- const map = {};
11502
- for (const part of getAllPartInfos(score)) {
11503
- map[part.id] = part.name;
11504
- }
11505
- return map;
11506
- }
11507
11558
  // Annotate the CommonJS export names for ESM import in node:
11508
11559
  0 && (module.exports = {
11509
11560
  STEPS,