@real-music-packages/web-core 0.23.0 → 0.24.1

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.
@@ -1,6 +1,10 @@
1
1
  import {
2
2
  intervalBySemitones
3
3
  } from "../chunk-N56UTMWA.js";
4
+ import {
5
+ noteNameToIndex,
6
+ pitchClass
7
+ } from "../chunk-25RUSM2X.js";
4
8
  import {
5
9
  ctaScene,
6
10
  drawSafeGuides,
@@ -3657,6 +3661,419 @@ var intervalArcsFactory = {
3657
3661
  }
3658
3662
  };
3659
3663
 
3664
+ // src/scene/layers/kineticText.ts
3665
+ var DEFAULTS5 = { mode: "word-pop", startMs: 0, revealMs: 1400, fontPx: 88, centerFrac: 0.42, cursor: true };
3666
+ function clamp014(x) {
3667
+ return x < 0 ? 0 : x > 1 ? 1 : x;
3668
+ }
3669
+ function wrap2(c, words, maxW) {
3670
+ const lines = [];
3671
+ let cur = [];
3672
+ let curW = 0;
3673
+ const spaceW = c.measureText(" ").width;
3674
+ for (const w of words) {
3675
+ const ww = c.measureText(w).width;
3676
+ const add = cur.length ? spaceW + ww : ww;
3677
+ if (cur.length && curW + add > maxW) {
3678
+ lines.push(cur);
3679
+ cur = [];
3680
+ curW = 0;
3681
+ }
3682
+ cur.push(w);
3683
+ curW += cur.length === 1 ? ww : spaceW + ww;
3684
+ }
3685
+ if (cur.length) lines.push(cur);
3686
+ return lines;
3687
+ }
3688
+ function kineticTextLayer() {
3689
+ let text = "";
3690
+ let mode = DEFAULTS5.mode;
3691
+ let startMs = DEFAULTS5.startMs;
3692
+ let revealMs = DEFAULTS5.revealMs;
3693
+ let fontPx = DEFAULTS5.fontPx;
3694
+ let color;
3695
+ let centerFrac = DEFAULTS5.centerFrac;
3696
+ let cursor = DEFAULTS5.cursor;
3697
+ return {
3698
+ key: "kinetic-text",
3699
+ init(_ctx, props) {
3700
+ text = props.text ?? "";
3701
+ mode = props.mode ?? DEFAULTS5.mode;
3702
+ startMs = props.startMs ?? DEFAULTS5.startMs;
3703
+ revealMs = props.revealMs ?? DEFAULTS5.revealMs;
3704
+ fontPx = props.fontPx ?? DEFAULTS5.fontPx;
3705
+ color = props.color;
3706
+ centerFrac = props.centerFrac ?? DEFAULTS5.centerFrac;
3707
+ cursor = props.cursor ?? DEFAULTS5.cursor;
3708
+ },
3709
+ draw(ctx, tMs) {
3710
+ if (!text) return;
3711
+ const c = ctx.ctx2d;
3712
+ const sb = ctx.safeBox;
3713
+ const col = color ?? ctx.theme.ink;
3714
+ c.save();
3715
+ c.font = `700 ${fontPx}px ${ctx.theme.fontDisplay}`;
3716
+ c.textBaseline = "middle";
3717
+ const rawLines = text.split("\n");
3718
+ const lines = [];
3719
+ for (const rl of rawLines) lines.push(...wrap2(c, rl.split(/\s+/).filter(Boolean), sb.w));
3720
+ const lineH = fontPx * 1.25;
3721
+ const totalH = lines.length * lineH;
3722
+ const top = ctx.H * centerFrac - totalH / 2 + lineH / 2;
3723
+ const p = clamp014((tMs - startMs) / Math.max(1, revealMs));
3724
+ if (mode === "typewriter") {
3725
+ const full = lines.map((l) => l.join(" "));
3726
+ const totalChars = full.reduce((n, l) => n + l.length, 0);
3727
+ let shown = Math.floor(p * totalChars);
3728
+ c.textAlign = "center";
3729
+ c.fillStyle = col;
3730
+ let y = top;
3731
+ let lastDrawn = { x: 0, y: 0, end: false };
3732
+ for (const line of full) {
3733
+ const take = Math.max(0, Math.min(line.length, shown));
3734
+ const sub = line.slice(0, take);
3735
+ if (sub) c.fillText(sub, ctx.W / 2, y);
3736
+ shown -= line.length;
3737
+ if (take < line.length && take >= 0 && !lastDrawn.end) {
3738
+ lastDrawn = { x: ctx.W / 2 + c.measureText(sub).width / 2, y, end: true };
3739
+ }
3740
+ y += lineH;
3741
+ }
3742
+ if (cursor && p < 1 && Math.floor(tMs / 450) % 2 === 0) {
3743
+ c.fillRect(lastDrawn.x + 6, lastDrawn.y - fontPx * 0.42, 6, fontPx * 0.8);
3744
+ }
3745
+ } else {
3746
+ const allWords = lines.flat();
3747
+ const n = allWords.length || 1;
3748
+ const stagger = revealMs / n;
3749
+ const POP = 280;
3750
+ c.textAlign = "left";
3751
+ const spaceW = c.measureText(" ").width;
3752
+ let wi = 0;
3753
+ let y = top;
3754
+ for (const line of lines) {
3755
+ const lineW = line.reduce((w, word, i) => w + c.measureText(word).width + (i ? spaceW : 0), 0);
3756
+ let x = sb.left + (sb.w - lineW) / 2;
3757
+ for (const word of line) {
3758
+ const ww = c.measureText(word).width;
3759
+ const wp = clamp014((tMs - startMs - wi * stagger) / POP);
3760
+ if (wp > 0) {
3761
+ const scale = 0.7 + 0.3 * wp;
3762
+ c.save();
3763
+ c.globalAlpha = wp;
3764
+ c.translate(x + ww / 2, y);
3765
+ c.scale(scale, scale);
3766
+ c.fillStyle = col;
3767
+ c.fillText(word, -ww / 2, 0);
3768
+ c.restore();
3769
+ }
3770
+ x += ww + spaceW;
3771
+ wi++;
3772
+ }
3773
+ y += lineH;
3774
+ }
3775
+ }
3776
+ c.restore();
3777
+ }
3778
+ };
3779
+ }
3780
+ var kineticTextFactory = {
3781
+ key: "kinetic-text",
3782
+ create: kineticTextLayer,
3783
+ validateProps(props) {
3784
+ if (props == null || typeof props !== "object") return ["kinetic-text: props must be an object"];
3785
+ const p = props;
3786
+ const errs = [];
3787
+ if (typeof p.text !== "string" || !p.text) errs.push("kinetic-text.text must be a non-empty string");
3788
+ if (p.mode != null && p.mode !== "typewriter" && p.mode !== "word-pop") errs.push('kinetic-text.mode must be "typewriter" | "word-pop"');
3789
+ if (p.startMs != null && typeof p.startMs !== "number") errs.push("kinetic-text.startMs must be a number");
3790
+ if (p.revealMs != null && (typeof p.revealMs !== "number" || p.revealMs <= 0)) errs.push("kinetic-text.revealMs must be a positive number");
3791
+ if (p.fontPx != null && (typeof p.fontPx !== "number" || p.fontPx <= 0)) errs.push("kinetic-text.fontPx must be a positive number");
3792
+ if (p.color != null && typeof p.color !== "string") errs.push("kinetic-text.color must be a string");
3793
+ if (p.centerFrac != null && (typeof p.centerFrac !== "number" || p.centerFrac < 0 || p.centerFrac > 1)) errs.push("kinetic-text.centerFrac must be in [0,1]");
3794
+ if (p.cursor != null && typeof p.cursor !== "boolean") errs.push("kinetic-text.cursor must be a boolean");
3795
+ return errs;
3796
+ }
3797
+ };
3798
+
3799
+ // src/scene/layers/countdown.ts
3800
+ var DEFAULTS6 = { from: 3, startMs: 0, durationMs: 3e3, fontPx: 280, centerFrac: 0.46, ring: true };
3801
+ function rgba6(hex, a) {
3802
+ const h = hex.replace("#", "");
3803
+ const n = h.length === 3 ? parseInt(h.split("").map((c) => c + c).join(""), 16) : parseInt(h, 16);
3804
+ return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
3805
+ }
3806
+ function countdownLayer() {
3807
+ let from = DEFAULTS6.from;
3808
+ let startMs = DEFAULTS6.startMs;
3809
+ let durationMs = DEFAULTS6.durationMs;
3810
+ let color;
3811
+ let fontPx = DEFAULTS6.fontPx;
3812
+ let centerFrac = DEFAULTS6.centerFrac;
3813
+ let ring = DEFAULTS6.ring;
3814
+ return {
3815
+ key: "countdown",
3816
+ init(_ctx, props) {
3817
+ from = props.from ?? DEFAULTS6.from;
3818
+ startMs = props.startMs ?? DEFAULTS6.startMs;
3819
+ durationMs = props.durationMs ?? DEFAULTS6.durationMs;
3820
+ color = props.color;
3821
+ fontPx = props.fontPx ?? DEFAULTS6.fontPx;
3822
+ centerFrac = props.centerFrac ?? DEFAULTS6.centerFrac;
3823
+ ring = props.ring ?? DEFAULTS6.ring;
3824
+ },
3825
+ draw(ctx, tMs) {
3826
+ const elapsed = tMs - startMs;
3827
+ if (elapsed < 0 || elapsed >= durationMs) return;
3828
+ const perTick = durationMs / from;
3829
+ const idx = Math.floor(elapsed / perTick);
3830
+ const n = from - idx;
3831
+ if (n < 1 || n > from) return;
3832
+ const tickElapsed = elapsed - idx * perTick;
3833
+ const tickFrac = tickElapsed / perTick;
3834
+ const c = ctx.ctx2d;
3835
+ const cx = ctx.W / 2;
3836
+ const cy = ctx.H * centerFrac;
3837
+ const col = color ?? ctx.theme.accent;
3838
+ const pop = 1 + 0.28 * Math.exp(-tickElapsed / 120);
3839
+ const fade = 1 - Math.max(0, (tickFrac - 0.7) / 0.3) * 0.85;
3840
+ c.save();
3841
+ if (ring) {
3842
+ const r = fontPx * 0.62;
3843
+ c.lineWidth = 10;
3844
+ c.lineCap = "round";
3845
+ c.strokeStyle = rgba6(col, 0.18 * fade);
3846
+ c.beginPath();
3847
+ c.arc(cx, cy, r, 0, Math.PI * 2);
3848
+ c.stroke();
3849
+ c.strokeStyle = rgba6(col, 0.9 * fade);
3850
+ c.beginPath();
3851
+ c.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + (1 - tickFrac) * Math.PI * 2);
3852
+ c.stroke();
3853
+ }
3854
+ c.globalAlpha = fade;
3855
+ c.fillStyle = col;
3856
+ c.font = `800 ${Math.round(fontPx * pop)}px ${ctx.theme.fontDisplay}`;
3857
+ c.textAlign = "center";
3858
+ c.textBaseline = "middle";
3859
+ c.fillText(String(n), cx, cy + 2);
3860
+ c.restore();
3861
+ }
3862
+ };
3863
+ }
3864
+ var countdownFactory = {
3865
+ key: "countdown",
3866
+ create: countdownLayer,
3867
+ validateProps(props) {
3868
+ if (props == null || typeof props !== "object") return ["countdown: props must be an object"];
3869
+ const p = props;
3870
+ const errs = [];
3871
+ if (p.from != null && (typeof p.from !== "number" || p.from < 1 || !Number.isInteger(p.from))) errs.push("countdown.from must be an integer >= 1");
3872
+ if (p.startMs != null && typeof p.startMs !== "number") errs.push("countdown.startMs must be a number");
3873
+ if (p.durationMs != null && (typeof p.durationMs !== "number" || p.durationMs <= 0)) errs.push("countdown.durationMs must be a positive number");
3874
+ if (p.color != null && typeof p.color !== "string") errs.push("countdown.color must be a string");
3875
+ if (p.fontPx != null && (typeof p.fontPx !== "number" || p.fontPx <= 0)) errs.push("countdown.fontPx must be a positive number");
3876
+ if (p.centerFrac != null && (typeof p.centerFrac !== "number" || p.centerFrac < 0 || p.centerFrac > 1)) errs.push("countdown.centerFrac must be in [0,1]");
3877
+ if (p.ring != null && typeof p.ring !== "boolean") errs.push("countdown.ring must be a boolean");
3878
+ return errs;
3879
+ }
3880
+ };
3881
+
3882
+ // src/scene/layers/waveform.ts
3883
+ var SAMPLES = 128;
3884
+ function clampUnit(x) {
3885
+ return x < -1 ? -1 : x > 1 ? 1 : x;
3886
+ }
3887
+ function syntheticSample(tSec, u) {
3888
+ const phase = u * Math.PI * 2;
3889
+ return 0.55 * Math.sin(phase * 3 + tSec * 6) + 0.28 * Math.sin(phase * 5 + tSec * 9 + 1.1) + 0.14 * Math.sin(phase * 8 + tSec * 4 + 2.3);
3890
+ }
3891
+ function waveformLayer() {
3892
+ let n = SAMPLES;
3893
+ let centerFrac = 0.46;
3894
+ let amplitudeFrac = 0.1;
3895
+ let lineWidth = 5;
3896
+ let color;
3897
+ let samplesFn;
3898
+ function samplesAt(ctx, tMs) {
3899
+ const fromProp = samplesFn?.(tMs, n);
3900
+ if (fromProp && fromProp.length) {
3901
+ return Array.from({ length: n }, (_, i) => clampUnit(fromProp[Math.min(fromProp.length - 1, i)] ?? 0));
3902
+ }
3903
+ const bt = ctx.waveform?.byteTime?.(tMs);
3904
+ if (bt && bt.length) {
3905
+ return Array.from({ length: n }, (_, i) => clampUnit((bt[Math.floor(i / n * bt.length)] - 128) / 128));
3906
+ }
3907
+ const tSec = tMs / 1e3;
3908
+ return Array.from({ length: n }, (_, i) => clampUnit(syntheticSample(tSec, i / (n - 1))));
3909
+ }
3910
+ return {
3911
+ key: "waveform",
3912
+ init(_ctx, props) {
3913
+ n = props.samples ?? SAMPLES;
3914
+ centerFrac = props.centerFrac ?? 0.46;
3915
+ amplitudeFrac = props.amplitudeFrac ?? 0.1;
3916
+ lineWidth = props.lineWidth ?? 5;
3917
+ color = props.color;
3918
+ samplesFn = props.samplesFn;
3919
+ },
3920
+ draw(ctx, tMs) {
3921
+ const c = ctx.ctx2d;
3922
+ const sb = ctx.safeBox;
3923
+ const left = sb.left, right = sb.right;
3924
+ const cy = ctx.H * centerFrac;
3925
+ const amp = ctx.H * amplitudeFrac;
3926
+ const s = samplesAt(ctx, tMs);
3927
+ c.save();
3928
+ c.lineJoin = "round";
3929
+ c.lineCap = "round";
3930
+ c.strokeStyle = color ?? ctx.theme.accent;
3931
+ c.lineWidth = lineWidth;
3932
+ c.beginPath();
3933
+ for (let i = 0; i < n; i++) {
3934
+ const x = left + (right - left) * (i / (n - 1));
3935
+ const y = cy + s[i] * amp;
3936
+ if (i === 0) c.moveTo(x, y);
3937
+ else c.lineTo(x, y);
3938
+ }
3939
+ c.stroke();
3940
+ c.restore();
3941
+ }
3942
+ };
3943
+ }
3944
+ var waveformFactory = {
3945
+ key: "waveform",
3946
+ create: waveformLayer,
3947
+ validateProps(props) {
3948
+ if (props == null || typeof props !== "object") return ["waveform: props must be an object"];
3949
+ const p = props;
3950
+ const errs = [];
3951
+ if (p.samples != null && (typeof p.samples !== "number" || p.samples < 8)) errs.push("waveform.samples must be a number >= 8");
3952
+ if (p.centerFrac != null && (typeof p.centerFrac !== "number" || p.centerFrac < 0 || p.centerFrac > 1)) errs.push("waveform.centerFrac must be in [0,1]");
3953
+ if (p.amplitudeFrac != null && (typeof p.amplitudeFrac !== "number" || p.amplitudeFrac <= 0)) errs.push("waveform.amplitudeFrac must be a positive number");
3954
+ if (p.lineWidth != null && (typeof p.lineWidth !== "number" || p.lineWidth <= 0)) errs.push("waveform.lineWidth must be a positive number");
3955
+ if (p.color != null && typeof p.color !== "string") errs.push("waveform.color must be a string");
3956
+ if (p.samplesFn != null && typeof p.samplesFn !== "function") errs.push("waveform.samplesFn must be a function");
3957
+ return errs;
3958
+ }
3959
+ };
3960
+
3961
+ // src/scene/layers/scaleHighlight.ts
3962
+ var SCALE_INTERVALS = {
3963
+ major: [0, 2, 4, 5, 7, 9, 11],
3964
+ ionian: [0, 2, 4, 5, 7, 9, 11],
3965
+ minor: [0, 2, 3, 5, 7, 8, 10],
3966
+ aeolian: [0, 2, 3, 5, 7, 8, 10],
3967
+ dorian: [0, 2, 3, 5, 7, 9, 10],
3968
+ phrygian: [0, 1, 3, 5, 7, 8, 10],
3969
+ lydian: [0, 2, 4, 6, 7, 9, 11],
3970
+ mixolydian: [0, 2, 4, 5, 7, 9, 10],
3971
+ locrian: [0, 1, 3, 5, 6, 8, 10],
3972
+ harmonicMinor: [0, 2, 3, 5, 7, 8, 11],
3973
+ melodicMinor: [0, 2, 3, 5, 7, 9, 11],
3974
+ pentatonicMajor: [0, 2, 4, 7, 9],
3975
+ pentatonicMinor: [0, 3, 5, 7, 10],
3976
+ blues: [0, 3, 5, 6, 7, 10],
3977
+ chromatic: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
3978
+ };
3979
+ var PRETTY = {
3980
+ major: "Major",
3981
+ ionian: "Ionian",
3982
+ minor: "Minor",
3983
+ aeolian: "Aeolian",
3984
+ dorian: "Dorian",
3985
+ phrygian: "Phrygian",
3986
+ lydian: "Lydian",
3987
+ mixolydian: "Mixolydian",
3988
+ locrian: "Locrian",
3989
+ harmonicMinor: "Harmonic Minor",
3990
+ melodicMinor: "Melodic Minor",
3991
+ pentatonicMajor: "Pentatonic",
3992
+ pentatonicMinor: "Minor Pentatonic",
3993
+ blues: "Blues",
3994
+ chromatic: "Chromatic"
3995
+ };
3996
+ function rootPc(root) {
3997
+ if (typeof root === "number") return (root % 12 + 12) % 12;
3998
+ return noteNameToIndex(root);
3999
+ }
4000
+ function scaleHighlightLayer() {
4001
+ let rootProp = "C";
4002
+ let scaleKey = "major";
4003
+ let scaleColor;
4004
+ let tonicColor;
4005
+ let alpha = 0.55;
4006
+ let label = true;
4007
+ let labelTextProp;
4008
+ let labelCenterFrac;
4009
+ return {
4010
+ key: "scale-highlight",
4011
+ init(_ctx, props) {
4012
+ rootProp = props.root ?? "C";
4013
+ scaleKey = props.scale ?? "major";
4014
+ scaleColor = props.scaleColor;
4015
+ tonicColor = props.tonicColor;
4016
+ alpha = props.alpha ?? 0.55;
4017
+ label = props.label ?? true;
4018
+ labelTextProp = props.labelText;
4019
+ labelCenterFrac = props.labelCenterFrac;
4020
+ },
4021
+ draw(ctx, _tMs) {
4022
+ const c = ctx.ctx2d;
4023
+ const intervals = SCALE_INTERVALS[scaleKey] ?? SCALE_INTERVALS.major;
4024
+ const root = rootPc(rootProp);
4025
+ const inScale = new Set(intervals.map((iv) => (root + iv) % 12));
4026
+ const sc = scaleColor ?? ctx.theme.accent;
4027
+ const tc = tonicColor ?? ctx.theme.gold;
4028
+ const layout = getKeyboardLayout(ctx);
4029
+ if (layout) {
4030
+ c.save();
4031
+ c.globalAlpha = alpha;
4032
+ for (let m = layout.lowMidi; m <= layout.highMidi; m++) {
4033
+ if (!inRange(layout, m)) continue;
4034
+ const pc = pitchClass(m);
4035
+ if (!inScale.has(pc)) continue;
4036
+ const r = keyRect(layout, m);
4037
+ c.fillStyle = pc === root ? tc : sc;
4038
+ c.fillRect(r.x, r.y, r.w, r.h);
4039
+ }
4040
+ c.restore();
4041
+ }
4042
+ if (label) {
4043
+ const sb = ctx.safeBox;
4044
+ const name = typeof rootProp === "string" ? rootProp : ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"][root];
4045
+ const text = labelTextProp ?? `${name} ${PRETTY[scaleKey] ?? scaleKey}`;
4046
+ c.save();
4047
+ c.fillStyle = ctx.theme.ink;
4048
+ c.font = `700 ${Math.round(ctx.H * 0.03)}px ${ctx.theme.fontDisplay}`;
4049
+ c.textAlign = "center";
4050
+ c.textBaseline = labelCenterFrac != null ? "middle" : "top";
4051
+ const ly = labelCenterFrac != null ? ctx.H * labelCenterFrac : sb.top + sb.h * 0.04;
4052
+ c.fillText(text, ctx.W / 2, ly);
4053
+ c.restore();
4054
+ }
4055
+ }
4056
+ };
4057
+ }
4058
+ var scaleHighlightFactory = {
4059
+ key: "scale-highlight",
4060
+ create: scaleHighlightLayer,
4061
+ validateProps(props) {
4062
+ if (props == null || typeof props !== "object") return ["scale-highlight: props must be an object"];
4063
+ const p = props;
4064
+ const errs = [];
4065
+ if (p.root != null && typeof p.root !== "string" && typeof p.root !== "number") errs.push("scale-highlight.root must be a note name or pitch-class number");
4066
+ if (p.scale != null && (typeof p.scale !== "string" || !(p.scale in SCALE_INTERVALS))) errs.push(`scale-highlight.scale must be one of: ${Object.keys(SCALE_INTERVALS).join(", ")}`);
4067
+ if (p.scaleColor != null && typeof p.scaleColor !== "string") errs.push("scale-highlight.scaleColor must be a string");
4068
+ if (p.tonicColor != null && typeof p.tonicColor !== "string") errs.push("scale-highlight.tonicColor must be a string");
4069
+ if (p.alpha != null && (typeof p.alpha !== "number" || p.alpha < 0 || p.alpha > 1)) errs.push("scale-highlight.alpha must be in [0,1]");
4070
+ if (p.label != null && typeof p.label !== "boolean") errs.push("scale-highlight.label must be a boolean");
4071
+ if (p.labelText != null && typeof p.labelText !== "string") errs.push("scale-highlight.labelText must be a string");
4072
+ if (p.labelCenterFrac != null && (typeof p.labelCenterFrac !== "number" || p.labelCenterFrac < 0 || p.labelCenterFrac > 1)) errs.push("scale-highlight.labelCenterFrac must be in [0,1]");
4073
+ return errs;
4074
+ }
4075
+ };
4076
+
3660
4077
  // src/scene/registry.ts
3661
4078
  var REGISTRY = /* @__PURE__ */ new Map();
3662
4079
  function registerLayer(factory) {
@@ -3695,6 +4112,10 @@ registerLayer(progressRingFactory);
3695
4112
  registerLayer(radialSpectrumFactory);
3696
4113
  registerLayer(karaokeCaptionFactory);
3697
4114
  registerLayer(intervalArcsFactory);
4115
+ registerLayer(kineticTextFactory);
4116
+ registerLayer(countdownFactory);
4117
+ registerLayer(waveformFactory);
4118
+ registerLayer(scaleHighlightFactory);
3698
4119
 
3699
4120
  // src/scene/audioLayers.ts
3700
4121
  function countInSchedule(opts) {
@@ -3851,7 +4272,9 @@ var SCREEN_PINNED_KEYS = /* @__PURE__ */ new Set([
3851
4272
  "beat-pulse",
3852
4273
  "chord-ribbon",
3853
4274
  "progress-ring",
3854
- "karaoke-caption"
4275
+ "karaoke-caption",
4276
+ "kinetic-text",
4277
+ "countdown"
3855
4278
  ]);
3856
4279
  function defaultBufferFactory() {
3857
4280
  const g = globalThis;
@@ -4438,6 +4861,7 @@ export {
4438
4861
  FOLLOW_PAD,
4439
4862
  PIANO_HIGH,
4440
4863
  PIANO_LOW,
4864
+ SCALE_INTERVALS,
4441
4865
  activeChord,
4442
4866
  activeCue,
4443
4867
  activeSection,
@@ -4467,6 +4891,7 @@ export {
4467
4891
  contourPolyline,
4468
4892
  countInLeadSec,
4469
4893
  countInSchedule,
4894
+ countdownFactory,
4470
4895
  countdownRemaining,
4471
4896
  countdownSeconds,
4472
4897
  countingDegreeDemoSpec,
@@ -4517,6 +4942,7 @@ export {
4517
4942
  keySlot,
4518
4943
  keyboardFactory,
4519
4944
  keyboardLayout,
4945
+ kineticTextFactory,
4520
4946
  lerp,
4521
4947
  lerpBox,
4522
4948
  lerpCamera,
@@ -4562,6 +4988,7 @@ export {
4562
4988
  revealProgress,
4563
4989
  runGate,
4564
4990
  safeGuidesFactory,
4991
+ scaleHighlightFactory,
4565
4992
  scoreFromMusicXML,
4566
4993
  scorePitchSpan,
4567
4994
  scrollCursorFactory,
@@ -4584,6 +5011,7 @@ export {
4584
5011
  visualTimelineMs,
4585
5012
  vstackAudioPlayheadLine,
4586
5013
  vstackFollowBox,
5014
+ waveformFactory,
4587
5015
  whiteKeys,
4588
5016
  worldToViewport
4589
5017
  };