@real-music-packages/web-core 0.42.2 → 0.44.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 +49 -6
- package/dist/scene/index.js +174 -8
- package/dist/scene/index.js.map +1 -1
- package/package.json +1 -1
package/dist/scene/index.d.ts
CHANGED
|
@@ -891,10 +891,23 @@ declare const portraitFactory: LayerFactory<PortraitProps>;
|
|
|
891
891
|
interface BrandingProps {
|
|
892
892
|
/** Wordmark to draw. Default theme.brand. */
|
|
893
893
|
logo?: string;
|
|
894
|
+
/** Optional second line under the wordmark (e.g. a domain) — the brand-card
|
|
895
|
+
* form used for an end-of-clip mark: wordmark + URL, nothing else. Omit
|
|
896
|
+
* for the original single-line chrome mark. */
|
|
897
|
+
sub?: string;
|
|
898
|
+
/** Sub-line font size in px. Default size*0.5. */
|
|
899
|
+
subSize?: number;
|
|
900
|
+
/** Sub-line colour. Default color/theme.sepia at reduced alpha handled by
|
|
901
|
+
* the caller's theme choice (no alpha applied here so callers stay in
|
|
902
|
+
* control of exact tone). */
|
|
903
|
+
subColor?: string;
|
|
904
|
+
/** Gap between wordmark baseline and sub-line baseline, px. Default size*0.85. */
|
|
905
|
+
gap?: number;
|
|
894
906
|
/** Show the safe-zone debug overlay on top (apps' ?safe=1). Default false. */
|
|
895
907
|
safezone?: boolean;
|
|
896
908
|
/** Vertical anchor as a fraction of safeBox height from its TOP. Default 1
|
|
897
|
-
* (bottom edge of the safe box).
|
|
909
|
+
* (bottom edge of the safe box). With `sub` set, this anchors the WORDMARK
|
|
910
|
+
* line and the sub-line is drawn `gap` px below it. */
|
|
898
911
|
yFrac?: number;
|
|
899
912
|
/** Font size in px. Default 34. */
|
|
900
913
|
size?: number;
|
|
@@ -1731,10 +1744,15 @@ interface CompositeRhythmProps {
|
|
|
1731
1744
|
tickH?: number;
|
|
1732
1745
|
/** Merged-mark radius (world px). Default 12. */
|
|
1733
1746
|
mergeR?: number;
|
|
1734
|
-
/** Reference subdivision
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
|
|
1747
|
+
/** Reference subdivision rows, drawn as faint rows ABOVE the LH row. Two
|
|
1748
|
+
* forms per entry: a plain number N divides `windowMs` into N EQUAL ticks
|
|
1749
|
+
* (e.g. `3` for a triplet feel, `2` for straight eighths — a 3-against-2
|
|
1750
|
+
* bar is `grids: [3, 2]`); an array of positive weights draws ticks at
|
|
1751
|
+
* each GROUP's start per those relative durations (e.g. `[3, 3, 2]` for a
|
|
1752
|
+
* 3-3-2 clave bar — ticks at fractions 0, 3/8, 6/8, labeled "3+3+2") — for
|
|
1753
|
+
* asymmetric groupings the rhythm montage needs that an equal-N grid can't
|
|
1754
|
+
* represent. Default none. */
|
|
1755
|
+
grids?: Array<number | number[]>;
|
|
1738
1756
|
/** Onsets within this many ms of each other collapse into ONE composite
|
|
1739
1757
|
* mark. Default 60. */
|
|
1740
1758
|
mergeWindowMs?: number;
|
|
@@ -1755,4 +1773,29 @@ interface CompositeRhythmProps {
|
|
|
1755
1773
|
}
|
|
1756
1774
|
declare const compositeRhythmFactory: LayerFactory<CompositeRhythmProps>;
|
|
1757
1775
|
|
|
1758
|
-
|
|
1776
|
+
interface HandIndicatorProps {
|
|
1777
|
+
/** Top of the hand band, world px. Required — the caller owns this route's
|
|
1778
|
+
* vertical layout; this layer only draws at the given y. */
|
|
1779
|
+
y: number;
|
|
1780
|
+
/** Band height, world px. Default 60. */
|
|
1781
|
+
height?: number;
|
|
1782
|
+
/** Hand colours — the SAME tokens the keyboard/composite-rhythm layers use.
|
|
1783
|
+
* Default R = theme.accent, L = theme.gold. */
|
|
1784
|
+
handColors?: HandColors;
|
|
1785
|
+
/** How fast a hand fades from lit back to its resting outline after its
|
|
1786
|
+
* last active note ends, ms. Default 200 (the "quickly" in ~150-250ms). */
|
|
1787
|
+
fadeMs?: number;
|
|
1788
|
+
/** Icon size, px (roughly the glyph's bounding box). Default height*0.85. */
|
|
1789
|
+
size?: number;
|
|
1790
|
+
/** LH icon x, fraction of safeBox width from its left edge. Default 0.26
|
|
1791
|
+
* (under the lower/bass register). */
|
|
1792
|
+
leftXFrac?: number;
|
|
1793
|
+
/** RH icon x, fraction of safeBox width from its left edge. Default 0.74
|
|
1794
|
+
* (under the upper/treble register). */
|
|
1795
|
+
rightXFrac?: number;
|
|
1796
|
+
/** Baseline (resting/unlit) outline alpha. Default 0.32. */
|
|
1797
|
+
restAlpha?: number;
|
|
1798
|
+
}
|
|
1799
|
+
declare const handIndicatorFactory: LayerFactory<HandIndicatorProps>;
|
|
1800
|
+
|
|
1801
|
+
export { type Affine, AudioClock, type AudioEvent, type BackgroundProps, type BeatGridOpts, type BeatPulseProps, type BeatTick, type BrandingProps, type BufferFactory, type BuildSceneOpts, type BuiltScene, type CameraState, type CaptionCue, type CaptionProps, type CaptionScript, type CaptionStyle, type ChordRibbonProps, type ChordSpan, type CircleOfFifthsProps, type CirclePoint, type ClickTrackOpts, type ColorBy, type CompositeRhythmProps, type ContourPlot, type ContourPoint, type CountInOpts, type CountdownProps, type CountingTrackProps, type CtaProps, DEFAULT_FUNCTION_COLORS, type DegreeLabel, type DegreeLabelsProps, type DroneOpts, type DuckWindow, type Easing, type EndCardProps, type ExtendedDemoOpts, FIFTHS_MAJOR, FIFTHS_MINOR, type FallingKeyboardDemoOpts, type FallingNotesProps, type FretboardProps, type FunctionColors, type FunctionalHarmonyProps, type GateError, type GateInput, type HandColors, type HandIndicatorProps, type HarmonicFunction, type HarmonyMode, type HighlightRegion, type HookProps, type ImageRevealMode, type ImageRevealProps, type IntervalArcsProps, type KaraokeCaptionProps, type KaraokeWord, type KeyRect, type KeyboardLayout, type KeyboardLayoutOpts, type KeyboardProps, type KineticMode, type KineticTextProps, type LabelMode, LayerFactory, type McqCardProps, type NotationEngraving, NotationLayout, type NotationProps, type OutputProbe, PIANO_HIGH, PIANO_LOW, type ParticleBurstProps, type PitchContourProps, type Placement, type PortraitProps, type ProgressRingProps, type PromoCardsDemoOpts, type PulseStyle, type Quiz, type QuizOption, type QuizPhase, type RadialSpectrumProps, type RayEndpoints, type RecordSceneSpecOpts, type Rect, RenderCtx, type ResolvedSegment, type RevealEasing, type RevealProps, SCALE_INTERVALS, type SafeGuidesProps, type ScaleHighlightProps, type SceneSpec, type ScheduleTarget, Score, ScoreFromMusicXMLOpts, ScoreNote, type ScrollCursorProps, type Section, type SectionMinimapProps, type SegmentAudio, type SegmentAudioCtx, type SegmentTransition, type SpecLayer, type StaffKeyboardRayProps, type StatCounterProps, TempoMap, type TensionGraphProps, type TensionPoint, type TextureProps, type TimeAnchor, type TimelineSegment, activeChord, activeCue, activeSection, animatedSlot, applySchedule, applyToContext, assertGate, ballArc, ballX, beatGrid, beatPhase, beatPulseFactory, blackKeys, bpmOf, brandingFactory, buildScene, cameraForFollow, cameraTransform, chordRibbonFactory, circleOfFifthsDemoSpec, circleOfFifthsFactory, clamp, clickTrackSchedule, compositeRhythmFactory, contourMinimapDemoSpec, contourPoints, contourPolyline, countInLeadSec, countInSchedule, countdownFactory, countdownRemaining, countdownSeconds, countingDegreeDemoSpec, countingTrackFactory, ctaFactory, cueOpacity, degreeLabel, degreeLabelsFactory, dotAt, drawCaption, drawHighlight, droneSchedule, duckGainAt, easeIn, easeInOut, easeOut, endCardFactory, fallingKeyboardDemoScore, fallingKeyboardDemoSpec, fallingNotesFactory, followSrcBox, fracSlotPoint, frameRect, fretboardFactory, functionColor, functionalHarmonyFactory, getKeyboardLayout, getLayerFactory, getNotationEngraving, handIndicatorFactory, harmonyDemoSpec, highlightIntensity, hookFactory, identityCamera, imageRevealFactory, inRange, intervalArcsFactory, invLerp, isBlackKey, karaokeCaptionFactory, kenBurns, keyCenterX, keyColumnWidth, keyRect, keySlot, keyboardFactory, keyboardLayout, kineticTextFactory, lerp, lerpCamera, linear, mapBoxThroughLayout, mcqCardFactory, mcqDemoSpec, measureSpans, timeToX as minimapTimeToX, msPerBeat, notationFactory, noteColor, noteSetXRange, parseKey, parseTimeSig, particleBurstFactory, pcToSlot, pitchAt, pitchContourFactory, pitchRange, portraitFactory, progress01, progressRingFactory, projectPoint, promoCardsDemoSpec, pulseAt, quizPhase, radialSpectrumFactory, rayEndpoints, rayPointAt, recordSceneSpec, registerLayer, registeredKeys, resolveAnchor, resolveKeyboardLayout, resolveTimeline, revealFactory, revealProgress, runGate, safeGuidesFactory, scaleHighlightFactory, scorePitchSpan, scrollCursorFactory, sectionMinimapFactory, setFollowLayoutProvider, setKeyboardLayout, setNotationEngraving, slotAngle, slotPc, slotPoint, staffAnchor, staffKeyboardRayFactory, staffRayDemoSpec, statCounterFactory, tensionGraphFactory, textureFactory, transitionProgress, validateChordTrack, validateQuiz, validateSections, visualTimelineMs, whiteKeys, worldToViewport };
|
package/dist/scene/index.js
CHANGED
|
@@ -1245,6 +1245,10 @@ var spectrumFactory = {
|
|
|
1245
1245
|
// src/scene/layers/branding.ts
|
|
1246
1246
|
function brandingLayer() {
|
|
1247
1247
|
let logo;
|
|
1248
|
+
let sub;
|
|
1249
|
+
let subSize = 0;
|
|
1250
|
+
let subColor;
|
|
1251
|
+
let gap = 0;
|
|
1248
1252
|
let safezone = false;
|
|
1249
1253
|
let yFrac = 1;
|
|
1250
1254
|
let size = 34;
|
|
@@ -1253,10 +1257,14 @@ function brandingLayer() {
|
|
|
1253
1257
|
key: "branding",
|
|
1254
1258
|
init(_ctx, props) {
|
|
1255
1259
|
logo = props.logo;
|
|
1260
|
+
sub = props.sub;
|
|
1256
1261
|
safezone = props.safezone ?? false;
|
|
1257
1262
|
yFrac = props.yFrac ?? 1;
|
|
1258
1263
|
size = props.size ?? 34;
|
|
1264
|
+
subSize = props.subSize ?? size * 0.5;
|
|
1265
|
+
gap = props.gap ?? size * 0.85;
|
|
1259
1266
|
color = props.color;
|
|
1267
|
+
subColor = props.subColor;
|
|
1260
1268
|
},
|
|
1261
1269
|
draw(ctx) {
|
|
1262
1270
|
const c = ctx.ctx2d;
|
|
@@ -1266,8 +1274,14 @@ function brandingLayer() {
|
|
|
1266
1274
|
c.textAlign = "center";
|
|
1267
1275
|
c.font = `italic ${size}px ${ctx.theme.fontBody}`;
|
|
1268
1276
|
c.fillStyle = color ?? ctx.theme.sepia;
|
|
1269
|
-
const
|
|
1277
|
+
const pairShift = sub ? gap / 2 : 0;
|
|
1278
|
+
const y = sb.top + sb.h * yFrac - (yFrac >= 1 ? size * 0.3 : 0) - pairShift;
|
|
1270
1279
|
c.fillText(text, sb.cx, y);
|
|
1280
|
+
if (sub) {
|
|
1281
|
+
c.font = `${subSize}px ${ctx.theme.fontBody}`;
|
|
1282
|
+
c.fillStyle = subColor ?? color ?? ctx.theme.sepia;
|
|
1283
|
+
c.fillText(sub, sb.cx, y + gap);
|
|
1284
|
+
}
|
|
1271
1285
|
c.restore();
|
|
1272
1286
|
if (safezone) drawSafeGuides(c);
|
|
1273
1287
|
}
|
|
@@ -1281,6 +1295,10 @@ var brandingFactory = {
|
|
|
1281
1295
|
const p = props;
|
|
1282
1296
|
const errs = [];
|
|
1283
1297
|
if (p.logo != null && typeof p.logo !== "string") errs.push("branding.logo must be a string");
|
|
1298
|
+
if (p.sub != null && typeof p.sub !== "string") errs.push("branding.sub must be a string");
|
|
1299
|
+
if (p.subSize != null && (typeof p.subSize !== "number" || p.subSize <= 0)) errs.push("branding.subSize must be a positive number");
|
|
1300
|
+
if (p.subColor != null && typeof p.subColor !== "string") errs.push("branding.subColor must be a string");
|
|
1301
|
+
if (p.gap != null && typeof p.gap !== "number") errs.push("branding.gap must be a number");
|
|
1284
1302
|
if (p.safezone != null && typeof p.safezone !== "boolean") errs.push("branding.safezone must be a boolean");
|
|
1285
1303
|
if (p.yFrac != null && typeof p.yFrac !== "number") errs.push("branding.yFrac must be a number");
|
|
1286
1304
|
if (p.size != null && (typeof p.size !== "number" || p.size <= 0)) errs.push("branding.size must be a positive number");
|
|
@@ -4416,8 +4434,11 @@ function compositeRhythmLayer() {
|
|
|
4416
4434
|
const lhY = yCenter - rowGap;
|
|
4417
4435
|
const rhY = yCenter + rowGap;
|
|
4418
4436
|
c.save();
|
|
4419
|
-
grids.forEach((
|
|
4420
|
-
|
|
4437
|
+
grids.forEach((entry, gi) => {
|
|
4438
|
+
const isGrouping = Array.isArray(entry);
|
|
4439
|
+
const weights = isGrouping ? entry.filter((w) => w > 0) : [];
|
|
4440
|
+
if (isGrouping && weights.length === 0) return;
|
|
4441
|
+
if (!isGrouping && entry <= 0) return;
|
|
4421
4442
|
const gy = lhY - rowGap * (grids.length - gi);
|
|
4422
4443
|
c.strokeStyle = "rgba(26,22,20,0.22)";
|
|
4423
4444
|
c.lineWidth = 2;
|
|
@@ -4425,8 +4446,20 @@ function compositeRhythmLayer() {
|
|
|
4425
4446
|
c.moveTo(sb.left, gy);
|
|
4426
4447
|
c.lineTo(sb.left + sb.w, gy);
|
|
4427
4448
|
c.stroke();
|
|
4428
|
-
|
|
4429
|
-
|
|
4449
|
+
const starts = [];
|
|
4450
|
+
if (isGrouping) {
|
|
4451
|
+
const total = weights.reduce((a, b) => a + b, 0);
|
|
4452
|
+
let acc = 0;
|
|
4453
|
+
for (const w of weights) {
|
|
4454
|
+
starts.push(acc / total);
|
|
4455
|
+
acc += w;
|
|
4456
|
+
}
|
|
4457
|
+
} else {
|
|
4458
|
+
const n = entry;
|
|
4459
|
+
for (let i = 0; i < n; i++) starts.push(i / n);
|
|
4460
|
+
}
|
|
4461
|
+
for (const f of starts) {
|
|
4462
|
+
const t = w0 + (w1 - w0) * f;
|
|
4430
4463
|
if (!revealed(t)) continue;
|
|
4431
4464
|
const gx = x(t);
|
|
4432
4465
|
c.strokeStyle = "rgba(26,22,20,0.38)";
|
|
@@ -4440,7 +4473,7 @@ function compositeRhythmLayer() {
|
|
|
4440
4473
|
c.font = "600 22px system-ui, sans-serif";
|
|
4441
4474
|
c.textAlign = "right";
|
|
4442
4475
|
c.textBaseline = "middle";
|
|
4443
|
-
c.fillText(String(
|
|
4476
|
+
c.fillText(isGrouping ? weights.join("+") : String(entry), sb.left - 10, gy);
|
|
4444
4477
|
});
|
|
4445
4478
|
const drawTicks = (ticks, y, color) => {
|
|
4446
4479
|
c.fillStyle = color;
|
|
@@ -4532,8 +4565,14 @@ var compositeRhythmFactory = {
|
|
|
4532
4565
|
if (p.rowGap != null && (typeof p.rowGap !== "number" || p.rowGap <= 0)) errs.push("composite-rhythm.rowGap must be a positive number");
|
|
4533
4566
|
if (p.tickH != null && (typeof p.tickH !== "number" || p.tickH <= 0)) errs.push("composite-rhythm.tickH must be a positive number");
|
|
4534
4567
|
if (p.mergeR != null && (typeof p.mergeR !== "number" || p.mergeR <= 0)) errs.push("composite-rhythm.mergeR must be a positive number");
|
|
4535
|
-
if (p.grids != null
|
|
4536
|
-
|
|
4568
|
+
if (p.grids != null) {
|
|
4569
|
+
const gridsValid = Array.isArray(p.grids) && p.grids.every((v) => {
|
|
4570
|
+
if (typeof v === "number") return v > 0;
|
|
4571
|
+
if (Array.isArray(v)) return v.length > 0 && v.every((w) => typeof w === "number" && w > 0);
|
|
4572
|
+
return false;
|
|
4573
|
+
});
|
|
4574
|
+
if (!gridsValid) errs.push("composite-rhythm.grids must be an array of positive numbers or arrays of positive-number weights");
|
|
4575
|
+
}
|
|
4537
4576
|
if (p.mergeWindowMs != null && (typeof p.mergeWindowMs !== "number" || p.mergeWindowMs < 0))
|
|
4538
4577
|
errs.push("composite-rhythm.mergeWindowMs must be a non-negative number");
|
|
4539
4578
|
if (p.mergeColor != null && typeof p.mergeColor !== "string") errs.push("composite-rhythm.mergeColor must be a string");
|
|
@@ -4546,6 +4585,131 @@ var compositeRhythmFactory = {
|
|
|
4546
4585
|
}
|
|
4547
4586
|
};
|
|
4548
4587
|
|
|
4588
|
+
// src/scene/layers/handIndicator.ts
|
|
4589
|
+
function activeOpacity(notes, tMs, fadeMs) {
|
|
4590
|
+
let lastEnd = -Infinity;
|
|
4591
|
+
for (const n of notes) {
|
|
4592
|
+
const end = n.onsetMs + n.durMs;
|
|
4593
|
+
if (tMs >= n.onsetMs && tMs < end) return 1;
|
|
4594
|
+
if (end <= tMs && end > lastEnd) lastEnd = end;
|
|
4595
|
+
}
|
|
4596
|
+
if (lastEnd === -Infinity) return 0;
|
|
4597
|
+
const dt = tMs - lastEnd;
|
|
4598
|
+
if (dt >= fadeMs) return 0;
|
|
4599
|
+
return 1 - dt / fadeMs;
|
|
4600
|
+
}
|
|
4601
|
+
function roundedPath2(c, x, y, w, h, r) {
|
|
4602
|
+
c.beginPath();
|
|
4603
|
+
const rr = c.roundRect;
|
|
4604
|
+
if (typeof rr === "function") {
|
|
4605
|
+
rr.call(c, x, y, w, h, r);
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
c.rect(x, y, w, h);
|
|
4609
|
+
}
|
|
4610
|
+
function drawHandGlyph(c, cx, cy, size, mirror, mode) {
|
|
4611
|
+
const paint = () => {
|
|
4612
|
+
if (mode === "fill") c.fill();
|
|
4613
|
+
else c.stroke();
|
|
4614
|
+
};
|
|
4615
|
+
const palmW = size * 0.66, palmH = size * 0.56;
|
|
4616
|
+
const px0 = cx - palmW / 2, py0 = cy - palmH * 0.1;
|
|
4617
|
+
roundedPath2(c, px0, py0, palmW, palmH, palmW * 0.32);
|
|
4618
|
+
paint();
|
|
4619
|
+
const fingerW = palmW * 0.17;
|
|
4620
|
+
const lens = [0.36, 0.46, 0.42, 0.32];
|
|
4621
|
+
for (let i = 0; i < 4; i++) {
|
|
4622
|
+
const fx = px0 + palmW * (0.12 + i * 0.25) - fingerW / 2;
|
|
4623
|
+
const fLen = size * lens[i];
|
|
4624
|
+
const fy = py0 - fLen + size * 0.06;
|
|
4625
|
+
roundedPath2(c, fx, fy, fingerW, fLen, fingerW / 2);
|
|
4626
|
+
paint();
|
|
4627
|
+
}
|
|
4628
|
+
const thumbW = palmW * 0.24, thumbLen = size * 0.3;
|
|
4629
|
+
const sign = mirror ? -1 : 1;
|
|
4630
|
+
c.save();
|
|
4631
|
+
c.translate(cx + sign * palmW * 0.42, py0 + palmH * 0.32);
|
|
4632
|
+
c.rotate(sign * 0.55);
|
|
4633
|
+
roundedPath2(c, -thumbW / 2, 0, thumbW, thumbLen, thumbW / 2);
|
|
4634
|
+
paint();
|
|
4635
|
+
c.restore();
|
|
4636
|
+
}
|
|
4637
|
+
function handIndicatorLayer() {
|
|
4638
|
+
let y = 0;
|
|
4639
|
+
let height = 60;
|
|
4640
|
+
let hands = { R: "#7b2436", L: "#c8a55b" };
|
|
4641
|
+
let fadeMs = 200;
|
|
4642
|
+
let size = 0;
|
|
4643
|
+
let leftXFrac = 0.26;
|
|
4644
|
+
let rightXFrac = 0.74;
|
|
4645
|
+
let restAlpha = 0.32;
|
|
4646
|
+
let lNotes = [];
|
|
4647
|
+
let rNotes = [];
|
|
4648
|
+
return {
|
|
4649
|
+
key: "hand-indicator",
|
|
4650
|
+
init(ctx, props) {
|
|
4651
|
+
y = props.y;
|
|
4652
|
+
height = props.height ?? 60;
|
|
4653
|
+
hands = props.handColors ?? { R: ctx.theme.accent, L: ctx.theme.gold };
|
|
4654
|
+
fadeMs = props.fadeMs ?? 200;
|
|
4655
|
+
size = props.size ?? height * 0.85;
|
|
4656
|
+
leftXFrac = props.leftXFrac ?? 0.26;
|
|
4657
|
+
rightXFrac = props.rightXFrac ?? 0.74;
|
|
4658
|
+
restAlpha = props.restAlpha ?? 0.32;
|
|
4659
|
+
const notes = ctx.score?.notes ?? [];
|
|
4660
|
+
lNotes = notes.filter((n) => n.hand === "L").map((n) => ({ onsetMs: n.onsetMs, durMs: n.durMs }));
|
|
4661
|
+
rNotes = notes.filter((n) => n.hand === "R").map((n) => ({ onsetMs: n.onsetMs, durMs: n.durMs }));
|
|
4662
|
+
},
|
|
4663
|
+
draw(ctx, tMs) {
|
|
4664
|
+
const c = ctx.ctx2d;
|
|
4665
|
+
const sb = ctx.safeBox;
|
|
4666
|
+
const cy = y + height / 2;
|
|
4667
|
+
const drawHand = (frac, color, notes, mirror) => {
|
|
4668
|
+
const cx = sb.left + sb.w * frac;
|
|
4669
|
+
const op = activeOpacity(notes, tMs, fadeMs);
|
|
4670
|
+
c.save();
|
|
4671
|
+
c.globalAlpha = restAlpha;
|
|
4672
|
+
c.strokeStyle = color;
|
|
4673
|
+
c.lineWidth = Math.max(1.5, size * 0.035);
|
|
4674
|
+
drawHandGlyph(c, cx, cy, size, mirror, "stroke");
|
|
4675
|
+
c.restore();
|
|
4676
|
+
if (op > 0.01) {
|
|
4677
|
+
c.save();
|
|
4678
|
+
c.globalAlpha = op;
|
|
4679
|
+
c.shadowColor = color;
|
|
4680
|
+
c.shadowBlur = 16 * op;
|
|
4681
|
+
c.fillStyle = color;
|
|
4682
|
+
drawHandGlyph(c, cx, cy, size, mirror, "fill");
|
|
4683
|
+
c.restore();
|
|
4684
|
+
}
|
|
4685
|
+
};
|
|
4686
|
+
drawHand(leftXFrac, hands.L, lNotes, true);
|
|
4687
|
+
drawHand(rightXFrac, hands.R, rNotes, false);
|
|
4688
|
+
},
|
|
4689
|
+
dispose() {
|
|
4690
|
+
lNotes = [];
|
|
4691
|
+
rNotes = [];
|
|
4692
|
+
}
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
var handIndicatorFactory = {
|
|
4696
|
+
key: "hand-indicator",
|
|
4697
|
+
create: handIndicatorLayer,
|
|
4698
|
+
validateProps(props) {
|
|
4699
|
+
if (props == null || typeof props !== "object") return ["hand-indicator: props must be an object"];
|
|
4700
|
+
const p = props;
|
|
4701
|
+
const errs = [];
|
|
4702
|
+
if (typeof p.y !== "number") errs.push("hand-indicator.y is required and must be a number");
|
|
4703
|
+
if (p.height != null && (typeof p.height !== "number" || p.height <= 0)) errs.push("hand-indicator.height must be a positive number");
|
|
4704
|
+
if (p.fadeMs != null && (typeof p.fadeMs !== "number" || p.fadeMs <= 0)) errs.push("hand-indicator.fadeMs must be a positive number");
|
|
4705
|
+
if (p.size != null && (typeof p.size !== "number" || p.size <= 0)) errs.push("hand-indicator.size must be a positive number");
|
|
4706
|
+
if (p.leftXFrac != null && typeof p.leftXFrac !== "number") errs.push("hand-indicator.leftXFrac must be a number");
|
|
4707
|
+
if (p.rightXFrac != null && typeof p.rightXFrac !== "number") errs.push("hand-indicator.rightXFrac must be a number");
|
|
4708
|
+
if (p.restAlpha != null && (typeof p.restAlpha !== "number" || p.restAlpha < 0 || p.restAlpha > 1)) errs.push("hand-indicator.restAlpha must be a number in [0,1]");
|
|
4709
|
+
return errs;
|
|
4710
|
+
}
|
|
4711
|
+
};
|
|
4712
|
+
|
|
4549
4713
|
// src/scene/registry.ts
|
|
4550
4714
|
var REGISTRY = /* @__PURE__ */ new Map();
|
|
4551
4715
|
function registerLayer(factory) {
|
|
@@ -4596,6 +4760,7 @@ registerLayer(statCounterFactory);
|
|
|
4596
4760
|
registerLayer(endCardFactory);
|
|
4597
4761
|
registerLayer(imageRevealFactory);
|
|
4598
4762
|
registerLayer(compositeRhythmFactory);
|
|
4763
|
+
registerLayer(handIndicatorFactory);
|
|
4599
4764
|
|
|
4600
4765
|
// src/scene/audioLayers.ts
|
|
4601
4766
|
function countInSchedule(opts) {
|
|
@@ -5413,6 +5578,7 @@ export {
|
|
|
5413
5578
|
getKeyboardLayout,
|
|
5414
5579
|
getLayerFactory,
|
|
5415
5580
|
getNotationEngraving,
|
|
5581
|
+
handIndicatorFactory,
|
|
5416
5582
|
harmonyDemoSpec,
|
|
5417
5583
|
highlightIntensity,
|
|
5418
5584
|
hookFactory,
|