@real-music-packages/web-core 0.19.0 → 0.21.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.
- package/dist/scene/index.d.ts +27 -17
- package/dist/scene/index.js +120 -13
- package/dist/scene/index.js.map +1 -1
- package/package.json +1 -1
package/dist/scene/index.d.ts
CHANGED
|
@@ -731,29 +731,38 @@ declare function measureSpanBox(rn: RenderedNotation, lo: number, hi: number): B
|
|
|
731
731
|
declare function firstMeasureBox(rn: RenderedNotation): Box | null;
|
|
732
732
|
/** Number of distinct measures in the notation. (RSR measureCount) */
|
|
733
733
|
declare function measureCount(rn: RenderedNotation): number;
|
|
734
|
+
/** The DISTINCT measure indices present in the engraving, ASCENDING. For excerpts
|
|
735
|
+
* these do NOT start at 0 (e.g. bars 5-8 → [4,5,6,7]) and may be non-contiguous,
|
|
736
|
+
* so the follow window must scroll across THESE indices, not a 0..measureCount
|
|
737
|
+
* range. (measureCount returns maxIndex+1 — a count valid only for 0-based scores.) */
|
|
738
|
+
declare function distinctMeasureIndices(rn: RenderedNotation): number[];
|
|
734
739
|
/** The follow window (canvas coords) for a continuous measure-start position,
|
|
735
740
|
* spanning FOLLOW_BARS and lerping between adjacent windows. (RSR followBoxAt) */
|
|
736
741
|
declare function followBoxAt(rn: RenderedNotation, posMeasures: number): Box | null;
|
|
737
742
|
/**
|
|
738
|
-
* The continuous follow-window START measure for an
|
|
743
|
+
* The continuous follow-window START measure (ABSOLUTE index) for an hstack
|
|
744
|
+
* audio-clock progress 0..1.
|
|
739
745
|
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* once the playhead reaches the bottom bar. Concretely the resting window start is
|
|
745
|
-
* `curBar - (FOLLOW_BARS - 1)`; over the tail of each bar we ease that forward by
|
|
746
|
-
* one bar so the NEXT bar slides into the bottom slot just as the playhead crosses
|
|
747
|
-
* into it. Because the eased start stays within `[curBar-(FOLLOW_BARS-1),
|
|
748
|
-
* curBar-(FOLLOW_BARS-2)]`, `curBar` never leaves `[start, start+FOLLOW_BARS)` —
|
|
749
|
-
* the current measure (and its playhead) can never ride off the top.
|
|
746
|
+
* hstack (single horizontal staffline): pan CONTINUOUSLY so the playhead sits at a
|
|
747
|
+
* stable fraction (`HSTACK_PLAYHEAD_LEAD`) from the LEFT of the window — the playing
|
|
748
|
+
* bar is always on screen with look-ahead to its right, and the cursor never drifts
|
|
749
|
+
* to the screen edge.
|
|
750
750
|
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
751
|
+
* BUG FIX (web-core 0.21.0) — playhead pinned at the right edge for EXCERPTS. This
|
|
752
|
+
* used to take `nBars = measureCount(rn) = maxIndex+1` and pan progress across a
|
|
753
|
+
* 0..nBars range. That is only correct when measure indices are 0-based: RSR's
|
|
754
|
+
* excerpts engrave e.g. bars 5-8 → indices [4,5,6,7], so measureCount=8 but only 4
|
|
755
|
+
* measures exist. The window then panned a phantom 0..8 axis while the playhead
|
|
756
|
+
* (which steps over the 4 REAL columns) raced ahead — pinning the cursor at the far
|
|
757
|
+
* right while the music barely scrolled. We now pan across the ACTUAL distinct
|
|
758
|
+
* indices: progress 0..1 maps to absolute index `firstIndex .. firstIndex+nReal`,
|
|
759
|
+
* and `followBoxAt` (which already indexes absolute measures via measureSpanBox)
|
|
760
|
+
* frames the right bars. Clamped so the window never runs past the last measure.
|
|
761
|
+
*
|
|
762
|
+
* Pass the RenderedNotation so the real index range is known. (The legacy 0-based
|
|
763
|
+
* `nBars`-only call still works via the `firstIndex=0, nReal=nBars` fallback.)
|
|
755
764
|
*/
|
|
756
|
-
declare function followWindowStart(
|
|
765
|
+
declare function followWindowStart(rn: RenderedNotation | number, camProgress01: number): number;
|
|
757
766
|
/** For each measure index, the system (row) it lives on — ordered by index.
|
|
758
767
|
* Empty when geometry is missing. */
|
|
759
768
|
declare function measureSystemMap(rn: RenderedNotation): number[];
|
|
@@ -834,6 +843,7 @@ declare function distinctOnsets(notes: {
|
|
|
834
843
|
* (first→last onset), not on musicMs — so the fade tracks the notes too.
|
|
835
844
|
*/
|
|
836
845
|
declare function audioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number): PlayheadLine | null;
|
|
846
|
+
declare function vstackAudioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number, nBars: number): PlayheadLine | null;
|
|
837
847
|
|
|
838
848
|
/** Map a canvas-space box through a base notation layout into world/screen coords. */
|
|
839
849
|
declare function mapBoxThroughLayout(base: NotationLayout, b: Box): Box;
|
|
@@ -1619,4 +1629,4 @@ interface SectionMinimapProps {
|
|
|
1619
1629
|
}
|
|
1620
1630
|
declare const sectionMinimapFactory: LayerFactory<SectionMinimapProps>;
|
|
1621
1631
|
|
|
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 };
|
|
1632
|
+
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, distinctMeasureIndices, 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 };
|
package/dist/scene/index.js
CHANGED
|
@@ -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,12 +384,17 @@ 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);
|
|
369
391
|
return idx.length ? Math.max(...idx) + 1 : 0;
|
|
370
392
|
}
|
|
393
|
+
function distinctMeasureIndices(rn) {
|
|
394
|
+
const set = /* @__PURE__ */ new Set();
|
|
395
|
+
for (const m of rn.measures ?? []) set.add(m.index);
|
|
396
|
+
return [...set].sort((a, b) => a - b);
|
|
397
|
+
}
|
|
371
398
|
function followBoxAt(rn, posMeasures) {
|
|
372
399
|
const cur = Math.floor(posMeasures);
|
|
373
400
|
const frac = posMeasures - cur;
|
|
@@ -377,14 +404,25 @@ function followBoxAt(rn, posMeasures) {
|
|
|
377
404
|
if (!b) return a;
|
|
378
405
|
return lerpBox(a, b, frac);
|
|
379
406
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
407
|
+
var HSTACK_PLAYHEAD_LEAD = 0.35;
|
|
408
|
+
function followWindowStart(rn, camProgress01) {
|
|
409
|
+
let firstIndex;
|
|
410
|
+
let nReal;
|
|
411
|
+
if (typeof rn === "number") {
|
|
412
|
+
firstIndex = 0;
|
|
413
|
+
nReal = rn;
|
|
414
|
+
} else {
|
|
415
|
+
const idx = distinctMeasureIndices(rn);
|
|
416
|
+
firstIndex = idx.length ? idx[0] : 0;
|
|
417
|
+
nReal = idx.length;
|
|
418
|
+
}
|
|
419
|
+
const p = Math.max(0, Math.min(1, camProgress01));
|
|
420
|
+
const posBars = firstIndex + p * nReal;
|
|
421
|
+
const start = posBars - HSTACK_PLAYHEAD_LEAD * FOLLOW_BARS;
|
|
422
|
+
const lastIndex = firstIndex + Math.max(0, nReal - 1);
|
|
423
|
+
const minStart = firstIndex;
|
|
424
|
+
const maxStart = Math.max(minStart, lastIndex - (FOLLOW_BARS - 1));
|
|
425
|
+
return Math.max(minStart, Math.min(maxStart, start));
|
|
388
426
|
}
|
|
389
427
|
function systemIndexOfBox(systems, box) {
|
|
390
428
|
if (!systems.length) return 0;
|
|
@@ -421,7 +459,7 @@ function systemBox(rn, sys, sysMap) {
|
|
|
421
459
|
const y0 = Math.min(...boxes.map((b) => b.y));
|
|
422
460
|
const x1 = Math.max(...boxes.map((b) => b.x + b.w));
|
|
423
461
|
const y1 = Math.max(...boxes.map((b) => b.y + b.h));
|
|
424
|
-
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
462
|
+
return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 8, false);
|
|
425
463
|
}
|
|
426
464
|
function vstackFollowBox(rn, camProgress01) {
|
|
427
465
|
const sysMap = measureSystemMap(rn);
|
|
@@ -617,6 +655,69 @@ function audioPlayheadLine(layout, onsetsMs, tMs) {
|
|
|
617
655
|
if (alpha <= 0.02) return null;
|
|
618
656
|
return { x, y0, y1, alpha };
|
|
619
657
|
}
|
|
658
|
+
function vstackAudioPlayheadLine(layout, onsetsMs, tMs, nBars) {
|
|
659
|
+
const padV = 10;
|
|
660
|
+
const cols = measureColumnsFromLayout(layout.measures);
|
|
661
|
+
if (!cols.length || !onsetsMs.length || nBars <= 0) return null;
|
|
662
|
+
const n = onsetsMs.length;
|
|
663
|
+
const first = onsetsMs[0];
|
|
664
|
+
const last = onsetsMs[n - 1];
|
|
665
|
+
const span = last - first;
|
|
666
|
+
let progress;
|
|
667
|
+
if (tMs <= first || n === 1) progress = 0;
|
|
668
|
+
else if (tMs >= last || last <= first) progress = 1;
|
|
669
|
+
else {
|
|
670
|
+
let lo = 0, hi = n - 1;
|
|
671
|
+
while (lo < hi) {
|
|
672
|
+
const mid = lo + hi + 1 >> 1;
|
|
673
|
+
if (onsetsMs[mid] <= tMs) lo = mid;
|
|
674
|
+
else hi = mid - 1;
|
|
675
|
+
}
|
|
676
|
+
const segFrac = cubicEaseInOut((tMs - onsetsMs[lo]) / (onsetsMs[lo + 1] - onsetsMs[lo]));
|
|
677
|
+
progress = (lo + segFrac) / (n - 1);
|
|
678
|
+
}
|
|
679
|
+
const colX = (m2, f) => {
|
|
680
|
+
const sx = Math.min(m2.noteStartX, m2.x + m2.w);
|
|
681
|
+
return sx + Math.min(1, Math.max(0, f)) * (m2.x + m2.w - sx);
|
|
682
|
+
};
|
|
683
|
+
const posBars = Math.min(nBars - 1e-6, Math.max(0, progress * nBars));
|
|
684
|
+
const i = Math.min(cols.length - 1, Math.floor(posBars));
|
|
685
|
+
const barFrac = posBars - i;
|
|
686
|
+
const m = cols[i];
|
|
687
|
+
let x = colX(m, barFrac);
|
|
688
|
+
let y0 = m.y - padV;
|
|
689
|
+
let y1 = m.y + m.h + padV;
|
|
690
|
+
if (i + 1 < cols.length) {
|
|
691
|
+
const nxt = cols[i + 1];
|
|
692
|
+
const crossesSystem = Math.abs(nxt.y - m.y) > m.h * 1.5;
|
|
693
|
+
if (crossesSystem) {
|
|
694
|
+
const s = cubicEaseInOut(Math.min(1, Math.max(0, (barFrac - 0.66) / 0.34)));
|
|
695
|
+
x = x + (colX(nxt, 0) - x) * s;
|
|
696
|
+
y0 = y0 + (nxt.y - padV - y0) * s;
|
|
697
|
+
y1 = y1 + (nxt.y + nxt.h + padV - y1) * s;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const bandY0 = layout.rect.dy;
|
|
701
|
+
const bandY1 = layout.rect.dy + layout.rect.dh;
|
|
702
|
+
const h = Math.max(1, y1 - y0);
|
|
703
|
+
if (y0 < bandY0) {
|
|
704
|
+
y0 = bandY0;
|
|
705
|
+
y1 = bandY0 + h;
|
|
706
|
+
}
|
|
707
|
+
if (y1 > bandY1) {
|
|
708
|
+
y1 = bandY1;
|
|
709
|
+
y0 = bandY1 - h;
|
|
710
|
+
}
|
|
711
|
+
let alpha = 0.85;
|
|
712
|
+
if (span > 0) {
|
|
713
|
+
const fadeIn = Math.min(250, span * 0.5);
|
|
714
|
+
const fadeOut = Math.min(400, span * 0.5);
|
|
715
|
+
if (tMs < first + fadeIn) alpha *= clamp01((tMs - (first - fadeIn)) / (2 * fadeIn));
|
|
716
|
+
if (tMs > last - fadeOut) alpha *= clamp01((last + fadeOut - tMs) / (2 * fadeOut));
|
|
717
|
+
}
|
|
718
|
+
if (alpha <= 0.02) return null;
|
|
719
|
+
return { x, y0, y1, alpha };
|
|
720
|
+
}
|
|
620
721
|
|
|
621
722
|
// src/scene/engravingStore.ts
|
|
622
723
|
var STORE = /* @__PURE__ */ new WeakMap();
|
|
@@ -722,7 +823,7 @@ function followLayoutFor(ctx, camProgress01, scrollMode) {
|
|
|
722
823
|
if (!eng) return null;
|
|
723
824
|
const nBars = measureCount(eng.rendered);
|
|
724
825
|
if (nBars <= 0) return eng.base;
|
|
725
|
-
const focusBox = scrollMode === "vstack" ? vstackFollowBox(eng.rendered, camProgress01) : followBoxAt(eng.rendered, followWindowStart(
|
|
826
|
+
const focusBox = scrollMode === "vstack" ? vstackFollowBox(eng.rendered, camProgress01) : followBoxAt(eng.rendered, followWindowStart(eng.rendered, camProgress01));
|
|
726
827
|
return notationLayout(eng.rendered, ctx.W, ctx.H, eng.bandTop, eng.bandHeight, { focusBox });
|
|
727
828
|
}
|
|
728
829
|
function scrollCursorLayer() {
|
|
@@ -783,7 +884,11 @@ function scrollCursorLayer() {
|
|
|
783
884
|
let line;
|
|
784
885
|
const onsets = mode === "audio" ? onsetsFor(ctx) : [];
|
|
785
886
|
if (mode === "audio" && onsets.length) {
|
|
786
|
-
|
|
887
|
+
if (scrollMode === "vstack") {
|
|
888
|
+
line = vstackAudioPlayheadLine(layout, onsets, tMs, measureCount(eng.rendered));
|
|
889
|
+
} else {
|
|
890
|
+
line = audioPlayheadLine(layout, onsets, tMs);
|
|
891
|
+
}
|
|
787
892
|
} else {
|
|
788
893
|
line = playheadLine(layout, camProgress(tMs));
|
|
789
894
|
}
|
|
@@ -3723,6 +3828,7 @@ export {
|
|
|
3723
3828
|
cueOpacity,
|
|
3724
3829
|
degreeLabel,
|
|
3725
3830
|
degreeLabelsFactory,
|
|
3831
|
+
distinctMeasureIndices,
|
|
3726
3832
|
distinctOnsets,
|
|
3727
3833
|
dotAt,
|
|
3728
3834
|
drawCaption,
|
|
@@ -3822,6 +3928,7 @@ export {
|
|
|
3822
3928
|
validateQuiz,
|
|
3823
3929
|
validateSections,
|
|
3824
3930
|
visualTimelineMs,
|
|
3931
|
+
vstackAudioPlayheadLine,
|
|
3825
3932
|
vstackFollowBox,
|
|
3826
3933
|
whiteKeys,
|
|
3827
3934
|
worldToViewport
|