@real-music-packages/web-core 0.20.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.
@@ -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 audio-clock progress 0..1.
743
+ * The continuous follow-window START measure (ABSOLUTE index) for an hstack
744
+ * audio-clock progress 0..1.
739
745
  *
740
- * INVARIANT: the bar the playhead is in (`curBar`) is ALWAYS fully inside the
741
- * FOLLOW_BARS-wide window. We anchor `curBar` as the BOTTOM (last) visible bar
742
- * with the preceding FOLLOW_BARS-1 bars above it for context so the window
743
- * holds steady while the playhead works through the visible bars and only scrolls
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
- * The scroll is cubic-eased (smooth, no hard jump) over the last portion of each
752
- * bar; result is clamped to [0, nBars-FOLLOW_BARS]. (Replaces RSR's original
753
- * +page.svelte:738-744 logic, which advanced the window to the NEXT bar over the
754
- * last third of EVERY bar and so pushed the still-current bar off the top.)
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(nBars: number, camProgress01: number): number;
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[];
@@ -1620,4 +1629,4 @@ interface SectionMinimapProps {
1620
1629
  }
1621
1630
  declare const sectionMinimapFactory: LayerFactory<SectionMinimapProps>;
1622
1631
 
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 };
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 };
@@ -390,6 +390,11 @@ function measureCount(rn) {
390
390
  const idx = (rn.measures ?? []).map((m) => m.index);
391
391
  return idx.length ? Math.max(...idx) + 1 : 0;
392
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
+ }
393
398
  function followBoxAt(rn, posMeasures) {
394
399
  const cur = Math.floor(posMeasures);
395
400
  const frac = posMeasures - cur;
@@ -399,14 +404,25 @@ function followBoxAt(rn, posMeasures) {
399
404
  if (!b) return a;
400
405
  return lerpBox(a, b, frac);
401
406
  }
402
- function followWindowStart(nBars, camProgress01) {
403
- const posBars = camProgress01 * nBars;
404
- const curBar = Math.floor(posBars);
405
- const frac = posBars - curBar;
406
- const scrollFrac = cubicEaseInOut(Math.min(1, Math.max(0, (frac - 0.66) / 0.34)));
407
- const restStart = curBar - (FOLLOW_BARS - 1);
408
- const maxStart = Math.max(0, nBars - FOLLOW_BARS);
409
- return Math.max(0, Math.min(maxStart, restStart + scrollFrac));
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));
410
426
  }
411
427
  function systemIndexOfBox(systems, box) {
412
428
  if (!systems.length) return 0;
@@ -807,7 +823,7 @@ function followLayoutFor(ctx, camProgress01, scrollMode) {
807
823
  if (!eng) return null;
808
824
  const nBars = measureCount(eng.rendered);
809
825
  if (nBars <= 0) return eng.base;
810
- const focusBox = scrollMode === "vstack" ? vstackFollowBox(eng.rendered, camProgress01) : followBoxAt(eng.rendered, followWindowStart(nBars, camProgress01));
826
+ const focusBox = scrollMode === "vstack" ? vstackFollowBox(eng.rendered, camProgress01) : followBoxAt(eng.rendered, followWindowStart(eng.rendered, camProgress01));
811
827
  return notationLayout(eng.rendered, ctx.W, ctx.H, eng.bandTop, eng.bandHeight, { focusBox });
812
828
  }
813
829
  function scrollCursorLayer() {
@@ -3812,6 +3828,7 @@ export {
3812
3828
  cueOpacity,
3813
3829
  degreeLabel,
3814
3830
  degreeLabelsFactory,
3831
+ distinctMeasureIndices,
3815
3832
  distinctOnsets,
3816
3833
  dotAt,
3817
3834
  drawCaption,