@viritura/score-engine 0.2.1-next.40 → 0.2.1-next.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -602,9 +602,9 @@ function parseChordSymbolText(input, position) {
602
602
 
603
603
  // packages/core/src/operations/lyricExtenders.ts
604
604
  init_define_import_meta_env();
605
- function durationBeats(duration) {
606
- let base = DURATION_BEATS[duration.base], dots = 1;
607
- for (let index = 0; index < (duration.dots ?? 0); index++) dots += 0.5 ** (index + 1);
605
+ function durationBeats(duration2) {
606
+ let base = DURATION_BEATS[duration2.base], dots = 1;
607
+ for (let index = 0; index < (duration2.dots ?? 0); index++) dots += 0.5 ** (index + 1);
608
608
  return base * dots;
609
609
  }
610
610
  function quantityBeats(quantity) {
@@ -614,7 +614,7 @@ function walkTimedContent(content, startBeat, scale, partIndex, sequenceIndex, v
614
614
  let cursor = startBeat;
615
615
  for (let item of content) {
616
616
  if (item.type === "event") {
617
- let duration = durationBeats(item.duration) * scale;
617
+ let duration2 = durationBeats(item.duration) * scale;
618
618
  output.push({
619
619
  event: item,
620
620
  partIndex,
@@ -623,10 +623,10 @@ function walkTimedContent(content, startBeat, scale, partIndex, sequenceIndex, v
623
623
  staff,
624
624
  measureIndex,
625
625
  startBeat: cursor,
626
- durationBeats: duration,
626
+ durationBeats: duration2,
627
627
  rest: isRest(item),
628
628
  ordinal: ordinal.value++
629
- }), cursor += duration;
629
+ }), cursor += duration2;
630
630
  continue;
631
631
  }
632
632
  if (item.type === "space") {
@@ -2231,9 +2231,9 @@ function interpolateBeatAnchors(bounds, beat, endX) {
2231
2231
 
2232
2232
  // packages/score-engine/src/buildInfo.ts
2233
2233
  init_define_import_meta_env();
2234
- var PACKAGE_VERSION = "0.2.1-next.40", BUNDLE_WORKER_FILE = "score-engine.worker.js";
2234
+ var PACKAGE_VERSION = "0.2.1-next.41", BUNDLE_WORKER_FILE = "score-engine.worker.js";
2235
2235
  function buildCommit() {
2236
- return "aa2b59d2a29c0334fa5cf09255eae831c99f366d";
2236
+ return "715e88d4c5a7118150857b4e89598e4560d44b58";
2237
2237
  }
2238
2238
  function bundledAssetBase() {
2239
2239
  let moduleUrl = import.meta.url;
@@ -16659,8 +16659,8 @@ function parseVideoMedia(raw) {
16659
16659
  typeof contentHash == "string" && contentHash.length > 0 && (media.contentHash = contentHash);
16660
16660
  let demoSourceId = raw?.demoSourceId;
16661
16661
  typeof demoSourceId == "string" && demoSourceId.length > 0 && (media.demoSourceId = demoSourceId);
16662
- let duration = raw?.durationSeconds;
16663
- return typeof duration == "number" && Number.isFinite(duration) && duration > 0 && (media.durationSeconds = duration), media;
16662
+ let duration2 = raw?.durationSeconds;
16663
+ return typeof duration2 == "number" && Number.isFinite(duration2) && duration2 > 0 && (media.durationSeconds = duration2), media;
16664
16664
  }
16665
16665
  function parseRootVideoSync(rootX) {
16666
16666
  let raw = rootX?.viritura?.videoSync;
@@ -17153,7 +17153,7 @@ function buildTempoMap(globalMeasures, holds) {
17153
17153
  if (gm.tempos && gm.tempos.length > 0) {
17154
17154
  for (let tempo of gm.tempos) {
17155
17155
  if (tempo.bpm === void 0) continue;
17156
- let beatOffset = tempo.location ? tempo.location.fraction[0] / tempo.location.fraction[1] * mBeats : 0, qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
17156
+ let beatOffset = tempo.location ? posBeats(tempo.location.fraction) : 0, qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
17157
17157
  changes.push({ beatOffset, qpm });
17158
17158
  }
17159
17159
  changes.sort((a, b) => a.beatOffset - b.beatOffset);
@@ -17186,10 +17186,10 @@ function buildTempoModel(globalMeasures, holds) {
17186
17186
  nominalTime && (currentTime = { count: nominalTime.count, unit: nominalTime.unit });
17187
17187
  let mBeats = measureBeatsFromTime(gm.time ?? currentTime);
17188
17188
  if (measureStartBeats.push(cumBeats), gm.tempos && gm.tempos.length > 0) {
17189
- let sorted = [...gm.tempos].sort((a, b) => tempoBeat(a, mBeats) - tempoBeat(b, mBeats));
17189
+ let sorted = [...gm.tempos].sort((a, b) => tempoBeat(a) - tempoBeat(b));
17190
17190
  for (let tempo of sorted) {
17191
17191
  if (tempo.bpm === void 0) continue;
17192
- let startBeat = cumBeats + tempoBeat(tempo, mBeats), qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
17192
+ let startBeat = cumBeats + tempoBeat(tempo), qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
17193
17193
  regions.push({ startBeat, endBeat: 1 / 0, startBpm: qpm, endBpm: qpm });
17194
17194
  }
17195
17195
  }
@@ -17243,8 +17243,8 @@ function resolveGradualEndBeat(end, fromIdx, globalMeasures, measureStartBeats,
17243
17243
  if (start !== void 0)
17244
17244
  return start + posBeats(end.position.fraction);
17245
17245
  }
17246
- function tempoBeat(tempo, mBeats) {
17247
- return tempo.location ? tempo.location.fraction[0] / tempo.location.fraction[1] * mBeats : 0;
17246
+ function tempoBeat(tempo) {
17247
+ return tempo.location ? posBeats(tempo.location.fraction) : 0;
17248
17248
  }
17249
17249
 
17250
17250
  // packages/midi/src/cadenzaTiming.ts
@@ -17284,14 +17284,14 @@ function playbackGlobalMeasures(score, measureOrder) {
17284
17284
  let activeBeats = 4, activeTime = { count: 4, unit: 4 }, activeTempo = {
17285
17285
  bpm: DEFAULT_BPM,
17286
17286
  value: { base: "quarter" }
17287
- }, resolved = score.global.measures.map((original, measureIndex) => {
17288
- original.time && (activeTime = original.time);
17287
+ }, inheritedTempos = [], resolved = score.global.measures.map((original, measureIndex) => {
17288
+ inheritedTempos.push(activeTempo), original.time && (activeTime = original.time);
17289
17289
  let measure = {
17290
17290
  ...original,
17291
17291
  time: { ...activeTime },
17292
- tempos: original.tempos?.some((tempo) => !tempo.location || tempo.location.fraction[0] === 0) ? original.tempos : [{ ...activeTempo, location: { fraction: [0, 1] } }, ...original.tempos ?? []]
17293
- };
17294
- if (original.tempos?.length && (activeTempo = [...original.tempos].sort(
17292
+ tempos: original.tempos
17293
+ }, metronomicTempos = original.tempos?.filter((tempo) => tempo.bpm !== void 0);
17294
+ if (metronomicTempos?.length && (activeTempo = [...metronomicTempos].sort(
17295
17295
  (a, b) => (a.location?.fraction[0] ?? 0) / (a.location?.fraction[1] ?? 1) - (b.location?.fraction[0] ?? 0) / (b.location?.fraction[1] ?? 1)
17296
17296
  ).at(-1)), measure.time && (activeBeats = measure.time.count * 4 / measure.time.unit), measureIndex === 0 && measure.number === 0) {
17297
17297
  let pickupBeats = writtenMeasureBeats(score, measureIndex, 0);
@@ -17317,7 +17317,13 @@ function playbackGlobalMeasures(score, measureOrder) {
17317
17317
  }
17318
17318
  };
17319
17319
  });
17320
- return measureOrder.map((index) => resolved[index]);
17320
+ return measureOrder.map((index, expandedIndex) => {
17321
+ let measure = resolved[index];
17322
+ return expandedIndex === 0 || index === measureOrder[expandedIndex - 1] + 1 || measure.tempos?.some((tempo) => tempo.bpm !== void 0 && (!tempo.location || tempo.location.fraction[0] === 0)) ? measure : {
17323
+ ...measure,
17324
+ tempos: [{ ...inheritedTempos[index], location: { fraction: [0, 1] } }, ...measure.tempos ?? []]
17325
+ };
17326
+ });
17321
17327
  }
17322
17328
  function suppressCadenzaFermataHolds(measures, holds) {
17323
17329
  return holds.map(
@@ -17687,9 +17693,9 @@ function compileChordPlayback(score, includeGlobalChords = !0) {
17687
17693
  function realizeChordPlayback(program, measureOrder, timing) {
17688
17694
  let notes = [], pitches = [], startBeat = 0, occurrence = 0, finish = (endBeat) => {
17689
17695
  if (endBeat <= startBeat) return;
17690
- let startTime = timing.model.timeAtBeat(startBeat), duration = timing.model.timeAtBeat(endBeat) - startTime;
17696
+ let startTime = timing.model.timeAtBeat(startBeat), duration2 = timing.model.timeAtBeat(endBeat) - startTime;
17691
17697
  for (let pitch of pitches)
17692
- notes.push({ id: `${CHORDS_PART_ID}:${occurrence}:${pitch}`, pitch, startTime, duration });
17698
+ notes.push({ id: `${CHORDS_PART_ID}:${occurrence}:${pitch}`, pitch, startTime, duration: duration2 });
17693
17699
  occurrence++;
17694
17700
  };
17695
17701
  for (let expandedIndex = 0; expandedIndex < measureOrder.length; expandedIndex++) {
@@ -17758,9 +17764,9 @@ var WHOLE_DURATIONS = {
17758
17764
  "512th": 1 / 512,
17759
17765
  "1024th": 1 / 1024
17760
17766
  };
17761
- function durationValue(duration) {
17762
- let base = WHOLE_DURATIONS[duration.base] ?? 0, value = base;
17763
- for (let dot = 0; dot < (duration.dots ?? 0); dot++) value += base / 2 ** (dot + 1);
17767
+ function durationValue(duration2) {
17768
+ let base = WHOLE_DURATIONS[duration2.base] ?? 0, value = base;
17769
+ for (let dot = 0; dot < (duration2.dots ?? 0); dot++) value += base / 2 ** (dot + 1);
17764
17770
  return value;
17765
17771
  }
17766
17772
  function contentInfo(content, scale = 1) {
@@ -17769,8 +17775,8 @@ function contentInfo(content, scale = 1) {
17769
17775
  if (item.type === "event") {
17770
17776
  let sounding = !!item.notes?.length || !!item.kitNotes?.length;
17771
17777
  sounding && (active = !0, onset ??= cursor);
17772
- let duration = durationValue(item.duration) * scale;
17773
- return cursor += duration, {
17778
+ let duration2 = durationValue(item.duration) * scale;
17779
+ return cursor += duration2, {
17774
17780
  duration: item.duration,
17775
17781
  sounding
17776
17782
  };
@@ -17780,13 +17786,13 @@ function contentInfo(content, scale = 1) {
17780
17786
  return nested.active && (active = !0, onset ??= cursor + nested.onset), cursor += nested.duration, { type: item.type, inner: item.inner, outer: item.outer, content: nested.signature };
17781
17787
  }
17782
17788
  if (item.type === "tremolo") {
17783
- let duration = durationValue(item.outer.duration) * item.outer.multiple * scale;
17784
- return item.content.some((event) => !!event.notes?.length || !!event.kitNotes?.length) && (active = !0, onset ??= cursor), cursor += duration, { type: item.type, outer: item.outer, content: item.content.map((event) => event.duration) };
17789
+ let duration2 = durationValue(item.outer.duration) * item.outer.multiple * scale;
17790
+ return item.content.some((event) => !!event.notes?.length || !!event.kitNotes?.length) && (active = !0, onset ??= cursor), cursor += duration2, { type: item.type, outer: item.outer, content: item.content.map((event) => event.duration) };
17785
17791
  }
17786
17792
  if (item.type === "grace") return { type: item.type, content: item.content.map((event) => event.duration) };
17787
17793
  if (item.type === "space") {
17788
- let duration = (item.duration[1] === 0 ? 0 : item.duration[0] / item.duration[1]) * scale;
17789
- return cursor += duration, { type: item.type, duration: item.duration };
17794
+ let duration2 = (item.duration[1] === 0 ? 0 : item.duration[0] / item.duration[1]) * scale;
17795
+ return cursor += duration2, { type: item.type, duration: item.duration };
17790
17796
  }
17791
17797
  return {};
17792
17798
  }), active, onset, duration: cursor };
@@ -18128,8 +18134,8 @@ function resolveVoiceLegato(a) {
18128
18134
  if (!bucket || bucket.length === 0) return;
18129
18135
  let onset = Math.max(
18130
18136
  0,
18131
- a.model.timeAtBeat(a.measureStartBeat + a.beatOffset) + timingHumanize(a.measureStartTime, a.beatOffset, a.partIndex)
18132
- ), nextDur = a.model.secondsForBeats(a.measureStartBeat + a.beatOffset, a.beats);
18137
+ (a.soundingOnset ?? a.model.timeAtBeat(a.measureStartBeat + a.beatOffset)) + timingHumanize(a.measureStartTime, a.beatOffset, a.partIndex)
18138
+ ), nextDur = a.soundingDuration ?? a.model.secondsForBeats(a.measureStartBeat + a.beatOffset, a.beats);
18133
18139
  resolveLegato(bucket, a.out, onset, nextDur, a.notes);
18134
18140
  }
18135
18141
 
@@ -18808,6 +18814,107 @@ function laneForScope(program, partIndex, staff, voice) {
18808
18814
  return program.lanes.get(id) ?? program.lanes.values().next().value;
18809
18815
  }
18810
18816
 
18817
+ // packages/midi/src/rhythmRealization/index.ts
18818
+ init_define_import_meta_env();
18819
+ var EPS3 = 1e-9;
18820
+ function duration(d) {
18821
+ return DURATION_BEATS[d.base] * (d.dots ? 2 - 2 ** -d.dots : 1);
18822
+ }
18823
+ function itemBeats(item) {
18824
+ switch (item.type) {
18825
+ case "event":
18826
+ return duration(item.duration);
18827
+ case "space":
18828
+ return item.duration[0] * 4 / item.duration[1];
18829
+ case "tuplet":
18830
+ case "tremolo":
18831
+ return duration(item.outer.duration) * item.outer.multiple;
18832
+ case "grace":
18833
+ return 0;
18834
+ }
18835
+ }
18836
+ function eligible(item, sequence) {
18837
+ return item?.type === "event" && !item.duration.dots && (item.staff === void 0 || item.staff === (sequence.staff ?? 1)) && item.markings?.tremolo === void 0 && item.markings?.trill === void 0;
18838
+ }
18839
+ function swingAt(anchors, beat) {
18840
+ let result = { mode: "straight" };
18841
+ for (let anchor of anchors) {
18842
+ if (anchor.beat > beat + EPS3) break;
18843
+ result = anchor.swing;
18844
+ }
18845
+ return result;
18846
+ }
18847
+ function containsGrace(content) {
18848
+ return content.some((item) => item.type === "grace" || item.type === "tuplet" && containsGrace(item.content));
18849
+ }
18850
+ function compileSequence(sequence, anchors, metricOffset, measureBeats3, holdBeats) {
18851
+ let timings = /* @__PURE__ */ new Map();
18852
+ if (sequence.fullMeasure || containsGrace(sequence.content)) return timings;
18853
+ let cursor = 0;
18854
+ for (let index = 0; index < sequence.content.length; index++) {
18855
+ let first = sequence.content[index], second = sequence.content[index + 1], swing2 = swingAt(anchors, cursor), beats = itemBeats(first), pairEnd = cursor + 2 * beats;
18856
+ if (swing2.mode === "swing" && eligible(first, sequence) && eligible(second, sequence) && first.duration.base === swing2.subdivision && second.duration.base === swing2.subdivision && Math.abs((cursor + metricOffset) / (2 * beats) - Math.round((cursor + metricOffset) / (2 * beats))) < EPS3 && pairEnd <= measureBeats3 + EPS3 && !holdBeats.some((beat) => beat > cursor + EPS3 && beat < pairEnd - EPS3) && !anchors.some((anchor) => anchor.beat > cursor + EPS3 && anchor.beat < pairEnd - EPS3)) {
18857
+ let split = cursor + 2 * beats * swing2.ratio[0] / (swing2.ratio[0] + swing2.ratio[1]);
18858
+ timings.set(first, { start: cursor, end: split, pairStart: cursor, pairEnd }), timings.set(second, { start: split, end: pairEnd, pairStart: cursor, pairEnd });
18859
+ }
18860
+ cursor += beats;
18861
+ }
18862
+ return timings;
18863
+ }
18864
+ function compileRhythmProgram(score) {
18865
+ let indices = score.global.measures.map((_, index) => index), playbackMeasures = playbackGlobalMeasures(score, indices), holds = buildHoldSchedule(score, indices, score.global.measures), inherited = { mode: "straight" }, anchorsByMeasure = score.global.measures.map((measure) => {
18866
+ let anchors = [{ beat: -1 / 0, swing: inherited }], ordered = (measure.tempos ?? []).map((tempo, index) => ({
18867
+ tempo,
18868
+ index,
18869
+ beat: (tempo.location?.fraction[0] ?? 0) * 4 / (tempo.location?.fraction[1] ?? 1)
18870
+ })).sort((a, b) => a.beat - b.beat || a.index - b.index);
18871
+ for (let { tempo, beat } of ordered)
18872
+ if (tempo.swing) {
18873
+ if (tempo.swing.mode === "swing" && !tempo.swing.ratio.every((value) => Number.isSafeInteger(value) && value > 0))
18874
+ throw new Error("Swing ratios must be positive safe integers.");
18875
+ inherited = tempo.swing, anchors.push({ beat, swing: inherited });
18876
+ }
18877
+ return anchors;
18878
+ }), sequences = /* @__PURE__ */ new Map(), diagnostics = [];
18879
+ return score.parts.forEach((part, partIndex) => {
18880
+ let staffMeters = resolvePartStaffMeterTable(part, score.global.measures);
18881
+ part.measures.forEach((measure, measureIndex) => {
18882
+ let anchors = anchorsByMeasure[measureIndex];
18883
+ if (!anchors) return;
18884
+ let time = resolveActiveTime(score.global.measures, measureIndex), playbackTime = playbackMeasures[measureIndex].time, measureBeats3 = playbackTime.count * 4 / playbackTime.unit, metricOffset = measureIndex === 0 && score.global.measures[0].number === 0 ? time.count * 4 / time.unit - measureBeats3 : 0, diagnosed = /* @__PURE__ */ new Set(), hasSwing = [
18885
+ 0,
18886
+ ...anchors.map((anchor) => anchor.beat).filter((beat) => beat > 0 && beat < measureBeats3)
18887
+ ].some((beat) => swingAt(anchors, beat).mode === "swing");
18888
+ for (let sequence of measure.sequences) {
18889
+ let staff = sequence.staff ?? 1;
18890
+ if (staffMeters[measureIndex]?.has(staff)) {
18891
+ !diagnosed.has(staff) && hasSwing && (diagnostics.push({
18892
+ code: "swing-staff-meter-unsupported",
18893
+ source: { partIndex, measureIndex, staff },
18894
+ message: `Swing left literal in part ${partIndex}, measure ${measureIndex}, staff ${staff}: local staff meters are not supported.`,
18895
+ playbackLaneIds: []
18896
+ }), diagnosed.add(staff));
18897
+ continue;
18898
+ }
18899
+ time.display !== "senzaMisura" && sequences.set(
18900
+ sequence,
18901
+ compileSequence(
18902
+ sequence,
18903
+ anchors,
18904
+ metricOffset,
18905
+ measureBeats3,
18906
+ (holds[measureIndex] ?? []).map((hold) => hold.atBeat)
18907
+ )
18908
+ );
18909
+ }
18910
+ });
18911
+ }), { sequences, diagnostics };
18912
+ }
18913
+ function realizeRhythmEvent(timings, event, start, beats, span) {
18914
+ let timing = timings?.get(event);
18915
+ return !timing || span && (timing.pairStart < span.startBeat - EPS3 || timing.pairEnd > span.endBeat + EPS3) ? { start, beats } : { start: timing.start, beats: timing.end - timing.start };
18916
+ }
18917
+
18811
18918
  // packages/midi/src/timeline.ts
18812
18919
  var GM_DRUM_CHANNEL = 9;
18813
18920
  function noteToMidi(note) {
@@ -18848,7 +18955,7 @@ function generateTimeline(inputScore, options) {
18848
18955
  let expandedGlobal = playbackGlobalMeasures(score, measureOrder), holdSchedule = suppressCadenzaFermataHolds(
18849
18956
  expandedGlobal,
18850
18957
  buildHoldSchedule(score, measureOrder, globalMeasures)
18851
- ), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild, timing = { tempoMap, measureStartTimes, model, measureStartBeats, playedSpans: spans }, allEvents = [], partChannels = allocatePartChannels(score.parts);
18958
+ ), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild, rhythm = compileRhythmProgram(score), timing = { tempoMap, measureStartTimes, model, measureStartBeats, playedSpans: spans, rhythm }, allEvents = [], partChannels = allocatePartChannels(score.parts);
18852
18959
  for (let partIdx = 0; partIdx < score.parts.length; partIdx++)
18853
18960
  processPart(
18854
18961
  score,
@@ -18876,14 +18983,19 @@ function generateTimeline(inputScore, options) {
18876
18983
  { model, measureStartBeats, measureOrder },
18877
18984
  options?.instrumentPrograms,
18878
18985
  options?.partPrograms
18879
- ), diagnostics = [...assignPhysicalMidiChannels(allEvents, score), ...instrumentDiagnostics];
18986
+ ), diagnostics = [
18987
+ ...assignPhysicalMidiChannels(allEvents, score),
18988
+ ...instrumentDiagnostics,
18989
+ ...rhythm.diagnostics
18990
+ ];
18991
+ for (let diagnostic of diagnostics) options?.onDiagnostic?.(diagnostic);
18880
18992
  allEvents.sort(compareEvents);
18881
- let duration = model.timeAtBeat(tempoBuild.totalBeats), measureTimeSignatures = measureOrder.map((origIdx) => {
18993
+ let duration2 = model.timeAtBeat(tempoBuild.totalBeats), measureTimeSignatures = measureOrder.map((origIdx) => {
18882
18994
  let ts = resolveActiveTime(globalMeasures, origIdx);
18883
18995
  return { count: ts.count, unit: ts.unit };
18884
18996
  }), result = {
18885
18997
  events: allEvents,
18886
- duration,
18998
+ duration: duration2,
18887
18999
  tempoMap,
18888
19000
  measureStartTimes,
18889
19001
  model,
@@ -19056,7 +19168,7 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
19056
19168
  let lane = laneForSequence(dynamicProgram, partIdx, seq, seqIdx);
19057
19169
  ctx.sequenceStaff = seq.staff ?? 1, ctx.sequenceVoice = lane.voice, ctx.dynamicsEnvelope = lane.envelope, ctx.playbackLaneId = lane.id;
19058
19170
  let effectiveStaffMeter = staffMeterTable[origMeasureIdx]?.get(ctx.sequenceStaff);
19059
- ctx.timeSig = effectiveStaffMeter ? effectiveStaffMeter.timeSignature : activeTime, ctx.staffMeterRatio = staffMeterRatioAt(staffMeterTable, origMeasureIdx, ctx.sequenceStaff);
19171
+ ctx.timeSig = effectiveStaffMeter ? effectiveStaffMeter.timeSignature : activeTime, ctx.staffMeterRatio = staffMeterRatioAt(staffMeterTable, origMeasureIdx, ctx.sequenceStaff), ctx.rhythm = timing.rhythm.sequences.get(seq);
19060
19172
  let firstNewEvent = allEvents.length;
19061
19173
  processSequence(ctx, seq, measureStartTime, measureHolds, measureSpq), stampPlaybackLane(allEvents, firstNewEvent, lane.id), stampPendingLane(ctx, lane.id);
19062
19174
  });
@@ -19093,7 +19205,11 @@ function processScopedNoteEvent(ctx, event, measureStartTime, beatOffset, tuplet
19093
19205
  event,
19094
19206
  measureStartTime,
19095
19207
  beatOffset,
19096
- dynamicVelocityAt(ctx, measureStartTime, beatOffset),
19208
+ dynamicVelocityAt(
19209
+ ctx,
19210
+ measureStartTime,
19211
+ realizeRhythmEvent(ctx.rhythm, event, beatOffset, durationBeats4(event.duration) * tupletRatio, ctx.playedSpan).start
19212
+ ),
19097
19213
  tupletRatio
19098
19214
  );
19099
19215
  return stampPlaybackLane(ctx.out, firstNewEvent, lane.id), stampPendingLane(ctx, lane.id), ctx.dynamicsEnvelope = previousEnvelope, ctx.playbackLaneId = previousLaneId, beats;
@@ -19120,13 +19236,13 @@ function processContentItem(ctx, item, measureStartTime, beatOffset, tupletRatio
19120
19236
  return 0;
19121
19237
  }
19122
19238
  }
19123
- function resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset) {
19239
+ function resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset, soundingBeatOffset = beatOffset) {
19124
19240
  if (event.fermata && event.fermata.duration !== "none" && ctx.fermataGroups.length > 0) {
19125
19241
  let globalBeatOffset = beatOffset * ctx.staffMeterRatio, group = ctx.fermataGroups.find(
19126
19242
  (g) => globalBeatOffset >= g.startBeat - 1e-9 && globalBeatOffset < g.spanEndBeat - 1e-9
19127
19243
  );
19128
19244
  if (group) {
19129
- let startTime = eventTime(ctx, beatOffset), resumeTime = ctx.model.timeAtBeat(ctx.measureStartBeat + group.insertBeat);
19245
+ let startTime = eventTime(ctx, soundingBeatOffset), resumeTime = ctx.model.timeAtBeat(ctx.measureStartBeat + group.insertBeat);
19130
19246
  return { durationSec: Math.max(naturalDurationSec, resumeTime - startTime), durationScale: 1, held: !0 };
19131
19247
  }
19132
19248
  }
@@ -19147,6 +19263,7 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
19147
19263
  if (!beatInPlayedSpan(beatOffset * ctx.staffMeterRatio, ctx.playedSpan)) return beats;
19148
19264
  if (isRest(event))
19149
19265
  return flushVoiceLegato(ctx.pendingLegato, ctx.voiceKey, ctx.out), beats;
19266
+ let realized = realizeRhythmEvent(ctx.rhythm, event, beatOffset, beats, ctx.playedSpan), startTime = eventTime(ctx, realized.start), naturalDurationSec = eventSeconds(ctx, realized.start, realized.beats);
19150
19267
  resolveVoiceLegato({
19151
19268
  map: ctx.pendingLegato,
19152
19269
  voiceKey: ctx.voiceKey,
@@ -19157,15 +19274,17 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
19157
19274
  measureStartTime,
19158
19275
  beatOffset,
19159
19276
  beats,
19160
- notes: event.notes ?? []
19277
+ notes: event.notes ?? [],
19278
+ soundingOnset: startTime,
19279
+ soundingDuration: naturalDurationSec
19161
19280
  });
19162
19281
  let specialBeats = tryProcessSpecialEvent(ctx, event, measureStartTime, beatOffset, baseVelocity, beats);
19163
19282
  if (specialBeats !== null) return specialBeats;
19164
- let startTime = eventTime(ctx, beatOffset), timingOffset = timingHumanize(measureStartTime, beatOffset, partIndex), naturalDurationSec = eventSeconds(ctx, beatOffset, beats), {
19283
+ let timingOffset = timingHumanize(measureStartTime, beatOffset, partIndex), {
19165
19284
  durationSec,
19166
19285
  durationScale,
19167
19286
  held: fermataHeld
19168
- } = resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset), velocity = Math.min(
19287
+ } = resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset, realized.start), velocity = Math.min(
19169
19288
  127,
19170
19289
  Math.max(
19171
19290
  1,