musicxml-io 0.7.1 → 0.7.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.
@@ -8,7 +8,7 @@ import {
8
8
  getMeasureEndPosition,
9
9
  pitchToSemitone,
10
10
  semitoneToKeyAwarePitch
11
- } from "./chunk-MA2TPIIG.mjs";
11
+ } from "./chunk-VQUFSGB5.mjs";
12
12
 
13
13
  // src/id.ts
14
14
  import { customAlphabet } from "nanoid";
@@ -8,7 +8,7 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkF6GPX6VWjs = require('./chunk-F6GPX6VW.js');
11
+ var _chunkCHMV7XWYjs = require('./chunk-CHMV7XWY.js');
12
12
 
13
13
  // src/id.ts
14
14
  var _nanoid = require('nanoid');
@@ -1562,18 +1562,18 @@ function setNotePitchBySemitone(score, options) {
1562
1562
  const result = cloneScore(score);
1563
1563
  const measure = result.parts[options.partIndex].measures[options.measureIndex];
1564
1564
  const measureNumber = _nullishCoalesce(measure.number, () => ( String(options.measureIndex + 1)));
1565
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1565
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1566
1566
  const keySignature = _nullishCoalesce(attrs.key, () => ( { fifths: 0 }));
1567
1567
  let noteCount = 0;
1568
1568
  for (const entry of measure.entries) {
1569
1569
  if (entry.type === "note" && !entry.rest) {
1570
1570
  if (noteCount === options.noteIndex) {
1571
- const notePosition = _chunkF6GPX6VWjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1572
- const accidentalsInMeasure = _chunkF6GPX6VWjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1573
- const newPitch = _chunkF6GPX6VWjs.semitoneToKeyAwarePitch.call(void 0, options.semitone, keySignature, {
1571
+ const notePosition = _chunkCHMV7XWYjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1572
+ const accidentalsInMeasure = _chunkCHMV7XWYjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1573
+ const newPitch = _chunkCHMV7XWYjs.semitoneToKeyAwarePitch.call(void 0, options.semitone, keySignature, {
1574
1574
  preferSharp: options.preferSharp
1575
1575
  });
1576
- const accidental = _chunkF6GPX6VWjs.determineAccidental.call(void 0, newPitch, keySignature, accidentalsInMeasure);
1576
+ const accidental = _chunkCHMV7XWYjs.determineAccidental.call(void 0, newPitch, keySignature, accidentalsInMeasure);
1577
1577
  entry.pitch = newPitch;
1578
1578
  if (accidental) {
1579
1579
  entry.accidental = { value: accidental };
@@ -1607,7 +1607,7 @@ function shiftNotePitch(score, options) {
1607
1607
  if (!entry.pitch) {
1608
1608
  return failure([operationError("NOTE_NOT_FOUND", "Note has no pitch", { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
1609
1609
  }
1610
- currentSemitone = _chunkF6GPX6VWjs.pitchToSemitone.call(void 0, entry.pitch);
1610
+ currentSemitone = _chunkCHMV7XWYjs.pitchToSemitone.call(void 0, entry.pitch);
1611
1611
  break;
1612
1612
  }
1613
1613
  noteCount++;
@@ -1635,7 +1635,7 @@ function raiseAccidental(score, options) {
1635
1635
  const result = cloneScore(score);
1636
1636
  const measure = result.parts[options.partIndex].measures[options.measureIndex];
1637
1637
  const measureNumber = _nullishCoalesce(measure.number, () => ( String(options.measureIndex + 1)));
1638
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1638
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1639
1639
  const keySignature = _nullishCoalesce(attrs.key, () => ( { fifths: 0 }));
1640
1640
  let noteCount = 0;
1641
1641
  for (const entry of measure.entries) {
@@ -1650,9 +1650,9 @@ function raiseAccidental(score, options) {
1650
1650
  return failure([operationError("ACCIDENTAL_OUT_OF_BOUNDS", `Cannot raise accidental beyond double-sharp (current: ${currentAlter})`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
1651
1651
  }
1652
1652
  entry.pitch.alter = newAlter === 0 ? void 0 : newAlter;
1653
- const notePosition = _chunkF6GPX6VWjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1654
- const accidentalsInMeasure = _chunkF6GPX6VWjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1655
- const accidental = _chunkF6GPX6VWjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1653
+ const notePosition = _chunkCHMV7XWYjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1654
+ const accidentalsInMeasure = _chunkCHMV7XWYjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1655
+ const accidental = _chunkCHMV7XWYjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1656
1656
  if (accidental) {
1657
1657
  entry.accidental = { value: accidental };
1658
1658
  } else {
@@ -1676,7 +1676,7 @@ function lowerAccidental(score, options) {
1676
1676
  const result = cloneScore(score);
1677
1677
  const measure = result.parts[options.partIndex].measures[options.measureIndex];
1678
1678
  const measureNumber = _nullishCoalesce(measure.number, () => ( String(options.measureIndex + 1)));
1679
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1679
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1680
1680
  const keySignature = _nullishCoalesce(attrs.key, () => ( { fifths: 0 }));
1681
1681
  let noteCount = 0;
1682
1682
  for (const entry of measure.entries) {
@@ -1691,9 +1691,9 @@ function lowerAccidental(score, options) {
1691
1691
  return failure([operationError("ACCIDENTAL_OUT_OF_BOUNDS", `Cannot lower accidental beyond double-flat (current: ${currentAlter})`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
1692
1692
  }
1693
1693
  entry.pitch.alter = newAlter === 0 ? void 0 : newAlter;
1694
- const notePosition = _chunkF6GPX6VWjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1695
- const accidentalsInMeasure = _chunkF6GPX6VWjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1696
- const accidental = _chunkF6GPX6VWjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1694
+ const notePosition = _chunkCHMV7XWYjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1695
+ const accidentalsInMeasure = _chunkCHMV7XWYjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1696
+ const accidental = _chunkCHMV7XWYjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1697
1697
  if (accidental) {
1698
1698
  entry.accidental = { value: accidental };
1699
1699
  } else {
@@ -1727,7 +1727,7 @@ function addVoice(score, options) {
1727
1727
  const context = getMeasureContext(result, options.partIndex, options.measureIndex);
1728
1728
  const measureDuration = context.time ? getMeasureDuration(context.divisions, context.time) : context.divisions * 4;
1729
1729
  const rest = createRest(measureDuration, options.voice, options.staff);
1730
- const currentEnd = _chunkF6GPX6VWjs.getMeasureEndPosition.call(void 0, measure);
1730
+ const currentEnd = _chunkCHMV7XWYjs.getMeasureEndPosition.call(void 0, measure);
1731
1731
  if (currentEnd > 0) {
1732
1732
  measure.entries.push({ _id: generateId(), type: "backup", duration: currentEnd });
1733
1733
  }
@@ -1735,14 +1735,14 @@ function addVoice(score, options) {
1735
1735
  return success(result);
1736
1736
  }
1737
1737
  function transposePitch(pitch, semitones) {
1738
- const currentSemitone = _chunkF6GPX6VWjs.STEP_SEMITONES[pitch.step] + (_nullishCoalesce(pitch.alter, () => ( 0))) + pitch.octave * 12;
1738
+ const currentSemitone = _chunkCHMV7XWYjs.STEP_SEMITONES[pitch.step] + (_nullishCoalesce(pitch.alter, () => ( 0))) + pitch.octave * 12;
1739
1739
  const targetSemitone = currentSemitone + semitones;
1740
1740
  const targetOctave = Math.floor(targetSemitone / 12);
1741
1741
  const targetPitchClass = (targetSemitone % 12 + 12) % 12;
1742
1742
  let bestStep = "C";
1743
1743
  let bestAlter = 99;
1744
- for (const step of _chunkF6GPX6VWjs.STEPS) {
1745
- const stepSemitone = _chunkF6GPX6VWjs.STEP_SEMITONES[step];
1744
+ for (const step of _chunkCHMV7XWYjs.STEPS) {
1745
+ const stepSemitone = _chunkCHMV7XWYjs.STEP_SEMITONES[step];
1746
1746
  let diff = targetPitchClass - stepSemitone;
1747
1747
  if (diff > 6) diff -= 12;
1748
1748
  if (diff < -6) diff += 12;
@@ -3805,7 +3805,7 @@ function addDaCapo(score, options) {
3805
3805
  }
3806
3806
  const result = cloneScore(score);
3807
3807
  const measure = result.parts[partIndex].measures[measureIndex];
3808
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3808
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3809
3809
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3810
3810
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3811
3811
  const direction = {
@@ -3834,7 +3834,7 @@ function addDalSegno(score, options) {
3834
3834
  }
3835
3835
  const result = cloneScore(score);
3836
3836
  const measure = result.parts[partIndex].measures[measureIndex];
3837
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3837
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3838
3838
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3839
3839
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3840
3840
  const direction = {
@@ -3863,7 +3863,7 @@ function addFine(score, options) {
3863
3863
  }
3864
3864
  const result = cloneScore(score);
3865
3865
  const measure = result.parts[partIndex].measures[measureIndex];
3866
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3866
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3867
3867
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3868
3868
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3869
3869
  const direction = {
@@ -3892,7 +3892,7 @@ function addToCoda(score, options) {
3892
3892
  }
3893
3893
  const result = cloneScore(score);
3894
3894
  const measure = result.parts[partIndex].measures[measureIndex];
3895
- const attrs = _chunkF6GPX6VWjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3895
+ const attrs = _chunkCHMV7XWYjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3896
3896
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3897
3897
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3898
3898
  const direction = {
@@ -510,6 +510,10 @@ function hasPlaybackControls(score, options) {
510
510
  }
511
511
 
512
512
  // src/query/playback-timeline.ts
513
+ var DEFAULT_FERMATA_HOLD = 1.75;
514
+ var DEFAULT_CAESURA_SECONDS = 0.4;
515
+ var DEFAULT_BREATH_SECONDS = 0.25;
516
+ var DEFAULT_RAMP_STEPS = 12;
513
517
  function metronomeBpm(perMinute) {
514
518
  if (perMinute === void 0) return null;
515
519
  const bpm = typeof perMinute === "number" ? perMinute : parseFloat(perMinute);
@@ -562,15 +566,48 @@ function measureEndTick(measure, part, divisions, measureStartTick, maxPosition,
562
566
  }
563
567
  return measureStartTick + actualTicks;
564
568
  }
565
- function buildGridTimeline(score, ticksPerQuarterNote, sequence) {
566
- const tempoEvents = [];
569
+ function hasFermata(note) {
570
+ return note.notations?.some((n) => n.type === "fermata") ?? false;
571
+ }
572
+ function pauseArticulation(note) {
573
+ if (!note.notations) return null;
574
+ for (const n of note.notations) {
575
+ if (n.type === "articulation") {
576
+ if (n.articulation === "caesura") return "caesura";
577
+ if (n.articulation === "breath-mark") return "breath";
578
+ }
579
+ }
580
+ return null;
581
+ }
582
+ function tempoRampWord(text) {
583
+ const t = text.trim().toLowerCase().replace(/[.\s]+$/, "");
584
+ if (!t) return null;
585
+ if (/^(rit|riten|rall|allarg|slarg|cala|smorz|morend)/.test(t)) return "rit";
586
+ if (/^(accel|string|affret|incalz|pi[uù]\s*mosso)/.test(t)) return "accel";
587
+ return null;
588
+ }
589
+ function buildGridTimeline(score, ticksPerQuarterNote, sequence, options = {}) {
590
+ const rawTempo = [];
567
591
  const measures = [];
592
+ const rampMarkers = [];
593
+ const stretches = [];
568
594
  if (score.parts.length === 0) {
569
- return { tempoEvents, measures, totalTicks: 0 };
595
+ return { tempoEvents: [], measures, totalTicks: 0, expressions: [] };
570
596
  }
597
+ const fermataHold = options.fermataHoldMultiplier ?? DEFAULT_FERMATA_HOLD;
598
+ const caesuraSeconds = options.caesuraSeconds ?? DEFAULT_CAESURA_SECONDS;
599
+ const breathSeconds = options.breathSeconds ?? DEFAULT_BREATH_SECONDS;
600
+ const rampSteps = Math.max(1, Math.round(options.tempoRampSteps ?? DEFAULT_RAMP_STEPS));
601
+ const defaultTempo = options.defaultTempo ?? 120;
571
602
  const part = score.parts[0];
572
603
  let divisions = 1;
573
604
  let currentTick = 0;
605
+ let prevailingBpm = defaultTempo;
606
+ const pauseFactor = (windowTicks, bpm, pauseSeconds) => {
607
+ const baseSeconds = windowTicks / ticksPerQuarterNote * (60 / bpm);
608
+ if (baseSeconds <= 0) return 1;
609
+ return (baseSeconds + pauseSeconds) / baseSeconds;
610
+ };
574
611
  for (const { measureIndex, repeatIteration } of sequence) {
575
612
  const measure = part.measures[measureIndex];
576
613
  if (!measure) continue;
@@ -579,22 +616,62 @@ function buildGridTimeline(score, ticksPerQuarterNote, sequence) {
579
616
  }
580
617
  const measureStartTick = currentTick;
581
618
  let position = 0;
619
+ let chordBasePosition = 0;
582
620
  const tickAt = (pos) => measureStartTick + Math.round(pos * ticksPerQuarterNote / divisions);
583
621
  for (const entry of measure.entries) {
584
622
  if (entry.type === "direction") {
585
623
  for (const dirType of entry.directionTypes) {
586
624
  if (dirType.kind === "metronome") {
587
625
  const bpm = metronomeBpm(dirType.perMinute);
588
- if (bpm !== null) tempoEvents.push({ tick: tickAt(position), bpm });
626
+ if (bpm !== null) {
627
+ rawTempo.push({ tick: tickAt(position), bpm });
628
+ prevailingBpm = bpm;
629
+ }
630
+ } else if (dirType.kind === "words") {
631
+ const kind = tempoRampWord(dirType.text);
632
+ if (kind) rampMarkers.push({ tick: tickAt(position), kind });
589
633
  }
590
634
  }
591
635
  if (entry.sound?.tempo) {
592
- tempoEvents.push({ tick: tickAt(position), bpm: entry.sound.tempo });
636
+ rawTempo.push({ tick: tickAt(position), bpm: entry.sound.tempo });
637
+ prevailingBpm = entry.sound.tempo;
593
638
  }
594
639
  } else if (entry.type === "sound") {
595
- if (entry.tempo) tempoEvents.push({ tick: tickAt(position), bpm: entry.tempo });
596
- } else if (entry.type === "note" && !entry.chord) {
597
- position += entry.duration;
640
+ if (entry.tempo) {
641
+ rawTempo.push({ tick: tickAt(position), bpm: entry.tempo });
642
+ prevailingBpm = entry.tempo;
643
+ }
644
+ } else if (entry.type === "note") {
645
+ const notePos = entry.chord ? chordBasePosition : position;
646
+ const noteStartTick = tickAt(notePos);
647
+ const noteEndTick = tickAt(notePos + entry.duration);
648
+ if (noteEndTick > noteStartTick) {
649
+ if (fermataHold !== 1 && hasFermata(entry)) {
650
+ stretches.push({
651
+ startTick: noteStartTick,
652
+ endTick: noteEndTick,
653
+ factor: fermataHold,
654
+ type: "fermata"
655
+ });
656
+ }
657
+ const pause = pauseArticulation(entry);
658
+ if (pause) {
659
+ const seconds = pause === "caesura" ? caesuraSeconds : breathSeconds;
660
+ if (seconds > 0) {
661
+ const window = Math.min(noteEndTick - noteStartTick, ticksPerQuarterNote);
662
+ stretches.push({
663
+ startTick: noteEndTick - window,
664
+ endTick: noteEndTick,
665
+ factor: pauseFactor(window, prevailingBpm, seconds),
666
+ type: pause
667
+ });
668
+ }
669
+ }
670
+ }
671
+ if (!entry.chord) {
672
+ chordBasePosition = position;
673
+ position += entry.duration;
674
+ }
598
675
  } else if (entry.type === "backup") {
599
676
  position -= entry.duration;
600
677
  } else if (entry.type === "forward") {
@@ -618,28 +695,112 @@ function buildGridTimeline(score, ticksPerQuarterNote, sequence) {
618
695
  });
619
696
  currentTick = endTick;
620
697
  }
621
- return { tempoEvents, measures, totalTicks: currentTick };
698
+ const { tempoEvents, expressions } = bakeTempoMap(
699
+ rawTempo,
700
+ rampMarkers,
701
+ stretches,
702
+ defaultTempo,
703
+ rampSteps
704
+ );
705
+ return { tempoEvents, measures, totalTicks: currentTick, expressions };
706
+ }
707
+ function bakeTempoMap(rawTempo, rampMarkers, stretches, defaultTempo, rampSteps) {
708
+ const sortedRaw = [...rawTempo].sort((a, b) => a.tick - b.tick);
709
+ const startBpm = sortedRaw.length > 0 && sortedRaw[0].tick === 0 ? sortedRaw[0].bpm : defaultTempo;
710
+ const baseChanges = [{ tick: 0, bpm: startBpm }];
711
+ let lastBpm = startBpm;
712
+ for (const ev of sortedRaw) {
713
+ if (ev.tick === 0) continue;
714
+ if (ev.bpm === lastBpm) continue;
715
+ if (baseChanges[baseChanges.length - 1].tick === ev.tick) {
716
+ baseChanges[baseChanges.length - 1].bpm = ev.bpm;
717
+ } else {
718
+ baseChanges.push({ tick: ev.tick, bpm: ev.bpm });
719
+ }
720
+ lastBpm = ev.bpm;
721
+ }
722
+ const baseBpmAt = (tick) => {
723
+ let i = baseChanges.length - 1;
724
+ while (i > 0 && baseChanges[i].tick > tick) i--;
725
+ return baseChanges[i].bpm;
726
+ };
727
+ const rampExpressions = [];
728
+ const rampPoints = [];
729
+ for (const marker of rampMarkers) {
730
+ const from = baseBpmAt(marker.tick);
731
+ const next = baseChanges.find((c) => c.tick > marker.tick && c.bpm !== from);
732
+ if (!next) continue;
733
+ const span = next.tick - marker.tick;
734
+ if (span <= 0) continue;
735
+ for (let k = 1; k < rampSteps; k++) {
736
+ const tick = Math.round(marker.tick + span * k / rampSteps);
737
+ if (tick <= marker.tick || tick >= next.tick) continue;
738
+ const bpm = from + (next.bpm - from) * k / rampSteps;
739
+ rampPoints.push({ tick, bpm });
740
+ }
741
+ rampExpressions.push({ type: marker.kind, startTick: marker.tick, endTick: next.tick });
742
+ }
743
+ const baseTicks = new Set(baseChanges.map((c) => c.tick));
744
+ const stepList = [...baseChanges];
745
+ for (const p of rampPoints) {
746
+ if (!baseTicks.has(p.tick)) stepList.push(p);
747
+ }
748
+ stepList.sort((a, b) => a.tick - b.tick);
749
+ const stepBpmAt = (tick) => {
750
+ let i = stepList.length - 1;
751
+ while (i > 0 && stepList[i].tick > tick) i--;
752
+ return stepList[i].bpm;
753
+ };
754
+ const boundaries = new Set(stepList.map((s) => s.tick));
755
+ for (const s of stretches) {
756
+ boundaries.add(s.startTick);
757
+ boundaries.add(s.endTick);
758
+ }
759
+ const sortedBoundaries = [...boundaries].sort((a, b) => a - b);
760
+ const tempoEvents = [];
761
+ let emittedBpm = null;
762
+ for (const tick of sortedBoundaries) {
763
+ let bpm = stepBpmAt(tick);
764
+ for (const s of stretches) {
765
+ if (tick >= s.startTick && tick < s.endTick) bpm /= s.factor;
766
+ }
767
+ if (emittedBpm === null || bpm !== emittedBpm) {
768
+ tempoEvents.push({ tick, bpm });
769
+ emittedBpm = bpm;
770
+ }
771
+ }
772
+ const expressions = [
773
+ ...stretches.map((s) => ({ type: s.type, startTick: s.startTick, endTick: s.endTick })),
774
+ ...rampExpressions
775
+ ].sort((a, b) => a.startTick - b.startTick || a.endTick - b.endTick);
776
+ return { tempoEvents, expressions };
777
+ }
778
+ function bpmToUsPerQuarter(bpm) {
779
+ return Math.round(6e7 / bpm);
622
780
  }
623
781
  function makeTickToSec(tempoEvents, defaultTempo, ticksPerQuarterNote) {
624
782
  const sorted = [...tempoEvents].sort((a, b) => a.tick - b.tick);
625
783
  const startBpm = sorted.length > 0 && sorted[0].tick === 0 ? sorted[0].bpm : defaultTempo;
626
- const changes = [{ tick: 0, bpm: startBpm }];
784
+ const changes = [
785
+ { tick: 0, usPerQuarter: bpmToUsPerQuarter(startBpm) }
786
+ ];
627
787
  let lastBpm = startBpm;
628
788
  for (const ev of sorted) {
629
789
  if (ev.tick === 0) continue;
630
790
  if (ev.bpm === lastBpm) continue;
631
- changes.push({ tick: ev.tick, bpm: ev.bpm });
791
+ changes.push({ tick: ev.tick, usPerQuarter: bpmToUsPerQuarter(ev.bpm) });
632
792
  lastBpm = ev.bpm;
633
793
  }
794
+ const secPerTick = (usPerQuarter) => usPerQuarter / 1e6 / ticksPerQuarterNote;
634
795
  const cumSec = [0];
635
796
  for (let i = 1; i < changes.length; i++) {
636
797
  const dTick = changes[i].tick - changes[i - 1].tick;
637
- cumSec[i] = cumSec[i - 1] + dTick / ticksPerQuarterNote * (60 / changes[i - 1].bpm);
798
+ cumSec[i] = cumSec[i - 1] + dTick * secPerTick(changes[i - 1].usPerQuarter);
638
799
  }
639
800
  return (tick) => {
640
801
  let i = changes.length - 1;
641
802
  while (i > 0 && changes[i].tick > tick) i--;
642
- return cumSec[i] + (tick - changes[i].tick) / ticksPerQuarterNote * (60 / changes[i].bpm);
803
+ return cumSec[i] + (tick - changes[i].tick) * secPerTick(changes[i].usPerQuarter);
643
804
  };
644
805
  }
645
806
  function buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote) {
@@ -676,18 +837,32 @@ function buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote) {
676
837
  repeatIteration: last ? last.repeatIteration : 0
677
838
  });
678
839
  breakpoints.sort((a, b) => a.midiSec - b.midiSec || a.quarterPos - b.quarterPos);
679
- return {
840
+ const sidecar = {
680
841
  version: "1",
681
842
  durationSec: tickToSec(grid.totalTicks),
682
843
  ticksPerQuarterNote,
683
844
  breakpoints
684
845
  };
846
+ if (grid.expressions.length > 0) {
847
+ sidecar.expressions = grid.expressions.map((e) => ({
848
+ type: e.type,
849
+ fromMidiSec: tickToSec(e.startTick),
850
+ toMidiSec: tickToSec(e.endTick)
851
+ })).sort((a, b) => a.fromMidiSec - b.fromMidiSec || a.toMidiSec - b.toMidiSec);
852
+ }
853
+ return sidecar;
685
854
  }
686
855
  function generatePlaybackTimeline(score, options = {}) {
687
856
  const ticksPerQuarterNote = options.ticksPerQuarterNote ?? 480;
688
857
  const defaultTempo = options.defaultTempo ?? 120;
689
858
  const sequence = generatePlaybackSequence(score);
690
- const grid = buildGridTimeline(score, ticksPerQuarterNote, sequence);
859
+ const grid = buildGridTimeline(score, ticksPerQuarterNote, sequence, {
860
+ defaultTempo,
861
+ fermataHoldMultiplier: options.fermataHoldMultiplier,
862
+ caesuraSeconds: options.caesuraSeconds,
863
+ breathSeconds: options.breathSeconds,
864
+ tempoRampSteps: options.tempoRampSteps
865
+ });
691
866
  return buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote);
692
867
  }
693
868
 
@@ -2246,6 +2421,7 @@ export {
2246
2421
  hasPlaybackControls,
2247
2422
  measureEndTick,
2248
2423
  buildGridTimeline,
2424
+ bpmToUsPerQuarter,
2249
2425
  buildTimingSidecar,
2250
2426
  generatePlaybackTimeline,
2251
2427
  STEPS,
package/dist/index.d.mts CHANGED
@@ -2,8 +2,8 @@ import { S as Score, P as Pitch, M as Measure, D as DirectionType, a as Directio
2
2
  export { A as Accidental, j as AccidentalInfo, O as AdjacentNotes, a4 as AssembledLyrics, B as BackupEntry, p as Barline, a5 as BarlineWithContext, a0 as BeamGroup, k as BeamInfo, s as Chord, C as Clef, aa as ClefChangeInfo, w as Credit, v as Defaults, Q as DirectionKind, z as DirectionWithContext, R as DynamicWithContext, m as DynamicsValue, a7 as EndingInfo, E as EntryWithContext, F as ForwardEntry, a2 as HarmonyWithContext, a8 as KeyChangeInfo, K as KeySignature, L as Lyric, a3 as LyricWithContext, g as MeasureAttributes, h as MeasureEntry, l as Notation, a1 as NotationType, t as NoteIteratorItem, i as NoteType, y as NoteWithContext, r as NoteWithPosition, Y as OctaveShiftWithContext, f as Part, e as PartGroup, W as PedalWithContext, H as PositionQueryOptions, u as Print, a6 as RepeatInfo, d as ScoreMetadata, _ as SlurSpan, q as StaffGroup, G as StaffRange, ab as StructuralChanges, U as TempoWithContext, T as TieInfo, Z as TiedNoteGroup, a9 as TimeChangeInfo, n as TimeSignature, o as Transpose, $ as TupletGroup, I as VerticalSlice, V as VoiceGroup, J as VoiceLine, x as VoiceToStaffMap, X as WedgeWithContext } from './types-CkeI8vw6.mjs';
3
3
  import { V as ValidateOptions, a as ValidationResult } from './index-8MkN7sbm.mjs';
4
4
  export { ba as AddArticulationOptions, bj as AddBeamOptions, c8 as AddBowingOptions, ch as AddBreathMarkOptions, ck as AddCaesuraOptions, aW as AddChordOptions, c2 as AddChordSymbolOptions, bS as AddCodaOptions, bc as AddDynamicsOptions, bM as AddEndingOptions, bz as AddFermataOptions, c5 as AddFingeringOptions, bU as AddGraceNoteOptions, b$ as AddHarmonyOptions, bX as AddLyricOptions, bT as AddNavigationOptions, cd as AddOctaveShiftOptions, bB as AddOrnamentOptions, b2 as AddPartOptions, bD as AddPedalOptions, bH as AddRehearsalMarkOptions, bI as AddRepeatBarlineOptions, bK as AddRepeatOptions, bR as AddSegnoOptions, b8 as AddSlurOptions, ca as AddStringNumberOptions, bu as AddTempoOptions, bF as AddTextDirectionOptions, bG as AddTextOptions, b6 as AddTieOptions, b1 as AddVoiceOptions, bx as AddWedgeOptions, bl as AutoBeamOptions, bQ as BarStyle, c7 as BowingType, cg as BreathMarkValue, cj as CaesuraValue, bO as ChangeBarlineOptions, bg as ChangeClefOptions, aX as ChangeNoteDurationOptions, bW as ConvertToGraceOptions, br as CopyNotesMultiMeasureOptions, bo as CopyNotesOptions, bh as CreateTupletOptions, bq as CutNotesOptions, b3 as DuplicatePartOptions, b_ as HarmonyKind, bf as InsertClefChangeOptions, aU as InsertNoteOptions, cM as LocalValidateOptions, b0 as LowerAccidentalOptions, cL as MeasureValidationContext, be as ModifyDynamicsOptions, bw as ModifyTempoOptions, b5 as MoveNoteToStaffOptions, bs as MultiMeasureSelection, bn as NoteSelection, cc as OctaveShiftType, aT as OperationErrorCode, aS as OperationResult, bt as PasteNotesMultiMeasureOptions, bp as PasteNotesOptions, a$ as RaiseAccidentalOptions, bb as RemoveArticulationOptions, bk as RemoveBeamOptions, c9 as RemoveBowingOptions, ci as RemoveBreathMarkOptions, cl as RemoveCaesuraOptions, c3 as RemoveChordSymbolOptions, bd as RemoveDynamicsOptions, bN as RemoveEndingOptions, bA as RemoveFermataOptions, c6 as RemoveFingeringOptions, bV as RemoveGraceNoteOptions, c0 as RemoveHarmonyOptions, bY as RemoveLyricOptions, aV as RemoveNoteOptions, cf as RemoveOctaveShiftOptions, bC as RemoveOrnamentOptions, bE as RemovePedalOptions, bJ as RemoveRepeatBarlineOptions, bL as RemoveRepeatOptions, b9 as RemoveSlurOptions, cb as RemoveStringNumberOptions, bv as RemoveTempoOptions, b7 as RemoveTieOptions, bi as RemoveTupletOptions, by as RemoveWedgeOptions, bP as SetBarlineOptions, bm as SetBeamingOptions, aZ as SetNotePitchBySemitoneOptions, aY as SetNotePitchOptions, b4 as SetStavesOptions, a_ as ShiftNotePitchOptions, ce as StopOctaveShiftOptions, c4 as UpdateChordSymbolOptions, c1 as UpdateHarmonyOptions, bZ as UpdateLyricOptions, cH as ValidationError, cJ as ValidationErrorCode, cD as ValidationException, cK as ValidationLevel, cI as ValidationLocation, K as addArticulation, T as addBeam, aH as addBowing, aO as addBreathMark, aQ as addCaesura, b as addChord, j as addChordNote, p as addChordNoteChecked, aC as addChordSymbol, ao as addCoda, ap as addDaCapo, aq as addDalSegno, M as addDynamics, aj as addEnding, a6 as addFermata, ar as addFine, aF as addFingering, at as addGraceNote, az as addHarmony, aw as addLyric, g as addNote, n as addNoteChecked, aL as addOctaveShift, a8 as addOrnament, x as addPart, aa as addPedal, ae as addRehearsalMark, ah as addRepeat, af as addRepeatBarline, an as addSegno, I as addSlur, aJ as addStringNumber, a1 as addTempo, ad as addText, ac as addTextDirection, G as addTie, as as addToCoda, w as addVoice, a4 as addWedge, cG as assertMeasureValid, co as assertValid, W as autoBeam, al as changeBarline, Q as changeClef, C as changeKey, e as changeNoteDuration, D as changeTime, av as convertToGrace, Y as copyNotes, $ as copyNotesMultiMeasure, R as createTuplet, _ as cutNotes, F as deleteMeasure, h as deleteNote, o as deleteNoteChecked, z as duplicatePart, cC as formatLocation, cF as getMeasureContext, P as insertClefChange, E as insertMeasure, i as insertNote, cn as isValid, l as lowerAccidental, O as modifyDynamics, k as modifyNoteDuration, u as modifyNoteDurationChecked, m as modifyNotePitch, q as modifyNotePitchChecked, a3 as modifyTempo, B as moveNoteToStaff, Z as pasteNotes, a0 as pasteNotesMultiMeasure, f as raiseAccidental, L as removeArticulation, U as removeBeam, aI as removeBowing, aP as removeBreathMark, aR as removeCaesura, aD as removeChordSymbol, N as removeDynamics, ak as removeEnding, a7 as removeFermata, aG as removeFingering, au as removeGraceNote, aA as removeHarmony, ax as removeLyric, r as removeNote, aN as removeOctaveShift, a9 as removeOrnament, y as removePart, ab as removePedal, ai as removeRepeat, ag as removeRepeatBarline, J as removeSlur, aK as removeStringNumber, a2 as removeTempo, H as removeTie, S as removeTuplet, a5 as removeWedge, am as setBarline, X as setBeaming, s as setNotePitch, c as setNotePitchBySemitone, A as setStaves, d as shiftNotePitch, aM as stopOctaveShift, t as transpose, v as transposeChecked, aE as updateChordSymbol, aB as updateHarmony, ay as updateLyric, cm as validate, cr as validateBackupForward, ct as validateBeams, cp as validateDivisions, cq as validateMeasureDuration, cE as validateMeasureLocal, cw as validatePartReferences, cx as validatePartStructure, cu as validateSlurs, cB as validateSlursAcrossMeasures, cy as validateStaffStructure, cs as validateTies, cA as validateTiesAcrossMeasures, cv as validateTuplets, cz as validateVoiceStaff } from './index-8MkN7sbm.mjs';
5
- import { TimingSidecar } from './query/index.mjs';
6
- export { FindNotesFilter, NormalizedPositionOptions, PitchRange, PlaybackControls, PlaybackMeasure, RoundtripMetrics, TimingBreakpoint, TimingMapOptions, VoiceFilter, buildVoiceToStaffMap, buildVoiceToStaffMapForPart, countNotes, extractPlaybackControls, findBarlines, findDirectionsByType, findNotes, findNotesWithNotation, generatePlaybackSequence, generatePlaybackTimeline, getAbsolutePosition, getAdjacentNotes, getAllNotes, getAttributesAtMeasure, getBeamGroups, getChordProgression, getChords, getClefChanges, getClefForStaff, getDirections, getDirectionsAtPosition, getDivisions, getDuration, getDynamics, getEffectiveStaff, getEndings, getEntriesAtPosition, getEntriesForStaff, getEntriesInRange, getHarmonies, getHarmonyAtPosition, getKeyChanges, getLyricText, getLyrics, getMeasure, getMeasureByIndex, getMeasureCount, getNextNote, getNormalizedDuration, getNormalizedPosition, getNotesAtPosition, getNotesForStaff, getNotesForVoice, getNotesInRange, getOctaveShifts, getPartById, getPartByIndex, getPartCount, getPartIds, getPartIndex, getPedalMarkings, getPrevNote, getRepeatStructure, getSlurSpans, getStaffRange, getStaveCount, getStaves, getStructuralChanges, getTempoMarkings, getTiedNoteGroups, getTimeChanges, getTupletGroups, getVerseCount, getVerticalSlice, getVoiceLine, getVoiceLineInRange, getVoices, getVoicesForStaff, getWedges, groupByStaff, groupByVoice, hasMultipleStaves, hasNotes, hasPlaybackControls, inferStaff, isRestMeasure, iterateEntries, iterateNotes, measureRoundtrip, scoresEqual, withAbsolutePositions } from './query/index.mjs';
5
+ import { ExpressionOptions, TimingSidecar } from './query/index.mjs';
6
+ export { ExpressionHint, ExpressionKind, FindNotesFilter, NormalizedPositionOptions, PitchRange, PlaybackControls, PlaybackMeasure, RoundtripMetrics, TimingBreakpoint, TimingMapOptions, VoiceFilter, buildVoiceToStaffMap, buildVoiceToStaffMapForPart, countNotes, extractPlaybackControls, findBarlines, findDirectionsByType, findNotes, findNotesWithNotation, generatePlaybackSequence, generatePlaybackTimeline, getAbsolutePosition, getAdjacentNotes, getAllNotes, getAttributesAtMeasure, getBeamGroups, getChordProgression, getChords, getClefChanges, getClefForStaff, getDirections, getDirectionsAtPosition, getDivisions, getDuration, getDynamics, getEffectiveStaff, getEndings, getEntriesAtPosition, getEntriesForStaff, getEntriesInRange, getHarmonies, getHarmonyAtPosition, getKeyChanges, getLyricText, getLyrics, getMeasure, getMeasureByIndex, getMeasureCount, getNextNote, getNormalizedDuration, getNormalizedPosition, getNotesAtPosition, getNotesForStaff, getNotesForVoice, getNotesInRange, getOctaveShifts, getPartById, getPartByIndex, getPartCount, getPartIds, getPartIndex, getPedalMarkings, getPrevNote, getRepeatStructure, getSlurSpans, getStaffRange, getStaveCount, getStaves, getStructuralChanges, getTempoMarkings, getTiedNoteGroups, getTimeChanges, getTupletGroups, getVerseCount, getVerticalSlice, getVoiceLine, getVoiceLineInRange, getVoices, getVoicesForStaff, getWedges, groupByStaff, groupByVoice, hasMultipleStaves, hasNotes, hasPlaybackControls, inferStaff, isRestMeasure, iterateEntries, iterateNotes, measureRoundtrip, scoresEqual, withAbsolutePositions } from './query/index.mjs';
7
7
 
8
8
  declare function parse(input: string | Uint8Array): Score;
9
9
 
@@ -76,8 +76,11 @@ declare function serializeCompressed(score: Score, options?: SerializeOptions):
76
76
 
77
77
  /**
78
78
  * MIDI export options
79
+ *
80
+ * Extends {@link ExpressionOptions} so fermata holds and rit./accel. ramps land
81
+ * in the exported tempo map (and the timing sidecar) identically.
79
82
  */
80
- interface MidiExportOptions {
83
+ interface MidiExportOptions extends ExpressionOptions {
81
84
  /** Ticks per quarter note (default: 480) */
82
85
  ticksPerQuarterNote?: number;
83
86
  /** Default tempo in BPM (default: 120) */
@@ -375,4 +378,4 @@ declare function getPartNameMap(score: Score): Record<string, string | undefined
375
378
  */
376
379
  declare function generateId(): string;
377
380
 
378
- export { type AbcSerializeOptions, DirectionEntry, DirectionType, type DirectionTypeOfKind, Measure, type MidiExportOptions, type MidiWithTimingMap, NoteEntry, PartInfo, PartListEntry, Pitch, STEPS, STEP_SEMITONES, Score, type SerializeOptions, TimingSidecar, ValidateOptions, ValidationResult, decodeBuffer, exportMidi, exportMidiWithTimingMap, generateId, getAllPartInfos, getDirectionOfKind, getDirectionsOfKind, getMeasureEndPosition, getPartAbbreviation, getPartInfo, getPartName, getPartNameMap, getSoundDamperPedal, getSoundDynamics, getSoundSoftPedal, getSoundSostenutoPedal, getSoundTempo, hasBeam, hasDirectionOfKind, hasLyrics, hasNotations, hasTie, hasTieStart, hasTieStop, hasTuplet, isChordNote, isCompressed, isCueNote, isGraceNote, isPartInfo, isPitchedNote, isRest, isUnpitchedNote, parse, parseAbc, parseAuto, parseCompressed, parseFile, pitchToSemitone, serialize, serializeAbc, serializeCompressed, serializeToFile };
381
+ export { type AbcSerializeOptions, DirectionEntry, DirectionType, type DirectionTypeOfKind, ExpressionOptions, Measure, type MidiExportOptions, type MidiWithTimingMap, NoteEntry, PartInfo, PartListEntry, Pitch, STEPS, STEP_SEMITONES, Score, type SerializeOptions, TimingSidecar, ValidateOptions, ValidationResult, decodeBuffer, exportMidi, exportMidiWithTimingMap, generateId, getAllPartInfos, getDirectionOfKind, getDirectionsOfKind, getMeasureEndPosition, getPartAbbreviation, getPartInfo, getPartName, getPartNameMap, getSoundDamperPedal, getSoundDynamics, getSoundSoftPedal, getSoundSostenutoPedal, getSoundTempo, hasBeam, hasDirectionOfKind, hasLyrics, hasNotations, hasTie, hasTieStart, hasTieStop, hasTuplet, isChordNote, isCompressed, isCueNote, isGraceNote, isPartInfo, isPitchedNote, isRest, isUnpitchedNote, parse, parseAbc, parseAuto, parseCompressed, parseFile, pitchToSemitone, serialize, serializeAbc, serializeCompressed, serializeToFile };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { S as Score, P as Pitch, M as Measure, D as DirectionType, a as Directio
2
2
  export { A as Accidental, j as AccidentalInfo, O as AdjacentNotes, a4 as AssembledLyrics, B as BackupEntry, p as Barline, a5 as BarlineWithContext, a0 as BeamGroup, k as BeamInfo, s as Chord, C as Clef, aa as ClefChangeInfo, w as Credit, v as Defaults, Q as DirectionKind, z as DirectionWithContext, R as DynamicWithContext, m as DynamicsValue, a7 as EndingInfo, E as EntryWithContext, F as ForwardEntry, a2 as HarmonyWithContext, a8 as KeyChangeInfo, K as KeySignature, L as Lyric, a3 as LyricWithContext, g as MeasureAttributes, h as MeasureEntry, l as Notation, a1 as NotationType, t as NoteIteratorItem, i as NoteType, y as NoteWithContext, r as NoteWithPosition, Y as OctaveShiftWithContext, f as Part, e as PartGroup, W as PedalWithContext, H as PositionQueryOptions, u as Print, a6 as RepeatInfo, d as ScoreMetadata, _ as SlurSpan, q as StaffGroup, G as StaffRange, ab as StructuralChanges, U as TempoWithContext, T as TieInfo, Z as TiedNoteGroup, a9 as TimeChangeInfo, n as TimeSignature, o as Transpose, $ as TupletGroup, I as VerticalSlice, V as VoiceGroup, J as VoiceLine, x as VoiceToStaffMap, X as WedgeWithContext } from './types-CkeI8vw6.js';
3
3
  import { V as ValidateOptions, a as ValidationResult } from './index-DxLMCMTr.js';
4
4
  export { ba as AddArticulationOptions, bj as AddBeamOptions, c8 as AddBowingOptions, ch as AddBreathMarkOptions, ck as AddCaesuraOptions, aW as AddChordOptions, c2 as AddChordSymbolOptions, bS as AddCodaOptions, bc as AddDynamicsOptions, bM as AddEndingOptions, bz as AddFermataOptions, c5 as AddFingeringOptions, bU as AddGraceNoteOptions, b$ as AddHarmonyOptions, bX as AddLyricOptions, bT as AddNavigationOptions, cd as AddOctaveShiftOptions, bB as AddOrnamentOptions, b2 as AddPartOptions, bD as AddPedalOptions, bH as AddRehearsalMarkOptions, bI as AddRepeatBarlineOptions, bK as AddRepeatOptions, bR as AddSegnoOptions, b8 as AddSlurOptions, ca as AddStringNumberOptions, bu as AddTempoOptions, bF as AddTextDirectionOptions, bG as AddTextOptions, b6 as AddTieOptions, b1 as AddVoiceOptions, bx as AddWedgeOptions, bl as AutoBeamOptions, bQ as BarStyle, c7 as BowingType, cg as BreathMarkValue, cj as CaesuraValue, bO as ChangeBarlineOptions, bg as ChangeClefOptions, aX as ChangeNoteDurationOptions, bW as ConvertToGraceOptions, br as CopyNotesMultiMeasureOptions, bo as CopyNotesOptions, bh as CreateTupletOptions, bq as CutNotesOptions, b3 as DuplicatePartOptions, b_ as HarmonyKind, bf as InsertClefChangeOptions, aU as InsertNoteOptions, cM as LocalValidateOptions, b0 as LowerAccidentalOptions, cL as MeasureValidationContext, be as ModifyDynamicsOptions, bw as ModifyTempoOptions, b5 as MoveNoteToStaffOptions, bs as MultiMeasureSelection, bn as NoteSelection, cc as OctaveShiftType, aT as OperationErrorCode, aS as OperationResult, bt as PasteNotesMultiMeasureOptions, bp as PasteNotesOptions, a$ as RaiseAccidentalOptions, bb as RemoveArticulationOptions, bk as RemoveBeamOptions, c9 as RemoveBowingOptions, ci as RemoveBreathMarkOptions, cl as RemoveCaesuraOptions, c3 as RemoveChordSymbolOptions, bd as RemoveDynamicsOptions, bN as RemoveEndingOptions, bA as RemoveFermataOptions, c6 as RemoveFingeringOptions, bV as RemoveGraceNoteOptions, c0 as RemoveHarmonyOptions, bY as RemoveLyricOptions, aV as RemoveNoteOptions, cf as RemoveOctaveShiftOptions, bC as RemoveOrnamentOptions, bE as RemovePedalOptions, bJ as RemoveRepeatBarlineOptions, bL as RemoveRepeatOptions, b9 as RemoveSlurOptions, cb as RemoveStringNumberOptions, bv as RemoveTempoOptions, b7 as RemoveTieOptions, bi as RemoveTupletOptions, by as RemoveWedgeOptions, bP as SetBarlineOptions, bm as SetBeamingOptions, aZ as SetNotePitchBySemitoneOptions, aY as SetNotePitchOptions, b4 as SetStavesOptions, a_ as ShiftNotePitchOptions, ce as StopOctaveShiftOptions, c4 as UpdateChordSymbolOptions, c1 as UpdateHarmonyOptions, bZ as UpdateLyricOptions, cH as ValidationError, cJ as ValidationErrorCode, cD as ValidationException, cK as ValidationLevel, cI as ValidationLocation, K as addArticulation, T as addBeam, aH as addBowing, aO as addBreathMark, aQ as addCaesura, b as addChord, j as addChordNote, p as addChordNoteChecked, aC as addChordSymbol, ao as addCoda, ap as addDaCapo, aq as addDalSegno, M as addDynamics, aj as addEnding, a6 as addFermata, ar as addFine, aF as addFingering, at as addGraceNote, az as addHarmony, aw as addLyric, g as addNote, n as addNoteChecked, aL as addOctaveShift, a8 as addOrnament, x as addPart, aa as addPedal, ae as addRehearsalMark, ah as addRepeat, af as addRepeatBarline, an as addSegno, I as addSlur, aJ as addStringNumber, a1 as addTempo, ad as addText, ac as addTextDirection, G as addTie, as as addToCoda, w as addVoice, a4 as addWedge, cG as assertMeasureValid, co as assertValid, W as autoBeam, al as changeBarline, Q as changeClef, C as changeKey, e as changeNoteDuration, D as changeTime, av as convertToGrace, Y as copyNotes, $ as copyNotesMultiMeasure, R as createTuplet, _ as cutNotes, F as deleteMeasure, h as deleteNote, o as deleteNoteChecked, z as duplicatePart, cC as formatLocation, cF as getMeasureContext, P as insertClefChange, E as insertMeasure, i as insertNote, cn as isValid, l as lowerAccidental, O as modifyDynamics, k as modifyNoteDuration, u as modifyNoteDurationChecked, m as modifyNotePitch, q as modifyNotePitchChecked, a3 as modifyTempo, B as moveNoteToStaff, Z as pasteNotes, a0 as pasteNotesMultiMeasure, f as raiseAccidental, L as removeArticulation, U as removeBeam, aI as removeBowing, aP as removeBreathMark, aR as removeCaesura, aD as removeChordSymbol, N as removeDynamics, ak as removeEnding, a7 as removeFermata, aG as removeFingering, au as removeGraceNote, aA as removeHarmony, ax as removeLyric, r as removeNote, aN as removeOctaveShift, a9 as removeOrnament, y as removePart, ab as removePedal, ai as removeRepeat, ag as removeRepeatBarline, J as removeSlur, aK as removeStringNumber, a2 as removeTempo, H as removeTie, S as removeTuplet, a5 as removeWedge, am as setBarline, X as setBeaming, s as setNotePitch, c as setNotePitchBySemitone, A as setStaves, d as shiftNotePitch, aM as stopOctaveShift, t as transpose, v as transposeChecked, aE as updateChordSymbol, aB as updateHarmony, ay as updateLyric, cm as validate, cr as validateBackupForward, ct as validateBeams, cp as validateDivisions, cq as validateMeasureDuration, cE as validateMeasureLocal, cw as validatePartReferences, cx as validatePartStructure, cu as validateSlurs, cB as validateSlursAcrossMeasures, cy as validateStaffStructure, cs as validateTies, cA as validateTiesAcrossMeasures, cv as validateTuplets, cz as validateVoiceStaff } from './index-DxLMCMTr.js';
5
- import { TimingSidecar } from './query/index.js';
6
- export { FindNotesFilter, NormalizedPositionOptions, PitchRange, PlaybackControls, PlaybackMeasure, RoundtripMetrics, TimingBreakpoint, TimingMapOptions, VoiceFilter, buildVoiceToStaffMap, buildVoiceToStaffMapForPart, countNotes, extractPlaybackControls, findBarlines, findDirectionsByType, findNotes, findNotesWithNotation, generatePlaybackSequence, generatePlaybackTimeline, getAbsolutePosition, getAdjacentNotes, getAllNotes, getAttributesAtMeasure, getBeamGroups, getChordProgression, getChords, getClefChanges, getClefForStaff, getDirections, getDirectionsAtPosition, getDivisions, getDuration, getDynamics, getEffectiveStaff, getEndings, getEntriesAtPosition, getEntriesForStaff, getEntriesInRange, getHarmonies, getHarmonyAtPosition, getKeyChanges, getLyricText, getLyrics, getMeasure, getMeasureByIndex, getMeasureCount, getNextNote, getNormalizedDuration, getNormalizedPosition, getNotesAtPosition, getNotesForStaff, getNotesForVoice, getNotesInRange, getOctaveShifts, getPartById, getPartByIndex, getPartCount, getPartIds, getPartIndex, getPedalMarkings, getPrevNote, getRepeatStructure, getSlurSpans, getStaffRange, getStaveCount, getStaves, getStructuralChanges, getTempoMarkings, getTiedNoteGroups, getTimeChanges, getTupletGroups, getVerseCount, getVerticalSlice, getVoiceLine, getVoiceLineInRange, getVoices, getVoicesForStaff, getWedges, groupByStaff, groupByVoice, hasMultipleStaves, hasNotes, hasPlaybackControls, inferStaff, isRestMeasure, iterateEntries, iterateNotes, measureRoundtrip, scoresEqual, withAbsolutePositions } from './query/index.js';
5
+ import { ExpressionOptions, TimingSidecar } from './query/index.js';
6
+ export { ExpressionHint, ExpressionKind, FindNotesFilter, NormalizedPositionOptions, PitchRange, PlaybackControls, PlaybackMeasure, RoundtripMetrics, TimingBreakpoint, TimingMapOptions, VoiceFilter, buildVoiceToStaffMap, buildVoiceToStaffMapForPart, countNotes, extractPlaybackControls, findBarlines, findDirectionsByType, findNotes, findNotesWithNotation, generatePlaybackSequence, generatePlaybackTimeline, getAbsolutePosition, getAdjacentNotes, getAllNotes, getAttributesAtMeasure, getBeamGroups, getChordProgression, getChords, getClefChanges, getClefForStaff, getDirections, getDirectionsAtPosition, getDivisions, getDuration, getDynamics, getEffectiveStaff, getEndings, getEntriesAtPosition, getEntriesForStaff, getEntriesInRange, getHarmonies, getHarmonyAtPosition, getKeyChanges, getLyricText, getLyrics, getMeasure, getMeasureByIndex, getMeasureCount, getNextNote, getNormalizedDuration, getNormalizedPosition, getNotesAtPosition, getNotesForStaff, getNotesForVoice, getNotesInRange, getOctaveShifts, getPartById, getPartByIndex, getPartCount, getPartIds, getPartIndex, getPedalMarkings, getPrevNote, getRepeatStructure, getSlurSpans, getStaffRange, getStaveCount, getStaves, getStructuralChanges, getTempoMarkings, getTiedNoteGroups, getTimeChanges, getTupletGroups, getVerseCount, getVerticalSlice, getVoiceLine, getVoiceLineInRange, getVoices, getVoicesForStaff, getWedges, groupByStaff, groupByVoice, hasMultipleStaves, hasNotes, hasPlaybackControls, inferStaff, isRestMeasure, iterateEntries, iterateNotes, measureRoundtrip, scoresEqual, withAbsolutePositions } from './query/index.js';
7
7
 
8
8
  declare function parse(input: string | Uint8Array): Score;
9
9
 
@@ -76,8 +76,11 @@ declare function serializeCompressed(score: Score, options?: SerializeOptions):
76
76
 
77
77
  /**
78
78
  * MIDI export options
79
+ *
80
+ * Extends {@link ExpressionOptions} so fermata holds and rit./accel. ramps land
81
+ * in the exported tempo map (and the timing sidecar) identically.
79
82
  */
80
- interface MidiExportOptions {
83
+ interface MidiExportOptions extends ExpressionOptions {
81
84
  /** Ticks per quarter note (default: 480) */
82
85
  ticksPerQuarterNote?: number;
83
86
  /** Default tempo in BPM (default: 120) */
@@ -375,4 +378,4 @@ declare function getPartNameMap(score: Score): Record<string, string | undefined
375
378
  */
376
379
  declare function generateId(): string;
377
380
 
378
- export { type AbcSerializeOptions, DirectionEntry, DirectionType, type DirectionTypeOfKind, Measure, type MidiExportOptions, type MidiWithTimingMap, NoteEntry, PartInfo, PartListEntry, Pitch, STEPS, STEP_SEMITONES, Score, type SerializeOptions, TimingSidecar, ValidateOptions, ValidationResult, decodeBuffer, exportMidi, exportMidiWithTimingMap, generateId, getAllPartInfos, getDirectionOfKind, getDirectionsOfKind, getMeasureEndPosition, getPartAbbreviation, getPartInfo, getPartName, getPartNameMap, getSoundDamperPedal, getSoundDynamics, getSoundSoftPedal, getSoundSostenutoPedal, getSoundTempo, hasBeam, hasDirectionOfKind, hasLyrics, hasNotations, hasTie, hasTieStart, hasTieStop, hasTuplet, isChordNote, isCompressed, isCueNote, isGraceNote, isPartInfo, isPitchedNote, isRest, isUnpitchedNote, parse, parseAbc, parseAuto, parseCompressed, parseFile, pitchToSemitone, serialize, serializeAbc, serializeCompressed, serializeToFile };
381
+ export { type AbcSerializeOptions, DirectionEntry, DirectionType, type DirectionTypeOfKind, ExpressionOptions, Measure, type MidiExportOptions, type MidiWithTimingMap, NoteEntry, PartInfo, PartListEntry, Pitch, STEPS, STEP_SEMITONES, Score, type SerializeOptions, TimingSidecar, ValidateOptions, ValidationResult, decodeBuffer, exportMidi, exportMidiWithTimingMap, generateId, getAllPartInfos, getDirectionOfKind, getDirectionsOfKind, getMeasureEndPosition, getPartAbbreviation, getPartInfo, getPartName, getPartNameMap, getSoundDamperPedal, getSoundDynamics, getSoundSoftPedal, getSoundSostenutoPedal, getSoundTempo, hasBeam, hasDirectionOfKind, hasLyrics, hasNotations, hasTie, hasTieStart, hasTieStop, hasTuplet, isChordNote, isCompressed, isCueNote, isGraceNote, isPartInfo, isPitchedNote, isRest, isUnpitchedNote, parse, parseAbc, parseAuto, parseCompressed, parseFile, pitchToSemitone, serialize, serializeAbc, serializeCompressed, serializeToFile };