@real-music-packages/web-core 0.23.0 → 0.24.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.
@@ -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,415 @@ 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
+ return {
4009
+ key: "scale-highlight",
4010
+ init(_ctx, props) {
4011
+ rootProp = props.root ?? "C";
4012
+ scaleKey = props.scale ?? "major";
4013
+ scaleColor = props.scaleColor;
4014
+ tonicColor = props.tonicColor;
4015
+ alpha = props.alpha ?? 0.55;
4016
+ label = props.label ?? true;
4017
+ labelTextProp = props.labelText;
4018
+ },
4019
+ draw(ctx, _tMs) {
4020
+ const c = ctx.ctx2d;
4021
+ const intervals = SCALE_INTERVALS[scaleKey] ?? SCALE_INTERVALS.major;
4022
+ const root = rootPc(rootProp);
4023
+ const inScale = new Set(intervals.map((iv) => (root + iv) % 12));
4024
+ const sc = scaleColor ?? ctx.theme.accent;
4025
+ const tc = tonicColor ?? ctx.theme.gold;
4026
+ const layout = getKeyboardLayout(ctx);
4027
+ if (layout) {
4028
+ c.save();
4029
+ c.globalAlpha = alpha;
4030
+ for (let m = layout.lowMidi; m <= layout.highMidi; m++) {
4031
+ if (!inRange(layout, m)) continue;
4032
+ const pc = pitchClass(m);
4033
+ if (!inScale.has(pc)) continue;
4034
+ const r = keyRect(layout, m);
4035
+ c.fillStyle = pc === root ? tc : sc;
4036
+ c.fillRect(r.x, r.y, r.w, r.h);
4037
+ }
4038
+ c.restore();
4039
+ }
4040
+ if (label) {
4041
+ const sb = ctx.safeBox;
4042
+ const name = typeof rootProp === "string" ? rootProp : ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"][root];
4043
+ const text = labelTextProp ?? `${name} ${PRETTY[scaleKey] ?? scaleKey}`;
4044
+ c.save();
4045
+ c.fillStyle = ctx.theme.ink;
4046
+ c.font = `700 ${Math.round(ctx.H * 0.03)}px ${ctx.theme.fontDisplay}`;
4047
+ c.textAlign = "center";
4048
+ c.textBaseline = "top";
4049
+ c.fillText(text, ctx.W / 2, sb.top + sb.h * 0.04);
4050
+ c.restore();
4051
+ }
4052
+ }
4053
+ };
4054
+ }
4055
+ var scaleHighlightFactory = {
4056
+ key: "scale-highlight",
4057
+ create: scaleHighlightLayer,
4058
+ validateProps(props) {
4059
+ if (props == null || typeof props !== "object") return ["scale-highlight: props must be an object"];
4060
+ const p = props;
4061
+ const errs = [];
4062
+ 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");
4063
+ 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(", ")}`);
4064
+ if (p.scaleColor != null && typeof p.scaleColor !== "string") errs.push("scale-highlight.scaleColor must be a string");
4065
+ if (p.tonicColor != null && typeof p.tonicColor !== "string") errs.push("scale-highlight.tonicColor must be a string");
4066
+ if (p.alpha != null && (typeof p.alpha !== "number" || p.alpha < 0 || p.alpha > 1)) errs.push("scale-highlight.alpha must be in [0,1]");
4067
+ if (p.label != null && typeof p.label !== "boolean") errs.push("scale-highlight.label must be a boolean");
4068
+ if (p.labelText != null && typeof p.labelText !== "string") errs.push("scale-highlight.labelText must be a string");
4069
+ return errs;
4070
+ }
4071
+ };
4072
+
3660
4073
  // src/scene/registry.ts
3661
4074
  var REGISTRY = /* @__PURE__ */ new Map();
3662
4075
  function registerLayer(factory) {
@@ -3695,6 +4108,10 @@ registerLayer(progressRingFactory);
3695
4108
  registerLayer(radialSpectrumFactory);
3696
4109
  registerLayer(karaokeCaptionFactory);
3697
4110
  registerLayer(intervalArcsFactory);
4111
+ registerLayer(kineticTextFactory);
4112
+ registerLayer(countdownFactory);
4113
+ registerLayer(waveformFactory);
4114
+ registerLayer(scaleHighlightFactory);
3698
4115
 
3699
4116
  // src/scene/audioLayers.ts
3700
4117
  function countInSchedule(opts) {
@@ -3851,7 +4268,9 @@ var SCREEN_PINNED_KEYS = /* @__PURE__ */ new Set([
3851
4268
  "beat-pulse",
3852
4269
  "chord-ribbon",
3853
4270
  "progress-ring",
3854
- "karaoke-caption"
4271
+ "karaoke-caption",
4272
+ "kinetic-text",
4273
+ "countdown"
3855
4274
  ]);
3856
4275
  function defaultBufferFactory() {
3857
4276
  const g = globalThis;
@@ -4438,6 +4857,7 @@ export {
4438
4857
  FOLLOW_PAD,
4439
4858
  PIANO_HIGH,
4440
4859
  PIANO_LOW,
4860
+ SCALE_INTERVALS,
4441
4861
  activeChord,
4442
4862
  activeCue,
4443
4863
  activeSection,
@@ -4467,6 +4887,7 @@ export {
4467
4887
  contourPolyline,
4468
4888
  countInLeadSec,
4469
4889
  countInSchedule,
4890
+ countdownFactory,
4470
4891
  countdownRemaining,
4471
4892
  countdownSeconds,
4472
4893
  countingDegreeDemoSpec,
@@ -4517,6 +4938,7 @@ export {
4517
4938
  keySlot,
4518
4939
  keyboardFactory,
4519
4940
  keyboardLayout,
4941
+ kineticTextFactory,
4520
4942
  lerp,
4521
4943
  lerpBox,
4522
4944
  lerpCamera,
@@ -4562,6 +4984,7 @@ export {
4562
4984
  revealProgress,
4563
4985
  runGate,
4564
4986
  safeGuidesFactory,
4987
+ scaleHighlightFactory,
4565
4988
  scoreFromMusicXML,
4566
4989
  scorePitchSpan,
4567
4990
  scrollCursorFactory,
@@ -4584,6 +5007,7 @@ export {
4584
5007
  visualTimelineMs,
4585
5008
  vstackAudioPlayheadLine,
4586
5009
  vstackFollowBox,
5010
+ waveformFactory,
4587
5011
  whiteKeys,
4588
5012
  worldToViewport
4589
5013
  };