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.mjs CHANGED
@@ -19,11 +19,22 @@ var xmlParser = new XMLParser({
19
19
  });
20
20
  function parse(xmlString) {
21
21
  const parsed = xmlParser.parse(xmlString);
22
- const scorePartwise = findElement(parsed, "score-partwise");
22
+ let scorePartwiseVersion;
23
+ let scorePartwise;
24
+ for (const el of parsed) {
25
+ if (el["score-partwise"]) {
26
+ scorePartwise = el["score-partwise"];
27
+ const attrs = getAttributes(el);
28
+ if (attrs["version"]) scorePartwiseVersion = attrs["version"];
29
+ break;
30
+ }
31
+ }
23
32
  if (!scorePartwise) {
24
33
  throw new Error("Unsupported MusicXML format: only score-partwise is supported");
25
34
  }
26
- return parseScorePartwise(scorePartwise);
35
+ const score = parseScorePartwise(scorePartwise);
36
+ if (scorePartwiseVersion) score.version = scorePartwiseVersion;
37
+ return score;
27
38
  }
28
39
  function findElement(elements, tagName) {
29
40
  for (const el of elements) {
@@ -3478,7 +3489,7 @@ function validateSlursAcrossMeasures(part) {
3478
3489
 
3479
3490
  // src/exporters/musicxml.ts
3480
3491
  function serialize(score, options = {}) {
3481
- const version = options.version || "4.0";
3492
+ const version = options.version || score.version || "4.0";
3482
3493
  const indent = options.indent ?? " ";
3483
3494
  if (options.validate) {
3484
3495
  const result = validate(score, options.validateOptions);
@@ -3493,11 +3504,7 @@ ${errorMessages}`);
3493
3504
  }
3494
3505
  const lines = [];
3495
3506
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
3496
- if (version === "4.0") {
3497
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3498
- } else {
3499
- lines.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">');
3500
- }
3507
+ lines.push(`<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML ${version} Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">`);
3501
3508
  lines.push(`<score-partwise version="${version}">`);
3502
3509
  lines.push(...serializeMetadata(score.metadata, indent));
3503
3510
  if (score.defaults) {
@@ -4371,283 +4378,309 @@ function serializeNotations(notations, indent) {
4371
4378
  function serializeNotationsGroup(notations, indent) {
4372
4379
  const lines = [];
4373
4380
  lines.push(`${indent}<notations>`);
4374
- const articulationsGroups = /* @__PURE__ */ new Map();
4375
- const ornaments = [];
4376
- const technicals = [];
4377
- const others = [];
4381
+ const chunks = [];
4378
4382
  for (const notation of notations) {
4379
4383
  if (notation.type === "articulation") {
4380
4384
  const artIdx = notation.articulationsIndex ?? 0;
4381
- if (!articulationsGroups.has(artIdx)) {
4382
- articulationsGroups.set(artIdx, []);
4385
+ const last = chunks[chunks.length - 1];
4386
+ if (last && last.kind === "articulations" && last.articulationsIndex === artIdx) {
4387
+ last.items.push(notation);
4388
+ } else {
4389
+ chunks.push({ kind: "articulations", items: [notation], articulationsIndex: artIdx });
4383
4390
  }
4384
- articulationsGroups.get(artIdx).push(notation);
4385
4391
  } else if (notation.type === "ornament") {
4386
- ornaments.push(notation);
4392
+ const last = chunks[chunks.length - 1];
4393
+ if (last && last.kind === "ornaments") {
4394
+ last.items.push(notation);
4395
+ } else {
4396
+ chunks.push({ kind: "ornaments", items: [notation] });
4397
+ }
4387
4398
  } else if (notation.type === "technical") {
4388
- technicals.push(notation);
4399
+ const last = chunks[chunks.length - 1];
4400
+ if (last && last.kind === "technical") {
4401
+ last.items.push(notation);
4402
+ } else {
4403
+ chunks.push({ kind: "technical", items: [notation] });
4404
+ }
4389
4405
  } else {
4390
- others.push(notation);
4406
+ chunks.push({ kind: "standalone", notation });
4391
4407
  }
4392
4408
  }
4393
- for (const notation of others) {
4394
- if (notation.type === "tied") {
4395
- let attrs = ` type="${notation.tiedType}"`;
4396
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4397
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4398
- lines.push(`${indent} <tied${attrs}/>`);
4399
- } else if (notation.type === "slur") {
4400
- let attrs = "";
4401
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4402
- attrs += ` type="${notation.slurType}"`;
4403
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4404
- if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4405
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4406
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4407
- if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4408
- if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4409
- if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4410
- if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4411
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4412
- lines.push(`${indent} <slur${attrs}/>`);
4413
- } else if (notation.type === "tuplet") {
4414
- let attrs = ` type="${notation.tupletType}"`;
4415
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4416
- if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4417
- if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4418
- if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4419
- if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4420
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4421
- const tup = notation;
4422
- if (tup.tupletActual || tup.tupletNormal) {
4423
- lines.push(`${indent} <tuplet${attrs}>`);
4424
- if (tup.tupletActual) {
4425
- lines.push(`${indent} <tuplet-actual>`);
4426
- if (tup.tupletActual.tupletNumber !== void 0) {
4427
- lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4428
- }
4429
- if (tup.tupletActual.tupletType) {
4430
- lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4431
- }
4432
- if (tup.tupletActual.tupletDots) {
4433
- for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4434
- lines.push(`${indent} <tuplet-dot/>`);
4435
- }
4409
+ for (const chunk of chunks) {
4410
+ if (chunk.kind === "standalone") {
4411
+ lines.push(...serializeStandaloneNotation(chunk.notation, indent));
4412
+ } else if (chunk.kind === "articulations") {
4413
+ lines.push(...serializeArticulationsGroup(chunk.items, indent));
4414
+ } else if (chunk.kind === "ornaments") {
4415
+ lines.push(...serializeOrnamentsGroup(chunk.items, indent));
4416
+ } else if (chunk.kind === "technical") {
4417
+ lines.push(...serializeTechnicalGroup(chunk.items, indent));
4418
+ }
4419
+ }
4420
+ lines.push(`${indent}</notations>`);
4421
+ return lines;
4422
+ }
4423
+ function serializeStandaloneNotation(notation, indent) {
4424
+ const lines = [];
4425
+ if (notation.type === "tied") {
4426
+ let attrs = ` type="${notation.tiedType}"`;
4427
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4428
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4429
+ lines.push(`${indent} <tied${attrs}/>`);
4430
+ } else if (notation.type === "slur") {
4431
+ let attrs = "";
4432
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4433
+ attrs += ` type="${notation.slurType}"`;
4434
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4435
+ if (notation.orientation) attrs += ` orientation="${notation.orientation}"`;
4436
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4437
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4438
+ if (notation.bezierX !== void 0) attrs += ` bezier-x="${notation.bezierX}"`;
4439
+ if (notation.bezierY !== void 0) attrs += ` bezier-y="${notation.bezierY}"`;
4440
+ if (notation.bezierX2 !== void 0) attrs += ` bezier-x2="${notation.bezierX2}"`;
4441
+ if (notation.bezierY2 !== void 0) attrs += ` bezier-y2="${notation.bezierY2}"`;
4442
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4443
+ lines.push(`${indent} <slur${attrs}/>`);
4444
+ } else if (notation.type === "tuplet") {
4445
+ let attrs = ` type="${notation.tupletType}"`;
4446
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4447
+ if (notation.bracket !== void 0) attrs += ` bracket="${notation.bracket ? "yes" : "no"}"`;
4448
+ if (notation.showNumber) attrs += ` show-number="${notation.showNumber}"`;
4449
+ if (notation.showType) attrs += ` show-type="${notation.showType}"`;
4450
+ if (notation.lineShape) attrs += ` line-shape="${notation.lineShape}"`;
4451
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4452
+ const tup = notation;
4453
+ if (tup.tupletActual || tup.tupletNormal) {
4454
+ lines.push(`${indent} <tuplet${attrs}>`);
4455
+ if (tup.tupletActual) {
4456
+ lines.push(`${indent} <tuplet-actual>`);
4457
+ if (tup.tupletActual.tupletNumber !== void 0) {
4458
+ lines.push(`${indent} <tuplet-number>${tup.tupletActual.tupletNumber}</tuplet-number>`);
4459
+ }
4460
+ if (tup.tupletActual.tupletType) {
4461
+ lines.push(`${indent} <tuplet-type>${tup.tupletActual.tupletType}</tuplet-type>`);
4462
+ }
4463
+ if (tup.tupletActual.tupletDots) {
4464
+ for (let i = 0; i < tup.tupletActual.tupletDots; i++) {
4465
+ lines.push(`${indent} <tuplet-dot/>`);
4436
4466
  }
4437
- lines.push(`${indent} </tuplet-actual>`);
4438
4467
  }
4439
- if (tup.tupletNormal) {
4440
- lines.push(`${indent} <tuplet-normal>`);
4441
- if (tup.tupletNormal.tupletNumber !== void 0) {
4442
- lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4443
- }
4444
- if (tup.tupletNormal.tupletType) {
4445
- lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4446
- }
4447
- if (tup.tupletNormal.tupletDots) {
4448
- for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4449
- lines.push(`${indent} <tuplet-dot/>`);
4450
- }
4468
+ lines.push(`${indent} </tuplet-actual>`);
4469
+ }
4470
+ if (tup.tupletNormal) {
4471
+ lines.push(`${indent} <tuplet-normal>`);
4472
+ if (tup.tupletNormal.tupletNumber !== void 0) {
4473
+ lines.push(`${indent} <tuplet-number>${tup.tupletNormal.tupletNumber}</tuplet-number>`);
4474
+ }
4475
+ if (tup.tupletNormal.tupletType) {
4476
+ lines.push(`${indent} <tuplet-type>${tup.tupletNormal.tupletType}</tuplet-type>`);
4477
+ }
4478
+ if (tup.tupletNormal.tupletDots) {
4479
+ for (let i = 0; i < tup.tupletNormal.tupletDots; i++) {
4480
+ lines.push(`${indent} <tuplet-dot/>`);
4451
4481
  }
4452
- lines.push(`${indent} </tuplet-normal>`);
4453
4482
  }
4454
- lines.push(`${indent} </tuplet>`);
4455
- } else {
4456
- lines.push(`${indent} <tuplet${attrs}/>`);
4457
- }
4458
- } else if (notation.type === "dynamics") {
4459
- const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4460
- lines.push(`${indent} <dynamics${placementAttr}>`);
4461
- for (const dyn of notation.dynamics) {
4462
- lines.push(`${indent} <${dyn}/>`);
4463
- }
4464
- if (notation.otherDynamics) {
4465
- lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4466
- }
4467
- lines.push(`${indent} </dynamics>`);
4468
- } else if (notation.type === "fermata") {
4469
- let attrs = "";
4470
- if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4471
- if (notation.placement) attrs += ` placement="${notation.placement}"`;
4472
- if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4473
- if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4474
- if (notation.shape) {
4475
- lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4476
- } else {
4477
- lines.push(`${indent} <fermata${attrs}/>`);
4478
- }
4479
- } else if (notation.type === "arpeggiate") {
4480
- let attrs = "";
4481
- if (notation.direction) attrs += ` direction="${notation.direction}"`;
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}"`;
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>`);
4495
- } else if (notation.type === "glissando") {
4496
- let attrs = ` type="${notation.glissandoType}"`;
4497
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4498
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4499
- if (notation.text) {
4500
- lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4501
- } else {
4502
- lines.push(`${indent} <glissando${attrs}/>`);
4503
- }
4504
- } else if (notation.type === "slide") {
4505
- let attrs = ` type="${notation.slideType}"`;
4506
- if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4507
- if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4508
- if (notation.text) {
4509
- lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4510
- } else {
4511
- lines.push(`${indent} <slide${attrs}/>`);
4483
+ lines.push(`${indent} </tuplet-normal>`);
4512
4484
  }
4485
+ lines.push(`${indent} </tuplet>`);
4486
+ } else {
4487
+ lines.push(`${indent} <tuplet${attrs}/>`);
4488
+ }
4489
+ } else if (notation.type === "dynamics") {
4490
+ const placementAttr = notation.placement ? ` placement="${notation.placement}"` : "";
4491
+ lines.push(`${indent} <dynamics${placementAttr}>`);
4492
+ for (const dyn of notation.dynamics) {
4493
+ lines.push(`${indent} <${dyn}/>`);
4494
+ }
4495
+ if (notation.otherDynamics) {
4496
+ lines.push(`${indent} <other-dynamics>${escapeXml(notation.otherDynamics)}</other-dynamics>`);
4497
+ }
4498
+ lines.push(`${indent} </dynamics>`);
4499
+ } else if (notation.type === "fermata") {
4500
+ let attrs = "";
4501
+ if (notation.fermataType) attrs += ` type="${notation.fermataType}"`;
4502
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4503
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4504
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4505
+ if (notation.shape) {
4506
+ lines.push(`${indent} <fermata${attrs}>${notation.shape}</fermata>`);
4507
+ } else {
4508
+ lines.push(`${indent} <fermata${attrs}/>`);
4509
+ }
4510
+ } else if (notation.type === "arpeggiate") {
4511
+ let attrs = "";
4512
+ if (notation.direction) attrs += ` direction="${notation.direction}"`;
4513
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4514
+ if (notation.defaultX !== void 0) attrs += ` default-x="${notation.defaultX}"`;
4515
+ if (notation.defaultY !== void 0) attrs += ` default-y="${notation.defaultY}"`;
4516
+ lines.push(`${indent} <arpeggiate${attrs}/>`);
4517
+ } else if (notation.type === "non-arpeggiate") {
4518
+ let attrs = ` type="${notation.nonArpeggiateType}"`;
4519
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4520
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4521
+ lines.push(`${indent} <non-arpeggiate${attrs}/>`);
4522
+ } else if (notation.type === "accidental-mark") {
4523
+ let attrs = "";
4524
+ if (notation.placement) attrs += ` placement="${notation.placement}"`;
4525
+ lines.push(`${indent} <accidental-mark${attrs}>${escapeXml(notation.value)}</accidental-mark>`);
4526
+ } else if (notation.type === "glissando") {
4527
+ let attrs = ` type="${notation.glissandoType}"`;
4528
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4529
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4530
+ if (notation.text) {
4531
+ lines.push(`${indent} <glissando${attrs}>${escapeXml(notation.text)}</glissando>`);
4532
+ } else {
4533
+ lines.push(`${indent} <glissando${attrs}/>`);
4534
+ }
4535
+ } else if (notation.type === "slide") {
4536
+ let attrs = ` type="${notation.slideType}"`;
4537
+ if (notation.number !== void 0) attrs += ` number="${notation.number}"`;
4538
+ if (notation.lineType) attrs += ` line-type="${notation.lineType}"`;
4539
+ if (notation.text) {
4540
+ lines.push(`${indent} <slide${attrs}>${escapeXml(notation.text)}</slide>`);
4541
+ } else {
4542
+ lines.push(`${indent} <slide${attrs}/>`);
4513
4543
  }
4514
4544
  }
4515
- const sortedArtIndices = Array.from(articulationsGroups.keys()).sort((a, b) => a - b);
4516
- for (const artIdx of sortedArtIndices) {
4517
- const artGroup = articulationsGroups.get(artIdx);
4518
- lines.push(`${indent} <articulations>`);
4519
- for (const art of artGroup) {
4520
- if (art.type === "articulation") {
4521
- let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4522
- if (art.articulation === "strong-accent" && art.strongAccentType) {
4523
- artAttrs += ` type="${art.strongAccentType}"`;
4524
- }
4525
- if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4526
- if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4527
- lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4545
+ return lines;
4546
+ }
4547
+ function serializeArticulationsGroup(artGroup, indent) {
4548
+ const lines = [];
4549
+ lines.push(`${indent} <articulations>`);
4550
+ for (const art of artGroup) {
4551
+ if (art.type === "articulation") {
4552
+ let artAttrs = art.placement ? ` placement="${art.placement}"` : "";
4553
+ if (art.articulation === "strong-accent" && art.strongAccentType) {
4554
+ artAttrs += ` type="${art.strongAccentType}"`;
4528
4555
  }
4556
+ if (art.defaultX !== void 0) artAttrs += ` default-x="${art.defaultX}"`;
4557
+ if (art.defaultY !== void 0) artAttrs += ` default-y="${art.defaultY}"`;
4558
+ lines.push(`${indent} <${art.articulation}${artAttrs}/>`);
4529
4559
  }
4530
- lines.push(`${indent} </articulations>`);
4531
4560
  }
4532
- if (ornaments.length > 0) {
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
- }
4561
+ lines.push(`${indent} </articulations>`);
4562
+ return lines;
4563
+ }
4564
+ function serializeOrnamentsGroup(ornaments, indent) {
4565
+ const lines = [];
4566
+ const hasOnlyEmptyMarker = ornaments.length === 1 && ornaments[0].type === "ornament" && ornaments[0].ornament === "empty";
4567
+ if (hasOnlyEmptyMarker) {
4568
+ lines.push(`${indent} <ornaments/>`);
4569
+ } else {
4570
+ lines.push(`${indent} <ornaments>`);
4571
+ const allAccidentalMarks = [];
4572
+ for (const orn of ornaments) {
4573
+ if (orn.type === "ornament") {
4574
+ if (orn.ornament === "empty") continue;
4575
+ const placementAttr = orn.placement ? ` placement="${orn.placement}"` : "";
4576
+ if (orn.ornament === "wavy-line") {
4577
+ let wlAttrs = "";
4578
+ if (orn.wavyLineType) wlAttrs += ` type="${orn.wavyLineType}"`;
4579
+ if (orn.number !== void 0) wlAttrs += ` number="${orn.number}"`;
4580
+ wlAttrs += placementAttr;
4581
+ if (orn.defaultY !== void 0) wlAttrs += ` default-y="${orn.defaultY}"`;
4582
+ lines.push(`${indent} <wavy-line${wlAttrs}/>`);
4583
+ } else if (orn.ornament === "tremolo") {
4584
+ let tremAttrs = "";
4585
+ if (orn.tremoloType) tremAttrs += ` type="${orn.tremoloType}"`;
4586
+ tremAttrs += placementAttr;
4587
+ if (orn.defaultX !== void 0) tremAttrs += ` default-x="${orn.defaultX}"`;
4588
+ if (orn.defaultY !== void 0) tremAttrs += ` default-y="${orn.defaultY}"`;
4589
+ if (orn.tremoloMarks !== void 0) {
4590
+ lines.push(`${indent} <tremolo${tremAttrs}>${orn.tremoloMarks}</tremolo>`);
4561
4591
  } else {
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);
4592
+ lines.push(`${indent} <tremolo${tremAttrs}/>`);
4568
4593
  }
4594
+ } else {
4595
+ let ornAttrs = placementAttr;
4596
+ if (orn.defaultY !== void 0) ornAttrs += ` default-y="${orn.defaultY}"`;
4597
+ lines.push(`${indent} <${orn.ornament}${ornAttrs}/>`);
4598
+ }
4599
+ if (orn.accidentalMarks) {
4600
+ allAccidentalMarks.push(...orn.accidentalMarks);
4569
4601
  }
4570
4602
  }
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>`);
4576
4603
  }
4604
+ for (const am of allAccidentalMarks) {
4605
+ const amPlacement = am.placement ? ` placement="${am.placement}"` : "";
4606
+ lines.push(`${indent} <accidental-mark${amPlacement}>${am.value}</accidental-mark>`);
4607
+ }
4608
+ lines.push(`${indent} </ornaments>`);
4577
4609
  }
4578
- if (technicals.length > 0) {
4579
- lines.push(`${indent} <technical>`);
4580
- for (const tech of technicals) {
4581
- if (tech.type === "technical") {
4582
- let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4583
- const techNotation = tech;
4584
- if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4585
- if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4586
- if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4587
- lines.push(`${indent} <bend${placementAttr}>`);
4588
- if (techNotation.bendAlter !== void 0) {
4589
- lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4590
- }
4591
- if (techNotation.preBend) {
4592
- lines.push(`${indent} <pre-bend/>`);
4593
- }
4594
- if (techNotation.release) {
4595
- lines.push(`${indent} <release/>`);
4596
- }
4597
- if (techNotation.withBar) {
4598
- lines.push(`${indent} <with-bar/>`);
4599
- }
4600
- lines.push(`${indent} </bend>`);
4601
- } else if (tech.technical === "harmonic") {
4602
- const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4603
- if (hasChildren) {
4604
- lines.push(`${indent} <harmonic${placementAttr}>`);
4605
- if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4606
- if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4607
- if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4608
- if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4609
- if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4610
- lines.push(`${indent} </harmonic>`);
4611
- } else {
4612
- lines.push(`${indent} <harmonic${placementAttr}/>`);
4613
- }
4614
- } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4615
- let attrs = "";
4616
- if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4617
- if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4618
- attrs += placementAttr;
4619
- if (techNotation.text !== void 0) {
4620
- lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4621
- } else {
4622
- lines.push(`${indent} <${tech.technical}${attrs}/>`);
4623
- }
4624
- } else if (tech.technical === "string" && techNotation.string !== void 0) {
4625
- lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4626
- } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4627
- lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4628
- } else if (tech.technical === "fingering") {
4629
- let fAttrs = placementAttr;
4630
- if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4631
- if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4632
- if (techNotation.text !== void 0) {
4633
- lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4634
- } else {
4635
- lines.push(`${indent} <fingering${fAttrs}/>`);
4636
- }
4637
- } else if (tech.technical === "heel" || tech.technical === "toe") {
4638
- let htAttrs = placementAttr;
4639
- if (techNotation.substitution) htAttrs += ' substitution="yes"';
4640
- lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4641
- } else if (techNotation.text !== void 0) {
4642
- lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4610
+ return lines;
4611
+ }
4612
+ function serializeTechnicalGroup(technicals, indent) {
4613
+ const lines = [];
4614
+ lines.push(`${indent} <technical>`);
4615
+ for (const tech of technicals) {
4616
+ if (tech.type === "technical") {
4617
+ let placementAttr = tech.placement ? ` placement="${tech.placement}"` : "";
4618
+ const techNotation = tech;
4619
+ if (techNotation.defaultX !== void 0) placementAttr += ` default-x="${techNotation.defaultX}"`;
4620
+ if (techNotation.defaultY !== void 0) placementAttr += ` default-y="${techNotation.defaultY}"`;
4621
+ if (tech.technical === "bend" && (techNotation.bendAlter !== void 0 || techNotation.preBend || techNotation.release)) {
4622
+ lines.push(`${indent} <bend${placementAttr}>`);
4623
+ if (techNotation.bendAlter !== void 0) {
4624
+ lines.push(`${indent} <bend-alter>${techNotation.bendAlter}</bend-alter>`);
4625
+ }
4626
+ if (techNotation.preBend) {
4627
+ lines.push(`${indent} <pre-bend/>`);
4628
+ }
4629
+ if (techNotation.release) {
4630
+ lines.push(`${indent} <release/>`);
4631
+ }
4632
+ if (techNotation.withBar) {
4633
+ lines.push(`${indent} <with-bar/>`);
4634
+ }
4635
+ lines.push(`${indent} </bend>`);
4636
+ } else if (tech.technical === "harmonic") {
4637
+ const hasChildren = techNotation.harmonicNatural || techNotation.harmonicArtificial || techNotation.basePitch || techNotation.touchingPitch || techNotation.soundingPitch;
4638
+ if (hasChildren) {
4639
+ lines.push(`${indent} <harmonic${placementAttr}>`);
4640
+ if (techNotation.harmonicNatural) lines.push(`${indent} <natural/>`);
4641
+ if (techNotation.harmonicArtificial) lines.push(`${indent} <artificial/>`);
4642
+ if (techNotation.basePitch) lines.push(`${indent} <base-pitch/>`);
4643
+ if (techNotation.touchingPitch) lines.push(`${indent} <touching-pitch/>`);
4644
+ if (techNotation.soundingPitch) lines.push(`${indent} <sounding-pitch/>`);
4645
+ lines.push(`${indent} </harmonic>`);
4643
4646
  } else {
4644
- lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4645
- }
4647
+ lines.push(`${indent} <harmonic${placementAttr}/>`);
4648
+ }
4649
+ } else if (tech.technical === "hammer-on" || tech.technical === "pull-off") {
4650
+ let attrs = "";
4651
+ if (techNotation.number !== void 0) attrs += ` number="${techNotation.number}"`;
4652
+ if (techNotation.startStop) attrs += ` type="${techNotation.startStop}"`;
4653
+ attrs += placementAttr;
4654
+ if (techNotation.text !== void 0) {
4655
+ lines.push(`${indent} <${tech.technical}${attrs}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4656
+ } else {
4657
+ lines.push(`${indent} <${tech.technical}${attrs}/>`);
4658
+ }
4659
+ } else if (tech.technical === "string" && techNotation.string !== void 0) {
4660
+ lines.push(`${indent} <string${placementAttr}>${techNotation.string}</string>`);
4661
+ } else if (tech.technical === "fret" && techNotation.fret !== void 0) {
4662
+ lines.push(`${indent} <fret${placementAttr}>${techNotation.fret}</fret>`);
4663
+ } else if (tech.technical === "fingering") {
4664
+ let fAttrs = placementAttr;
4665
+ if (techNotation.fingeringSubstitution) fAttrs += ' substitution="yes"';
4666
+ if (techNotation.fingeringAlternate) fAttrs += ' alternate="yes"';
4667
+ if (techNotation.text !== void 0) {
4668
+ lines.push(`${indent} <fingering${fAttrs}>${escapeXml(techNotation.text)}</fingering>`);
4669
+ } else {
4670
+ lines.push(`${indent} <fingering${fAttrs}/>`);
4671
+ }
4672
+ } else if (tech.technical === "heel" || tech.technical === "toe") {
4673
+ let htAttrs = placementAttr;
4674
+ if (techNotation.substitution) htAttrs += ' substitution="yes"';
4675
+ lines.push(`${indent} <${tech.technical}${htAttrs}/>`);
4676
+ } else if (techNotation.text !== void 0) {
4677
+ lines.push(`${indent} <${tech.technical}${placementAttr}>${escapeXml(techNotation.text)}</${tech.technical}>`);
4678
+ } else {
4679
+ lines.push(`${indent} <${tech.technical}${placementAttr}/>`);
4646
4680
  }
4647
4681
  }
4648
- lines.push(`${indent} </technical>`);
4649
4682
  }
4650
- lines.push(`${indent}</notations>`);
4683
+ lines.push(`${indent} </technical>`);
4651
4684
  return lines;
4652
4685
  }
4653
4686
  function serializeLyric(lyric, indent) {