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.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) {
@@ -5268,6 +5301,97 @@ function serializeCompressed(score, options = {}) {
5268
5301
  return zipSync(files, { level: 6 });
5269
5302
  }
5270
5303
 
5304
+ // src/entry-accessors.ts
5305
+ function getDirectionOfKind(entry, kind) {
5306
+ return entry.directionTypes.find((d) => d.kind === kind);
5307
+ }
5308
+ function getDirectionsOfKind(entry, kind) {
5309
+ return entry.directionTypes.filter((d) => d.kind === kind);
5310
+ }
5311
+ function hasDirectionOfKind(entry, kind) {
5312
+ return entry.directionTypes.some((d) => d.kind === kind);
5313
+ }
5314
+ function getSoundTempo(entry) {
5315
+ return entry.sound?.tempo;
5316
+ }
5317
+ function getSoundDynamics(entry) {
5318
+ return entry.sound?.dynamics;
5319
+ }
5320
+ function getSoundDamperPedal(entry) {
5321
+ return entry.sound?.damperPedal;
5322
+ }
5323
+ function getSoundSoftPedal(entry) {
5324
+ return entry.sound?.softPedal;
5325
+ }
5326
+ function getSoundSostenutoPedal(entry) {
5327
+ return entry.sound?.sostenutoPedal;
5328
+ }
5329
+ function isRest(entry) {
5330
+ return entry.rest !== void 0 || !entry.pitch && !entry.unpitched;
5331
+ }
5332
+ function isPitchedNote(entry) {
5333
+ return entry.pitch !== void 0;
5334
+ }
5335
+ function isUnpitchedNote(entry) {
5336
+ return entry.unpitched !== void 0;
5337
+ }
5338
+ function isChordNote(entry) {
5339
+ return entry.chord === true;
5340
+ }
5341
+ function isGraceNote(entry) {
5342
+ return entry.grace !== void 0;
5343
+ }
5344
+ function hasTie(entry) {
5345
+ return entry.tie !== void 0 || entry.ties !== void 0 && entry.ties.length > 0;
5346
+ }
5347
+ function hasTieStart(entry) {
5348
+ if (entry.tie?.type === "start") return true;
5349
+ return entry.ties?.some((t) => t.type === "start") ?? false;
5350
+ }
5351
+ function hasTieStop(entry) {
5352
+ if (entry.tie?.type === "stop") return true;
5353
+ return entry.ties?.some((t) => t.type === "stop") ?? false;
5354
+ }
5355
+ function isCueNote(entry) {
5356
+ return entry.cue === true;
5357
+ }
5358
+ function hasBeam(entry) {
5359
+ return entry.beam !== void 0 && entry.beam.length > 0;
5360
+ }
5361
+ function hasLyrics(entry) {
5362
+ return entry.lyrics !== void 0 && entry.lyrics.length > 0;
5363
+ }
5364
+ function hasNotations(entry) {
5365
+ return entry.notations !== void 0 && entry.notations.length > 0;
5366
+ }
5367
+ function hasTuplet(entry) {
5368
+ return entry.timeModification !== void 0;
5369
+ }
5370
+ function isPartInfo(entry) {
5371
+ return entry.type === "score-part";
5372
+ }
5373
+ function getPartInfo(score, partId) {
5374
+ return score.partList.find((entry) => {
5375
+ return entry.type === "score-part" && entry.id === partId;
5376
+ });
5377
+ }
5378
+ function getPartName(score, partId) {
5379
+ return getPartInfo(score, partId)?.name;
5380
+ }
5381
+ function getPartAbbreviation(score, partId) {
5382
+ return getPartInfo(score, partId)?.abbreviation;
5383
+ }
5384
+ function getAllPartInfos(score) {
5385
+ return score.partList.filter(isPartInfo);
5386
+ }
5387
+ function getPartNameMap(score) {
5388
+ const map = {};
5389
+ for (const part of getAllPartInfos(score)) {
5390
+ map[part.id] = part.name;
5391
+ }
5392
+ return map;
5393
+ }
5394
+
5271
5395
  // src/exporters/midi.ts
5272
5396
  function exportMidi(score, options = {}) {
5273
5397
  const ticksPerQuarterNote = options.ticksPerQuarterNote ?? 480;
@@ -5425,18 +5549,24 @@ function createPartTrack(part, _score, channel, program, ticksPerQuarterNote, de
5425
5549
  const notePosition = note.chord ? chordBasePosition : position;
5426
5550
  const startTick = measureStartTick + Math.round(notePosition * ticksPerQuarterNote / divisions);
5427
5551
  const durationTicks = Math.round(note.duration * ticksPerQuarterNote / divisions);
5428
- noteEvents.push({
5429
- tick: startTick,
5430
- type: "on",
5431
- note: midiNote,
5432
- velocity: defaultVelocity
5433
- });
5434
- noteEvents.push({
5435
- tick: startTick + durationTicks,
5436
- type: "off",
5437
- note: midiNote,
5438
- velocity: 0
5439
- });
5552
+ const isTieStop = hasTieStop(note);
5553
+ const isTieStart = hasTieStart(note);
5554
+ if (!isTieStop) {
5555
+ noteEvents.push({
5556
+ tick: startTick,
5557
+ type: "on",
5558
+ note: midiNote,
5559
+ velocity: defaultVelocity
5560
+ });
5561
+ }
5562
+ if (!isTieStart) {
5563
+ noteEvents.push({
5564
+ tick: startTick + durationTicks,
5565
+ type: "off",
5566
+ note: midiNote,
5567
+ velocity: 0
5568
+ });
5569
+ }
5440
5570
  }
5441
5571
  if (!note.chord) {
5442
5572
  chordBasePosition = position;
@@ -5469,6 +5599,18 @@ function createPartTrack(part, _score, channel, program, ticksPerQuarterNote, de
5469
5599
  }
5470
5600
  }
5471
5601
  }
5602
+ const onCounts = /* @__PURE__ */ new Map();
5603
+ const offCounts = /* @__PURE__ */ new Map();
5604
+ for (const e of noteEvents) {
5605
+ const map = e.type === "on" ? onCounts : offCounts;
5606
+ map.set(e.note, (map.get(e.note) ?? 0) + 1);
5607
+ }
5608
+ for (const [note, onCount] of onCounts) {
5609
+ const offCount = offCounts.get(note) ?? 0;
5610
+ for (let i = 0; i < onCount - offCount; i++) {
5611
+ noteEvents.push({ tick: currentTick, type: "off", note, velocity: 0 });
5612
+ }
5613
+ }
5472
5614
  noteEvents.sort((a, b) => {
5473
5615
  if (a.tick !== b.tick) return a.tick - b.tick;
5474
5616
  if (a.type !== b.type) return a.type === "off" ? -1 : 1;
@@ -11142,97 +11284,6 @@ async function serializeToFile(score, filePath, options = {}) {
11142
11284
  await writeFile(filePath, xmlString, "utf-8");
11143
11285
  }
11144
11286
  }
11145
-
11146
- // src/entry-accessors.ts
11147
- function getDirectionOfKind(entry, kind) {
11148
- return entry.directionTypes.find((d) => d.kind === kind);
11149
- }
11150
- function getDirectionsOfKind(entry, kind) {
11151
- return entry.directionTypes.filter((d) => d.kind === kind);
11152
- }
11153
- function hasDirectionOfKind(entry, kind) {
11154
- return entry.directionTypes.some((d) => d.kind === kind);
11155
- }
11156
- function getSoundTempo(entry) {
11157
- return entry.sound?.tempo;
11158
- }
11159
- function getSoundDynamics(entry) {
11160
- return entry.sound?.dynamics;
11161
- }
11162
- function getSoundDamperPedal(entry) {
11163
- return entry.sound?.damperPedal;
11164
- }
11165
- function getSoundSoftPedal(entry) {
11166
- return entry.sound?.softPedal;
11167
- }
11168
- function getSoundSostenutoPedal(entry) {
11169
- return entry.sound?.sostenutoPedal;
11170
- }
11171
- function isRest(entry) {
11172
- return entry.rest !== void 0 || !entry.pitch && !entry.unpitched;
11173
- }
11174
- function isPitchedNote(entry) {
11175
- return entry.pitch !== void 0;
11176
- }
11177
- function isUnpitchedNote(entry) {
11178
- return entry.unpitched !== void 0;
11179
- }
11180
- function isChordNote(entry) {
11181
- return entry.chord === true;
11182
- }
11183
- function isGraceNote(entry) {
11184
- return entry.grace !== void 0;
11185
- }
11186
- function hasTie(entry) {
11187
- return entry.tie !== void 0 || entry.ties !== void 0 && entry.ties.length > 0;
11188
- }
11189
- function hasTieStart(entry) {
11190
- if (entry.tie?.type === "start") return true;
11191
- return entry.ties?.some((t) => t.type === "start") ?? false;
11192
- }
11193
- function hasTieStop(entry) {
11194
- if (entry.tie?.type === "stop") return true;
11195
- return entry.ties?.some((t) => t.type === "stop") ?? false;
11196
- }
11197
- function isCueNote(entry) {
11198
- return entry.cue === true;
11199
- }
11200
- function hasBeam(entry) {
11201
- return entry.beam !== void 0 && entry.beam.length > 0;
11202
- }
11203
- function hasLyrics(entry) {
11204
- return entry.lyrics !== void 0 && entry.lyrics.length > 0;
11205
- }
11206
- function hasNotations(entry) {
11207
- return entry.notations !== void 0 && entry.notations.length > 0;
11208
- }
11209
- function hasTuplet(entry) {
11210
- return entry.timeModification !== void 0;
11211
- }
11212
- function isPartInfo(entry) {
11213
- return entry.type === "score-part";
11214
- }
11215
- function getPartInfo(score, partId) {
11216
- return score.partList.find((entry) => {
11217
- return entry.type === "score-part" && entry.id === partId;
11218
- });
11219
- }
11220
- function getPartName(score, partId) {
11221
- return getPartInfo(score, partId)?.name;
11222
- }
11223
- function getPartAbbreviation(score, partId) {
11224
- return getPartInfo(score, partId)?.abbreviation;
11225
- }
11226
- function getAllPartInfos(score) {
11227
- return score.partList.filter(isPartInfo);
11228
- }
11229
- function getPartNameMap(score) {
11230
- const map = {};
11231
- for (const part of getAllPartInfos(score)) {
11232
- map[part.id] = part.name;
11233
- }
11234
- return map;
11235
- }
11236
11287
  export {
11237
11288
  STEPS,
11238
11289
  STEP_SEMITONES,