@real-music-packages/web-core 0.19.0 → 0.20.0

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.
@@ -834,6 +834,7 @@ declare function distinctOnsets(notes: {
834
834
  * (first→last onset), not on musicMs — so the fade tracks the notes too.
835
835
  */
836
836
  declare function audioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number): PlayheadLine | null;
837
+ declare function vstackAudioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number, nBars: number): PlayheadLine | null;
837
838
 
838
839
  /** Map a canvas-space box through a base notation layout into world/screen coords. */
839
840
  declare function mapBoxThroughLayout(base: NotationLayout, b: Box): Box;
@@ -1619,4 +1620,4 @@ interface SectionMinimapProps {
1619
1620
  }
1620
1621
  declare const sectionMinimapFactory: LayerFactory<SectionMinimapProps>;
1621
1622
 
1622
- export { type Affine, type AudioClock, type AudioEvent, type BackgroundProps, type BeatGridOpts, type BeatTick, type BrandingProps, type BufferFactory, type BuildSceneOpts, type BuiltScene, type CameraState, type CaptionCue, type CaptionProps, type CaptionScript, type CaptionStyle, type ChordSpan, type CircleOfFifthsProps, type CirclePoint, type ClickTrackOpts, type ColorBy, type ContourPlot, type ContourPoint, type CountInOpts, type CountingTrackProps, type CtaProps, DEFAULT_FUNCTION_COLORS, type DegreeLabel, type DegreeLabelsProps, type DroneOpts, type DuckWindow, type Easing, type ExtendedDemoOpts, FIFTHS_MAJOR, FIFTHS_MINOR, FOLLOW_BARS, FOLLOW_PAD, type FallingKeyboardDemoOpts, type FallingNotesProps, type FunctionColors, type FunctionalHarmonyProps, type GateError, type GateInput, type HandColors, type HarmonicFunction, type HarmonyMode, type HighlightRegion, type HookProps, type KeyRect, type KeyboardLayout, type KeyboardLayoutOpts, type KeyboardProps, type LabelMode, type Layer, type LayerFactory, type McqCardProps, type NotationEngraving, type NotationLayout, type NotationLayoutOpts, type NotationProps, type NotationRect, type OutputProbe, PIANO_HIGH, PIANO_LOW, type PitchContourProps, type Placement, type PlayheadLine, type PortraitProps, type PromoCardsDemoOpts, type Quiz, type QuizOption, type QuizPhase, type RayEndpoints, type RecordSceneSpecOpts, type Rect, type RenderCtx, type ResolvedSegment, type RevealProps, type SafeGuidesProps, type SceneSpec, type ScheduleTarget, type Score, type ScoreFromMusicXMLOpts, type ScoreNote, type ScrollCursorProps, type Section, type SectionMinimapProps, type SegmentAudio, type SegmentAudioCtx, type SegmentTransition, type SpecLayer, type SpectrumInput, type SpectrumProps, type StaffKeyboardRayProps, type TempoMap, type TimeAnchor, type TimelineSegment, activeChord, activeCue, activeSection, animatedSlot, applySchedule, applyToContext, assertGate, audioPlayheadLine, ballArc, ballX, beatGrid, beatPhase, blackKeys, bpmOf, brandingFactory, buildScene, cameraForFollow, cameraTransform, circleOfFifthsDemoSpec, circleOfFifthsFactory, clamp, clickTrackSchedule, contourMinimapDemoSpec, contourPoints, contourPolyline, countInLeadSec, countInSchedule, countdownRemaining, countdownSeconds, countingDegreeDemoSpec, countingTrackFactory, cropAroundBox, ctaFactory, cubicEaseInOut, cueOpacity, degreeLabel, degreeLabelsFactory, distinctOnsets, dotAt, drawCaption, drawHighlight, droneSchedule, duckGainAt, easeIn, easeInOut, easeOut, fallingKeyboardDemoScore, fallingKeyboardDemoSpec, fallingNotesFactory, firstMeasureBox, followBoxAt, followSrcBox, followWindowStart, fracSlotPoint, frameRect, functionColor, functionalHarmonyFactory, getKeyboardLayout, getLayerFactory, getNotationEngraving, harmonyDemoSpec, highlightIntensity, hookFactory, identityCamera, inRange, invLerp, isBlackKey, kenBurns, keyCenterX, keyColumnWidth, keyRect, keySlot, keyboardFactory, keyboardLayout, lerp, lerpBox, lerpCamera, linear, mapBoxThroughLayout, mcqCardFactory, mcqDemoSpec, measureColumnsFromLayout, measureCount, measureSpanBox, measureSpans, measureSystemMap, timeToX as minimapTimeToX, msPerBeat, notationFactory, notationLayout, noteColor, noteSetXRange, parseKey, parseTimeSig, pcToSlot, pitchAt, pitchContourFactory, pitchRange, playheadLine, portraitFactory, progress01, projectPoint, promoCardsDemoSpec, quizPhase, rayEndpoints, rayPointAt, recordSceneSpec, registerLayer, registeredKeys, resolveAnchor, resolveKeyboardLayout, resolveTimeline, revealFactory, revealProgress, runGate, safeGuidesFactory, scoreFromMusicXML, scorePitchSpan, scrollCursorFactory, sectionMinimapFactory, setFollowLayoutProvider, setKeyboardLayout, setNotationEngraving, slotAngle, slotPc, slotPoint, spectrumFactory, staffAnchor, staffKeyboardRayFactory, staffRayDemoSpec, systemBox, transitionProgress, validateChordTrack, validateQuiz, validateSections, visualTimelineMs, vstackFollowBox, whiteKeys, worldToViewport };
1623
+ export { type Affine, type AudioClock, type AudioEvent, type BackgroundProps, type BeatGridOpts, type BeatTick, type BrandingProps, type BufferFactory, type BuildSceneOpts, type BuiltScene, type CameraState, type CaptionCue, type CaptionProps, type CaptionScript, type CaptionStyle, type ChordSpan, type CircleOfFifthsProps, type CirclePoint, type ClickTrackOpts, type ColorBy, type ContourPlot, type ContourPoint, type CountInOpts, type CountingTrackProps, type CtaProps, DEFAULT_FUNCTION_COLORS, type DegreeLabel, type DegreeLabelsProps, type DroneOpts, type DuckWindow, type Easing, type ExtendedDemoOpts, FIFTHS_MAJOR, FIFTHS_MINOR, FOLLOW_BARS, FOLLOW_PAD, type FallingKeyboardDemoOpts, type FallingNotesProps, type FunctionColors, type FunctionalHarmonyProps, type GateError, type GateInput, type HandColors, type HarmonicFunction, type HarmonyMode, type HighlightRegion, type HookProps, type KeyRect, type KeyboardLayout, type KeyboardLayoutOpts, type KeyboardProps, type LabelMode, type Layer, type LayerFactory, type McqCardProps, type NotationEngraving, type NotationLayout, type NotationLayoutOpts, type NotationProps, type NotationRect, type OutputProbe, PIANO_HIGH, PIANO_LOW, type PitchContourProps, type Placement, type PlayheadLine, type PortraitProps, type PromoCardsDemoOpts, type Quiz, type QuizOption, type QuizPhase, type RayEndpoints, type RecordSceneSpecOpts, type Rect, type RenderCtx, type ResolvedSegment, type RevealProps, type SafeGuidesProps, type SceneSpec, type ScheduleTarget, type Score, type ScoreFromMusicXMLOpts, type ScoreNote, type ScrollCursorProps, type Section, type SectionMinimapProps, type SegmentAudio, type SegmentAudioCtx, type SegmentTransition, type SpecLayer, type SpectrumInput, type SpectrumProps, type StaffKeyboardRayProps, type TempoMap, type TimeAnchor, type TimelineSegment, activeChord, activeCue, activeSection, animatedSlot, applySchedule, applyToContext, assertGate, audioPlayheadLine, ballArc, ballX, beatGrid, beatPhase, blackKeys, bpmOf, brandingFactory, buildScene, cameraForFollow, cameraTransform, circleOfFifthsDemoSpec, circleOfFifthsFactory, clamp, clickTrackSchedule, contourMinimapDemoSpec, contourPoints, contourPolyline, countInLeadSec, countInSchedule, countdownRemaining, countdownSeconds, countingDegreeDemoSpec, countingTrackFactory, cropAroundBox, ctaFactory, cubicEaseInOut, cueOpacity, degreeLabel, degreeLabelsFactory, distinctOnsets, dotAt, drawCaption, drawHighlight, droneSchedule, duckGainAt, easeIn, easeInOut, easeOut, fallingKeyboardDemoScore, fallingKeyboardDemoSpec, fallingNotesFactory, firstMeasureBox, followBoxAt, followSrcBox, followWindowStart, fracSlotPoint, frameRect, functionColor, functionalHarmonyFactory, getKeyboardLayout, getLayerFactory, getNotationEngraving, harmonyDemoSpec, highlightIntensity, hookFactory, identityCamera, inRange, invLerp, isBlackKey, kenBurns, keyCenterX, keyColumnWidth, keyRect, keySlot, keyboardFactory, keyboardLayout, lerp, lerpBox, lerpCamera, linear, mapBoxThroughLayout, mcqCardFactory, mcqDemoSpec, measureColumnsFromLayout, measureCount, measureSpanBox, measureSpans, measureSystemMap, timeToX as minimapTimeToX, msPerBeat, notationFactory, notationLayout, noteColor, noteSetXRange, parseKey, parseTimeSig, pcToSlot, pitchAt, pitchContourFactory, pitchRange, playheadLine, portraitFactory, progress01, projectPoint, promoCardsDemoSpec, quizPhase, rayEndpoints, rayPointAt, recordSceneSpec, registerLayer, registeredKeys, resolveAnchor, resolveKeyboardLayout, resolveTimeline, revealFactory, revealProgress, runGate, safeGuidesFactory, scoreFromMusicXML, scorePitchSpan, scrollCursorFactory, sectionMinimapFactory, setFollowLayoutProvider, setKeyboardLayout, setNotationEngraving, slotAngle, slotPc, slotPoint, spectrumFactory, staffAnchor, staffKeyboardRayFactory, staffRayDemoSpec, systemBox, transitionProgress, validateChordTrack, validateQuiz, validateSections, visualTimelineMs, vstackAudioPlayheadLine, vstackFollowBox, whiteKeys, worldToViewport };
@@ -323,6 +323,28 @@ function cubicEaseInOut(t) {
323
323
  const f = 2 * t - 2;
324
324
  return 0.5 * f * f * f + 1;
325
325
  }
326
+ function staffSpacePx(rn) {
327
+ const hs = (rn.measures ?? []).map((m) => m.box.h).filter((h) => h > 1).sort((a, b) => a - b);
328
+ if (!hs.length) return 14;
329
+ const med = hs[Math.floor(hs.length / 2)];
330
+ return Math.max(6, med / 4);
331
+ }
332
+ function withLedgerHeadroom(rn, box, spaces, unionInk) {
333
+ const pad = staffSpacePx(rn) * spaces;
334
+ const ch = rn.canvas.height || box.y + box.h;
335
+ let y0 = box.y - pad;
336
+ let y1 = box.y + box.h + pad;
337
+ if (unionInk) {
338
+ const c = rn.content;
339
+ if (c && c.h > 0) {
340
+ y0 = Math.min(y0, c.y);
341
+ y1 = Math.max(y1, c.y + c.h);
342
+ }
343
+ }
344
+ y0 = Math.max(0, y0);
345
+ y1 = Math.min(ch, y1);
346
+ return { x: box.x, y: y0, w: box.w, h: Math.max(1, y1 - y0) };
347
+ }
326
348
  function lerpBox(a, b, e) {
327
349
  return {
328
350
  x: a.x + (b.x - a.x) * e,
@@ -353,7 +375,7 @@ function measureSpanBox(rn, lo, hi) {
353
375
  const y0 = Math.min(...ms.map((b) => b.y));
354
376
  const x1 = Math.max(...ms.map((b) => b.x + b.w));
355
377
  const y1 = Math.max(...ms.map((b) => b.y + b.h));
356
- return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
378
+ return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 6, true);
357
379
  }
358
380
  function firstMeasureBox(rn) {
359
381
  const first = (rn.measures ?? []).filter((m) => m.index === 0).map((m) => m.box);
@@ -362,7 +384,7 @@ function firstMeasureBox(rn) {
362
384
  const y0 = Math.min(...first.map((b) => b.y));
363
385
  const x1 = Math.max(...first.map((b) => b.x + b.w));
364
386
  const y1 = Math.max(...first.map((b) => b.y + b.h));
365
- return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
387
+ return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 6, false);
366
388
  }
367
389
  function measureCount(rn) {
368
390
  const idx = (rn.measures ?? []).map((m) => m.index);
@@ -421,7 +443,7 @@ function systemBox(rn, sys, sysMap) {
421
443
  const y0 = Math.min(...boxes.map((b) => b.y));
422
444
  const x1 = Math.max(...boxes.map((b) => b.x + b.w));
423
445
  const y1 = Math.max(...boxes.map((b) => b.y + b.h));
424
- return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
446
+ return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 8, false);
425
447
  }
426
448
  function vstackFollowBox(rn, camProgress01) {
427
449
  const sysMap = measureSystemMap(rn);
@@ -617,6 +639,69 @@ function audioPlayheadLine(layout, onsetsMs, tMs) {
617
639
  if (alpha <= 0.02) return null;
618
640
  return { x, y0, y1, alpha };
619
641
  }
642
+ function vstackAudioPlayheadLine(layout, onsetsMs, tMs, nBars) {
643
+ const padV = 10;
644
+ const cols = measureColumnsFromLayout(layout.measures);
645
+ if (!cols.length || !onsetsMs.length || nBars <= 0) return null;
646
+ const n = onsetsMs.length;
647
+ const first = onsetsMs[0];
648
+ const last = onsetsMs[n - 1];
649
+ const span = last - first;
650
+ let progress;
651
+ if (tMs <= first || n === 1) progress = 0;
652
+ else if (tMs >= last || last <= first) progress = 1;
653
+ else {
654
+ let lo = 0, hi = n - 1;
655
+ while (lo < hi) {
656
+ const mid = lo + hi + 1 >> 1;
657
+ if (onsetsMs[mid] <= tMs) lo = mid;
658
+ else hi = mid - 1;
659
+ }
660
+ const segFrac = cubicEaseInOut((tMs - onsetsMs[lo]) / (onsetsMs[lo + 1] - onsetsMs[lo]));
661
+ progress = (lo + segFrac) / (n - 1);
662
+ }
663
+ const colX = (m2, f) => {
664
+ const sx = Math.min(m2.noteStartX, m2.x + m2.w);
665
+ return sx + Math.min(1, Math.max(0, f)) * (m2.x + m2.w - sx);
666
+ };
667
+ const posBars = Math.min(nBars - 1e-6, Math.max(0, progress * nBars));
668
+ const i = Math.min(cols.length - 1, Math.floor(posBars));
669
+ const barFrac = posBars - i;
670
+ const m = cols[i];
671
+ let x = colX(m, barFrac);
672
+ let y0 = m.y - padV;
673
+ let y1 = m.y + m.h + padV;
674
+ if (i + 1 < cols.length) {
675
+ const nxt = cols[i + 1];
676
+ const crossesSystem = Math.abs(nxt.y - m.y) > m.h * 1.5;
677
+ if (crossesSystem) {
678
+ const s = cubicEaseInOut(Math.min(1, Math.max(0, (barFrac - 0.66) / 0.34)));
679
+ x = x + (colX(nxt, 0) - x) * s;
680
+ y0 = y0 + (nxt.y - padV - y0) * s;
681
+ y1 = y1 + (nxt.y + nxt.h + padV - y1) * s;
682
+ }
683
+ }
684
+ const bandY0 = layout.rect.dy;
685
+ const bandY1 = layout.rect.dy + layout.rect.dh;
686
+ const h = Math.max(1, y1 - y0);
687
+ if (y0 < bandY0) {
688
+ y0 = bandY0;
689
+ y1 = bandY0 + h;
690
+ }
691
+ if (y1 > bandY1) {
692
+ y1 = bandY1;
693
+ y0 = bandY1 - h;
694
+ }
695
+ let alpha = 0.85;
696
+ if (span > 0) {
697
+ const fadeIn = Math.min(250, span * 0.5);
698
+ const fadeOut = Math.min(400, span * 0.5);
699
+ if (tMs < first + fadeIn) alpha *= clamp01((tMs - (first - fadeIn)) / (2 * fadeIn));
700
+ if (tMs > last - fadeOut) alpha *= clamp01((last + fadeOut - tMs) / (2 * fadeOut));
701
+ }
702
+ if (alpha <= 0.02) return null;
703
+ return { x, y0, y1, alpha };
704
+ }
620
705
 
621
706
  // src/scene/engravingStore.ts
622
707
  var STORE = /* @__PURE__ */ new WeakMap();
@@ -783,7 +868,11 @@ function scrollCursorLayer() {
783
868
  let line;
784
869
  const onsets = mode === "audio" ? onsetsFor(ctx) : [];
785
870
  if (mode === "audio" && onsets.length) {
786
- line = audioPlayheadLine(layout, onsets, tMs);
871
+ if (scrollMode === "vstack") {
872
+ line = vstackAudioPlayheadLine(layout, onsets, tMs, measureCount(eng.rendered));
873
+ } else {
874
+ line = audioPlayheadLine(layout, onsets, tMs);
875
+ }
787
876
  } else {
788
877
  line = playheadLine(layout, camProgress(tMs));
789
878
  }
@@ -3822,6 +3911,7 @@ export {
3822
3911
  validateQuiz,
3823
3912
  validateSections,
3824
3913
  visualTimelineMs,
3914
+ vstackAudioPlayheadLine,
3825
3915
  vstackFollowBox,
3826
3916
  whiteKeys,
3827
3917
  worldToViewport