musicxml-io 0.7.0 → 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.
- package/README.md +40 -0
- package/dist/{chunk-CJZK2DGV.js → chunk-CHMV7XWY.js} +428 -66
- package/dist/{chunk-LS5OLRZP.mjs → chunk-IM3FS32Q.mjs} +1 -1
- package/dist/{chunk-YCNKCOR2.js → chunk-S5MWUJU2.js} +23 -23
- package/dist/{chunk-CCSOG7HU.mjs → chunk-VQUFSGB5.mjs} +362 -0
- package/dist/index.d.mts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +180 -254
- package/dist/index.mjs +64 -138
- package/dist/operations/index.js +3 -3
- package/dist/operations/index.mjs +2 -2
- package/dist/query/index.d.mts +127 -1
- package/dist/query/index.d.ts +127 -1
- package/dist/query/index.js +4 -2
- package/dist/query/index.mjs +3 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
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 =
|
|
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 =
|
|
1572
|
-
const accidentalsInMeasure =
|
|
1573
|
-
const newPitch =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1654
|
-
const accidentalsInMeasure =
|
|
1655
|
-
const accidental =
|
|
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 =
|
|
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 =
|
|
1695
|
-
const accidentalsInMeasure =
|
|
1696
|
-
const accidental =
|
|
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 =
|
|
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 =
|
|
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
|
|
1745
|
-
const stepSemitone =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 = {
|
|
@@ -509,6 +509,363 @@ function hasPlaybackControls(score, options) {
|
|
|
509
509
|
return controls.repeatStarts.length > 0 || controls.repeatEnds.length > 0 || controls.voltas.length > 0 || controls.jumps.length > 0 || controls.segnoIndex !== null || controls.codaIndex !== null;
|
|
510
510
|
}
|
|
511
511
|
|
|
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;
|
|
517
|
+
function metronomeBpm(perMinute) {
|
|
518
|
+
if (perMinute === void 0) return null;
|
|
519
|
+
const bpm = typeof perMinute === "number" ? perMinute : parseFloat(perMinute);
|
|
520
|
+
return isNaN(bpm) ? null : bpm;
|
|
521
|
+
}
|
|
522
|
+
function measureMaxPosition(measure) {
|
|
523
|
+
let position = 0;
|
|
524
|
+
let max = 0;
|
|
525
|
+
for (const entry of measure.entries) {
|
|
526
|
+
if (entry.type === "note") {
|
|
527
|
+
if (!entry.chord && !entry.grace) {
|
|
528
|
+
position += entry.duration;
|
|
529
|
+
if (position > max) max = position;
|
|
530
|
+
}
|
|
531
|
+
} else if (entry.type === "backup") {
|
|
532
|
+
position -= entry.duration;
|
|
533
|
+
} else if (entry.type === "forward") {
|
|
534
|
+
position += entry.duration;
|
|
535
|
+
if (position > max) max = position;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return max;
|
|
539
|
+
}
|
|
540
|
+
function findTimeSignature(part, measureNumber) {
|
|
541
|
+
const targetMeasure = parseInt(String(measureNumber), 10);
|
|
542
|
+
let time;
|
|
543
|
+
for (const measure of part.measures) {
|
|
544
|
+
const mNum = parseInt(measure.number, 10);
|
|
545
|
+
if (!isNaN(targetMeasure) && !isNaN(mNum) && mNum > targetMeasure) break;
|
|
546
|
+
if (measure.attributes?.time) {
|
|
547
|
+
time = {
|
|
548
|
+
beats: parseInt(measure.attributes.time.beats, 10) || 4,
|
|
549
|
+
beatType: measure.attributes.time.beatType
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return time;
|
|
554
|
+
}
|
|
555
|
+
function measureEndTick(measure, part, divisions, measureStartTick, maxPosition, ticksPerQuarterNote) {
|
|
556
|
+
const actualTicks = Math.round(maxPosition * ticksPerQuarterNote / divisions);
|
|
557
|
+
if (measure.implicit) {
|
|
558
|
+
return measureStartTick + actualTicks;
|
|
559
|
+
}
|
|
560
|
+
const timeAttrs = findTimeSignature(part, measure.number);
|
|
561
|
+
if (timeAttrs) {
|
|
562
|
+
const measureDuration = timeAttrs.beats / timeAttrs.beatType * 4 * divisions;
|
|
563
|
+
const calculatedTicks = Math.round(measureDuration * ticksPerQuarterNote / divisions);
|
|
564
|
+
const ticksToAdd = Math.min(calculatedTicks, actualTicks > 0 ? actualTicks : calculatedTicks);
|
|
565
|
+
return measureStartTick + ticksToAdd;
|
|
566
|
+
}
|
|
567
|
+
return measureStartTick + actualTicks;
|
|
568
|
+
}
|
|
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 = [];
|
|
591
|
+
const measures = [];
|
|
592
|
+
const rampMarkers = [];
|
|
593
|
+
const stretches = [];
|
|
594
|
+
if (score.parts.length === 0) {
|
|
595
|
+
return { tempoEvents: [], measures, totalTicks: 0, expressions: [] };
|
|
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;
|
|
602
|
+
const part = score.parts[0];
|
|
603
|
+
let divisions = 1;
|
|
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
|
+
};
|
|
611
|
+
for (const { measureIndex, repeatIteration } of sequence) {
|
|
612
|
+
const measure = part.measures[measureIndex];
|
|
613
|
+
if (!measure) continue;
|
|
614
|
+
if (measure.attributes?.divisions) {
|
|
615
|
+
divisions = measure.attributes.divisions;
|
|
616
|
+
}
|
|
617
|
+
const measureStartTick = currentTick;
|
|
618
|
+
let position = 0;
|
|
619
|
+
let chordBasePosition = 0;
|
|
620
|
+
const tickAt = (pos) => measureStartTick + Math.round(pos * ticksPerQuarterNote / divisions);
|
|
621
|
+
for (const entry of measure.entries) {
|
|
622
|
+
if (entry.type === "direction") {
|
|
623
|
+
for (const dirType of entry.directionTypes) {
|
|
624
|
+
if (dirType.kind === "metronome") {
|
|
625
|
+
const bpm = metronomeBpm(dirType.perMinute);
|
|
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 });
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (entry.sound?.tempo) {
|
|
636
|
+
rawTempo.push({ tick: tickAt(position), bpm: entry.sound.tempo });
|
|
637
|
+
prevailingBpm = entry.sound.tempo;
|
|
638
|
+
}
|
|
639
|
+
} else if (entry.type === "sound") {
|
|
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
|
+
}
|
|
675
|
+
} else if (entry.type === "backup") {
|
|
676
|
+
position -= entry.duration;
|
|
677
|
+
} else if (entry.type === "forward") {
|
|
678
|
+
position += entry.duration;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const endTick = measureEndTick(
|
|
682
|
+
measure,
|
|
683
|
+
part,
|
|
684
|
+
divisions,
|
|
685
|
+
measureStartTick,
|
|
686
|
+
measureMaxPosition(measure),
|
|
687
|
+
ticksPerQuarterNote
|
|
688
|
+
);
|
|
689
|
+
measures.push({
|
|
690
|
+
measureIndex,
|
|
691
|
+
repeatIteration,
|
|
692
|
+
measureNumber: measure.number,
|
|
693
|
+
startTick: measureStartTick,
|
|
694
|
+
endTick
|
|
695
|
+
});
|
|
696
|
+
currentTick = endTick;
|
|
697
|
+
}
|
|
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);
|
|
780
|
+
}
|
|
781
|
+
function makeTickToSec(tempoEvents, defaultTempo, ticksPerQuarterNote) {
|
|
782
|
+
const sorted = [...tempoEvents].sort((a, b) => a.tick - b.tick);
|
|
783
|
+
const startBpm = sorted.length > 0 && sorted[0].tick === 0 ? sorted[0].bpm : defaultTempo;
|
|
784
|
+
const changes = [
|
|
785
|
+
{ tick: 0, usPerQuarter: bpmToUsPerQuarter(startBpm) }
|
|
786
|
+
];
|
|
787
|
+
let lastBpm = startBpm;
|
|
788
|
+
for (const ev of sorted) {
|
|
789
|
+
if (ev.tick === 0) continue;
|
|
790
|
+
if (ev.bpm === lastBpm) continue;
|
|
791
|
+
changes.push({ tick: ev.tick, usPerQuarter: bpmToUsPerQuarter(ev.bpm) });
|
|
792
|
+
lastBpm = ev.bpm;
|
|
793
|
+
}
|
|
794
|
+
const secPerTick = (usPerQuarter) => usPerQuarter / 1e6 / ticksPerQuarterNote;
|
|
795
|
+
const cumSec = [0];
|
|
796
|
+
for (let i = 1; i < changes.length; i++) {
|
|
797
|
+
const dTick = changes[i].tick - changes[i - 1].tick;
|
|
798
|
+
cumSec[i] = cumSec[i - 1] + dTick * secPerTick(changes[i - 1].usPerQuarter);
|
|
799
|
+
}
|
|
800
|
+
return (tick) => {
|
|
801
|
+
let i = changes.length - 1;
|
|
802
|
+
while (i > 0 && changes[i].tick > tick) i--;
|
|
803
|
+
return cumSec[i] + (tick - changes[i].tick) * secPerTick(changes[i].usPerQuarter);
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote) {
|
|
807
|
+
const tickToSec = makeTickToSec(grid.tempoEvents, defaultTempo, ticksPerQuarterNote);
|
|
808
|
+
const breakpoints = [];
|
|
809
|
+
const measureStartTicks = new Set(grid.measures.map((m) => m.startTick));
|
|
810
|
+
for (const m of grid.measures) {
|
|
811
|
+
breakpoints.push({
|
|
812
|
+
midiSec: tickToSec(m.startTick),
|
|
813
|
+
quarterPos: m.startTick / ticksPerQuarterNote,
|
|
814
|
+
measureNumber: m.measureNumber,
|
|
815
|
+
beatInMeasure: 0,
|
|
816
|
+
repeatIteration: m.repeatIteration
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
for (const ev of grid.tempoEvents) {
|
|
820
|
+
if (measureStartTicks.has(ev.tick)) continue;
|
|
821
|
+
const m = grid.measures.find((mm) => ev.tick >= mm.startTick && ev.tick < mm.endTick);
|
|
822
|
+
if (!m) continue;
|
|
823
|
+
breakpoints.push({
|
|
824
|
+
midiSec: tickToSec(ev.tick),
|
|
825
|
+
quarterPos: ev.tick / ticksPerQuarterNote,
|
|
826
|
+
measureNumber: m.measureNumber,
|
|
827
|
+
beatInMeasure: (ev.tick - m.startTick) / ticksPerQuarterNote,
|
|
828
|
+
repeatIteration: m.repeatIteration
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
const last = grid.measures[grid.measures.length - 1];
|
|
832
|
+
breakpoints.push({
|
|
833
|
+
midiSec: tickToSec(grid.totalTicks),
|
|
834
|
+
quarterPos: grid.totalTicks / ticksPerQuarterNote,
|
|
835
|
+
measureNumber: last ? last.measureNumber : "0",
|
|
836
|
+
beatInMeasure: last ? (grid.totalTicks - last.startTick) / ticksPerQuarterNote : 0,
|
|
837
|
+
repeatIteration: last ? last.repeatIteration : 0
|
|
838
|
+
});
|
|
839
|
+
breakpoints.sort((a, b) => a.midiSec - b.midiSec || a.quarterPos - b.quarterPos);
|
|
840
|
+
const sidecar = {
|
|
841
|
+
version: "1",
|
|
842
|
+
durationSec: tickToSec(grid.totalTicks),
|
|
843
|
+
ticksPerQuarterNote,
|
|
844
|
+
breakpoints
|
|
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;
|
|
854
|
+
}
|
|
855
|
+
function generatePlaybackTimeline(score, options = {}) {
|
|
856
|
+
const ticksPerQuarterNote = options.ticksPerQuarterNote ?? 480;
|
|
857
|
+
const defaultTempo = options.defaultTempo ?? 120;
|
|
858
|
+
const sequence = generatePlaybackSequence(score);
|
|
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
|
+
});
|
|
866
|
+
return buildTimingSidecar(grid, defaultTempo, ticksPerQuarterNote);
|
|
867
|
+
}
|
|
868
|
+
|
|
512
869
|
// src/query/index.ts
|
|
513
870
|
function getNotesForVoice(measure, filter) {
|
|
514
871
|
return measure.entries.filter((entry) => {
|
|
@@ -2062,6 +2419,11 @@ export {
|
|
|
2062
2419
|
extractPlaybackControls,
|
|
2063
2420
|
generatePlaybackSequence,
|
|
2064
2421
|
hasPlaybackControls,
|
|
2422
|
+
measureEndTick,
|
|
2423
|
+
buildGridTimeline,
|
|
2424
|
+
bpmToUsPerQuarter,
|
|
2425
|
+
buildTimingSidecar,
|
|
2426
|
+
generatePlaybackTimeline,
|
|
2065
2427
|
STEPS,
|
|
2066
2428
|
STEP_SEMITONES,
|
|
2067
2429
|
pitchToSemitone,
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +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
|
-
|
|
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';
|
|
6
7
|
|
|
7
8
|
declare function parse(input: string | Uint8Array): Score;
|
|
8
9
|
|
|
@@ -75,8 +76,11 @@ declare function serializeCompressed(score: Score, options?: SerializeOptions):
|
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
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.
|
|
78
82
|
*/
|
|
79
|
-
interface MidiExportOptions {
|
|
83
|
+
interface MidiExportOptions extends ExpressionOptions {
|
|
80
84
|
/** Ticks per quarter note (default: 480) */
|
|
81
85
|
ticksPerQuarterNote?: number;
|
|
82
86
|
/** Default tempo in BPM (default: 120) */
|
|
@@ -84,6 +88,22 @@ interface MidiExportOptions {
|
|
|
84
88
|
/** Default velocity for notes when no dynamics are present (default: 80) */
|
|
85
89
|
defaultVelocity?: number;
|
|
86
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* A single point on the timing sidecar: a correspondence between a time in the
|
|
93
|
+
* generated MIDI and a conceptual musical position (measure + beat).
|
|
94
|
+
*
|
|
95
|
+
* Breakpoints are emitted at every played-measure start and every tempo change
|
|
96
|
+
* (plus a terminal point). Between two consecutive breakpoints the relationship
|
|
97
|
+
* `midiSec ↔ quarterPos` is linear (tempo is piecewise-constant), so a consumer
|
|
98
|
+
* can interpolate any intermediate time exactly.
|
|
99
|
+
*/
|
|
100
|
+
/** Result of {@link exportMidiWithTimingMap}. */
|
|
101
|
+
interface MidiWithTimingMap {
|
|
102
|
+
/** The Standard MIDI File data. */
|
|
103
|
+
midi: Uint8Array;
|
|
104
|
+
/** The MIDI-time ↔ musical-position sidecar. */
|
|
105
|
+
sidecar: TimingSidecar;
|
|
106
|
+
}
|
|
87
107
|
/**
|
|
88
108
|
* Export a Score to Standard MIDI File format (SMF Type 1)
|
|
89
109
|
* @param score - The Score to export
|
|
@@ -91,6 +111,18 @@ interface MidiExportOptions {
|
|
|
91
111
|
* @returns The MIDI file data as Uint8Array
|
|
92
112
|
*/
|
|
93
113
|
declare function exportMidi(score: Score, options?: MidiExportOptions): Uint8Array;
|
|
114
|
+
/**
|
|
115
|
+
* Export a Score to MIDI together with a timing sidecar that maps the generated
|
|
116
|
+
* MIDI timeline to conceptual musical positions (measure + beat).
|
|
117
|
+
*
|
|
118
|
+
* The MIDI bytes are byte-for-byte identical to {@link exportMidi}; the sidecar
|
|
119
|
+
* is derived from the same internal time computation so the two can never drift.
|
|
120
|
+
*
|
|
121
|
+
* @param score - The Score to export
|
|
122
|
+
* @param options - Export options (must match those used for any aligned audio)
|
|
123
|
+
* @returns The MIDI data and its timing sidecar
|
|
124
|
+
*/
|
|
125
|
+
declare function exportMidiWithTimingMap(score: Score, options?: MidiExportOptions): MidiWithTimingMap;
|
|
94
126
|
|
|
95
127
|
/**
|
|
96
128
|
* ABC Notation Serializer
|
|
@@ -346,4 +378,4 @@ declare function getPartNameMap(score: Score): Record<string, string | undefined
|
|
|
346
378
|
*/
|
|
347
379
|
declare function generateId(): string;
|
|
348
380
|
|
|
349
|
-
export { type AbcSerializeOptions, DirectionEntry, DirectionType, type DirectionTypeOfKind, Measure, type MidiExportOptions, NoteEntry, PartInfo, PartListEntry, Pitch, STEPS, STEP_SEMITONES, Score, type SerializeOptions, ValidateOptions, ValidationResult, decodeBuffer, exportMidi, 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 };
|