@real-music-packages/web-core 0.35.0 → 0.36.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.
@@ -9,6 +9,33 @@ import {
9
9
  noteNameToIndex,
10
10
  pitchClass
11
11
  } from "../chunk-GORQ5YMR.js";
12
+ import {
13
+ FOLLOW_BARS,
14
+ FOLLOW_PAD,
15
+ audioPlayheadLine,
16
+ cropAroundBox,
17
+ cubicEaseInOut,
18
+ distinctMeasureIndices,
19
+ distinctOnsets,
20
+ firstMeasureBox,
21
+ followBoxAt,
22
+ followWindowStart,
23
+ getNotationEngraving,
24
+ lerpBox,
25
+ measureColumnsFromLayout,
26
+ measureCount,
27
+ measureSpanBox,
28
+ measureSystemMap,
29
+ notationFactory,
30
+ notationLayout,
31
+ playheadLine,
32
+ scrollCursorFactory,
33
+ setFollowLayoutProvider,
34
+ setNotationEngraving,
35
+ systemBox,
36
+ vstackAudioPlayheadLine,
37
+ vstackFollowBox
38
+ } from "../chunk-VPC2EKEY.js";
12
39
  import {
13
40
  ctaScene,
14
41
  drawSafeGuides,
@@ -328,680 +355,6 @@ var captionFactory = {
328
355
  }
329
356
  };
330
357
 
331
- // src/scene/notationGeometry.ts
332
- var FOLLOW_BARS = 2;
333
- var FOLLOW_PAD = 1.06;
334
- function cubicEaseInOut(t) {
335
- if (t < 0.5) return 4 * t * t * t;
336
- const f = 2 * t - 2;
337
- return 0.5 * f * f * f + 1;
338
- }
339
- function staffSpacePx(rn) {
340
- const hs = (rn.measures ?? []).map((m) => m.box.h).filter((h) => h > 1).sort((a, b) => a - b);
341
- if (!hs.length) return 14;
342
- const med = hs[Math.floor(hs.length / 2)];
343
- return Math.max(6, med / 4);
344
- }
345
- function withLedgerHeadroom(rn, box, spaces, unionInk) {
346
- const pad = staffSpacePx(rn) * spaces;
347
- const ch = rn.canvas.height || box.y + box.h;
348
- let y0 = box.y - pad;
349
- let y1 = box.y + box.h + pad;
350
- if (unionInk) {
351
- const c = rn.content;
352
- if (c && c.h > 0) {
353
- y0 = Math.min(y0, c.y);
354
- y1 = Math.max(y1, c.y + c.h);
355
- }
356
- }
357
- y0 = Math.max(0, y0);
358
- y1 = Math.min(ch, y1);
359
- return { x: box.x, y: y0, w: box.w, h: Math.max(1, y1 - y0) };
360
- }
361
- function lerpBox(a, b, e) {
362
- return {
363
- x: a.x + (b.x - a.x) * e,
364
- y: a.y + (b.y - a.y) * e,
365
- w: a.w + (b.w - a.w) * e,
366
- h: a.h + (b.h - a.h) * e
367
- };
368
- }
369
- function cropAroundBox(box, aspect, pad, cw, ch) {
370
- const bw = box.w * pad;
371
- const bh = box.h * pad;
372
- let w = Math.max(bw, bh * aspect);
373
- let h = w / aspect;
374
- w = Math.min(w, cw);
375
- h = Math.min(h, ch);
376
- const ccx = box.x + box.w / 2;
377
- const ccy = box.y + box.h / 2;
378
- let x = ccx - w / 2;
379
- let y = ccy - h / 2;
380
- x = Math.max(0, Math.min(cw - w, x));
381
- y = Math.max(0, Math.min(ch - h, y));
382
- return { x, y, w, h };
383
- }
384
- function measureSpanBox(rn, lo, hi) {
385
- const ms = (rn.measures ?? []).filter((m) => m.index >= lo && m.index < hi).map((m) => m.box);
386
- if (!ms.length) return null;
387
- const x0 = Math.min(...ms.map((b) => b.x));
388
- const y0 = Math.min(...ms.map((b) => b.y));
389
- const x1 = Math.max(...ms.map((b) => b.x + b.w));
390
- const y1 = Math.max(...ms.map((b) => b.y + b.h));
391
- return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 6, true);
392
- }
393
- function firstMeasureBox(rn) {
394
- const first = (rn.measures ?? []).filter((m) => m.index === 0).map((m) => m.box);
395
- if (!first.length) return rn.systems?.[0] ?? null;
396
- const x0 = Math.min(...first.map((b) => b.x));
397
- const y0 = Math.min(...first.map((b) => b.y));
398
- const x1 = Math.max(...first.map((b) => b.x + b.w));
399
- const y1 = Math.max(...first.map((b) => b.y + b.h));
400
- return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 6, false);
401
- }
402
- function measureCount(rn) {
403
- const idx = (rn.measures ?? []).map((m) => m.index);
404
- return idx.length ? Math.max(...idx) + 1 : 0;
405
- }
406
- function distinctMeasureIndices(rn) {
407
- const set = /* @__PURE__ */ new Set();
408
- for (const m of rn.measures ?? []) set.add(m.index);
409
- return [...set].sort((a, b) => a - b);
410
- }
411
- function followBoxAt(rn, posMeasures) {
412
- const cur = Math.floor(posMeasures);
413
- const frac = posMeasures - cur;
414
- const a = measureSpanBox(rn, cur, cur + FOLLOW_BARS);
415
- const b = measureSpanBox(rn, cur + 1, cur + 1 + FOLLOW_BARS) ?? a;
416
- if (!a) return b;
417
- if (!b) return a;
418
- return lerpBox(a, b, frac);
419
- }
420
- var HSTACK_PLAYHEAD_LEAD = 0.35;
421
- function followWindowStart(rn, camProgress01) {
422
- let firstIndex;
423
- let nReal;
424
- if (typeof rn === "number") {
425
- firstIndex = 0;
426
- nReal = rn;
427
- } else {
428
- const idx = distinctMeasureIndices(rn);
429
- firstIndex = idx.length ? idx[0] : 0;
430
- nReal = idx.length;
431
- }
432
- const p = Math.max(0, Math.min(1, camProgress01));
433
- const posBars = firstIndex + p * nReal;
434
- const start = posBars - HSTACK_PLAYHEAD_LEAD * FOLLOW_BARS;
435
- const lastIndex = firstIndex + Math.max(0, nReal - 1);
436
- const minStart = firstIndex;
437
- const maxStart = Math.max(minStart, lastIndex - (FOLLOW_BARS - 1));
438
- return Math.max(minStart, Math.min(maxStart, start));
439
- }
440
- function systemIndexOfBox(systems, box) {
441
- if (!systems.length) return 0;
442
- const cy = box.y + box.h / 2;
443
- let best = 0;
444
- let bestD = Infinity;
445
- for (let i = 0; i < systems.length; i++) {
446
- const s = systems[i];
447
- if (cy >= s.y && cy <= s.y + s.h) return i;
448
- const sc = s.y + s.h / 2;
449
- const d = Math.abs(cy - sc);
450
- if (d < bestD) {
451
- bestD = d;
452
- best = i;
453
- }
454
- }
455
- return best;
456
- }
457
- function measureSystemMap(rn) {
458
- const systems = rn.systems ?? [];
459
- const n = measureCount(rn);
460
- const map = new Array(n).fill(0);
461
- for (let idx = 0; idx < n; idx++) {
462
- const m = (rn.measures ?? []).find((mm) => mm.index === idx);
463
- map[idx] = m ? systemIndexOfBox(systems, m.box) : 0;
464
- }
465
- return map;
466
- }
467
- function systemBox(rn, sys, sysMap) {
468
- const map = sysMap ?? measureSystemMap(rn);
469
- const boxes = (rn.measures ?? []).filter((m) => map[m.index] === sys).map((m) => m.box);
470
- if (!boxes.length) return (rn.systems ?? [])[sys] ?? null;
471
- const x0 = Math.min(...boxes.map((b) => b.x));
472
- const y0 = Math.min(...boxes.map((b) => b.y));
473
- const x1 = Math.max(...boxes.map((b) => b.x + b.w));
474
- const y1 = Math.max(...boxes.map((b) => b.y + b.h));
475
- return withLedgerHeadroom(rn, { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 8, false);
476
- }
477
- function vstackFollowBox(rn, camProgress01) {
478
- const sysMap = measureSystemMap(rn);
479
- const nBars = measureCount(rn);
480
- const nSys = (rn.systems ?? []).length || (sysMap.length ? Math.max(...sysMap) + 1 : 0);
481
- if (nBars <= 0 || nSys <= 0) return null;
482
- const posBars = Math.max(0, Math.min(1, camProgress01)) * nBars;
483
- const curBar = Math.min(nBars - 1, Math.floor(posBars));
484
- const barFrac = posBars - Math.floor(posBars);
485
- const curSys = sysMap[curBar] ?? 0;
486
- const isLastBarOfSys = curBar + 1 >= nBars || (sysMap[curBar + 1] ?? curSys) !== curSys;
487
- const scrollFrac = isLastBarOfSys ? cubicEaseInOut(Math.min(1, Math.max(0, (barFrac - 0.66) / 0.34))) : 0;
488
- const nextSys = Math.min(nSys - 1, curSys + 1);
489
- const a = systemBox(rn, curSys, sysMap);
490
- const b = systemBox(rn, nextSys, sysMap) ?? a;
491
- if (!a) return b;
492
- if (!b) return a;
493
- const w = Math.max(a.w, b.w);
494
- const h = Math.max(a.h, b.h);
495
- const x = a.x + (b.x - a.x) * scrollFrac;
496
- const y = a.y + (b.y - a.y) * scrollFrac;
497
- return { x, y, w, h };
498
- }
499
- function notationLayout(rn, W, H, boxTop, boxH, opts = {}) {
500
- const { zoom01 = 1, focusBox = null } = opts;
501
- const sb = safeBox(W, H);
502
- const maxW = sb.centeredW;
503
- const c = rn.content && rn.content.w > 0 && rn.content.h > 0 ? rn.content : { x: 0, y: 0, w: rn.canvas.width || 1400, h: rn.canvas.height || 300 };
504
- let src;
505
- if (focusBox) {
506
- const px = focusBox.w * (FOLLOW_PAD - 1) / 2;
507
- const py = focusBox.h * (FOLLOW_PAD - 1) / 2;
508
- src = {
509
- x: Math.max(0, focusBox.x - px),
510
- y: Math.max(0, focusBox.y - py),
511
- w: focusBox.w + 2 * px,
512
- h: focusBox.h + 2 * py
513
- };
514
- src.w = Math.min(src.w, rn.canvas.width - src.x);
515
- src.h = Math.min(src.h, rn.canvas.height - src.y);
516
- } else {
517
- src = c;
518
- const focal = firstMeasureBox(rn);
519
- if (zoom01 < 1 && focal) {
520
- const start = cropAroundBox(focal, c.w / c.h, 1.25, rn.canvas.width, rn.canvas.height);
521
- src = lerpBox(start, c, cubicEaseInOut(zoom01));
522
- }
523
- }
524
- const srcAspect = src.w / src.h;
525
- let dw = maxW;
526
- let dh = dw / srcAspect;
527
- if (dh > boxH) {
528
- dh = boxH;
529
- dw = dh * srcAspect;
530
- }
531
- const dx = (W - dw) / 2;
532
- const dy = boxTop + (boxH - dh) / 2;
533
- const fx = dw / src.w;
534
- const fy = dh / src.h;
535
- const map = (b) => ({
536
- x: dx + (b.x - src.x) * fx,
537
- y: dy + (b.y - src.y) * fy,
538
- w: b.w * fx,
539
- h: b.h * fy
540
- });
541
- const systems = (rn.systems ?? []).map(map);
542
- const measures = (rn.measures ?? []).map((m) => ({
543
- ...m,
544
- box: map(m.box),
545
- noteStartX: dx + (m.noteStartX - src.x) * fx
546
- }));
547
- return { src, rect: { dx, dy, dw, dh }, systems, measures };
548
- }
549
- function measureColumnsFromLayout(measures) {
550
- const byIndex = /* @__PURE__ */ new Map();
551
- for (const m of measures) {
552
- const cur = byIndex.get(m.index);
553
- if (!cur) {
554
- byIndex.set(m.index, { ...m.box, noteStartX: m.noteStartX });
555
- } else {
556
- const x0 = Math.min(cur.x, m.box.x);
557
- const y0 = Math.min(cur.y, m.box.y);
558
- const x1 = Math.max(cur.x + cur.w, m.box.x + m.box.w);
559
- const y1 = Math.max(cur.y + cur.h, m.box.y + m.box.h);
560
- byIndex.set(m.index, {
561
- x: x0,
562
- y: y0,
563
- w: x1 - x0,
564
- h: y1 - y0,
565
- noteStartX: Math.min(cur.noteStartX, m.noteStartX)
566
- });
567
- }
568
- }
569
- return [...byIndex.keys()].sort((a, b) => a - b).map((k) => byIndex.get(k));
570
- }
571
- function playheadLine(layout, t01) {
572
- const tt = Math.max(0, Math.min(1, t01));
573
- let alpha = 0.85;
574
- if (tt < 0.03) alpha *= tt / 0.03;
575
- if (tt > 0.94) alpha *= Math.max(0, (1 - tt) / 0.06);
576
- if (alpha <= 0.02) return null;
577
- const padV = 10;
578
- let x, y0, y1;
579
- const cols = measureColumnsFromLayout(layout.measures);
580
- if (cols.length) {
581
- const pos = tt * cols.length;
582
- const i = Math.min(cols.length - 1, Math.floor(pos));
583
- const m = cols[i];
584
- const startX = Math.min(m.noteStartX, m.x + m.w);
585
- x = startX + (pos - i) * (m.x + m.w - startX);
586
- y0 = m.y - padV;
587
- y1 = m.y + m.h + padV;
588
- } else if (layout.systems.length) {
589
- const pos = tt * layout.systems.length;
590
- const row = Math.min(layout.systems.length - 1, Math.floor(pos));
591
- const s = layout.systems[row];
592
- x = s.x + (pos - row) * s.w;
593
- y0 = s.y - padV;
594
- y1 = s.y + s.h + padV;
595
- } else {
596
- const r = layout.rect;
597
- x = r.dx + tt * r.dw;
598
- y0 = r.dy - padV;
599
- y1 = r.dy + r.dh + padV;
600
- }
601
- return { x, y0, y1, alpha };
602
- }
603
- var clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;
604
- function anchorAtColumnPos(cols, pos, padV) {
605
- const i = Math.min(cols.length - 1, Math.max(0, Math.floor(pos)));
606
- const m = cols[i];
607
- const startX = Math.min(m.noteStartX, m.x + m.w);
608
- const frac = Math.min(1, Math.max(0, pos - i));
609
- return {
610
- onsetMs: 0,
611
- x: startX + frac * (m.x + m.w - startX),
612
- y0: m.y - padV,
613
- y1: m.y + m.h + padV
614
- };
615
- }
616
- function noteAnchors(layout, onsetsMs, padV, barDurMs, noteCols) {
617
- const cols = measureColumnsFromLayout(layout.measures);
618
- if (!cols.length || !onsetsMs.length) return [];
619
- const n = onsetsMs.length;
620
- const first = onsetsMs[0];
621
- const useEngraved = !!noteCols && noteCols.length === n;
622
- return onsetsMs.map((onsetMs, k) => {
623
- const pos = useEngraved ? noteCols[k] : barDurMs && barDurMs > 0 ? (onsetMs - first) / barDurMs : (n === 1 ? 0 : k / (n - 1)) * cols.length;
624
- const a = anchorAtColumnPos(cols, pos, padV);
625
- return { ...a, onsetMs };
626
- });
627
- }
628
- function distinctOnsets(notes) {
629
- const set = /* @__PURE__ */ new Set();
630
- for (const n of notes) set.add(n.onsetMs);
631
- return [...set].sort((a, b) => a - b);
632
- }
633
- function audioPlayheadLine(layout, onsetsMs, tMs, barDurMs, noteCols) {
634
- const padV = 10;
635
- const anchors = noteAnchors(layout, onsetsMs, padV, barDurMs, noteCols);
636
- if (!anchors.length) return null;
637
- const first = anchors[0].onsetMs;
638
- const last = anchors[anchors.length - 1].onsetMs;
639
- const span = last - first;
640
- let a, b, frac;
641
- if (tMs <= first || anchors.length === 1) {
642
- a = b = anchors[0];
643
- frac = 0;
644
- } else if (tMs >= last) {
645
- a = b = anchors[anchors.length - 1];
646
- frac = 0;
647
- } else {
648
- let lo = 0;
649
- let hi = anchors.length - 1;
650
- while (lo < hi) {
651
- const mid = lo + hi + 1 >> 1;
652
- if (anchors[mid].onsetMs <= tMs) lo = mid;
653
- else hi = mid - 1;
654
- }
655
- a = anchors[lo];
656
- b = anchors[lo + 1];
657
- const dt = b.onsetMs - a.onsetMs;
658
- frac = dt > 0 ? (tMs - a.onsetMs) / dt : 0;
659
- }
660
- const x = a.x + (b.x - a.x) * frac;
661
- const y0 = a.y0 + (b.y0 - a.y0) * frac;
662
- const y1 = a.y1 + (b.y1 - a.y1) * frac;
663
- let alpha = 0.85;
664
- if (span > 0) {
665
- const fadeIn = Math.min(250, span * 0.5);
666
- const fadeOut = Math.min(400, span * 0.5);
667
- if (tMs < first + fadeIn) alpha *= clamp01((tMs - (first - fadeIn)) / (2 * fadeIn));
668
- if (tMs > last - fadeOut) alpha *= clamp01((last + fadeOut - tMs) / (2 * fadeOut));
669
- }
670
- if (alpha <= 0.02) return null;
671
- return { x, y0, y1, alpha };
672
- }
673
- function vstackAudioPlayheadLine(layout, onsetsMs, tMs, nBars, noteCols) {
674
- const padV = 10;
675
- const cols = measureColumnsFromLayout(layout.measures);
676
- if (!cols.length || !onsetsMs.length || nBars <= 0) return null;
677
- const n = onsetsMs.length;
678
- const first = onsetsMs[0];
679
- const last = onsetsMs[n - 1];
680
- const span = last - first;
681
- const colX = (m, f) => {
682
- const sx = Math.min(m.noteStartX, m.x + m.w);
683
- return sx + Math.min(1, Math.max(0, f)) * (m.x + m.w - sx);
684
- };
685
- const useEng = !!noteCols && noteCols.length === n;
686
- const anchorFor = (k) => {
687
- const pos = useEng ? noteCols[k] : (n === 1 ? 0 : k / (n - 1)) * cols.length;
688
- const i = Math.min(cols.length - 1, Math.max(0, Math.floor(pos)));
689
- const m = cols[i];
690
- return { x: colX(m, pos - i), rowY: m.y, rowH: m.h };
691
- };
692
- const sameRow = (ya, yb) => Math.abs(ya - yb) < Math.max(8, ya * 0 + 8);
693
- const rowRightEdge = (rowY2) => Math.max(...cols.filter((c) => sameRow(c.y, rowY2)).map((c) => c.x + c.w));
694
- const rowLeftEdge = (rowY2) => Math.min(...cols.filter((c) => sameRow(c.y, rowY2)).map((c) => Math.min(c.noteStartX, c.x)));
695
- let a, b, frac;
696
- if (tMs <= first || n === 1) {
697
- a = b = anchorFor(0);
698
- frac = 0;
699
- } else if (tMs >= last) {
700
- a = b = anchorFor(n - 1);
701
- frac = 0;
702
- } else {
703
- let lo = 0, hi = n - 1;
704
- while (lo < hi) {
705
- const mid = lo + hi + 1 >> 1;
706
- if (onsetsMs[mid] <= tMs) lo = mid;
707
- else hi = mid - 1;
708
- }
709
- a = anchorFor(lo);
710
- b = anchorFor(lo + 1);
711
- frac = (tMs - onsetsMs[lo]) / (onsetsMs[lo + 1] - onsetsMs[lo]);
712
- }
713
- let x, rowY, rowH;
714
- if (sameRow(a.rowY, b.rowY)) {
715
- x = a.x + (b.x - a.x) * frac;
716
- rowY = a.rowY;
717
- rowH = a.rowH;
718
- } else {
719
- if (frac < 0.5) {
720
- const s = frac / 0.5;
721
- x = a.x + (rowRightEdge(a.rowY) - a.x) * s;
722
- rowY = a.rowY;
723
- rowH = a.rowH;
724
- } else {
725
- const s = (frac - 0.5) / 0.5;
726
- const nl = rowLeftEdge(b.rowY);
727
- x = nl + (b.x - nl) * s;
728
- rowY = b.rowY;
729
- rowH = b.rowH;
730
- }
731
- }
732
- let y0 = rowY - padV;
733
- let y1 = rowY + rowH + padV;
734
- const bandY0 = layout.rect.dy;
735
- const bandY1 = layout.rect.dy + layout.rect.dh;
736
- const h = Math.max(1, y1 - y0);
737
- if (y0 < bandY0) {
738
- y0 = bandY0;
739
- y1 = bandY0 + h;
740
- }
741
- if (y1 > bandY1) {
742
- y1 = bandY1;
743
- y0 = bandY1 - h;
744
- }
745
- let alpha = 0.85;
746
- if (span > 0) {
747
- const fadeIn = Math.min(250, span * 0.5);
748
- const fadeOut = Math.min(400, span * 0.5);
749
- if (tMs < first + fadeIn) alpha *= clamp01((tMs - (first - fadeIn)) / (2 * fadeIn));
750
- if (tMs > last - fadeOut) alpha *= clamp01((last + fadeOut - tMs) / (2 * fadeOut));
751
- }
752
- if (alpha <= 0.02) return null;
753
- return { x, y0, y1, alpha };
754
- }
755
-
756
- // src/scene/engravingStore.ts
757
- var STORE = /* @__PURE__ */ new WeakMap();
758
- function keyFor(ctx) {
759
- return ctx.audioClock;
760
- }
761
- function setNotationEngraving(ctx, eng) {
762
- STORE.set(keyFor(ctx), eng);
763
- }
764
- function getNotationEngraving(ctx) {
765
- return STORE.get(keyFor(ctx));
766
- }
767
- function setFollowLayoutProvider(ctx, fn) {
768
- const e = STORE.get(keyFor(ctx));
769
- if (e) e.followLayoutAt = fn;
770
- }
771
-
772
- // src/scene/layers/notation.ts
773
- function notationLayer() {
774
- let rn = null;
775
- let base = null;
776
- let propScale = 1;
777
- let propBandTop;
778
- let propBandHeight;
779
- function bandTop(_ctx, sb) {
780
- return propBandTop ?? sb.top;
781
- }
782
- function bandHeight(ctx, sb) {
783
- const top = bandTop(ctx, sb);
784
- return (propBandHeight ?? sb.bottom - top) * propScale;
785
- }
786
- return {
787
- key: "notation",
788
- async init(ctx, props) {
789
- propScale = props.scale ?? 1;
790
- propBandTop = props.bandTop;
791
- propBandHeight = props.bandHeight;
792
- if (props.rendered) {
793
- rn = props.rendered;
794
- } else if (props.xml) {
795
- const { renderNotation } = await import("../promo.js");
796
- rn = await renderNotation(props.xml, {
797
- bars: props.bars,
798
- paper: ctx.theme.paper,
799
- scrollMode: props.scrollMode ?? "hstack"
800
- });
801
- } else {
802
- throw new Error("notation layer: provide `rendered` or `xml`");
803
- }
804
- const sb = safeBox(ctx.W, ctx.H);
805
- const top = bandTop(ctx, sb);
806
- const height = bandHeight(ctx, sb);
807
- base = notationLayout(rn, ctx.W, ctx.H, top, height, {});
808
- setNotationEngraving(ctx, { rendered: rn, base, bandTop: top, bandHeight: height });
809
- },
810
- draw(ctx, tMs) {
811
- if (!rn || !base) return;
812
- const eng = getNotationEngraving(ctx);
813
- const l = eng?.followLayoutAt ? eng.followLayoutAt(ctx, tMs) : base;
814
- const c = ctx.ctx2d;
815
- c.drawImage(
816
- rn.canvas,
817
- l.src.x,
818
- l.src.y,
819
- l.src.w,
820
- l.src.h,
821
- l.rect.dx,
822
- l.rect.dy,
823
- l.rect.dw,
824
- l.rect.dh
825
- );
826
- },
827
- dispose() {
828
- rn = null;
829
- base = null;
830
- }
831
- };
832
- }
833
- var notationFactory = {
834
- key: "notation",
835
- create: notationLayer,
836
- validateProps(props) {
837
- const errs = [];
838
- if (props == null || typeof props !== "object") return ["notation: props must be an object"];
839
- const p = props;
840
- if (p.system != null && p.system !== "grand" && p.system !== "single")
841
- errs.push('notation.system must be "grand" | "single"');
842
- if (p.scrollMode != null && p.scrollMode !== "hstack" && p.scrollMode !== "vstack")
843
- errs.push('notation.scrollMode must be "hstack" | "vstack"');
844
- if (p.scale != null && (typeof p.scale !== "number" || p.scale <= 0))
845
- errs.push("notation.scale must be a positive number");
846
- if (p.rendered == null && typeof p.xml !== "string")
847
- errs.push("notation: provide `rendered` (RenderedNotation) or `xml` (string)");
848
- if (p.bars != null && (!Array.isArray(p.bars) || p.bars.length !== 2))
849
- errs.push("notation.bars must be [from,to]");
850
- return errs;
851
- }
852
- };
853
-
854
- // src/scene/layers/scrollCursor.ts
855
- function followLayoutFor(ctx, progress012, scrollMode) {
856
- const eng = getNotationEngraving(ctx);
857
- if (!eng) return null;
858
- const nBars = measureCount(eng.rendered);
859
- if (nBars <= 0) return eng.base;
860
- const focusBox = scrollMode === "vstack" ? vstackFollowBox(eng.rendered, progress012) : followBoxAt(eng.rendered, followWindowStart(eng.rendered, progress012));
861
- return notationLayout(eng.rendered, ctx.W, ctx.H, eng.bandTop, eng.bandHeight, { focusBox });
862
- }
863
- function scrollCursorLayer() {
864
- let scrollMode = "hstack";
865
- let openingZoomMs = 900;
866
- let color;
867
- let barDurMs;
868
- let propNoteCols;
869
- function onsetsFor(ctx) {
870
- const notes = ctx.score?.notes;
871
- return notes && notes.length ? distinctOnsets(notes) : [];
872
- }
873
- function notesProgress(onsetsMs, tMs, totalMeasures, noteCols) {
874
- if (tMs < openingZoomMs) return 0;
875
- const n = onsetsMs.length;
876
- if (n <= 1) return 0;
877
- const first = onsetsMs[0];
878
- const last = onsetsMs[n - 1];
879
- if (tMs <= first) return 0;
880
- if (tMs >= last || last <= first) return 1;
881
- let lo = 0, hi = n - 1;
882
- while (lo < hi) {
883
- const mid = lo + hi + 1 >> 1;
884
- if (onsetsMs[mid] <= tMs) lo = mid;
885
- else hi = mid - 1;
886
- }
887
- const segFrac = (tMs - onsetsMs[lo]) / (onsetsMs[lo + 1] - onsetsMs[lo]);
888
- if (noteCols && noteCols.length === n && totalMeasures > 0) {
889
- const pos = noteCols[lo] + (noteCols[lo + 1] - noteCols[lo]) * segFrac;
890
- return Math.min(1, Math.max(0, pos / totalMeasures));
891
- }
892
- if (barDurMs && barDurMs > 0 && totalMeasures > 0) {
893
- const posLo = (onsetsMs[lo] - first) / barDurMs;
894
- const posHi = (onsetsMs[lo + 1] - first) / barDurMs;
895
- const pos = posLo + (posHi - posLo) * segFrac;
896
- return Math.min(1, Math.max(0, pos / totalMeasures));
897
- }
898
- return (lo + segFrac) / (n - 1);
899
- }
900
- function noteColsFor(ctx) {
901
- const eng = getNotationEngraving(ctx)?.rendered?.noteCols;
902
- const onsets = onsetsFor(ctx);
903
- const n = onsets.length;
904
- if (!eng || !eng.length) return propNoteCols;
905
- if (eng.length === n) return eng;
906
- if (!propNoteCols || propNoteCols.length !== n) return eng;
907
- const measureOf = (c) => Math.floor(c + 1e-6);
908
- const engByMeasure = /* @__PURE__ */ new Map();
909
- for (const e of eng) {
910
- const m = measureOf(e);
911
- const a = engByMeasure.get(m);
912
- if (a) a.push(e);
913
- else engByMeasure.set(m, [e]);
914
- }
915
- for (const a of engByMeasure.values()) a.sort((x, y) => x - y);
916
- const idxByMeasure = /* @__PURE__ */ new Map();
917
- propNoteCols.forEach((c, i) => {
918
- const m = measureOf(c);
919
- const a = idxByMeasure.get(m);
920
- if (a) a.push(i);
921
- else idxByMeasure.set(m, [i]);
922
- });
923
- const out = propNoteCols.slice();
924
- for (const [m, idxs] of idxByMeasure) {
925
- const e = engByMeasure.get(m);
926
- if (!e || !e.length) continue;
927
- const K = idxs.length;
928
- idxs.forEach((origIdx, i) => {
929
- const j = K > 1 ? Math.round(i * (e.length - 1) / (K - 1)) : 0;
930
- out[origIdx] = e[Math.min(j, e.length - 1)];
931
- });
932
- }
933
- return out;
934
- }
935
- function followProgress(ctx, tMs) {
936
- const eng = getNotationEngraving(ctx);
937
- const total = eng ? measureCount(eng.rendered) : 0;
938
- return notesProgress(onsetsFor(ctx), tMs, total, noteColsFor(ctx));
939
- }
940
- function layoutAt(ctx, tMs) {
941
- const eng = getNotationEngraving(ctx);
942
- return followLayoutFor(ctx, followProgress(ctx, tMs), scrollMode) ?? eng.base;
943
- }
944
- return {
945
- key: "scroll-cursor",
946
- init(ctx, props) {
947
- scrollMode = props.scrollMode ?? "hstack";
948
- openingZoomMs = props.openingZoomMs ?? 900;
949
- color = props.color;
950
- barDurMs = props.barDurMs;
951
- propNoteCols = Array.isArray(props.noteCols) ? props.noteCols : void 0;
952
- setFollowLayoutProvider(ctx, layoutAt);
953
- },
954
- draw(ctx, tMs) {
955
- const eng = getNotationEngraving(ctx);
956
- if (!eng) return;
957
- const layout = layoutAt(ctx, tMs);
958
- const onsets = onsetsFor(ctx);
959
- let line;
960
- if (scrollMode === "vstack") {
961
- line = vstackAudioPlayheadLine(layout, onsets, tMs, measureCount(eng.rendered), noteColsFor(ctx));
962
- } else {
963
- line = audioPlayheadLine(layout, onsets, tMs, barDurMs, noteColsFor(ctx));
964
- }
965
- if (!line) return;
966
- const c = ctx.ctx2d;
967
- c.save();
968
- c.strokeStyle = color ?? ctx.theme.accent;
969
- c.globalAlpha = line.alpha;
970
- c.lineWidth = 4;
971
- c.beginPath();
972
- c.moveTo(line.x, line.y0);
973
- c.lineTo(line.x, line.y1);
974
- c.stroke();
975
- c.restore();
976
- }
977
- };
978
- }
979
- var scrollCursorFactory = {
980
- key: "scroll-cursor",
981
- create: scrollCursorLayer,
982
- validateProps(props) {
983
- const errs = [];
984
- if (props == null || typeof props !== "object") return ["scroll-cursor: props must be an object"];
985
- const p = props;
986
- if (p.mode != null && p.mode !== "audio" && p.mode !== "linear")
987
- errs.push('scroll-cursor.mode must be "audio" | "linear"');
988
- if (p.scrollMode != null && p.scrollMode !== "hstack" && p.scrollMode !== "vstack")
989
- errs.push('scroll-cursor.scrollMode must be "hstack" | "vstack"');
990
- if (p.musicMs != null && (typeof p.musicMs !== "number" || !(p.musicMs > 0)))
991
- errs.push("scroll-cursor.musicMs must be a positive number");
992
- if (p.followBars != null && (typeof p.followBars !== "number" || p.followBars < 1))
993
- errs.push("scroll-cursor.followBars must be a number >= 1");
994
- if (p.openingZoomMs != null && (typeof p.openingZoomMs !== "number" || p.openingZoomMs < 0))
995
- errs.push("scroll-cursor.openingZoomMs must be a number >= 0");
996
- if (p.color != null && typeof p.color !== "string") errs.push("scroll-cursor.color must be a string");
997
- if (p.barDurMs != null && (typeof p.barDurMs !== "number" || !(p.barDurMs > 0)))
998
- errs.push("scroll-cursor.barDurMs must be a positive number");
999
- if (p.noteCols != null && (!Array.isArray(p.noteCols) || p.noteCols.some((c) => typeof c !== "number")))
1000
- errs.push("scroll-cursor.noteCols must be a number[]");
1001
- return errs;
1002
- }
1003
- };
1004
-
1005
358
  // src/scene/keyboardGeometry.ts
1006
359
  var BLACK_PC = /* @__PURE__ */ new Set([1, 3, 6, 8, 10]);
1007
360
  function whiteIndexAtOrBelow(midi) {
@@ -1118,15 +471,15 @@ function noteColor(midi, hand, colorBy, hands) {
1118
471
  }
1119
472
 
1120
473
  // src/scene/keyboardStore.ts
1121
- var STORE2 = /* @__PURE__ */ new WeakMap();
1122
- function keyFor2(ctx) {
474
+ var STORE = /* @__PURE__ */ new WeakMap();
475
+ function keyFor(ctx) {
1123
476
  return ctx.audioClock;
1124
477
  }
1125
478
  function setKeyboardLayout(ctx, layout) {
1126
- STORE2.set(keyFor2(ctx), layout);
479
+ STORE.set(keyFor(ctx), layout);
1127
480
  }
1128
481
  function getKeyboardLayout(ctx) {
1129
- return STORE2.get(keyFor2(ctx));
482
+ return STORE.get(keyFor(ctx));
1130
483
  }
1131
484
 
1132
485
  // src/scene/layers/keyboard.ts
@@ -1794,11 +1147,11 @@ function spectrumLayer() {
1794
1147
  const src = fromProp ?? resolveFromCtx(ctx, tMs);
1795
1148
  if (src && src.length) {
1796
1149
  const out = new Array(n);
1797
- for (let b = 0; b < n; b++) out[b] = clamp012(src[Math.min(src.length - 1, b)] ?? 0);
1150
+ for (let b = 0; b < n; b++) out[b] = clamp01(src[Math.min(src.length - 1, b)] ?? 0);
1798
1151
  return out;
1799
1152
  }
1800
1153
  const tSec = tMs / 1e3;
1801
- return Array.from({ length: n }, (_, b) => clamp012(syntheticMagnitude(tSec, b, n)));
1154
+ return Array.from({ length: n }, (_, b) => clamp01(syntheticMagnitude(tSec, b, n)));
1802
1155
  }
1803
1156
  function resolveFromCtx(ctx, tMs) {
1804
1157
  const sp = ctx.spectrum;
@@ -1860,7 +1213,7 @@ function spectrumLayer() {
1860
1213
  }
1861
1214
  };
1862
1215
  }
1863
- function clamp012(x) {
1216
+ function clamp01(x) {
1864
1217
  return x < 0 ? 0 : x > 1 ? 1 : x;
1865
1218
  }
1866
1219
  var spectrumFactory = {
@@ -3523,7 +2876,7 @@ var progressRingFactory = {
3523
2876
 
3524
2877
  // src/scene/layers/radialSpectrum.ts
3525
2878
  var SPEC_BARS2 = 64;
3526
- function clamp013(x) {
2879
+ function clamp012(x) {
3527
2880
  return x < 0 ? 0 : x > 1 ? 1 : x;
3528
2881
  }
3529
2882
  function lerpHex2(a, b, f) {
@@ -3572,11 +2925,11 @@ function radialSpectrumLayer() {
3572
2925
  const src = fromProp ?? resolveFromCtx(ctx, tMs);
3573
2926
  if (src && src.length) {
3574
2927
  const out = new Array(n);
3575
- for (let b = 0; b < n; b++) out[b] = clamp013(src[Math.min(src.length - 1, b)] ?? 0);
2928
+ for (let b = 0; b < n; b++) out[b] = clamp012(src[Math.min(src.length - 1, b)] ?? 0);
3576
2929
  return out;
3577
2930
  }
3578
2931
  const tSec = tMs / 1e3;
3579
- return Array.from({ length: n }, (_, b) => clamp013(syntheticMagnitude2(tSec, b, n)));
2932
+ return Array.from({ length: n }, (_, b) => clamp012(syntheticMagnitude2(tSec, b, n)));
3580
2933
  }
3581
2934
  function resolveFromCtx(ctx, tMs) {
3582
2935
  const sp = ctx.spectrum;
@@ -3862,7 +3215,7 @@ var intervalArcsFactory = {
3862
3215
 
3863
3216
  // src/scene/layers/kineticText.ts
3864
3217
  var DEFAULTS5 = { mode: "word-pop", startMs: 0, revealMs: 1400, fontPx: 88, centerFrac: 0.42, cursor: true };
3865
- function clamp014(x) {
3218
+ function clamp013(x) {
3866
3219
  return x < 0 ? 0 : x > 1 ? 1 : x;
3867
3220
  }
3868
3221
  function wrap2(c, words, maxW) {
@@ -3919,7 +3272,7 @@ function kineticTextLayer() {
3919
3272
  const lineH = fontPx * 1.25;
3920
3273
  const totalH = lines.length * lineH;
3921
3274
  const top = ctx.H * centerFrac - totalH / 2 + lineH / 2;
3922
- const p = clamp014((tMs - startMs) / Math.max(1, revealMs));
3275
+ const p = clamp013((tMs - startMs) / Math.max(1, revealMs));
3923
3276
  if (mode === "typewriter") {
3924
3277
  const full = lines.map((l) => l.join(" "));
3925
3278
  const totalChars = full.reduce((n, l) => n + l.length, 0);
@@ -3955,7 +3308,7 @@ function kineticTextLayer() {
3955
3308
  let x = sb.left + (sb.w - lineW) / 2;
3956
3309
  for (const word of line) {
3957
3310
  const ww = c.measureText(word).width;
3958
- const wp = clamp014((tMs - startMs - wi * stagger) / POP);
3311
+ const wp = clamp013((tMs - startMs - wi * stagger) / POP);
3959
3312
  if (wp > 0) {
3960
3313
  const scale = 0.7 + 0.3 * wp;
3961
3314
  c.save();
@@ -4597,7 +3950,7 @@ var DEFAULTS9 = { startMs: 0, durationMs: 1200, fontPx: 220, centerFrac: 0.46, g
4597
3950
  function easeOut2(t) {
4598
3951
  return 1 - Math.pow(1 - t, 3);
4599
3952
  }
4600
- function clamp015(x) {
3953
+ function clamp014(x) {
4601
3954
  return x < 0 ? 0 : x > 1 ? 1 : x;
4602
3955
  }
4603
3956
  function statCounterLayer() {
@@ -4627,7 +3980,7 @@ function statCounterLayer() {
4627
3980
  },
4628
3981
  draw(ctx, tMs) {
4629
3982
  const c = ctx.ctx2d;
4630
- const p = clamp015((tMs - startMs) / Math.max(1, durationMs));
3983
+ const p = clamp014((tMs - startMs) / Math.max(1, durationMs));
4631
3984
  const shown = Math.round(easeOut2(p) * value);
4632
3985
  const numStr = group ? shown.toLocaleString("en-US") : String(shown);
4633
3986
  const cx = ctx.W / 2;
@@ -4669,7 +4022,7 @@ var statCounterFactory = {
4669
4022
 
4670
4023
  // src/scene/layers/endCard.ts
4671
4024
  var DEFAULTS10 = { text: "Follow for daily", startMs: 0, inMs: 400, centerFrac: 0.5, fontPx: 72, arrow: true, arrowDir: "up" };
4672
- function clamp016(x) {
4025
+ function clamp015(x) {
4673
4026
  return x < 0 ? 0 : x > 1 ? 1 : x;
4674
4027
  }
4675
4028
  function easeOut3(t) {
@@ -4704,7 +4057,7 @@ function endCardLayer() {
4704
4057
  const cx = ctx.W / 2;
4705
4058
  const cy = ctx.H * centerFrac;
4706
4059
  const col = color ?? ctx.theme.accent;
4707
- const p = clamp016((tMs - startMs) / Math.max(1, inMs));
4060
+ const p = clamp015((tMs - startMs) / Math.max(1, inMs));
4708
4061
  const scale = 0.7 + 0.3 * easeOut3(p);
4709
4062
  const dir = arrowDir === "down" ? 1 : -1;
4710
4063
  const bounce = Math.sin(tMs / 260) * 14;
@@ -4781,7 +4134,7 @@ var DEFAULTS11 = {
4781
4134
  difficulty: 0.5,
4782
4135
  easing: "linear"
4783
4136
  };
4784
- function clamp017(x) {
4137
+ function clamp016(x) {
4785
4138
  return x < 0 ? 0 : x > 1 ? 1 : x;
4786
4139
  }
4787
4140
  function fitRect(iW, iH, bx, by, bw, bh) {
@@ -4850,7 +4203,7 @@ function imageRevealLayer() {
4850
4203
  },
4851
4204
  draw(ctx, tMs) {
4852
4205
  if (!image || imgW === 0 || imgH === 0) return;
4853
- const raw = clamp017((tMs - startMs) / (durationMs > 0 ? durationMs : 1));
4206
+ const raw = clamp016((tMs - startMs) / (durationMs > 0 ? durationMs : 1));
4854
4207
  const p = applyEasing(raw, easing);
4855
4208
  const sb = ctx.safeBox;
4856
4209
  const fit = fitRect(imgW, imgH, sb.left, sb.top, sb.w, sb.h);