@real-music-packages/web-core 0.9.7 → 0.11.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/audio.d.ts +9 -2
- package/dist/audio.js +1 -1
- package/dist/chunk-HXTRNE74.js +325 -0
- package/dist/chunk-HXTRNE74.js.map +1 -0
- package/dist/{chunk-BPL5LLQH.js → chunk-JVGAABTK.js} +4 -2
- package/dist/chunk-JVGAABTK.js.map +1 -0
- package/dist/promo.d.ts +17 -2
- package/dist/promo.js +66 -6
- package/dist/promo.js.map +1 -1
- package/dist/scene/index.d.ts +950 -0
- package/dist/scene/index.js +2008 -0
- package/dist/scene/index.js.map +1 -0
- package/dist/video.d.ts +9 -1
- package/dist/video.js +17 -244
- package/dist/video.js.map +1 -1
- package/package.json +10 -1
- package/dist/chunk-BPL5LLQH.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/scene/score.ts","../../src/scene/headless.ts","../../src/scene/math.ts","../../src/scene/caption.ts","../../src/scene/layers/demo.ts","../../src/scene/notationGeometry.ts","../../src/scene/engravingStore.ts","../../src/scene/layers/notation.ts","../../src/scene/layers/scrollCursor.ts","../../src/scene/keyboardGeometry.ts","../../src/scene/keyboardStore.ts","../../src/scene/layers/keyboard.ts","../../src/scene/layers/fallingNotes.ts","../../src/scene/layers/promoCards.ts","../../src/scene/layers/spectrum.ts","../../src/scene/layers/branding.ts","../../src/scene/registry.ts","../../src/scene/camera.ts","../../src/scene/runner.ts","../../src/scene/highlight.ts","../../src/scene/audioLayers.ts","../../src/scene/gate.ts","../../src/scene/notationCamera.ts","../../src/scene/demos/fallingKeyboardDemo.ts","../../src/scene/demos/promoCardsDemo.ts"],"sourcesContent":["// scoreFromMusicXML — derive a timed, typed note list from MusicXML, via OSMD's\n// parsed source model. One input (MusicXML) drives engraving + timing + pitch +\n// hands + lyrics, so engraved note, falling note, keyboard key, and sounding note\n// are the same object (the render-components design — see\n// docs/superpowers/specs/2026-06-14-render-components-spec.md).\n//\n// Approach (validated by the S0 spike — spikes/score-from-musicxml/FINDINGS.md):\n// - osmd.load(xml) builds osmd.sheet (the source model). We read ONLY the source\n// model, never the graphical layout, so it runs headless (Node + the fake-2D\n// shim in ./headless) and unchanged in a browser.\n// - sheet.MusicPartManager.getIterator() is OSMD's PLAYBACK iterator: available\n// without render(), walks the score in playback order, and expands\n// repeats/voltas. CurrentEnrolledTimestamp = linear playback clock (whole\n// notes); CurrentAudibleVoiceEntries() = the voice entries that ONSET now.\n// - Whole-note timestamps/durations -> ms via tempo. v1 uses a single constant\n// tempo (see the tempo-map stub below).\n//\n// OSMD-internals gotchas baked in (pin OSMD; the MIDI-oracle test guards drift):\n// - pitch.getHalfTone() returns MIDI - 12 (its octave 0 == scientific octave 1).\n// - pitch.FundamentalNote is a CHROMATIC enum (C=0,D=2,E=4,F=5,G=7,A=9,B=11),\n// not a 0-6 step index.\n// - alter is pitch.AccidentalHalfTones (NOT pitch.Accidental — a different enum).\n// - Ties: only the tie START note appears as audible; NoteTie.Duration is the\n// whole chain's sounding length. Continuations are skipped.\n\n/* OSMD's typings don't expose the source-model internals we read, so the OSMD\n instance and its model are treated as `any` at the boundary. Everything we\n PRODUCE is fully typed (Score / ScoreNote). */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n// ─── Contracts (from the render-components spec) ────────────────────────────\n\nexport interface ScoreNote {\n /** MIDI note number (middle C = 60). */\n pitchMidi: number;\n /** Diatonic letter name: C D E F G A B. */\n step: string;\n /** Chromatic alteration in semitones: -1 flat, +1 sharp, 0 natural, ±2 double. */\n alter: number;\n /** Scientific octave (middle C = C4). */\n octave: number;\n /** Onset on the linear playback clock, in ms (repeats expanded). */\n onsetMs: number;\n /** Sounding duration in ms (tie chains merged). */\n durMs: number;\n /** Staff index within the whole sheet (0-based). */\n staff: number;\n /** Voice id within the part. */\n voice: number;\n /** Performing hand: grand-staff top -> \"R\", bottom -> \"L\" (see hand-inference stub). */\n hand: 'L' | 'R';\n /** Lyric syllable attached to this note, if any. */\n lyric?: string;\n /** Fingering digit attached to this note, if any. */\n fingering?: number;\n}\n\n/**\n * Tempo map. v1 is a single constant tempo (one segment at t=0). The piecewise\n * shape is the documented seam for rubato / multiple `<sound tempo>` — see the\n * tempo-map stub note in scoreFromMusicXML.\n */\nexport interface TempoMap {\n /** Where the tempo came from: the XML's notated tempo, the fallback, or an override. */\n source: 'xml' | 'fallback' | 'override';\n /** Piecewise-constant segments, ordered by onset. v1 always has exactly one (at 0). */\n segments: Array<{ atMs: number; bpm: number }>;\n}\n\nexport interface Score {\n notes: ScoreNote[];\n tempoMap: TempoMap;\n /** End of the last sounding note, in ms. */\n durationMs: number;\n key?: string;\n timeSig?: string;\n title?: string;\n composer?: string;\n}\n\nexport interface ScoreFromMusicXMLOpts {\n /** bpm to use when the XML has no notated tempo (DefaultStartTempoInBpm === 0). Default 100. */\n tempoFallback?: number;\n /** Force this bpm regardless of the XML's notated tempo (per-recipe override). */\n tempoOverride?: number;\n /**\n * Provide the OSMD instance. Defaults to a literal `import('opensheetmusicdisplay')`\n * + a detached div (works in a browser, or in Node after `setupHeadlessDom()`).\n * Inject for tests or non-DOM environments.\n */\n osmdFactory?: () => any;\n}\n\n// A beat is a quarter note = 1/4 whole note. v1 assumes an x/4 meter for the\n// bpm->ms scaling (onsets in whole-note space are exact regardless; only the\n// beat unit affects scaling). Non-x/4 meters are a documented follow-up.\nconst BEATS_PER_WHOLE_NOTE = 4;\nconst MAX_ITERATOR_STEPS = 200_000;\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * Parse MusicXML into a timed, typed Score via OSMD's source model.\n *\n * @param xml MusicXML document (uncompressed string; unzip .mxl first).\n * @param opts tempoFallback / tempoOverride / osmdFactory.\n */\nexport async function scoreFromMusicXML(\n xml: string,\n opts: ScoreFromMusicXMLOpts = {},\n): Promise<Score> {\n const osmd = opts.osmdFactory ? opts.osmdFactory() : await defaultOsmd();\n\n // osmd.sheet (the source model) is populated even if the later graphical layout\n // throws (e.g. lyric text metrics under a bare DOM). So we tolerate load errors\n // and only fail if no source model came out.\n let loadError: unknown = null;\n try {\n await osmd.load(xml);\n } catch (e) {\n loadError = e;\n }\n const sheet = osmd.sheet;\n if (!sheet || !sheet.SourceMeasures?.length) {\n throw new Error(\n 'scoreFromMusicXML: OSMD produced no source model' +\n (loadError ? `: ${(loadError as Error).message}` : ''),\n );\n }\n\n const tempoMap = resolveTempo(sheet, opts);\n const wholeNoteToMs = makeWholeNoteToMs(tempoMap.segments[0].bpm);\n\n const it = sheet.MusicPartManager.getIterator();\n const notes: ScoreNote[] = [];\n\n let steps = 0;\n while (!it.EndReached && steps++ < MAX_ITERATOR_STEPS) {\n const enrolled = it.CurrentEnrolledTimestamp?.RealValue ?? 0;\n const onsetMs = wholeNoteToMs(enrolled);\n // CurrentAudibleVoiceEntries is a METHOD (not a property) — the entries\n // sounding/onsetting at this step.\n const voiceEntries: any[] = it.CurrentAudibleVoiceEntries?.() ?? [];\n\n for (const ve of voiceEntries) {\n const voiceId = ve.ParentVoice?.VoiceId ?? 1;\n const sse = ve.ParentSourceStaffEntry;\n const staffIdx = sse?.ParentStaff?.idInMusicSheet ?? 0;\n const instrument = sse?.ParentStaff?.ParentInstrument;\n const hand = inferHand(instrument, sse?.ParentStaff);\n\n for (const n of ve.Notes ?? []) {\n if (n.isRestFlag || n.IsRest) continue;\n const pitch = n.Pitch;\n if (!pitch) continue;\n\n // Ties: skip continuation notes (the iterator only reports the START as\n // audible anyway, but be defensive), and give the START the whole chain's\n // length via NoteTie.Duration.\n const tie = n.NoteTie;\n if (tie && tie.StartNote && tie.StartNote !== n) continue;\n const wholeNoteLen =\n tie && tie.StartNote === n && tie.Duration\n ? tie.Duration.RealValue\n : n.Length?.RealValue ?? 0;\n\n const halfTone = pitch.getHalfTone?.();\n const pitchMidi = halfTone == null ? NaN : halfTone + 12;\n\n const note: ScoreNote = {\n pitchMidi,\n step: fundamentalToStep(pitch.FundamentalNote),\n alter: pitch.AccidentalHalfTones ?? 0,\n octave: octaveFromMidi(pitchMidi),\n onsetMs: Math.round(onsetMs),\n durMs: Math.round(wholeNoteToMs(wholeNoteLen)),\n staff: staffIdx,\n voice: voiceId,\n hand,\n };\n const lyric = extractLyric(ve);\n if (lyric != null) note.lyric = lyric;\n const fingering = extractFingering(n);\n if (fingering != null) note.fingering = fingering;\n notes.push(note);\n }\n }\n it.moveToNext();\n }\n\n notes.sort((a, b) => a.onsetMs - b.onsetMs || a.pitchMidi - b.pitchMidi);\n const durationMs = notes.reduce((mx, n) => Math.max(mx, n.onsetMs + n.durMs), 0);\n\n return {\n notes,\n tempoMap,\n durationMs,\n key: readKey(sheet),\n timeSig: readTimeSig(sheet),\n title: sheet.TitleString || undefined,\n composer: sheet.ComposerString || undefined,\n };\n}\n\n// ─── Tempo ───────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the tempo for v1.\n *\n * Precedence: tempoOverride > XML notated tempo > tempoFallback (default 100).\n *\n * STUB — piecewise tempo map (rubato / accel. / multiple `<sound tempo>`): v1\n * emits a SINGLE constant segment at t=0. The {@link TempoMap.segments} array is\n * the documented seam: a follow-up reads per-measure tempo instructions from the\n * source model (sourceMeasure.TempoExpressions / TempoInBpm) and converts\n * enrolled-whole-note onsets to ms piecewise. Low priority — most promo clips use\n * one tempo, and the few rubato pieces are flagged by the MIDI-oracle test as\n * corpus issues, not extractor bugs (FINDINGS.md, risk #2).\n */\nfunction resolveTempo(sheet: any, opts: ScoreFromMusicXMLOpts): TempoMap {\n if (opts.tempoOverride && opts.tempoOverride > 0) {\n return { source: 'override', segments: [{ atMs: 0, bpm: opts.tempoOverride }] };\n }\n const xmlTempo = sheet.DefaultStartTempoInBpm;\n if (xmlTempo && xmlTempo > 0) {\n return { source: 'xml', segments: [{ atMs: 0, bpm: xmlTempo }] };\n }\n return { source: 'fallback', segments: [{ atMs: 0, bpm: opts.tempoFallback ?? 100 }] };\n}\n\n/** ms per whole note at a constant bpm (quarter-note beat; see BEATS_PER_WHOLE_NOTE). */\nfunction makeWholeNoteToMs(bpm: number): (wholeNotes: number) => number {\n const msPerBeat = 60000 / bpm;\n return (wholeNotes: number) => wholeNotes * BEATS_PER_WHOLE_NOTE * msPerBeat;\n}\n\n// ─── Hand inference ───────────────────────────────────────────────────────────\n\n/**\n * Infer the performing hand from the grand-staff layout.\n *\n * Grand staff (one instrument with `<staves>2`): top staff -> \"R\", bottom -> \"L\".\n * Verified correct on Für Elise / Twinkle (FINDINGS.md).\n *\n * STUB — SATB / two-`<score-part>` hand inference: when hands are SEPARATE parts\n * (RMT/SATB convention) there is no single grand staff, so we default to \"R\".\n * The documented follow-up (FINDINGS.md, risk #3) infers hand from clef\n * (treble -> R, bass -> L); OSMD doesn't expose clef cleanly post-parse, so that\n * needs either a raw-XML clef read or a per-recipe hint. Not blocking — RSR's\n * pieces use the grand-staff convention, which works today.\n */\nfunction inferHand(instrument: any, staff: any): 'L' | 'R' {\n const staves = instrument?.Staves;\n if (Array.isArray(staves) && staves.length >= 2 && staff) {\n const local = staves.indexOf(staff);\n if (local >= 0) return local === 0 ? 'R' : 'L';\n }\n return 'R';\n}\n\n// ─── Source-model readers ─────────────────────────────────────────────────────\n\n/** OSMD FundamentalNote is a CHROMATIC (pitch-class) enum, not a 0-6 step index. */\nfunction fundamentalToStep(f: number): string {\n return (\n ({ 0: 'C', 2: 'D', 4: 'E', 5: 'F', 7: 'G', 9: 'A', 11: 'B' } as Record<number, string>)[f] ??\n '?'\n );\n}\n\nfunction octaveFromMidi(midi: number): number {\n if (!Number.isFinite(midi)) return 0;\n return Math.floor(midi / 12) - 1;\n}\n\nfunction extractLyric(ve: any): string | undefined {\n const dict = ve.LyricsEntries;\n if (!dict || !dict.size) return undefined;\n const first = [...dict.values()][0];\n const text = first?.Text?.text ?? first?.Text;\n return typeof text === 'string' && text.length ? text : undefined;\n}\n\nfunction extractFingering(note: any): number | undefined {\n // OSMD attaches fingering as a Fingering technical instruction on the note.\n const raw = note?.Fingering?.value ?? note?.Fingering?.Value ?? note?.Fingering;\n const n = typeof raw === 'string' ? parseInt(raw, 10) : typeof raw === 'number' ? raw : NaN;\n return Number.isFinite(n) ? n : undefined;\n}\n\n// Diatonic key names by fifths on the circle, for major and minor modes.\nconst MAJOR_KEYS = ['Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F', 'C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#'];\nconst MINOR_KEYS = ['Ab', 'Eb', 'Bb', 'F', 'C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#'];\n\nfunction readKey(sheet: any): string | undefined {\n // Class names are minified in OSMD builds, so we identify the KeyInstruction by\n // its shape (it carries `keyType` = fifths and `mode`) rather than by name.\n for (const entry of sheet?.SourceMeasures?.[0]?.FirstInstructionsStaffEntries ?? []) {\n for (const ins of entry?.Instructions ?? []) {\n if (typeof ins?.keyType === 'number') {\n const fifths = ins.keyType; // -7..+7\n const idx = fifths + 7;\n if (idx < 0 || idx > 14) return undefined;\n // OSMD KeyEnum: major=0/1, minor=2 (others rare/modal — treat as major).\n const isMinor = ins.mode === 2;\n const name = (isMinor ? MINOR_KEYS : MAJOR_KEYS)[idx];\n return name ? `${name} ${isMinor ? 'minor' : 'major'}` : undefined;\n }\n }\n }\n return undefined;\n}\n\nfunction readTimeSig(sheet: any): string | undefined {\n const ts = sheet?.SourceMeasures?.[0]?.ActiveTimeSignature;\n if (ts && Number.isFinite(ts.Numerator) && Number.isFinite(ts.Denominator)) {\n return `${ts.Numerator}/${ts.Denominator}`;\n }\n return undefined;\n}\n\n// ─── Default OSMD acquisition ────────────────────────────────────────────────\n\n/**\n * Default OSMD instance: literal dynamic import + a detached div. Works in a\n * browser, or in Node after `setupHeadlessDom()` has installed DOM globals.\n *\n * The specifier is a LITERAL so consumers' bundlers (Vite/Rollup) can statically\n * resolve + code-split it — a variable specifier would reach the browser as a\n * bare import and fail at runtime (mirrors promo.ts:renderNotation).\n */\nasync function defaultOsmd(): Promise<any> {\n if (typeof document === 'undefined') {\n throw new Error(\n 'scoreFromMusicXML: no DOM. Call setupHeadlessDom() first (Node), ' +\n 'or pass opts.osmdFactory.',\n );\n }\n const mod: any = await import('opensheetmusicdisplay');\n // Browser bundlers expose the named export; Node's CJS interop puts it on default.\n const OpenSheetMusicDisplay = mod.OpenSheetMusicDisplay ?? mod.default?.OpenSheetMusicDisplay;\n return new OpenSheetMusicDisplay(document.createElement('div'), {\n backend: 'svg',\n autoResize: false,\n });\n}\n","// Headless DOM setup for running OSMD outside a browser (Node — CI, batch,\n// whozart's notation-less audio path).\n//\n// OSMD's load() runs graphical layout, which calls canvas 2D text metrics.\n// Bare jsdom returns null for getContext('2d'), so layout throws on scores\n// with lyrics. We install a minimal fake 2D context so layout completes; we\n// never read the pixels — only osmd.sheet (the source model) is consumed by\n// scoreFromMusicXML, so a no-op context is sufficient.\n//\n// The SAME extraction code (scoreFromMusicXML) runs unchanged in a real\n// browser: there the native 2D context handles lyric layout and this helper is\n// never called. This module is the only Node-specific seam.\n//\n// jsdom is a devDependency (used by tests + headless callers); it is imported\n// dynamically so bundling for the browser never pulls it in.\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nlet installed = false;\n\n/**\n * Install jsdom globals + a fake 2D canvas context so OSMD can `load()` a score\n * headlessly. Idempotent. Call once before constructing an OSMD instance in Node.\n *\n * No-ops if `window`/`document` already exist (e.g. a real browser, or a vitest\n * jsdom environment), so it is safe to call unconditionally.\n */\nexport async function setupHeadlessDom(): Promise<void> {\n if (installed) return;\n const g = globalThis as any;\n if (typeof g.document !== 'undefined' && typeof g.window !== 'undefined') {\n // A DOM is already present (browser or test env). Still ensure a usable 2D\n // context for OSMD's lyric layout if jsdom didn't provide one.\n ensureFakeContext(g.window);\n installed = true;\n return;\n }\n\n // Literal specifier so bundlers can tree-shake it out of browser builds.\n const { JSDOM } = await import('jsdom');\n const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {\n pretendToBeVisual: true,\n });\n const { window } = dom;\n\n ensureFakeContext(window);\n\n g.window = window;\n g.document = window.document;\n // Node 26: globalThis.navigator is read-only — tolerate the failure.\n try {\n g.navigator = window.navigator;\n } catch {\n /* read-only on some Node versions; OSMD doesn't require it */\n }\n g.HTMLElement = window.HTMLElement;\n g.Node = window.Node;\n g.DOMParser = window.DOMParser;\n g.XMLSerializer = window.XMLSerializer;\n g.requestAnimationFrame = (cb: (t: number) => void) => setTimeout(() => cb(Date.now()), 0);\n g.cancelAnimationFrame = () => {};\n\n installed = true;\n}\n\n/** Patch HTMLCanvasElement.getContext to return a no-op 2D context. */\nfunction ensureFakeContext(window: any): void {\n const proto = window.HTMLCanvasElement?.prototype;\n if (!proto) return;\n const fakeCtx = makeFakeContext();\n proto.getContext = function () {\n return fakeCtx;\n };\n}\n\n/** A minimal 2D context: text metrics return a rough width; everything else is a no-op. */\nfunction makeFakeContext(): any {\n return {\n font: '10px Arial',\n fillStyle: '#000',\n strokeStyle: '#000',\n lineWidth: 1,\n textAlign: 'left',\n textBaseline: 'alphabetic',\n globalAlpha: 1,\n measureText: (s: string) => ({\n width: (s ? s.length : 0) * 6,\n actualBoundingBoxAscent: 8,\n actualBoundingBoxDescent: 2,\n }),\n save() {},\n restore() {},\n beginPath() {},\n closePath() {},\n moveTo() {},\n lineTo() {},\n bezierCurveTo() {},\n quadraticCurveTo() {},\n arc() {},\n rect() {},\n fill() {},\n stroke() {},\n fillRect() {},\n clearRect() {},\n fillText() {},\n strokeText() {},\n translate() {},\n rotate() {},\n scale() {},\n setTransform() {},\n transform() {},\n drawImage() {},\n clip() {},\n createLinearGradient: () => ({ addColorStop() {} }),\n getImageData: () => ({ data: new Uint8ClampedArray(4) }),\n };\n}\n","// Pure math shared by the scene primitives (camera, highlight, captions,\n// timeline). Kept separate + dependency-free so it is trivially unit-testable.\n\nexport const clamp = (x: number, lo: number, hi: number): number =>\n x < lo ? lo : x > hi ? hi : x;\n\nexport const lerp = (a: number, b: number, t: number): number => a + (b - a) * t;\n\n/** Inverse-lerp: where does `x` sit in [a,b] as 0..1 (clamped, a===b -> 0). */\nexport const invLerp = (a: number, b: number, x: number): number =>\n a === b ? 0 : clamp((x - a) / (b - a), 0, 1);\n\nexport type Easing = (t: number) => number;\n\nexport const linear: Easing = (t) => t;\n/** Smoothstep ease-in-out. */\nexport const easeInOut: Easing = (t) => {\n const c = clamp(t, 0, 1);\n return c * c * (3 - 2 * c);\n};\nexport const easeIn: Easing = (t) => {\n const c = clamp(t, 0, 1);\n return c * c;\n};\nexport const easeOut: Easing = (t) => {\n const c = clamp(t, 0, 1);\n return 1 - (1 - c) * (1 - c);\n};\n","// Caption / subtitle engine (S1b) — timed burned-in text from a script.\n//\n// Silent-autoplay retention + accessibility: short-form video is watched muted,\n// so explainer clips need on-screen captions. A script is a list of cues with\n// in/out times; at any tMs the engine returns the active cue (one at a time) and\n// draws it inside the safe box near the bottom (above the phone action rail).\n//\n// The selection/visibility logic is pure (testable); drawCaption is the thin\n// renderer. respects safeBox so captions never sit under TikTok/Reels UI.\n\nimport type { SafeBox } from '../video';\nimport type { PromoTheme } from '../video';\nimport { clamp, invLerp } from './math';\n\nexport interface CaptionCue {\n text: string;\n /** Show from this time (ms). */\n inMs: number;\n /** Hide after this time (ms). */\n outMs: number;\n}\n\nexport type CaptionScript = CaptionCue[];\n\n/** The cue active at `tMs` (last one whose window contains t), or null. */\nexport function activeCue(script: CaptionScript, tMs: number): CaptionCue | null {\n let found: CaptionCue | null = null;\n for (const cue of script) {\n if (tMs >= cue.inMs && tMs < cue.outMs) found = cue;\n }\n return found;\n}\n\n/** Opacity 0..1 for a cue at `tMs` (short fade at both edges). */\nexport function cueOpacity(cue: CaptionCue, tMs: number, fadeMs = 150): number {\n if (tMs < cue.inMs || tMs >= cue.outMs) return 0;\n const inA = invLerp(cue.inMs, cue.inMs + fadeMs, tMs);\n const outA = 1 - invLerp(cue.outMs - fadeMs, cue.outMs, tMs);\n return clamp(Math.min(inA, outA), 0, 1);\n}\n\nexport interface CaptionStyle {\n /** Font size in px. Default 44. */\n size?: number;\n /** Pinned vertical position as a fraction of the safe-box height from its\n * top. Default 0.92 (near the bottom of the safe area). */\n yFrac?: number;\n /** Draw a translucent backing pill for legibility. Default true. */\n pill?: boolean;\n}\n\n/**\n * Greedy word-wrap into at most `maxLines` lines for the current ctx.font.\n * (Local copy so caption rendering doesn't depend on video.ts internals.)\n */\nfunction wrap(ctx: CanvasRenderingContext2D, text: string, maxW: number, maxLines: number): string[] {\n const words = text.split(/\\s+/).filter(Boolean);\n const lines: string[] = [];\n let cur = '';\n for (const w of words) {\n const next = cur ? `${cur} ${w}` : w;\n if (ctx.measureText(next).width > maxW && cur) {\n lines.push(cur);\n cur = w;\n if (lines.length === maxLines) break;\n } else {\n cur = next;\n }\n }\n if (cur && lines.length < maxLines) lines.push(cur);\n return lines;\n}\n\n/**\n * Draw the active caption (if any) inside the safe box. Pure function of tMs.\n * No-op when no cue is active. Drawn in VIEWPORT space (call after the camera\n * transform has been reset to identity) so captions stay pinned to the screen.\n */\nexport function drawCaption(\n ctx: CanvasRenderingContext2D,\n script: CaptionScript,\n tMs: number,\n safe: SafeBox,\n theme: PromoTheme,\n style: CaptionStyle = {},\n): void {\n const cue = activeCue(script, tMs);\n if (!cue) return;\n const alpha = cueOpacity(cue, tMs);\n if (alpha <= 0) return;\n\n const size = style.size ?? 44;\n const yFrac = style.yFrac ?? 0.92;\n const baseY = safe.top + safe.h * yFrac;\n\n ctx.save();\n ctx.globalAlpha = alpha;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.font = `bold ${size}px ${theme.fontBody}`;\n const lines = wrap(ctx, cue.text, safe.w * 0.92, 3);\n const lineH = size * 1.2;\n const blockH = lines.length * lineH;\n const top = baseY - blockH;\n\n if (style.pill !== false) {\n let maxW = 0;\n for (const ln of lines) maxW = Math.max(maxW, ctx.measureText(ln).width);\n const padX = size * 0.5;\n const padY = size * 0.3;\n ctx.fillStyle = 'rgba(0,0,0,0.55)';\n const pillW = Math.min(safe.w, maxW + padX * 2);\n ctx.fillRect(safe.cx - pillW / 2, top - padY, pillW, blockH + padY * 2);\n }\n\n ctx.fillStyle = '#ffffff';\n lines.forEach((ln, i) => {\n ctx.fillText(ln, safe.cx, top + lineH * (i + 0.5));\n });\n ctx.restore();\n}\n","// Trivial demo layers (S1b) — `background` and `caption`. They exist so the\n// runner is provably end-to-end (a SceneSpec renders frames through real layers)\n// WITHOUT pulling in the music layers, which are S2/S3. Each ships a\n// LayerFactory with a prop validator so the registry / gate can reject bad specs.\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport { drawCaption, type CaptionScript } from '../caption';\n\n// ─── background ────────────────────────────────────────────────────────────\n\nexport interface BackgroundProps {\n /** \"paper\" (theme.paper) | \"ink\" (theme.ink) | a literal CSS colour. */\n style?: string;\n}\n\nfunction backgroundLayer(): Layer<BackgroundProps> {\n let fill = '#000000';\n return {\n key: 'background',\n init(ctx, props) {\n const s = props.style ?? 'paper';\n fill = s === 'paper' ? ctx.theme.paper : s === 'ink' ? ctx.theme.ink : s;\n },\n draw(ctx) {\n const c = ctx.ctx2d;\n c.save();\n c.fillStyle = fill;\n c.fillRect(0, 0, ctx.W, ctx.H);\n c.restore();\n },\n };\n}\n\nexport const backgroundFactory: LayerFactory<BackgroundProps> = {\n key: 'background',\n create: backgroundLayer,\n validateProps(props) {\n const errs: string[] = [];\n if (props == null || typeof props !== 'object') return ['background: props must be an object'];\n const p = props as Record<string, unknown>;\n if (p.style != null && typeof p.style !== 'string') errs.push('background.style must be a string');\n return errs;\n },\n};\n\n// ─── caption ───────────────────────────────────────────────────────────────\n\nexport interface CaptionProps {\n /** Timed cues (in/out in ms relative to the clip start). */\n script: CaptionScript;\n size?: number;\n yFrac?: number;\n}\n\nfunction captionLayer(): Layer<CaptionProps> {\n let script: CaptionScript = [];\n let size: number | undefined;\n let yFrac: number | undefined;\n return {\n key: 'caption',\n init(_ctx: RenderCtx, props) {\n script = props.script ?? [];\n size = props.size;\n yFrac = props.yFrac;\n },\n draw(ctx, tMs) {\n drawCaption(ctx.ctx2d, script, tMs, ctx.safeBox, ctx.theme, { size, yFrac });\n },\n };\n}\n\nexport const captionFactory: LayerFactory<CaptionProps> = {\n key: 'caption',\n create: captionLayer,\n validateProps(props) {\n const errs: string[] = [];\n if (props == null || typeof props !== 'object') return ['caption: props must be an object'];\n const p = props as Record<string, unknown>;\n if (!Array.isArray(p.script)) {\n errs.push('caption.script must be an array of cues');\n } else {\n p.script.forEach((cue, i) => {\n const c = cue as Record<string, unknown>;\n if (typeof c?.text !== 'string') errs.push(`caption.script[${i}].text must be a string`);\n if (typeof c?.inMs !== 'number' || typeof c?.outMs !== 'number')\n errs.push(`caption.script[${i}] needs numeric inMs/outMs`);\n else if (c.outMs <= c.inMs) errs.push(`caption.script[${i}] outMs must be > inMs`);\n });\n }\n if (p.size != null && typeof p.size !== 'number') errs.push('caption.size must be a number');\n if (p.yFrac != null && typeof p.yFrac !== 'number') errs.push('caption.yFrac must be a number');\n return errs;\n },\n};\n","// Notation + scrolling-cursor geometry (S2) — the pure math extracted FAITHFULLY\n// from RSR's stave-web-sightread/src/routes/promo/+page.svelte. No canvas, no\n// behaviour change: these are the exact functions RSR uses to crop/scale the\n// engraving bitmap, scroll the 2-bar follow window, and place the playhead, lifted\n// verbatim so the notation + scroll-cursor layers can reuse them AND a test can\n// prove pixel-equivalence by comparing the geometry these produce against RSR's.\n//\n// Coordinate spaces (kept identical to RSR):\n// - canvas space: pixels of the rasterized OSMD bitmap (RenderedNotation.canvas).\n// - screen space: the W×H output frame. drawNotation maps a canvas `src` rect\n// onto a screen `dest` rect; cursor/highlight read the mapped boxes.\n//\n// IMPORTANT — easing: RSR uses a CUBIC ease-in-out for the zoom + scroll\n// (`cubicEaseInOut` below). web-core's math.easeInOut is SMOOTHSTEP — a different\n// curve. To stay pixel-faithful the extracted geometry uses RSR's cubic, NOT the\n// shared smoothstep. (Documented difference: none in output; the curve is ported.)\n\nimport type { Box, MeasureColumnBox, RenderedNotation, StaffMeasureBox } from '../promo';\nimport { safeBox } from '../video';\n\n// ─── Constants (verbatim from RSR +page.svelte:222-223) ──────────────────────\n\n/** measures (= rows) visible at once in the follow window. */\nexport const FOLLOW_BARS = 2;\n/** breathing room around the follow window. */\nexport const FOLLOW_PAD = 1.06;\n\n// ─── Easing (verbatim from RSR +page.svelte:175-179) ─────────────────────────\n\n/** Cubic ease-in-out, t in [0,1] (RSR port of stave-video core/camera.py). */\nexport function cubicEaseInOut(t: number): number {\n if (t < 0.5) return 4 * t * t * t;\n const f = 2 * t - 2;\n return 0.5 * f * f * f + 1;\n}\n\n// ─── Box helpers (verbatim from RSR) ─────────────────────────────────────────\n\n/** Linear interpolation of two boxes. (RSR lerpBox) */\nexport function lerpBox(a: Box, b: Box, e: number): Box {\n return {\n x: a.x + (b.x - a.x) * e,\n y: a.y + (b.y - a.y) * e,\n w: a.w + (b.w - a.w) * e,\n h: a.h + (b.h - a.h) * e,\n };\n}\n\n/** Smallest crop of `aspect` containing `box` padded by `pad`, clamped to the\n * canvas. (RSR cropAroundBox) */\nexport function cropAroundBox(box: Box, aspect: number, pad: number, cw: number, ch: number): Box {\n const bw = box.w * pad;\n const bh = box.h * pad;\n let w = Math.max(bw, bh * aspect);\n let h = w / aspect;\n w = Math.min(w, cw);\n h = Math.min(h, ch);\n const ccx = box.x + box.w / 2;\n const ccy = box.y + box.h / 2;\n let x = ccx - w / 2;\n let y = ccy - h / 2;\n x = Math.max(0, Math.min(cw - w, x));\n y = Math.max(0, Math.min(ch - h, y));\n return { x, y, w, h };\n}\n\n/** Union (canvas coords) of all staff measure boxes with index in [lo,hi).\n * (RSR measureSpanBox) */\nexport function measureSpanBox(rn: RenderedNotation, lo: number, hi: number): Box | null {\n const ms = (rn.measures ?? []).filter((m) => m.index >= lo && m.index < hi).map((m) => m.box);\n if (!ms.length) return null;\n const x0 = Math.min(...ms.map((b) => b.x));\n const y0 = Math.min(...ms.map((b) => b.y));\n const x1 = Math.max(...ms.map((b) => b.x + b.w));\n const y1 = Math.max(...ms.map((b) => b.y + b.h));\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };\n}\n\n/** Union of the index-0 measure boxes — the opening focal. (RSR firstMeasureBox) */\nexport function firstMeasureBox(rn: RenderedNotation): Box | null {\n const first = (rn.measures ?? []).filter((m) => m.index === 0).map((m) => m.box);\n if (!first.length) return rn.systems?.[0] ?? null;\n const x0 = Math.min(...first.map((b) => b.x));\n const y0 = Math.min(...first.map((b) => b.y));\n const x1 = Math.max(...first.map((b) => b.x + b.w));\n const y1 = Math.max(...first.map((b) => b.y + b.h));\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };\n}\n\n/** Number of distinct measures in the notation. (RSR measureCount) */\nexport function measureCount(rn: RenderedNotation): number {\n const idx = (rn.measures ?? []).map((m) => m.index);\n return idx.length ? Math.max(...idx) + 1 : 0;\n}\n\n/** The follow window (canvas coords) for a continuous measure-start position,\n * spanning FOLLOW_BARS and lerping between adjacent windows. (RSR followBoxAt) */\nexport function followBoxAt(rn: RenderedNotation, posMeasures: number): Box | null {\n const cur = Math.floor(posMeasures);\n const frac = posMeasures - cur;\n const a = measureSpanBox(rn, cur, cur + FOLLOW_BARS);\n const b = measureSpanBox(rn, cur + 1, cur + 1 + FOLLOW_BARS) ?? a;\n if (!a) return b;\n if (!b) return a;\n return lerpBox(a, b, frac);\n}\n\n/**\n * The continuous follow-window START measure for an audio-clock progress 0..1.\n * (RSR +page.svelte:738-744 — hold the current pair, ease-scroll over the last\n * third of each bar.) Returns the windowStart fed to followBoxAt.\n */\nexport function followWindowStart(nBars: number, camProgress01: number): number {\n const posBars = camProgress01 * nBars;\n const curBar = Math.floor(posBars);\n const frac = posBars - curBar;\n const scrollFrac = cubicEaseInOut(Math.min(1, Math.max(0, (frac - 0.66) / 0.34)));\n const maxStart = Math.max(0, nBars - FOLLOW_BARS);\n return Math.max(0, Math.min(maxStart, curBar + scrollFrac));\n}\n\n// ─── drawNotation geometry (verbatim from RSR +page.svelte:262-331) ──────────\n\n/** The dest rect a notation frame is drawn into, on screen. */\nexport interface NotationRect {\n dx: number;\n dy: number;\n dw: number;\n dh: number;\n}\n\n/** Geometry result of laying out the notation for one frame: which canvas `src`\n * rect is blitted to which screen `dest` rect, plus the mapped boxes. */\nexport interface NotationLayout {\n /** Canvas-space crop blitted this frame. */\n src: Box;\n /** Screen-space dest rect. */\n rect: NotationRect;\n /** Per-row system boxes mapped into screen coords (for the playhead fallback). */\n systems: Box[];\n /** Per-(measure,staff) boxes mapped into screen coords (for highlights/cursor). */\n measures: StaffMeasureBox[];\n}\n\nexport interface NotationLayoutOpts {\n /** 0 = zoomed on bar 1, 1 = full excerpt (opening camera). Default 1. */\n zoom01?: number;\n /** Follow window (canvas coords); when set, overrides zoom and scrolls. */\n focusBox?: Box | null;\n}\n\n/**\n * Compute the notation layout (src crop + dest rect + mapped boxes) for one\n * frame. This is RSR's `drawNotation` with the single `ctx.drawImage` call REMOVED\n * — pure geometry, so it is testable headless and shared by the notation +\n * scroll-cursor layers. The layer does the drawImage using `src` + `rect`.\n */\nexport function notationLayout(\n rn: RenderedNotation,\n W: number,\n H: number,\n boxTop: number,\n boxH: number,\n opts: NotationLayoutOpts = {},\n): NotationLayout {\n const { zoom01 = 1, focusBox = null } = opts;\n const sb = safeBox(W, H);\n const maxW = sb.centeredW;\n const c =\n rn.content && rn.content.w > 0 && rn.content.h > 0\n ? rn.content\n : { x: 0, y: 0, w: rn.canvas.width || 1400, h: rn.canvas.height || 300 };\n\n let src: Box;\n if (focusBox) {\n const px = (focusBox.w * (FOLLOW_PAD - 1)) / 2;\n const py = (focusBox.h * (FOLLOW_PAD - 1)) / 2;\n src = {\n x: Math.max(0, focusBox.x - px),\n y: Math.max(0, focusBox.y - py),\n w: focusBox.w + 2 * px,\n h: focusBox.h + 2 * py,\n };\n src.w = Math.min(src.w, rn.canvas.width - src.x);\n src.h = Math.min(src.h, rn.canvas.height - src.y);\n } else {\n src = c;\n const focal = firstMeasureBox(rn);\n if (zoom01 < 1 && focal) {\n const start = cropAroundBox(focal, c.w / c.h, 1.25, rn.canvas.width, rn.canvas.height);\n src = lerpBox(start, c, cubicEaseInOut(zoom01));\n }\n }\n\n const srcAspect = src.w / src.h;\n let dw = maxW;\n let dh = dw / srcAspect;\n if (dh > boxH) {\n dh = boxH;\n dw = dh * srcAspect;\n }\n const dx = (W - dw) / 2;\n const dy = boxTop + (boxH - dh) / 2;\n const fx = dw / src.w;\n const fy = dh / src.h;\n const map = (b: Box): Box => ({\n x: dx + (b.x - src.x) * fx,\n y: dy + (b.y - src.y) * fy,\n w: b.w * fx,\n h: b.h * fy,\n });\n const systems = (rn.systems ?? []).map(map);\n const measures = (rn.measures ?? []).map((m) => ({\n ...m,\n box: map(m.box),\n noteStartX: dx + (m.noteStartX - src.x) * fx,\n }));\n return { src, rect: { dx, dy, dw, dh }, systems, measures };\n}\n\n// ─── Playhead geometry (verbatim from RSR +page.svelte:446-518) ──────────────\n\n/** Per-measure grand-staff column boxes (both staves unioned) from a frame's\n * mapped measures, in render order. (RSR measureColumns) */\nexport function measureColumnsFromLayout(measures: StaffMeasureBox[]): MeasureColumnBox[] {\n const byIndex = new Map<number, MeasureColumnBox>();\n for (const m of measures) {\n const cur = byIndex.get(m.index);\n if (!cur) {\n byIndex.set(m.index, { ...m.box, noteStartX: m.noteStartX });\n } else {\n const x0 = Math.min(cur.x, m.box.x);\n const y0 = Math.min(cur.y, m.box.y);\n const x1 = Math.max(cur.x + cur.w, m.box.x + m.box.w);\n const y1 = Math.max(cur.y + cur.h, m.box.y + m.box.h);\n byIndex.set(m.index, {\n x: x0,\n y: y0,\n w: x1 - x0,\n h: y1 - y0,\n noteStartX: Math.min(cur.noteStartX, m.noteStartX),\n });\n }\n }\n return [...byIndex.keys()].sort((a, b) => a - b).map((k) => byIndex.get(k)!);\n}\n\n/** The playhead line + alpha for progress `t01`. null when fully faded. (RSR\n * drawPlayhead, with the actual stroke factored out into the layer.) */\nexport interface PlayheadLine {\n x: number;\n y0: number;\n y1: number;\n alpha: number;\n}\n\nexport function playheadLine(layout: NotationLayout, t01: number): PlayheadLine | null {\n const tt = Math.max(0, Math.min(1, t01));\n let alpha = 0.85;\n if (tt < 0.03) alpha *= tt / 0.03; // fade in\n if (tt > 0.94) alpha *= Math.max(0, (1 - tt) / 0.06); // fade out\n if (alpha <= 0.02) return null;\n\n const padV = 10;\n let x: number, y0: number, y1: number;\n const cols = measureColumnsFromLayout(layout.measures);\n if (cols.length) {\n const pos = tt * cols.length;\n const i = Math.min(cols.length - 1, Math.floor(pos));\n const m = cols[i];\n const startX = Math.min(m.noteStartX, m.x + m.w);\n x = startX + (pos - i) * (m.x + m.w - startX);\n y0 = m.y - padV;\n y1 = m.y + m.h + padV;\n } else if (layout.systems.length) {\n const pos = tt * layout.systems.length;\n const row = Math.min(layout.systems.length - 1, Math.floor(pos));\n const s = layout.systems[row];\n x = s.x + (pos - row) * s.w;\n y0 = s.y - padV;\n y1 = s.y + s.h + padV;\n } else {\n const r = layout.rect;\n x = r.dx + tt * r.dw;\n y0 = r.dy - padV;\n y1 = r.dy + r.dh + padV;\n }\n return { x, y0, y1, alpha };\n}\n","// Shared engraving handoff (S2) — lets the `notation` + `scroll-cursor` layers\n// cooperate without re-laying-out: notation publishes its rasterized engraving;\n// scroll-cursor publishes the per-frame follow LAYOUT (the src crop + dest rect),\n// which notation then blits. They agree by construction (one geometry source).\n//\n// Why a side store rather than Score.engraving: the spec's Score.engraving holds\n// browser-only canvas types, but Score is parsed headless in Node and is the\n// audio source for whozart (no notation). Keeping the engraving off Score keeps\n// score.ts DOM-free. We key the store by the runner's shared `audioClock` object\n// (one per render) via a WeakMap — per-render, GC-friendly, no globals.\n\nimport type { RenderCtx } from './layer';\nimport type { RenderedNotation } from '../promo';\nimport type { NotationLayout } from './notationGeometry';\n\nexport interface NotationEngraving {\n rendered: RenderedNotation;\n /** The base world layout (full content fitted to the band; zoom01=1). */\n base: NotationLayout;\n /** Notation band rect (screen px) the follow window fits into, per frame. */\n bandTop: number;\n bandHeight: number;\n /**\n * A pure follow-layout provider, published by scroll-cursor at init. notation\n * calls it each frame to blit the SAME followed-bars window the cursor sweeps —\n * so the two agree by construction AND z-order is correct (notation drawn first,\n * cursor line on top), regardless of which layer the runner draws first.\n * Absent when no scroll-cursor is in the scene (notation-only) -> base layout.\n */\n followLayoutAt?: (ctx: RenderCtx, tMs: number) => NotationLayout;\n}\n\nconst STORE = new WeakMap<object, NotationEngraving>();\n\nfunction keyFor(ctx: RenderCtx): object {\n return ctx.audioClock;\n}\n\nexport function setNotationEngraving(ctx: RenderCtx, eng: NotationEngraving): void {\n STORE.set(keyFor(ctx), eng);\n}\n\nexport function getNotationEngraving(ctx: RenderCtx): NotationEngraving | undefined {\n return STORE.get(keyFor(ctx));\n}\n\n/** scroll-cursor publishes its follow-layout provider; notation reads it. */\nexport function setFollowLayoutProvider(\n ctx: RenderCtx,\n fn: (ctx: RenderCtx, tMs: number) => NotationLayout,\n): void {\n const e = STORE.get(keyFor(ctx));\n if (e) e.followLayoutAt = fn;\n}\n","// `notation` layer (S2) — extracted FAITHFULLY from RSR's render code\n// (stave-web-sightread/src/routes/promo/+page.svelte drawNotation +\n// $lib/promo/notation.ts renderNotation).\n//\n// Responsibilities:\n// - init(): rasterize the engraving ONCE via web-core's renderNotation (OSMD).\n// The expensive OSMD relayout happens here, never per-frame (spec: \"render\n// once, rasterize, pan/scroll the bitmap\").\n// - lay out the bitmap into the base world rect once (RSR's drawNotation with\n// zoom01=1, focusBox=null) — the full content fitted to the notation band.\n// - draw(): blit the bitmap using the PER-FRAME follow layout published by the\n// scroll-cursor layer (RSR re-crops the src window each frame to keep the\n// followed 2 bars filling the band). When no scroll-cursor is present it falls\n// back to the base full-excerpt layout. This is RSR's drawNotation exactly,\n// with only the single drawImage living here.\n//\n// The engraving + base layout are published via the engraving store (set in init)\n// so the scroll-cursor layer reads the SAME geometry without re-laying-out. Both\n// layers therefore agree by construction (one geometry source).\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport type { RenderedNotation } from '../../promo';\nimport { safeBox } from '../../video';\nimport { notationLayout, type NotationLayout } from '../notationGeometry';\nimport { setNotationEngraving, getNotationEngraving } from '../engravingStore';\n\nexport interface NotationProps {\n /** Engraving system: \"grand\" (two staves) or \"single\". Informational for v1 —\n * the layout is driven by the rasterized bitmap's geometry either way. */\n system?: 'grand' | 'single';\n /** Extra scale applied to the band height the notation fits into. Default 1. */\n scale?: number;\n /**\n * A pre-rendered notation (test/headless injection). When omitted, init()\n * calls renderNotation(xml). One of `rendered` or `xml` is required.\n */\n rendered?: RenderedNotation;\n /** MusicXML to engrave (browser path). Ignored when `rendered` is given. */\n xml?: string;\n /** Bar range [from,to] forwarded to renderNotation (RSR drawFrom/drawUpTo). */\n bars?: [number, number];\n /** Top y of the notation band (screen px). Default safeBox.top. */\n bandTop?: number;\n /** Height of the notation band (screen px). Default safeBox.bottom - bandTop. */\n bandHeight?: number;\n}\n\nfunction notationLayer(): Layer<NotationProps> {\n let rn: RenderedNotation | null = null;\n let base: NotationLayout | null = null;\n\n let propScale = 1;\n let propBandTop: number | undefined;\n let propBandHeight: number | undefined;\n\n function bandTop(_ctx: RenderCtx, sb: ReturnType<typeof safeBox>): number {\n return propBandTop ?? sb.top;\n }\n function bandHeight(ctx: RenderCtx, sb: ReturnType<typeof safeBox>): number {\n const top = bandTop(ctx, sb);\n return (propBandHeight ?? sb.bottom - top) * propScale;\n }\n\n return {\n key: 'notation',\n async init(ctx, props) {\n propScale = props.scale ?? 1;\n propBandTop = props.bandTop;\n propBandHeight = props.bandHeight;\n\n if (props.rendered) {\n rn = props.rendered;\n } else if (props.xml) {\n // Browser path: rasterize the engraving once (OSMD). Literal import is\n // inside renderNotation so bundlers resolve it.\n const { renderNotation } = await import('../../promo');\n rn = await renderNotation(props.xml, { bars: props.bars, paper: ctx.theme.paper });\n } else {\n throw new Error('notation layer: provide `rendered` or `xml`');\n }\n\n // Publish the engraving + base layout + band so the scroll-cursor layer\n // reads the SAME geometry (no second relayout). ctx2d may be null in init.\n const sb = safeBox(ctx.W, ctx.H);\n const top = bandTop(ctx, sb);\n const height = bandHeight(ctx, sb);\n base = notationLayout(rn, ctx.W, ctx.H, top, height, {});\n setNotationEngraving(ctx, { rendered: rn, base, bandTop: top, bandHeight: height });\n },\n\n draw(ctx, tMs) {\n if (!rn || !base) return;\n // Blit the scroll-cursor's follow window when present (a scroll-cursor layer\n // published a pure follow-layout provider at init); else the base full-\n // excerpt layout (notation-only scene). One drawImage — the sole canvas op of\n // RSR's drawNotation. Calling the provider (rather than reading a value the\n // cursor's draw set) makes blit z-order independent of layer draw order.\n const eng = getNotationEngraving(ctx);\n const l = eng?.followLayoutAt ? eng.followLayoutAt(ctx, tMs) : base;\n const c = ctx.ctx2d;\n c.drawImage(\n rn.canvas,\n l.src.x, l.src.y, l.src.w, l.src.h,\n l.rect.dx, l.rect.dy, l.rect.dw, l.rect.dh,\n );\n },\n\n dispose() {\n rn = null;\n base = null;\n },\n };\n}\n\nexport const notationFactory: LayerFactory<NotationProps> = {\n key: 'notation',\n create: notationLayer,\n validateProps(props) {\n const errs: string[] = [];\n if (props == null || typeof props !== 'object') return ['notation: props must be an object'];\n const p = props as Record<string, unknown>;\n if (p.system != null && p.system !== 'grand' && p.system !== 'single')\n errs.push('notation.system must be \"grand\" | \"single\"');\n if (p.scale != null && (typeof p.scale !== 'number' || p.scale <= 0))\n errs.push('notation.scale must be a positive number');\n if (p.rendered == null && typeof p.xml !== 'string')\n errs.push('notation: provide `rendered` (RenderedNotation) or `xml` (string)');\n if (p.bars != null && (!Array.isArray(p.bars) || p.bars.length !== 2))\n errs.push('notation.bars must be [from,to]');\n return errs;\n },\n};\n","// `scroll-cursor` layer (S2) — the scrolling playhead + 2-bar follow window,\n// extracted FAITHFULLY from RSR's render code (stave-web-sightread/src/routes/\n// promo/+page.svelte: the showScene draw loop's camProgress/windowStart math,\n// followBoxAt, drawNotation focusBox, and drawPlayhead).\n//\n// Per frame it:\n// 1. reads the audio clock -> camProgress 0..1 across the music,\n// 2. computes the follow-window start (RSR's hold-then-ease-scroll) and the\n// follow box (followBoxAt), then the follow LAYOUT (notationLayout with that\n// focusBox) — the SAME math RSR's drawNotation runs,\n// 3. publishes that layout so the notation layer blits the followed bars,\n// 4. draws the playhead line on the mapped geometry (RSR drawPlayhead).\n//\n// Riding the camera primitive: the follow window is a world-space rect; the same\n// scroll/zoom RSR achieves by re-cropping `src` is expressible as a camera pose\n// (frameRect over the follow rect). `cameraForFollow()` (in ../notationCamera)\n// produces that pose, and scene-camera-equivalence.test.ts proves it reproduces\n// RSR's crop. We keep the faithful re-crop path for pixel-identity AND expose the\n// camera pose so downstream layers (falling-notes, labels) can ride it.\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport {\n followBoxAt,\n followWindowStart,\n measureCount,\n notationLayout,\n playheadLine,\n type NotationLayout,\n} from '../notationGeometry';\nimport { getNotationEngraving, setFollowLayoutProvider } from '../engravingStore';\n\nexport interface ScrollCursorProps {\n /** Bars visible in the follow window. Default 2 (RSR FOLLOW_BARS). Informational\n * for v1 — the geometry uses the module constant unless overridden here. */\n followBars?: number;\n /** Opening-zoom duration in ms before the music/cursor start (RSR INTRO_MS=900).\n * During this lead-in camProgress is held at 0. Default 900. */\n openingZoomMs?: number;\n /** Total music length in ms (RSR musicMs). Required to pace camProgress + the\n * cursor against the audio clock. */\n musicMs: number;\n /** Cursor stroke colour. Defaults to theme.accent. */\n color?: string;\n}\n\n/** Compute the follow layout (src crop + mapped boxes) for an audio progress. */\nfunction followLayoutFor(\n ctx: RenderCtx,\n camProgress01: number,\n): NotationLayout | null {\n const eng = getNotationEngraving(ctx);\n if (!eng) return null;\n const nBars = measureCount(eng.rendered);\n if (nBars <= 0) return eng.base;\n const windowStart = followWindowStart(nBars, camProgress01);\n const focusBox = followBoxAt(eng.rendered, windowStart);\n return notationLayout(eng.rendered, ctx.W, ctx.H, eng.bandTop, eng.bandHeight, { focusBox });\n}\n\nfunction scrollCursorLayer(): Layer<ScrollCursorProps> {\n let musicMs = 0;\n let openingZoomMs = 900;\n let color: string | undefined;\n\n /** Audio progress 0..1: 0 during the opening-zoom lead-in, then linear across\n * the music. Mirrors RSR's camProgress (read off the audio clock). */\n function camProgress(tMs: number): number {\n const phMs = tMs - openingZoomMs;\n if (phMs < 0 || musicMs <= 0) return 0;\n return Math.min(1, phMs / musicMs);\n }\n\n /** The follow layout for a frame time — the provider notation calls so it\n * blits the SAME window the cursor sweeps. Pure fn of (ctx, tMs). */\n function layoutAt(ctx: RenderCtx, tMs: number): NotationLayout {\n const eng = getNotationEngraving(ctx);\n return followLayoutFor(ctx, camProgress(tMs)) ?? eng!.base;\n }\n\n return {\n key: 'scroll-cursor',\n init(ctx, props) {\n musicMs = props.musicMs;\n openingZoomMs = props.openingZoomMs ?? 900;\n color = props.color;\n // Publish the provider so notation blits the followed bars (works whichever\n // layer the runner draws first; the cursor LINE below is the only thing that\n // must come after notation, which it does when notation precedes us).\n setFollowLayoutProvider(ctx, layoutAt);\n },\n draw(ctx, tMs) {\n const eng = getNotationEngraving(ctx);\n if (!eng) return; // notation layer absent — nothing to follow\n const p = camProgress(tMs);\n const layout = layoutAt(ctx, tMs);\n\n // Draw the playhead on the mapped geometry (RSR drawPlayhead). t01 is the\n // SAME progress that paces the scroll, so cursor + scroll stay locked.\n const line = playheadLine(layout, p);\n if (!line) return;\n const c = ctx.ctx2d;\n c.save();\n c.strokeStyle = color ?? ctx.theme.accent;\n c.globalAlpha = line.alpha;\n c.lineWidth = 4;\n c.beginPath();\n c.moveTo(line.x, line.y0);\n c.lineTo(line.x, line.y1);\n c.stroke();\n c.restore();\n },\n };\n}\n\nexport const scrollCursorFactory: LayerFactory<ScrollCursorProps> = {\n key: 'scroll-cursor',\n create: scrollCursorLayer,\n validateProps(props) {\n const errs: string[] = [];\n if (props == null || typeof props !== 'object') return ['scroll-cursor: props must be an object'];\n const p = props as Record<string, unknown>;\n if (typeof p.musicMs !== 'number' || !(p.musicMs > 0))\n errs.push('scroll-cursor.musicMs must be a positive number (total music length ms)');\n if (p.followBars != null && (typeof p.followBars !== 'number' || p.followBars < 1))\n errs.push('scroll-cursor.followBars must be a number >= 1');\n if (p.openingZoomMs != null && (typeof p.openingZoomMs !== 'number' || p.openingZoomMs < 0))\n errs.push('scroll-cursor.openingZoomMs must be a number >= 0');\n if (p.color != null && typeof p.color !== 'string') errs.push('scroll-cursor.color must be a string');\n return errs;\n },\n};\n","// Keyboard geometry (S3) — the pure, canvas-free layout shared by the `keyboard`\n// and `falling-notes` layers. Both compute (or read) the SAME KeyboardLayout, so\n// a falling note's column x is, by construction, the x of its key on the\n// keyboard (the headline \"two layers agree\" guarantee — proved in the invariant\n// tests).\n//\n// Coordinate space: world pixels (0,0 top-left, the runner's frame space). A\n// layout pins the keyboard to a strip [x, x+w] × [top, top+height]; key x's are\n// derived from an evenly-spaced WHITE-key grid with black keys overlaid at the\n// canonical between-the-whites offsets. Falling notes use `keyCenterX(midi)`.\n//\n// Pure (no canvas, no DOM) ⇒ exhaustively unit-testable and deterministic.\n\n/** Chromatic class is a black key (C#, D#, F#, G#, A#). */\nconst BLACK_PC = new Set([1, 3, 6, 8, 10]);\n\n/** Number of white keys at-or-below a midi note, counting from MIDI 0 (C-1). */\nfunction whiteIndexAtOrBelow(midi: number): number {\n // Whites per octave below the pitch class within the octave.\n const oct = Math.floor(midi / 12);\n const pc = midi - oct * 12;\n // White count for pitch classes 0..11 (inclusive of pc if white, else of the\n // white key immediately below pc).\n const WHITES_BELOW = [0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6]; // C..B\n return oct * 7 + WHITES_BELOW[pc];\n}\n\nexport function isBlackKey(midi: number): boolean {\n return BLACK_PC.has(((midi % 12) + 12) % 12);\n}\n\n/** A resolved keyboard layout: which keys, where, pinned to a world strip. */\nexport interface KeyboardLayout {\n /** Lowest MIDI note drawn (inclusive). */\n lowMidi: number;\n /** Highest MIDI note drawn (inclusive). */\n highMidi: number;\n /** Strip left edge (world px). */\n x: number;\n /** Strip width (world px) — the white-key span. */\n w: number;\n /** Strip top edge (world px). */\n top: number;\n /** Strip height (world px) = white-key height. */\n height: number;\n /** White-key width (world px). */\n whiteW: number;\n /** First (leftmost) white key index, used to offset the white grid to x. */\n firstWhiteIndex: number;\n /** Count of white keys in [lowMidi, highMidi]. */\n whiteCount: number;\n}\n\n/** Standard 88-key piano: A0 (21) .. C8 (108). */\nexport const PIANO_LOW = 21;\nexport const PIANO_HIGH = 108;\n\n/** Snap a midi down to the nearest white key (so the range starts/ends on a white). */\nfunction snapDownToWhite(midi: number): number {\n let m = midi;\n while (isBlackKey(m)) m--;\n return m;\n}\nfunction snapUpToWhite(midi: number): number {\n let m = midi;\n while (isBlackKey(m)) m++;\n return m;\n}\n\nexport interface KeyboardLayoutOpts {\n /** \"88\" = full piano; \"auto\" = derive from a pitch span (padded to whites). */\n range?: '88' | 'auto';\n /** For \"auto\": the [lowMidi, highMidi] span to cover (snapped out to whites,\n * padded by one white each side so edge keys aren't flush). */\n span?: [number, number];\n /** Strip left/width/top/height in world px. */\n x: number;\n w: number;\n top: number;\n height: number;\n}\n\n/** Build a KeyboardLayout. Pure. */\nexport function keyboardLayout(opts: KeyboardLayoutOpts): KeyboardLayout {\n let low: number;\n let high: number;\n if (opts.range === 'auto' && opts.span) {\n low = snapDownToWhite(opts.span[0]);\n high = snapUpToWhite(opts.span[1]);\n // Pad one white key each side for breathing room (and so falling columns at\n // the extremes aren't half-clipped).\n low = snapDownToWhite(low - 1);\n high = snapUpToWhite(high + 1);\n } else {\n low = PIANO_LOW;\n high = PIANO_HIGH;\n }\n const firstWhiteIndex = whiteIndexAtOrBelow(low);\n const lastWhiteIndex = whiteIndexAtOrBelow(high);\n const whiteCount = lastWhiteIndex - firstWhiteIndex + 1;\n const whiteW = opts.w / whiteCount;\n return {\n lowMidi: low,\n highMidi: high,\n x: opts.x,\n w: opts.w,\n top: opts.top,\n height: opts.height,\n whiteW,\n firstWhiteIndex,\n whiteCount,\n };\n}\n\n/** The pitch span [min,max] of a Score's notes (for \"auto\" range). */\nexport function scorePitchSpan(midis: number[]): [number, number] {\n if (!midis.length) return [PIANO_LOW, PIANO_HIGH];\n return [Math.min(...midis), Math.max(...midis)];\n}\n\n/**\n * Resolve a KeyboardLayout from the shared keyboard placement props + the Score's\n * pitch span. The `keyboard` and `falling-notes` layers BOTH call this with the\n * same inputs in standalone mode, so they produce identical geometry even without\n * the store (the store just covers props that differ). Pure.\n *\n * Placement: the strip spans the safe box horizontally; its bottom sits at\n * `bottomY` (world px) with the given `height`.\n */\nexport function resolveKeyboardLayout(args: {\n range: '88' | 'auto';\n pitchMidis: number[];\n left: number;\n width: number;\n bottomY: number;\n height: number;\n}): KeyboardLayout {\n return keyboardLayout({\n range: args.range,\n span: args.range === 'auto' ? scorePitchSpan(args.pitchMidis) : undefined,\n x: args.left,\n w: args.width,\n top: args.bottomY - args.height,\n height: args.height,\n });\n}\n\n/**\n * Center x (world px) of a note's KEY on the keyboard. White keys sit on the\n * even grid; black keys are nudged to sit between their two neighbouring whites\n * (the canonical piano offset), so a falling block lands centered over the right\n * key. This is the SINGLE source both layers use — they agree by construction.\n */\nexport function keyCenterX(layout: KeyboardLayout, midi: number): number {\n const wIdx = whiteIndexAtOrBelow(midi) - layout.firstWhiteIndex;\n const whiteCenter = layout.x + (wIdx + 0.5) * layout.whiteW;\n if (!isBlackKey(midi)) return whiteCenter;\n // A black key sits on the boundary between its lower white and the next white,\n // i.e. half a white-width to the RIGHT of the lower white's center.\n return whiteCenter + layout.whiteW / 2;\n}\n\n/** Width (world px) a falling-note column should use for a pitch. Black keys are\n * drawn narrower (like the physical key); white keys ~ a white-key width. */\nexport function keyColumnWidth(layout: KeyboardLayout, midi: number): number {\n return isBlackKey(midi) ? layout.whiteW * 0.6 : layout.whiteW * 0.9;\n}\n\n/** The drawable rectangle for a key on the keyboard strip (white or black). */\nexport interface KeyRect {\n x: number;\n y: number;\n w: number;\n h: number;\n black: boolean;\n}\n\nexport function keyRect(layout: KeyboardLayout, midi: number): KeyRect {\n const cx = keyCenterX(layout, midi);\n if (isBlackKey(midi)) {\n const w = layout.whiteW * 0.6;\n return { x: cx - w / 2, y: layout.top, w, h: layout.height * 0.62, black: true };\n }\n const w = layout.whiteW;\n return { x: cx - w / 2, y: layout.top, w, h: layout.height, black: false };\n}\n\n/** Whether `midi` is within the layout's drawn range. */\nexport function inRange(layout: KeyboardLayout, midi: number): boolean {\n return midi >= layout.lowMidi && midi <= layout.highMidi;\n}\n\n/** All white-key midis in the layout (low..high), for drawing the bed. */\nexport function whiteKeys(layout: KeyboardLayout): number[] {\n const out: number[] = [];\n for (let m = layout.lowMidi; m <= layout.highMidi; m++) if (!isBlackKey(m)) out.push(m);\n return out;\n}\n\n/** All black-key midis in the layout (low..high), drawn on top of the whites. */\nexport function blackKeys(layout: KeyboardLayout): number[] {\n const out: number[] = [];\n for (let m = layout.lowMidi; m <= layout.highMidi; m++) if (isBlackKey(m)) out.push(m);\n return out;\n}\n\n// ─── Colour ────────────────────────────────────────────────────────────────\n\nexport type ColorBy = 'hand' | 'pitch-class';\n\n/** Right/left hand colours. R = theme accent (the lead colour); L = a cooler\n * contrast. Both passed in so a host theme can override. */\nexport interface HandColors {\n R: string;\n L: string;\n}\n\n/** 12 pitch-class hues (a perceptually-spread wheel, C..B). Stable + readable. */\nconst PC_COLORS = [\n '#e64545', '#e6803a', '#e6c23a', '#9bcf3a', '#3acf6e', '#3acfb0',\n '#3aa6e6', '#3a5fe6', '#7a3ae6', '#b03ae6', '#e63ab0', '#e63a6e',\n];\n\n/**\n * Colour for a note. `hand` reads the Score's accurate hand (NOT a pitch\n * threshold guess); `pitch-class` colours by chroma. Pure.\n */\nexport function noteColor(\n midi: number,\n hand: 'L' | 'R',\n colorBy: ColorBy,\n hands: HandColors,\n): string {\n if (colorBy === 'pitch-class') return PC_COLORS[(((midi % 12) + 12) % 12)];\n return hand === 'L' ? hands.L : hands.R;\n}\n","// Shared keyboard handoff (S3) — lets the `falling-notes` layer read the EXACT\n// KeyboardLayout the `keyboard` layer computed, so falling-note columns land on\n// the same key x's the keyboard draws (the \"two layers agree by construction\"\n// guarantee). Mirrors engravingStore: a per-render WeakMap keyed by the runner's\n// shared audioClock object (one per render) — GC-friendly, no globals.\n//\n// When no keyboard layer is in the scene, falling-notes computes its OWN layout\n// from its own range/span props (standalone mode); the store is just the channel\n// that keeps the paired case in lockstep.\n\nimport type { RenderCtx } from './layer';\nimport type { KeyboardLayout } from './keyboardGeometry';\n\nconst STORE = new WeakMap<object, KeyboardLayout>();\n\nfunction keyFor(ctx: RenderCtx): object {\n return ctx.audioClock;\n}\n\nexport function setKeyboardLayout(ctx: RenderCtx, layout: KeyboardLayout): void {\n STORE.set(keyFor(ctx), layout);\n}\n\nexport function getKeyboardLayout(ctx: RenderCtx): KeyboardLayout | undefined {\n return STORE.get(keyFor(ctx));\n}\n","// `keyboard` layer (S3) — a piano-keyboard strip pinned to the bottom of the\n// safe box. Lights each key while its note SOUNDS (onsetMs ≤ t < onsetMs+durMs),\n// two-coloured by `hand` straight off the Score (accurate, not pitch-threshold-\n// guessed). Pairs with `falling-notes`: it publishes its resolved KeyboardLayout\n// (keyboardStore) so falling-note columns land on the same key x's.\n//\n// Range: \"88\" (full A0..C8) or \"auto\" (derived from the Score's pitch span,\n// snapped + padded to whites). Pure draw fn of t — no wall clock.\n\nimport type { Layer, LayerFactory } from '../layer';\nimport {\n resolveKeyboardLayout,\n keyRect,\n keyCenterX,\n whiteKeys,\n blackKeys,\n noteColor,\n type KeyboardLayout,\n type ColorBy,\n type HandColors,\n} from '../keyboardGeometry';\nimport { setKeyboardLayout } from '../keyboardStore';\n\nexport interface KeyboardProps {\n /** \"88\" = full piano; \"auto\" = fit the Score's pitch span. Default \"auto\". */\n range?: '88' | 'auto';\n /** Colour active keys by performing hand (Score-accurate) or by pitch class.\n * Default \"hand\". */\n colorBy?: ColorBy;\n /** Strip height in world px. Default 220. */\n height?: number;\n /** Strip bottom edge in world px. Default safeBox.bottom. */\n bottomY?: number;\n /** Right/left hand colours. Defaults: R = theme.accent, L = theme.gold. */\n handColors?: HandColors;\n}\n\nfunction keyboardLayer(): Layer<KeyboardProps> {\n let layout: KeyboardLayout | null = null;\n let colorBy: ColorBy = 'hand';\n let hands: HandColors = { R: '#7b2436', L: '#c8a55b' };\n\n return {\n key: 'keyboard',\n init(ctx, props) {\n colorBy = props.colorBy ?? 'hand';\n hands = props.handColors ?? { R: ctx.theme.accent, L: ctx.theme.gold };\n const sb = ctx.safeBox;\n const height = props.height ?? 220;\n const bottomY = props.bottomY ?? sb.bottom;\n const midis = (ctx.score?.notes ?? []).map((n) => n.pitchMidi);\n layout = resolveKeyboardLayout({\n range: props.range ?? 'auto',\n pitchMidis: midis,\n left: sb.left,\n width: sb.w,\n bottomY,\n height,\n });\n // Publish so falling-notes uses the IDENTICAL layout (agree by construction).\n setKeyboardLayout(ctx, layout);\n },\n\n draw(ctx, tMs) {\n if (!layout) return;\n const c = ctx.ctx2d;\n const L = layout;\n\n // Which midis are sounding now, and their hand (for the lit colour).\n const lit = new Map<number, 'L' | 'R'>();\n for (const n of ctx.score?.notes ?? []) {\n if (tMs >= n.onsetMs && tMs < n.onsetMs + n.durMs) lit.set(n.pitchMidi, n.hand);\n }\n\n c.save();\n // White-key bed.\n for (const m of whiteKeys(L)) {\n const r = keyRect(L, m);\n const onHand = lit.get(m);\n c.fillStyle = onHand ? noteColor(m, onHand, colorBy, hands) : '#fbfbfb';\n c.fillRect(r.x, r.y, r.w, r.h);\n c.strokeStyle = '#b8b0a4';\n c.lineWidth = 1;\n c.strokeRect(r.x, r.y, r.w, r.h);\n }\n // Black keys on top.\n for (const m of blackKeys(L)) {\n const r = keyRect(L, m);\n const onHand = lit.get(m);\n c.fillStyle = onHand ? noteColor(m, onHand, colorBy, hands) : '#1a1614';\n c.fillRect(r.x, r.y, r.w, r.h);\n }\n c.restore();\n },\n\n dispose() {\n layout = null;\n },\n };\n}\n\nexport const keyboardFactory: LayerFactory<KeyboardProps> = {\n key: 'keyboard',\n create: keyboardLayer,\n validateProps(props) {\n const errs: string[] = [];\n if (props == null || typeof props !== 'object') return ['keyboard: props must be an object'];\n const p = props as Record<string, unknown>;\n if (p.range != null && p.range !== '88' && p.range !== 'auto')\n errs.push('keyboard.range must be \"88\" | \"auto\"');\n if (p.colorBy != null && p.colorBy !== 'hand' && p.colorBy !== 'pitch-class')\n errs.push('keyboard.colorBy must be \"hand\" | \"pitch-class\"');\n if (p.height != null && (typeof p.height !== 'number' || p.height <= 0))\n errs.push('keyboard.height must be a positive number');\n if (p.bottomY != null && typeof p.bottomY !== 'number')\n errs.push('keyboard.bottomY must be a number');\n return errs;\n },\n};\n\n// Re-export keyCenterX so falling-notes' agreement is via the SAME function.\nexport { keyCenterX };\n","// `falling-notes` layer (S3) — Synthesia-style note blocks falling toward the\n// keyboard's hit-line. The headline new capability.\n//\n// Sync (pure fn of t, audio-clock-driven):\n// - The hit-line is the keyboard's top edge (or the bottom of the fall region\n// when standalone). A note arrives at the hit-line EXACTLY at onsetMs.\n// - leadMs is how long a note is visible falling before it lands. The note\n// travels the full fall height over leadMs, so px/ms = fallHeight / leadMs.\n// `speed` (px/sec) is an alternative spelling; leadMs wins if both given.\n// - A note's block: bottom edge tracks the time-to-onset; its LENGTH ∝ durMs\n// (length px = durMs * px/ms). The block crosses the hit-line at onset and is\n// fully gone once its TOP passes the hit-line, i.e. after onsetMs + durMs.\n// - hitGlow: a brief flash at the hit-line while a note is sounding.\n//\n// Column x: read from the keyboard layer's published KeyboardLayout (keyboardStore)\n// when paired, else compute the IDENTICAL layout from this layer's own props. So a\n// falling block is always centered on its key — the two layers agree by\n// construction (proved in the invariant tests via keyCenterX equality).\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport {\n resolveKeyboardLayout,\n keyCenterX,\n keyColumnWidth,\n inRange,\n noteColor,\n type KeyboardLayout,\n type ColorBy,\n type HandColors,\n} from '../keyboardGeometry';\nimport { getKeyboardLayout } from '../keyboardStore';\n\nexport interface FallingNotesProps {\n /** Pair with a `keyboard` layer (read its layout + hit-line). When false the\n * layer is standalone and builds its own layout from the props below.\n * Default true. */\n keyboard?: boolean;\n /** Colour by performing hand (Score-accurate) or by pitch class. Default \"hand\". */\n colorBy?: ColorBy;\n /** Lead time in ms a note is visible before landing. Default 2000. */\n leadMs?: number;\n /** Fall speed in px/sec. Alternative to leadMs (ignored when leadMs is given). */\n speed?: number;\n /** Glow flash at the hit-line while a note sounds. Default true. */\n hitGlow?: boolean;\n /** Standalone-only: keyboard range when `keyboard:false`. Default \"auto\". */\n range?: '88' | 'auto';\n /** Standalone-only: hit-line (world y). Default safeBox.bottom - 220. */\n hitLineY?: number;\n /** Top of the fall region (world y) — notes appear here. Default safeBox.top. */\n topY?: number;\n /** Right/left hand colours. Defaults: R = theme.accent, L = theme.gold. */\n handColors?: HandColors;\n}\n\nconst DEFAULT_LEAD_MS = 2000;\n\nfunction fallingNotesLayer(): Layer<FallingNotesProps> {\n let paired = true;\n let colorBy: ColorBy = 'hand';\n let hands: HandColors = { R: '#7b2436', L: '#c8a55b' };\n let hitGlow = true;\n let ownLayout: KeyboardLayout | null = null;\n let topY = 0;\n let leadMsProp: number | undefined;\n let speedProp: number | undefined;\n\n /** The layout to use this frame: the keyboard's published one when paired,\n * else this layer's own. */\n function layoutFor(ctx: RenderCtx): KeyboardLayout | null {\n if (paired) return getKeyboardLayout(ctx) ?? ownLayout;\n return ownLayout;\n }\n\n /** Hit-line y = keyboard top (paired) or the resolved layout top. */\n function hitLineY(layout: KeyboardLayout): number {\n return layout.top;\n }\n\n /** px per ms given the fall height; leadMs wins, else speed (px/sec). */\n function pxPerMs(fallHeight: number): number {\n if (leadMsProp != null) return fallHeight / leadMsProp;\n if (speedProp != null) return speedProp / 1000;\n return fallHeight / DEFAULT_LEAD_MS;\n }\n\n return {\n key: 'falling-notes',\n init(ctx, props) {\n paired = props.keyboard ?? true;\n colorBy = props.colorBy ?? 'hand';\n hands = props.handColors ?? { R: ctx.theme.accent, L: ctx.theme.gold };\n hitGlow = props.hitGlow ?? true;\n leadMsProp = props.leadMs;\n speedProp = props.speed;\n const sb = ctx.safeBox;\n topY = props.topY ?? sb.top;\n\n if (!paired) {\n // Standalone: build the SAME layout the keyboard layer would, so a later\n // keyboard pairing would line up exactly.\n const hitLine = props.hitLineY ?? sb.bottom - 220;\n const midis = (ctx.score?.notes ?? []).map((n) => n.pitchMidi);\n ownLayout = resolveKeyboardLayout({\n range: props.range ?? 'auto',\n pitchMidis: midis,\n left: sb.left,\n width: sb.w,\n // top of the keyboard == hit-line; height below it is irrelevant here.\n bottomY: hitLine + 1,\n height: 1,\n });\n }\n },\n\n draw(ctx, tMs) {\n const layout = layoutFor(ctx);\n if (!layout) return;\n const c = ctx.ctx2d;\n const hit = hitLineY(layout);\n const fallH = Math.max(1, hit - topY);\n const v = pxPerMs(fallH); // px per ms\n\n for (const n of ctx.score?.notes ?? []) {\n if (!inRange(layout, n.pitchMidi)) continue;\n // Bottom edge: at t==onset it sits ON the hit-line; before, it's above.\n const bottomY = hit - (n.onsetMs - tMs) * v;\n const lenPx = Math.max(2, n.durMs * v);\n const topEdge = bottomY - lenPx;\n // Cull: not yet entered the top of the region, or fully past the hit-line.\n if (bottomY < topY) continue; // hasn't appeared (before onset - leadMs)\n if (topEdge > hit) continue; // fully below the hit-line (after onset+dur)\n\n const cx = keyCenterX(layout, n.pitchMidi);\n const w = keyColumnWidth(layout, n.pitchMidi);\n const drawTop = Math.max(topY, topEdge);\n const drawBottom = Math.min(hit, bottomY);\n const fill = noteColor(n.pitchMidi, n.hand, colorBy, hands);\n\n c.save();\n c.fillStyle = fill;\n c.globalAlpha = 0.92;\n c.fillRect(cx - w / 2, drawTop, w, Math.max(1, drawBottom - drawTop));\n c.restore();\n }\n\n // Hit-line glow: flash over keys whose note is sounding now.\n if (hitGlow) {\n for (const n of ctx.score?.notes ?? []) {\n if (!(tMs >= n.onsetMs && tMs < n.onsetMs + n.durMs)) continue;\n if (!inRange(layout, n.pitchMidi)) continue;\n const cx = keyCenterX(layout, n.pitchMidi);\n const w = keyColumnWidth(layout, n.pitchMidi);\n c.save();\n c.globalAlpha = 0.5;\n c.fillStyle = noteColor(n.pitchMidi, n.hand, colorBy, hands);\n c.fillRect(cx - w / 2, hit - 8, w, 8);\n c.restore();\n }\n }\n },\n\n dispose() {\n ownLayout = null;\n },\n };\n}\n\nexport const fallingNotesFactory: LayerFactory<FallingNotesProps> = {\n key: 'falling-notes',\n create: fallingNotesLayer,\n validateProps(props) {\n const errs: string[] = [];\n if (props == null || typeof props !== 'object') return ['falling-notes: props must be an object'];\n const p = props as Record<string, unknown>;\n if (p.keyboard != null && typeof p.keyboard !== 'boolean')\n errs.push('falling-notes.keyboard must be a boolean');\n if (p.colorBy != null && p.colorBy !== 'hand' && p.colorBy !== 'pitch-class')\n errs.push('falling-notes.colorBy must be \"hand\" | \"pitch-class\"');\n if (p.leadMs != null && (typeof p.leadMs !== 'number' || p.leadMs <= 0))\n errs.push('falling-notes.leadMs must be a positive number');\n if (p.speed != null && (typeof p.speed !== 'number' || p.speed <= 0))\n errs.push('falling-notes.speed must be a positive number');\n if (p.hitGlow != null && typeof p.hitGlow !== 'boolean')\n errs.push('falling-notes.hitGlow must be a boolean');\n if (p.range != null && p.range !== '88' && p.range !== 'auto')\n errs.push('falling-notes.range must be \"88\" | \"auto\"');\n return errs;\n },\n};\n","// `hook` / `reveal` / `cta` layers (S4) — thin Layer wrappers around the\n// EXISTING, battle-tested scene builders in src/video.ts (hookScene /\n// revealScene / ctaScene). We do NOT re-implement the drawing: each wrapper\n// builds the original Scene once in init() and, per frame, converts the runner's\n// absolute `tMs` into the Scene's local 0..1 `t01` and calls Scene.draw — so the\n// pixels are equivalent to the current builders BY CONSTRUCTION.\n//\n// These are screen-pinned cards (the runner draws them in identity-transform\n// space — see SCREEN_PINNED_KEYS), matching how RMT/whozart/RSR draw their\n// hook/reveal/cta over the whole frame today.\n//\n// Timing: a Scene maps t01∈[0,1] across its own duration. The runner only calls\n// draw within a segment window, but it does NOT pass the window bounds to draw.\n// So each wrapper takes `startMs` (segment start) + `durationMs` (segment length)\n// props; `t01 = clamp((tMs - startMs)/durationMs, 0, 1)`. A host SceneSpec\n// builder sets these to the segment's `at` (the S4 demo does this); defaults fall\n// back to the original builder's natural durationMs anchored at 0.\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport {\n hookScene,\n revealScene,\n ctaScene,\n loadPortrait,\n type Scene,\n type HookSceneOpts,\n type RevealSceneOpts,\n type CtaSceneOpts,\n} from '../../video';\n\n// ─── shared timing helper ────────────────────────────────────────────────────\n\n/** Map absolute tMs to the wrapped Scene's local t01∈[0,1]. */\nfunction localT01(tMs: number, startMs: number, durationMs: number): number {\n if (durationMs <= 0) return 0;\n const t = (tMs - startMs) / durationMs;\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n\n/** Common window props shared by every card wrapper. */\ninterface WindowProps {\n /** Absolute segment start (ms) — t01 is measured from here. Default 0. */\n startMs?: number;\n /** Segment length (ms) over which t01 sweeps 0..1. Default = scene's natural duration. */\n durationMs?: number;\n}\n\nfunction validateWindow(p: Record<string, unknown>, key: string): string[] {\n const errs: string[] = [];\n if (p.startMs != null && (typeof p.startMs !== 'number' || p.startMs < 0))\n errs.push(`${key}.startMs must be a number >= 0`);\n if (p.durationMs != null && (typeof p.durationMs !== 'number' || !(p.durationMs > 0)))\n errs.push(`${key}.durationMs must be a positive number`);\n return errs;\n}\n\n// ─── hook ────────────────────────────────────────────────────────────────────\n\nexport interface HookProps extends WindowProps {\n /** Hook lines (wraps HookSceneOpts.lines). */\n lines: string[];\n /** Optional brand override (wraps HookSceneOpts.brand). */\n brand?: string;\n}\n\nfunction hookLayer(): Layer<HookProps> {\n let scene: Scene | null = null;\n let startMs = 0;\n let durationMs = 0;\n return {\n key: 'hook',\n init(ctx: RenderCtx, props) {\n const opts: HookSceneOpts = { lines: props.lines, brand: props.brand };\n scene = hookScene(ctx.theme, opts);\n startMs = props.startMs ?? 0;\n durationMs = props.durationMs ?? scene.durationMs;\n },\n draw(ctx, tMs) {\n if (!scene) return;\n scene.draw(ctx.ctx2d, localT01(tMs, startMs, durationMs));\n },\n };\n}\n\nexport const hookFactory: LayerFactory<HookProps> = {\n key: 'hook',\n create: hookLayer,\n validateProps(props) {\n if (props == null || typeof props !== 'object') return ['hook: props must be an object'];\n const p = props as Record<string, unknown>;\n const errs: string[] = [];\n if (!Array.isArray(p.lines) || !p.lines.every((l) => typeof l === 'string'))\n errs.push('hook.lines must be an array of strings');\n if (p.brand != null && typeof p.brand !== 'string') errs.push('hook.brand must be a string');\n return [...errs, ...validateWindow(p, 'hook')];\n },\n};\n\n// ─── reveal ──────────────────────────────────────────────────────────────────\n\nexport interface RevealProps extends WindowProps {\n /** Title (wraps RevealSceneOpts.title). */\n title: string;\n /** Subtitle (wraps RevealSceneOpts.subtitle). */\n subtitle: string;\n /** Optional initials override (wraps RevealSceneOpts.initials). */\n initials?: string;\n /** Optional fun fact (wraps RevealSceneOpts.funFact). */\n funFact?: string;\n}\n\nfunction revealLayer(): Layer<RevealProps> {\n let scene: Scene | null = null;\n let startMs = 0;\n let durationMs = 0;\n return {\n key: 'reveal',\n init(ctx: RenderCtx, props) {\n const opts: RevealSceneOpts = {\n title: props.title,\n subtitle: props.subtitle,\n initials: props.initials,\n funFact: props.funFact,\n portrait: null, // the `portrait` layer carries the medallion image; reveal\n // here is the initials-fallback look. See portrait.ts.\n };\n scene = revealScene(ctx.theme, opts);\n startMs = props.startMs ?? 0;\n durationMs = props.durationMs ?? scene.durationMs;\n },\n draw(ctx, tMs) {\n if (!scene) return;\n scene.draw(ctx.ctx2d, localT01(tMs, startMs, durationMs));\n },\n };\n}\n\nexport const revealFactory: LayerFactory<RevealProps> = {\n key: 'reveal',\n create: revealLayer,\n validateProps(props) {\n if (props == null || typeof props !== 'object') return ['reveal: props must be an object'];\n const p = props as Record<string, unknown>;\n const errs: string[] = [];\n if (typeof p.title !== 'string') errs.push('reveal.title must be a string');\n if (typeof p.subtitle !== 'string') errs.push('reveal.subtitle must be a string');\n if (p.initials != null && typeof p.initials !== 'string') errs.push('reveal.initials must be a string');\n if (p.funFact != null && typeof p.funFact !== 'string') errs.push('reveal.funFact must be a string');\n return [...errs, ...validateWindow(p, 'reveal')];\n },\n};\n\n// ─── cta ─────────────────────────────────────────────────────────────────────\n\nexport interface CtaProps extends WindowProps {\n /** End-card lines (wraps CtaSceneOpts.lines): [headline, ...accent lines]. */\n lines: string[];\n}\n\nfunction ctaLayer(): Layer<CtaProps> {\n let scene: Scene | null = null;\n let startMs = 0;\n let durationMs = 0;\n return {\n key: 'cta',\n init(ctx: RenderCtx, props) {\n const opts: CtaSceneOpts = { lines: props.lines };\n scene = ctaScene(ctx.theme, opts);\n startMs = props.startMs ?? 0;\n durationMs = props.durationMs ?? scene.durationMs;\n },\n draw(ctx, tMs) {\n if (!scene) return;\n scene.draw(ctx.ctx2d, localT01(tMs, startMs, durationMs));\n },\n };\n}\n\nexport const ctaFactory: LayerFactory<CtaProps> = {\n key: 'cta',\n create: ctaLayer,\n validateProps(props) {\n if (props == null || typeof props !== 'object') return ['cta: props must be an object'];\n const p = props as Record<string, unknown>;\n const errs: string[] = [];\n if (!Array.isArray(p.lines) || !p.lines.every((l) => typeof l === 'string'))\n errs.push('cta.lines must be an array of strings');\n return [...errs, ...validateWindow(p, 'cta')];\n },\n};\n\n// ─── portrait ────────────────────────────────────────────────────────────────\n//\n// The composer-portrait medallion + fun-fact. This is the SAME visual as\n// revealScene with a decoded `portrait` image, so we wrap revealScene exactly —\n// but the portrait layer owns the ASYNC image load (loadPortrait in init), which\n// the plain `reveal` wrapper can't because props are sync. With no/failed image\n// it falls back to revealScene's initials badge (loadPortrait resolves null →\n// identical to `reveal`).\n\nexport interface PortraitProps extends WindowProps {\n /** Composer name → title + initials badge fallback (wraps RevealSceneOpts.title). */\n title: string;\n /** Subtitle line (e.g. dates / era). */\n subtitle: string;\n /** Portrait image URL (Wikimedia etc.); loaded crossOrigin in init. */\n url?: string | null;\n /** Optional initials override. */\n initials?: string;\n /** Optional fun fact (corpus fun_fact). */\n funFact?: string;\n}\n\nfunction portraitLayer(): Layer<PortraitProps> {\n let scene: Scene | null = null;\n let startMs = 0;\n let durationMs = 0;\n return {\n key: 'portrait',\n async init(ctx: RenderCtx, props) {\n // ASYNC asset load — the reason this is its own layer. Fail-soft to null\n // (revealScene then draws the initials badge).\n const portrait = await loadPortrait(props.url ?? null);\n const opts: RevealSceneOpts = {\n title: props.title,\n subtitle: props.subtitle,\n initials: props.initials,\n funFact: props.funFact,\n portrait,\n };\n scene = revealScene(ctx.theme, opts);\n startMs = props.startMs ?? 0;\n durationMs = props.durationMs ?? scene.durationMs;\n },\n draw(ctx, tMs) {\n if (!scene) return;\n scene.draw(ctx.ctx2d, localT01(tMs, startMs, durationMs));\n },\n };\n}\n\nexport const portraitFactory: LayerFactory<PortraitProps> = {\n key: 'portrait',\n create: portraitLayer,\n validateProps(props) {\n if (props == null || typeof props !== 'object') return ['portrait: props must be an object'];\n const p = props as Record<string, unknown>;\n const errs: string[] = [];\n if (typeof p.title !== 'string') errs.push('portrait.title must be a string');\n if (typeof p.subtitle !== 'string') errs.push('portrait.subtitle must be a string');\n if (p.url != null && typeof p.url !== 'string') errs.push('portrait.url must be a string or null');\n if (p.initials != null && typeof p.initials !== 'string') errs.push('portrait.initials must be a string');\n if (p.funFact != null && typeof p.funFact !== 'string') errs.push('portrait.funFact must be a string');\n return [...errs, ...validateWindow(p, 'portrait')];\n },\n};\n","// `spectrum` layer (S4) — the audio-reactive mirrored frequency-bar visualizer,\n// reimplemented from whozart's drawSpectrum (whozart/src/lib/promo.ts) as a\n// Layer. web-core CANNOT depend on whozart, so the analyser does not live here:\n// the layer reads its levels through a host-provided input on RenderCtx (a\n// `levels` provider) or a `levelsFn` prop. With NEITHER it uses the SAME synthetic\n// taper-sine fallback whozart uses when no signal is present — which is also the\n// deterministic, pure-fn-of-t path the invariant + golden tests exercise.\n//\n// ── Input contract (how the layer gets its levels) ──────────────────────────\n// The host attaches a normalized-magnitude provider to RenderCtx:\n//\n// ctx.spectrum = {\n// // 0..1 magnitudes per band, OR raw byte-FFT (0..255) the layer log-bins.\n// levels?(tMs, bands): Float32Array | number[] | null\n// byteFreq?(tMs): Uint8Array | null // e.g. AnalyserNode.getByteFrequencyData\n// }\n//\n// In whozart the host wires byteFreq → AnalyserNode (the live master bus). In a\n// deterministic capture/test, the host omits it and the layer animates the\n// fallback. A `levelsFn` PROP overrides ctx (handy for previews / fixtures).\n//\n// The bar look (mirrored about a center line, wine→gold per magnitude, edge taper\n// on the fallback) is faithful to whozart; the colour lerp is inlined (web-core\n// has no lerpHex) and matches whozart's rgb() output.\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport type { SafeBox } from '../../video';\n\nconst SPEC_BARS = 44;\n\n/** Host-provided level source attached to RenderCtx by the app/capture harness. */\nexport interface SpectrumInput {\n /** Normalized 0..1 magnitudes for `bands` bars at time tMs (preferred). */\n levels?(tMs: number, bands: number): Float32Array | number[] | null | undefined;\n /** Raw byte-FFT (0..255), log-binned by the layer (AnalyserNode shape). */\n byteFreq?(tMs: number): Uint8Array | null | undefined;\n}\n\n/** Augment RenderCtx with the optional spectrum input (declaration merging). */\ndeclare module '../layer' {\n interface RenderCtx {\n /** Optional audio-reactive level source for the spectrum layer (host-wired). */\n spectrum?: SpectrumInput;\n }\n}\n\nexport interface SpectrumProps {\n /** Number of bars. Default 44 (whozart SPEC_BARS). */\n bars?: number;\n /** Vertical center as a fraction of H. Default 0.46 (whozart). */\n centerFrac?: number;\n /** Max half-height as a fraction of H. Default 0.135 (whozart). */\n maxHeightFrac?: number;\n /** Bar fill at low magnitude (wine). Default theme.accent. */\n colorLow?: string;\n /** Bar fill at high magnitude (gold). Default theme.gold. */\n colorHigh?: string;\n /** Per-frame level provider (overrides ctx.spectrum). Pure fn of t for tests. */\n levelsFn?: (tMs: number, bands: number) => Float32Array | number[] | null;\n}\n\n// rgb-space lerp between two #rrggbb colours — matches whozart lerpHex output.\nfunction lerpHex(a: string, b: string, f: number): string {\n const pa = parseInt(a.slice(1), 16);\n const pb = parseInt(b.slice(1), 16);\n const r = Math.round(((pa >> 16) & 255) + (((pb >> 16) & 255) - ((pa >> 16) & 255)) * f);\n const g = Math.round(((pa >> 8) & 255) + (((pb >> 8) & 255) - ((pa >> 8) & 255)) * f);\n const bl = Math.round((pa & 255) + ((pb & 255) - (pa & 255)) * f);\n return `rgb(${r},${g},${bl})`;\n}\n\n/** Deterministic synthetic magnitudes (whozart's no-signal fallback). Pure fn of\n * (tSec, band) so tests + offline captures are reproducible. */\nfunction syntheticMagnitude(tSec: number, b: number, n: number): number {\n const phase = tSec * 5 + b * 0.45;\n let m = 0.22 + 0.16 * Math.sin(phase) + 0.10 * Math.sin(phase * 1.7 + 1.2);\n m *= 0.5 + 0.5 * Math.sin((b / (n - 1)) * Math.PI); // taper toward the edges\n return m;\n}\n\n/** Log-bin a byte-FFT into `n` normalized magnitudes (whozart's lo=2..hi=440 map). */\nfunction binByteFreq(freq: Uint8Array, n: number): number[] | null {\n let peak = 0;\n for (let i = 0; i < freq.length; i++) if (freq[i] > peak) peak = freq[i];\n if (peak <= 4) return null; // treat as silence → fall back\n const lo = 2, hi = 440;\n const out: number[] = [];\n for (let b = 0; b < n; b++) {\n const f0 = lo * Math.pow(hi / lo, b / n);\n const f1 = lo * Math.pow(hi / lo, (b + 1) / n);\n const i0 = Math.floor(f0);\n const i1 = Math.max(i0 + 1, Math.floor(f1));\n let sum = 0, c = 0;\n for (let i = i0; i < i1 && i < freq.length; i++) { sum += freq[i]; c++; }\n let m = c ? (sum / c) / 255 : 0;\n m = Math.pow(m, 0.78); // lift quieter bands (whozart)\n out.push(m);\n }\n return out;\n}\n\nfunction spectrumLayer(): Layer<SpectrumProps> {\n let n = SPEC_BARS;\n let centerFrac = 0.46;\n let maxHeightFrac = 0.135;\n let colorLow: string | undefined;\n let colorHigh: string | undefined;\n let levelsFn: SpectrumProps['levelsFn'];\n\n /** Resolve normalized 0..1 magnitudes for this frame, with fallback. */\n function magnitudesAt(ctx: RenderCtx, tMs: number): number[] {\n const fromProp = levelsFn?.(tMs, n);\n const src = fromProp ?? resolveFromCtx(ctx, tMs);\n if (src && src.length) {\n const out = new Array(n);\n for (let b = 0; b < n; b++) out[b] = clamp01(src[Math.min(src.length - 1, b)] ?? 0);\n return out;\n }\n // synthetic fallback\n const tSec = tMs / 1000;\n return Array.from({ length: n }, (_, b) => clamp01(syntheticMagnitude(tSec, b, n)));\n }\n\n function resolveFromCtx(ctx: RenderCtx, tMs: number): number[] | null {\n const sp = ctx.spectrum;\n if (!sp) return null;\n const lv = sp.levels?.(tMs, n);\n if (lv && lv.length) return Array.from(lv as ArrayLike<number>);\n const bf = sp.byteFreq?.(tMs);\n if (bf && bf.length) return binByteFreq(bf, n);\n return null;\n }\n\n return {\n key: 'spectrum',\n init(_ctx: RenderCtx, props) {\n n = props.bars ?? SPEC_BARS;\n centerFrac = props.centerFrac ?? 0.46;\n maxHeightFrac = props.maxHeightFrac ?? 0.135;\n colorLow = props.colorLow;\n colorHigh = props.colorHigh;\n levelsFn = props.levelsFn;\n },\n draw(ctx, tMs) {\n const c = ctx.ctx2d;\n const W = ctx.W, H = ctx.H;\n const sb: SafeBox = ctx.safeBox;\n const cy = H * centerFrac;\n const left = Math.max(W * 0.10, sb.left);\n const right = sb.right;\n const span = right - left;\n const slot = span / n;\n const gap = slot * 0.34;\n const barW = slot - gap;\n const maxH = H * maxHeightFrac;\n const lo = colorLow ?? ctx.theme.accent;\n const hi = colorHigh ?? ctx.theme.gold;\n\n c.save();\n // Faint center baseline (whozart) so a quiet/paused moment still reads.\n c.strokeStyle = lerpHex(ctx.theme.paper, ctx.theme.sepia, 0.28);\n c.lineWidth = 2;\n c.beginPath(); c.moveTo(left, cy); c.lineTo(right, cy); c.stroke();\n\n const mags = magnitudesAt(ctx, tMs);\n const useRound = typeof c.roundRect === 'function';\n for (let b = 0; b < n; b++) {\n const m = mags[b];\n const half = Math.max(barW * 0.5, m * maxH);\n const x0 = left + b * slot + gap / 2;\n c.fillStyle = lerpHex(lo, hi, m);\n if (useRound) {\n const r = Math.min(barW / 2, half);\n c.beginPath(); c.roundRect(x0, cy - half, barW, half * 2, r); c.fill();\n } else {\n c.fillRect(x0, cy - half, barW, half * 2);\n }\n }\n c.restore();\n },\n };\n}\n\nfunction clamp01(x: number): number { return x < 0 ? 0 : x > 1 ? 1 : x; }\n\nexport const spectrumFactory: LayerFactory<SpectrumProps> = {\n key: 'spectrum',\n create: spectrumLayer,\n validateProps(props) {\n if (props == null || typeof props !== 'object') return ['spectrum: props must be an object'];\n const p = props as Record<string, unknown>;\n const errs: string[] = [];\n if (p.bars != null && (typeof p.bars !== 'number' || p.bars < 2)) errs.push('spectrum.bars must be a number >= 2');\n if (p.centerFrac != null && (typeof p.centerFrac !== 'number' || p.centerFrac < 0 || p.centerFrac > 1))\n errs.push('spectrum.centerFrac must be in [0,1]');\n if (p.maxHeightFrac != null && (typeof p.maxHeightFrac !== 'number' || p.maxHeightFrac <= 0))\n errs.push('spectrum.maxHeightFrac must be a positive number');\n if (p.colorLow != null && typeof p.colorLow !== 'string') errs.push('spectrum.colorLow must be a string');\n if (p.colorHigh != null && typeof p.colorHigh !== 'string') errs.push('spectrum.colorHigh must be a string');\n if (p.levelsFn != null && typeof p.levelsFn !== 'function') errs.push('spectrum.levelsFn must be a function');\n return errs;\n },\n};\n","// `branding` + `safe-guides` layers (S4).\n//\n// `branding` — a small persistent brand/logo wordmark anchored inside the safe\n// box (bottom-centred by default), the chrome RMT/RSR/whozart burn into every\n// clip. Plus an OPTIONAL safe-zone debug overlay when `safezone` is set, which\n// delegates to src/video.ts drawSafeGuides (the SAME overlay apps trigger with\n// ?safe=1). Screen-pinned (drawn in identity space by the runner).\n//\n// `safe-guides` — a thin alias that ALWAYS draws drawSafeGuides (debug-only\n// layer you drop into a spec while laying it out). Both reuse the existing\n// helper; neither re-implements the inset math.\n\nimport type { Layer, LayerFactory, RenderCtx } from '../layer';\nimport { drawSafeGuides } from '../../video';\n\n// ─── branding ──────────────────────────────────────────────────────────────\n\nexport interface BrandingProps {\n /** Wordmark to draw. Default theme.brand. */\n logo?: string;\n /** Show the safe-zone debug overlay on top (apps' ?safe=1). Default false. */\n safezone?: boolean;\n /** Vertical anchor as a fraction of safeBox height from its TOP. Default 1\n * (bottom edge of the safe box). */\n yFrac?: number;\n /** Font size in px. Default 34. */\n size?: number;\n /** Text colour. Default theme.sepia. */\n color?: string;\n}\n\nfunction brandingLayer(): Layer<BrandingProps> {\n let logo: string | undefined;\n let safezone = false;\n let yFrac = 1;\n let size = 34;\n let color: string | undefined;\n return {\n key: 'branding',\n init(_ctx: RenderCtx, props) {\n logo = props.logo;\n safezone = props.safezone ?? false;\n yFrac = props.yFrac ?? 1;\n size = props.size ?? 34;\n color = props.color;\n },\n draw(ctx) {\n const c = ctx.ctx2d;\n const sb = ctx.safeBox;\n const text = logo ?? ctx.theme.brand;\n c.save();\n c.textAlign = 'center';\n c.font = `italic ${size}px ${ctx.theme.fontBody}`;\n c.fillStyle = color ?? ctx.theme.sepia;\n // Anchor inside the safe box; at yFrac=1 sit the baseline a little above the\n // bottom edge so descenders stay inside.\n const y = sb.top + sb.h * yFrac - (yFrac >= 1 ? size * 0.3 : 0);\n c.fillText(text, sb.cx, y);\n c.restore();\n if (safezone) drawSafeGuides(c);\n },\n };\n}\n\nexport const brandingFactory: LayerFactory<BrandingProps> = {\n key: 'branding',\n create: brandingLayer,\n validateProps(props) {\n if (props == null || typeof props !== 'object') return ['branding: props must be an object'];\n const p = props as Record<string, unknown>;\n const errs: string[] = [];\n if (p.logo != null && typeof p.logo !== 'string') errs.push('branding.logo must be a string');\n if (p.safezone != null && typeof p.safezone !== 'boolean') errs.push('branding.safezone must be a boolean');\n if (p.yFrac != null && typeof p.yFrac !== 'number') errs.push('branding.yFrac must be a number');\n if (p.size != null && (typeof p.size !== 'number' || p.size <= 0)) errs.push('branding.size must be a positive number');\n if (p.color != null && typeof p.color !== 'string') errs.push('branding.color must be a string');\n return errs;\n },\n};\n\n// ─── safe-guides ─────────────────────────────────────────────────────────────\n\nexport type SafeGuidesProps = Record<string, never>;\n\nfunction safeGuidesLayer(): Layer<SafeGuidesProps> {\n return {\n key: 'safe-guides',\n init() {},\n draw(ctx) {\n drawSafeGuides(ctx.ctx2d);\n },\n };\n}\n\nexport const safeGuidesFactory: LayerFactory<SafeGuidesProps> = {\n key: 'safe-guides',\n create: safeGuidesLayer,\n validateProps(props) {\n if (props != null && typeof props !== 'object') return ['safe-guides: props must be an object'];\n return [];\n },\n};\n","// Layer registry (S1b) — maps a SceneSpec layer key (\"background\", \"caption\",\n// …) to its LayerFactory. The runner instantiates layers from here, and the\n// pre-render gate uses it to reject a spec that references an unknown layer or\n// passes invalid props (\"unknown layer/prop → fail fast\", spec gate check #5).\n//\n// S2+ register their real music layers (notation, falling-notes, …) by calling\n// registerLayer — they DON'T edit the runner.\n\nimport type { LayerFactory } from './layer';\nimport { backgroundFactory, captionFactory } from './layers/demo';\nimport { notationFactory } from './layers/notation';\nimport { scrollCursorFactory } from './layers/scrollCursor';\nimport { keyboardFactory } from './layers/keyboard';\nimport { fallingNotesFactory } from './layers/fallingNotes';\nimport { hookFactory, revealFactory, ctaFactory, portraitFactory } from './layers/promoCards';\nimport { spectrumFactory } from './layers/spectrum';\nimport { brandingFactory, safeGuidesFactory } from './layers/branding';\n\nconst REGISTRY = new Map<string, LayerFactory<any>>(); // eslint-disable-line @typescript-eslint/no-explicit-any\n\n/** Register (or replace) a layer factory under its key. */\nexport function registerLayer(factory: LayerFactory<any>): void { // eslint-disable-line @typescript-eslint/no-explicit-any\n REGISTRY.set(factory.key, factory);\n}\n\n/** Look up a factory by key, or undefined if not registered. */\nexport function getLayerFactory(key: string): LayerFactory<any> | undefined { // eslint-disable-line @typescript-eslint/no-explicit-any\n return REGISTRY.get(key);\n}\n\n/** All registered layer keys (for diagnostics / gate messages). */\nexport function registeredKeys(): string[] {\n return [...REGISTRY.keys()];\n}\n\n// Built-in demo layers, registered on import so the runner is end-to-end.\nregisterLayer(backgroundFactory);\nregisterLayer(captionFactory);\n\n// S2 — the extracted music layers (notation + scrolling cursor).\nregisterLayer(notationFactory);\nregisterLayer(scrollCursorFactory);\n\n// S3 — falling-notes (Synthesia) + keyboard.\nregisterLayer(keyboardFactory);\nregisterLayer(fallingNotesFactory);\n\n// S4 — promo cards (hook/reveal/cta/portrait), spectrum, branding/safe-guides.\nregisterLayer(hookFactory);\nregisterLayer(revealFactory);\nregisterLayer(ctaFactory);\nregisterLayer(portraitFactory);\nregisterLayer(spectrumFactory);\nregisterLayer(brandingFactory);\nregisterLayer(safeGuidesFactory);\n","// Camera / viewport director (S1b cross-cutting primitive).\n//\n// Generalizes RSR's followBox/crop/zoom: layers draw in WORLD coordinates\n// (the frame's natural pixel space); the camera maps a world rectangle onto the\n// W×H output viewport so the runner can pan to the active measure, zoom on a\n// chord, or Ken-Burns the whole frame. Nearly every later layer rides this.\n//\n// Pure math (no canvas) so it is exhaustively unit-testable; `applyToContext`\n// is the only canvas touchpoint and is a thin wrapper over setTransform/scale.\n\nimport { clamp, lerp, easeInOut, type Easing } from './math';\n\n/** A rectangle in WORLD coordinates (the layers' natural pixel space). */\nexport interface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** A camera pose: which world point sits at the viewport centre, and the zoom. */\nexport interface CameraState {\n /** World-space centre x. */\n cx: number;\n /** World-space centre y. */\n cy: number;\n /** Zoom factor (>1 = zoomed in). */\n zoom: number;\n}\n\n/** A 2D affine transform `[a,b,c,d,e,f]` (matches CanvasRenderingContext2D.setTransform). */\nexport type Affine = [number, number, number, number, number, number];\n\n/**\n * The world→viewport transform for a camera pose. We use a uniform scale = zoom\n * and translate so (cx,cy) world maps to (W/2,H/2) viewport.\n * world point (x,y) -> ( (x-cx)*zoom + W/2 , (y-cy)*zoom + H/2 ).\n */\nexport function cameraTransform(cam: CameraState, W: number, H: number): Affine {\n const s = cam.zoom;\n return [s, 0, 0, s, W / 2 - cam.cx * s, H / 2 - cam.cy * s];\n}\n\n/** Map a world point through a camera pose to viewport pixels. */\nexport function worldToViewport(\n cam: CameraState,\n W: number,\n H: number,\n x: number,\n y: number,\n): { x: number; y: number } {\n const [a, , , d, e, f] = cameraTransform(cam, W, H);\n return { x: a * x + e, y: d * y + f };\n}\n\n/**\n * The camera pose that frames `rect` to fill the W×H viewport (contain-fit:\n * whole rect visible, uniform zoom). `pad` (0..1) leaves margin around the rect.\n */\nexport function frameRect(rect: Rect, W: number, H: number, pad = 0): CameraState {\n const padded = (1 + Math.max(0, pad) * 2);\n const zoom = Math.min(W / (rect.w * padded), H / (rect.h * padded));\n return { cx: rect.x + rect.w / 2, cy: rect.y + rect.h / 2, zoom };\n}\n\n/** Interpolate between two camera poses (for pan/zoom moves). */\nexport function lerpCamera(a: CameraState, b: CameraState, t01: number, ease: Easing = easeInOut): CameraState {\n const k = ease(clamp(t01, 0, 1));\n return { cx: lerp(a.cx, b.cx, k), cy: lerp(a.cy, b.cy, k), zoom: lerp(a.zoom, b.zoom, k) };\n}\n\n/**\n * A Ken-Burns move: slow drift from `from` to `to` over a segment. `at` is the\n * segment-relative position 0..1. Pure — returns the pose for that t.\n */\nexport function kenBurns(from: CameraState, to: CameraState, at01: number): CameraState {\n return lerpCamera(from, to, at01, easeInOut);\n}\n\n/**\n * Apply a camera pose to a 2D context (sets the transform). Layers drawn after\n * this render in world coords and appear panned/zoomed. Pair with ctx.save()\n * /ctx.restore() in the runner so layers can't leak the transform.\n */\nexport function applyToContext(\n ctx: CanvasRenderingContext2D,\n cam: CameraState,\n W: number,\n H: number,\n): void {\n const m = cameraTransform(cam, W, H);\n ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);\n}\n\n/** The identity (no pan/zoom) camera: world == viewport. */\nexport function identityCamera(W: number, H: number): CameraState {\n return { cx: W / 2, cy: H / 2, zoom: 1 };\n}\n","// SceneSpec + runner (S1b).\n//\n// A SceneSpec is the declarative description a recipe produces: canvas size,\n// theme, how duration is decided, a timeline of segments (each a stack of\n// layers active over a time range), and audio settings. The runner:\n// 1. resolves segment times (incl. \"end\" / \"end-N\" anchored to the clip end),\n// 2. instantiates layers from the registry + init()s them,\n// 3. on each frame, draws every active segment's layers IN ORDER onto ONE\n// canvas, applying the camera transform, then resets transform so screen-\n// pinned chrome (captions/branding) draws in viewport space.\n//\n// Capture is delegated to src/video.ts recordScenes (canvas captureStream +\n// MediaRecorder + Tone audio) — we wrap the whole timeline as a single composite\n// Scene so we REUSE that machinery rather than reinventing it. A separate\n// deterministic `renderFrame` path (no MediaRecorder) backs the tests + golden\n// frames and runs anywhere a 2D context exists.\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { Layer, RenderCtx, AudioClock } from './layer';\nimport type { Score } from './score';\nimport type { PromoTheme } from '../video';\nimport { recordScenes, safeBox, type Scene, type RecordOpts } from '../video';\nimport { getLayerFactory, registeredKeys } from './registry';\nimport {\n identityCamera,\n applyToContext,\n type CameraState,\n} from './camera';\n\n// ─── SceneSpec ───────────────────────────────────────────────────────────────\n\n/** A timeline endpoint: a number (ms? no — seconds), \"end\", or \"end-N\" (N s before end). */\nexport type TimeAnchor = number | 'end' | `end-${number}`;\n\nexport interface SpecLayer {\n /** Registry key. */\n k: string;\n /** Props passed to the layer's init(). */\n p?: unknown;\n}\n\nexport interface TimelineSegment {\n /** [start, end] in SECONDS; end may be \"end\" / \"end-N\". */\n at: [number, TimeAnchor];\n layers: SpecLayer[];\n}\n\nexport interface SceneSpec {\n /** [width, height] px. */\n size: [number, number];\n /** Theme key (resolved to a PromoTheme by the host) OR an inline theme. */\n theme: string;\n /** \"audio\" = clip length follows the audio; \"fixed\" = use `durationSec`. */\n durationMode: 'audio' | 'fixed';\n /** Required when durationMode === \"fixed\". */\n durationSec?: number;\n timeline: TimelineSegment[];\n audio?: { voicing?: string; [k: string]: unknown };\n /** Capture frame rate. Default 30. */\n fps?: number;\n}\n\n// ─── Timeline resolution ──────────────────────────────────────────────────────\n\n/** Resolve a TimeAnchor to absolute seconds given the clip's total length. */\nexport function resolveAnchor(anchor: TimeAnchor, totalSec: number): number {\n if (typeof anchor === 'number') return anchor;\n if (anchor === 'end') return totalSec;\n const m = /^end-(\\d+(?:\\.\\d+)?)$/.exec(anchor);\n if (m) return totalSec - parseFloat(m[1]);\n throw new Error(`resolveAnchor: bad anchor \"${anchor}\"`);\n}\n\nexport interface ResolvedSegment {\n startMs: number;\n endMs: number;\n layers: SpecLayer[];\n}\n\n/** Resolve every segment's [start,end] to ms against the total clip length. */\nexport function resolveTimeline(spec: SceneSpec, totalSec: number): ResolvedSegment[] {\n return spec.timeline.map((seg) => {\n const startSec = resolveAnchor(seg.at[0], totalSec);\n const endSec = resolveAnchor(seg.at[1], totalSec);\n return { startMs: startSec * 1000, endMs: endSec * 1000, layers: seg.layers };\n });\n}\n\n/** The visual timeline length in ms = the latest resolved segment end. */\nexport function visualTimelineMs(resolved: ResolvedSegment[]): number {\n return resolved.reduce((mx, s) => Math.max(mx, s.endMs), 0);\n}\n\n// ─── Built runner ─────────────────────────────────────────────────────────────\n\n/** A layer instance bound to its segment window + resolved props. */\ninterface BoundLayer {\n layer: Layer<any>;\n startMs: number;\n endMs: number;\n /** Whether this layer renders pinned to the screen (caption/branding) — drawn\n * AFTER the camera transform is reset. Heuristic by key for v1. */\n screenPinned: boolean;\n}\n\nconst SCREEN_PINNED_KEYS = new Set([\n 'caption', 'branding', 'cta', 'hook', 'reveal', 'portrait', 'safe-guides',\n]);\n\nexport interface BuiltScene {\n W: number;\n H: number;\n fps: number;\n durationMs: number;\n /** Draw the whole composite at absolute time `tMs` onto `ctx2d`. Deterministic\n * (pure fn of tMs). Used by tests, golden frames, and the recordScenes wrapper. */\n renderFrame(ctx2d: CanvasRenderingContext2D, tMs: number): void;\n /** Free every layer's resources. */\n dispose(): void;\n /** The resolved timeline (for the gate / diagnostics). */\n resolved: ResolvedSegment[];\n}\n\nexport interface BuildSceneOpts {\n spec: SceneSpec;\n theme: PromoTheme;\n /** The parsed Score (timing source). Optional for non-musical specs. */\n score?: Score;\n /** Total clip length in seconds. With durationMode \"audio\" pass the audio\n * length; with \"fixed\" it defaults to spec.durationSec. */\n totalSec: number;\n /** Override the camera pose per absolute tMs (pan/zoom director). Identity by\n * default. */\n camera?: (tMs: number) => CameraState;\n}\n\n/**\n * Instantiate + init every layer in the spec and return a deterministic renderer.\n * Throws if a referenced layer key isn't registered or its props don't validate\n * (so build failures surface before capture — the gate also re-checks).\n */\nexport async function buildScene(opts: BuildSceneOpts): Promise<BuiltScene> {\n const { spec, theme, score } = opts;\n const [W, H] = spec.size;\n const fps = spec.fps ?? 30;\n const resolved = resolveTimeline(spec, opts.totalSec);\n const safe = safeBox(W, H);\n const camera = opts.camera ?? (() => identityCamera(W, H));\n\n const clock: AudioClock = { nowMs: () => 0 };\n const baseCtx: Omit<RenderCtx, 'ctx2d'> = {\n W, H, score, audioClock: clock, theme, safeBox: safe, fps,\n };\n\n const bound: BoundLayer[] = [];\n for (const seg of resolved) {\n for (const sl of seg.layers) {\n const factory = getLayerFactory(sl.k);\n if (!factory) {\n throw new Error(`buildScene: unknown layer \"${sl.k}\" (registered: ${registeredKeys().join(', ')})`);\n }\n const errs = factory.validateProps(sl.p ?? {});\n if (errs.length) {\n throw new Error(`buildScene: invalid props for \"${sl.k}\": ${errs.join('; ')}`);\n }\n const layer = factory.create();\n // init runs against a ctx that has no live ctx2d yet; layers that need to\n // rasterize use init's ctx only for sizing/theme (drawing happens in draw).\n await layer.init({ ...baseCtx, ctx2d: null as any }, sl.p ?? {});\n bound.push({\n layer,\n startMs: seg.startMs,\n endMs: seg.endMs,\n screenPinned: SCREEN_PINNED_KEYS.has(sl.k),\n });\n }\n }\n\n const durationMs = visualTimelineMs(resolved);\n\n function renderFrame(ctx2d: CanvasRenderingContext2D, tMs: number): void {\n clock.nowMs = () => tMs;\n const ctx: RenderCtx = { ...baseCtx, ctx2d };\n const cam = camera(tMs);\n\n // World-space pass (camera applied): draw non-pinned active layers in order.\n ctx2d.save();\n applyToContext(ctx2d, cam, W, H);\n for (const b of bound) {\n if (b.screenPinned) continue;\n if (tMs < b.startMs || tMs >= b.endMs) continue;\n b.layer.draw(ctx, tMs);\n }\n ctx2d.restore();\n\n // Screen-space pass (identity transform): pinned chrome on top.\n ctx2d.save();\n ctx2d.setTransform(1, 0, 0, 1, 0, 0);\n for (const b of bound) {\n if (!b.screenPinned) continue;\n if (tMs < b.startMs || tMs >= b.endMs) continue;\n b.layer.draw(ctx, tMs);\n }\n ctx2d.restore();\n }\n\n return {\n W, H, fps, durationMs, resolved, renderFrame,\n dispose() {\n for (const b of bound) b.layer.dispose?.();\n },\n };\n}\n\n// ─── Capture wrapper (reuses recordScenes) ────────────────────────────────────\n\nexport interface RecordSceneSpecOpts {\n built: BuiltScene;\n audioStream: MediaStream;\n background?: string;\n onProgress?: RecordOpts['onProgress'];\n /** Inject the recorder for tests. Defaults to recordScenes. */\n record?: (scenes: Scene[], o: RecordOpts) => Promise<Blob>;\n}\n\n/**\n * Capture a built scene to a Blob by wrapping the whole timeline as a SINGLE\n * composite Scene and handing it to recordScenes — so audio sync, captureStream,\n * and MediaRecorder all come from the existing, working path. recordScenes\n * clears to `background` each frame and gives us a 0..1 t; we expand it back to\n * absolute ms and call renderFrame.\n */\nexport async function recordSceneSpec(opts: RecordSceneSpecOpts): Promise<Blob> {\n const { built } = opts;\n const record = opts.record ?? recordScenes;\n const composite: Scene = {\n durationMs: built.durationMs,\n draw: (ctx, t01) => built.renderFrame(ctx, t01 * built.durationMs),\n };\n return record([composite], {\n audioStream: opts.audioStream,\n width: built.W,\n height: built.H,\n fps: built.fps,\n background: opts.background,\n onProgress: opts.onProgress,\n });\n}\n","// Highlight-region primitive (S1b) — one reusable helper to spotlight a region\n// for a time range. Used by harmony/theory/ear-training/\"play this part\" layers.\n//\n// A region is an x-range band (world coords) shown only while tMs is inside\n// [inMs, outMs], with a short fade so it doesn't pop. The intensity envelope is\n// pure math (testable without a canvas); the draw helper is a thin band/dim\n// renderer over it.\n\nimport { clamp, invLerp } from './math';\n\nexport interface HighlightRegion {\n /** World x of the band's left edge. */\n x: number;\n /** World y of the band's top edge. */\n y: number;\n /** Band width (world px). */\n w: number;\n /** Band height (world px). */\n h: number;\n /** Show from this time (ms). */\n inMs: number;\n /** Hide after this time (ms). */\n outMs: number;\n /** Fade in/out duration (ms). Default 120. */\n fadeMs?: number;\n /** Fill colour (defaults to a translucent accent at draw time). */\n color?: string;\n}\n\n/**\n * Highlight intensity 0..1 at time `tMs`: 0 outside the window, ramping to 1\n * across `fadeMs` at each edge, flat 1 in the middle. Pure.\n */\nexport function highlightIntensity(region: HighlightRegion, tMs: number): number {\n const fade = region.fadeMs ?? 120;\n if (tMs <= region.inMs - fade || tMs >= region.outMs + fade) return 0;\n const rampIn = invLerp(region.inMs - fade, region.inMs, tMs);\n const rampOut = 1 - invLerp(region.outMs, region.outMs + fade, tMs);\n return clamp(Math.min(rampIn, rampOut), 0, 1);\n}\n\n/**\n * Compute the world x-range covering a set of note onsets that fall inside a\n * time window, given a time→x mapping. Returns null if none qualify. Useful for\n * \"spotlight the bar that's sounding now\" without a layer re-deriving geometry.\n */\nexport function noteSetXRange(\n onsetsMs: number[],\n windowMs: [number, number],\n timeToX: (ms: number) => number,\n): { x: number; w: number } | null {\n let lo = Infinity;\n let hi = -Infinity;\n for (const on of onsetsMs) {\n if (on < windowMs[0] || on > windowMs[1]) continue;\n const x = timeToX(on);\n if (x < lo) lo = x;\n if (x > hi) hi = x;\n }\n if (lo === Infinity) return null;\n return { x: lo, w: Math.max(0, hi - lo) };\n}\n\n/**\n * Draw a highlight band for the current time. No-op when intensity is 0, so it\n * is safe to call every frame. Draws in WORLD coords (the camera transform, if\n * any, is already on the context).\n */\nexport function drawHighlight(\n ctx: CanvasRenderingContext2D,\n region: HighlightRegion,\n tMs: number,\n accent: string,\n): void {\n const a = highlightIntensity(region, tMs);\n if (a <= 0) return;\n ctx.save();\n ctx.globalAlpha = a * 0.35;\n ctx.fillStyle = region.color ?? accent;\n ctx.fillRect(region.x, region.y, region.w, region.h);\n ctx.restore();\n}\n","// Audio utility layers (S1b) — count-in, click/metronome, ambient drone bed,\n// and a VO-duck helper. These EXTEND the existing promo sampler/mastering bus\n// (src/promo.ts createPromoSampler) rather than introducing a new audio stack:\n// each one computes a deterministic SCHEDULE (pure → unit-testable) and then\n// applies it to a Tone-like target (a synth/sampler + the audio context clock).\n//\n// \"Audio layers\" are not visual Layers — they have no per-frame draw. They are\n// scheduled ONCE at render start against the audio clock, alongside the Score's\n// note schedule, and the visual layers read the same clock. Keeping the schedule\n// pure lets the gate / tests assert event times without real audio (the spec's\n// \"audio schedule: mock the sampler, assert trigger times == onsets ± tol\").\n\n/* Tone is typed loose (`any`) exactly like promo.ts / audioHelpers.ts. */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/** One scheduled audio event, in seconds RELATIVE to the schedule start. */\nexport interface AudioEvent {\n /** Offset from schedule start, seconds. */\n atSec: number;\n /** Pitch (note name or frequency) for tonal hits; omitted for noise clicks. */\n note?: string | number;\n /** Duration, seconds. Default short. */\n durSec?: number;\n /** Linear gain 0..1. Default 1. */\n gain?: number;\n /** Tag for tests/debugging: \"count\" | \"click\" | \"drone\". */\n kind: 'count' | 'click' | 'drone';\n}\n\n// ─── Count-in (3-2-1 + click) ────────────────────────────────────────────────\n\nexport interface CountInOpts {\n /** Beats to count (default 4 — a full bar of 4/4). */\n beats?: number;\n /** Tempo for the count, bpm. */\n bpm: number;\n /** Accent the downbeat (beat 1) with a higher pitch. Default true. */\n accentDownbeat?: boolean;\n}\n\n/**\n * The count-in schedule: one click per beat, starting at t=0, ending exactly on\n * the downbeat of bar 1 (i.e. the music starts at `beats * 60/bpm` seconds).\n * Pure.\n */\nexport function countInSchedule(opts: CountInOpts): AudioEvent[] {\n const beats = opts.beats ?? 4;\n const accent = opts.accentDownbeat ?? true;\n const beatSec = 60 / opts.bpm;\n const events: AudioEvent[] = [];\n for (let i = 0; i < beats; i++) {\n const isDownbeat = accent && i === 0;\n events.push({\n atSec: i * beatSec,\n note: isDownbeat ? 'C6' : 'C5',\n durSec: 0.05,\n gain: isDownbeat ? 1 : 0.7,\n kind: 'count',\n });\n }\n return events;\n}\n\n/** Seconds after t=0 that the music should start, for a given count-in. */\nexport function countInLeadSec(opts: CountInOpts): number {\n return (opts.beats ?? 4) * (60 / opts.bpm);\n}\n\n// ─── Click / metronome track ─────────────────────────────────────────────────\n\nexport interface ClickTrackOpts {\n bpm: number;\n /** Total duration to fill with clicks, seconds. */\n durationSec: number;\n /** Beats per bar, for downbeat accents. Default 4. */\n beatsPerBar?: number;\n /** Start offset, seconds. Default 0. */\n startSec?: number;\n}\n\n/** A click on every beat across `durationSec`, accenting each bar's downbeat. */\nexport function clickTrackSchedule(opts: ClickTrackOpts): AudioEvent[] {\n const beatSec = 60 / opts.bpm;\n const bpb = opts.beatsPerBar ?? 4;\n const start = opts.startSec ?? 0;\n const events: AudioEvent[] = [];\n const n = Math.floor(opts.durationSec / beatSec);\n for (let i = 0; i < n; i++) {\n const isDownbeat = i % bpb === 0;\n events.push({\n atSec: start + i * beatSec,\n note: isDownbeat ? 'C6' : 'C5',\n durSec: 0.03,\n gain: isDownbeat ? 0.6 : 0.4,\n kind: 'click',\n });\n }\n return events;\n}\n\n// ─── Ambient drone / pad bed ─────────────────────────────────────────────────\n\nexport interface DroneOpts {\n /** Root pitch of the pad (e.g. \"C2\"). */\n root: string;\n /** Pad duration, seconds. */\n durationSec: number;\n /** Add a perfect fifth above the root. Default true. */\n fifth?: boolean;\n /** Bed gain 0..1. Default 0.15 (sits well under the melody). */\n gain?: number;\n}\n\n/** A sustained root (+fifth) pad for the whole clip. */\nexport function droneSchedule(opts: DroneOpts): AudioEvent[] {\n const gain = opts.gain ?? 0.15;\n const events: AudioEvent[] = [\n { atSec: 0, note: opts.root, durSec: opts.durationSec, gain, kind: 'drone' },\n ];\n if (opts.fifth ?? true) {\n events.push({ atSec: 0, note: transposeFifth(opts.root), durSec: opts.durationSec, gain, kind: 'drone' });\n }\n return events;\n}\n\n/** Transpose a note name up a perfect fifth (7 semitones), keeping it simple\n * for the small set of roots a drone uses (naturals). Falls back to the input\n * if it can't parse. */\nfunction transposeFifth(note: string): string {\n const m = /^([A-G])(#|b)?(\\d)$/.exec(note);\n if (!m) return note;\n const order = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];\n const pcMap: Record<string, number> = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 };\n let pc = pcMap[m[1]] + (m[2] === '#' ? 1 : m[2] === 'b' ? -1 : 0);\n let oct = parseInt(m[3], 10);\n pc += 7;\n if (pc >= 12) {\n pc -= 12;\n oct += 1;\n }\n return `${order[pc]}${oct}`;\n}\n\n// ─── VO duck helper ──────────────────────────────────────────────────────────\n\nexport interface DuckWindow {\n /** Voice-over window start, seconds. */\n startSec: number;\n /** Voice-over window end, seconds. */\n endSec: number;\n}\n\n/**\n * Gain envelope for a music/bed bus that ducks under voice-over windows. Returns\n * the linear gain 0..1 at time `tSec`: `floor` inside a window (after a short\n * attack ramp), 1 outside (after a release ramp). Pure — feed it to a Tone\n * Gain's `.gain.value` per frame, or sample it to build a ramp automation.\n */\nexport function duckGainAt(\n windows: DuckWindow[],\n tSec: number,\n floor = 0.25,\n rampSec = 0.2,\n): number {\n for (const w of windows) {\n if (tSec >= w.startSec - rampSec && tSec <= w.endSec + rampSec) {\n // attack into the window, release out of it\n if (tSec < w.startSec) return lerpGain(1, floor, (tSec - (w.startSec - rampSec)) / rampSec);\n if (tSec > w.endSec) return lerpGain(floor, 1, (tSec - w.endSec) / rampSec);\n return floor;\n }\n }\n return 1;\n}\n\nfunction lerpGain(a: number, b: number, t: number): number {\n const k = t < 0 ? 0 : t > 1 ? 1 : t;\n return a + (b - a) * k;\n}\n\n// ─── Apply a schedule to a Tone target ───────────────────────────────────────\n\n/**\n * Trigger a computed schedule on a Tone instrument at `startTime` (audio-clock\n * seconds, e.g. `Tone.now()` or sampler.audioNow()). Thin glue over the existing\n * sampler/synth — no new mastering bus. Tonal events use triggerAttackRelease;\n * the instrument is whatever the caller wires into the promo mastering chain.\n *\n * Returns the number of events fired (handy for tests/gate sanity).\n */\nexport function applySchedule(\n instrument: { triggerAttackRelease: (note: any, dur: any, time?: any, velocity?: any) => void },\n schedule: AudioEvent[],\n startTime: number,\n): number {\n for (const ev of schedule) {\n if (ev.note == null) continue;\n instrument.triggerAttackRelease(\n ev.note,\n ev.durSec ?? 0.05,\n startTime + ev.atSec,\n ev.gain ?? 1,\n );\n }\n return schedule.length;\n}\n","// Pre-render invariant GATE (S1b) — the production \"don't ship a broken video\"\n// guard. Runs around a render and FAILS (returns errors / throws) so a broken\n// clip is never upserted into the catalog. Generalizes RSR's verify()/avdiag.\n//\n// Checks (spec: Testing & quality gates §0):\n// A. A/V duration match: |visualTimelineMs − audioMs| ≤ 60ms.\n// B. Placement sanity: required positions resolve (no NaN / out-of-bounds);\n// nothing required drawn outside safeBox when safezone is on.\n// C. Fonts loaded before the first captured frame.\n// D. Spec valid: every layer key registered + props validate.\n// E. Real output: capture produced ≥ expected frames + an audio track.\n//\n// In-code (pure, unit-tested here): A, B, D, and the *math* of E (frame-count /\n// track-count comparison). Environment-coupled probes are exposed as HOOKS the\n// smvp web-capture recipe wires up: font readiness (C, document.fonts) and\n// ffprobe (E, the actual stream/frame inspection of the produced file).\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { SceneSpec, ResolvedSegment } from './runner';\nimport { resolveTimeline, visualTimelineMs } from './runner';\nimport { getLayerFactory } from './registry';\nimport type { SafeBox } from '../video';\n\nexport interface GateError {\n check: 'av-duration' | 'placement' | 'fonts' | 'spec' | 'output';\n message: string;\n}\n\n/** A position a layer reports placing (for the placement-sanity check). */\nexport interface Placement {\n /** Label for diagnostics (e.g. \"note@1200ms\"). */\n label: string;\n x: number;\n y: number;\n /** Whether this placement is REQUIRED to be inside the safe box. */\n mustBeSafe?: boolean;\n}\n\nexport interface OutputProbe {\n /** Frames the capture actually produced. */\n frames: number;\n /** Audio tracks present (ffprobe a:0…). */\n audioTracks: number;\n /** Optional measured duration in ms (ffprobe), for a secondary A/V check. */\n durationMs?: number;\n}\n\nexport interface GateInput {\n spec: SceneSpec;\n /** Audio length in ms (the sounding length the capture targets). */\n audioMs: number;\n /** Total clip length in seconds used to resolve the timeline. */\n totalSec: number;\n /** Frame size for bounds + safe checks. */\n W: number;\n H: number;\n safeBox: SafeBox;\n /** Whether safezone enforcement is on for this clip. */\n safezone: boolean;\n /** Frame rate (for the expected-frame-count math). Default 30. */\n fps?: number;\n /** Required placements a layer reported (optional; pass [] if none). */\n placements?: Placement[];\n /**\n * Font-readiness hook. The smvp recipe passes `() => document.fonts.ready\n * .then(() => true)` (after loadBrandFonts). Default: assume loaded (true) so\n * headless unit runs don't fail on a missing document.\n */\n fontsReady?: () => boolean | Promise<boolean>;\n /**\n * Output probe hook. The smvp recipe runs ffprobe on the produced file and\n * passes the result. Omit to skip the output check (e.g. a pre-capture gate\n * pass that only validates the spec).\n */\n output?: OutputProbe;\n}\n\nconst AV_TOLERANCE_MS = 60;\n\n/**\n * Run the gate. Returns the list of failures ([] = passes). Pure except for the\n * optional async `fontsReady` hook. Call before capture (spec + A/V + placement)\n * and again after capture with `output` set (real-output check).\n */\nexport async function runGate(input: GateInput): Promise<GateError[]> {\n const errors: GateError[] = [];\n\n // D. Spec valid — fail fast on unknown layer / bad props.\n let resolved: ResolvedSegment[] = [];\n for (const seg of input.spec.timeline) {\n for (const sl of seg.layers) {\n const factory = getLayerFactory(sl.k);\n if (!factory) {\n errors.push({ check: 'spec', message: `unknown layer \"${sl.k}\"` });\n continue;\n }\n const errs = factory.validateProps(sl.p ?? {});\n for (const e of errs) errors.push({ check: 'spec', message: e });\n }\n }\n // Resolve the timeline; a malformed anchor surfaces as a spec error too.\n try {\n resolved = resolveTimeline(input.spec, input.totalSec);\n } catch (e) {\n errors.push({ check: 'spec', message: `timeline: ${(e as Error).message}` });\n }\n\n // A. A/V duration match.\n if (resolved.length) {\n const vis = visualTimelineMs(resolved);\n if (!Number.isFinite(vis) || !Number.isFinite(input.audioMs)) {\n errors.push({ check: 'av-duration', message: 'non-finite visual/audio duration' });\n } else if (Math.abs(vis - input.audioMs) > AV_TOLERANCE_MS) {\n errors.push({\n check: 'av-duration',\n message: `|visual ${Math.round(vis)}ms − audio ${Math.round(input.audioMs)}ms| = ${Math.round(\n Math.abs(vis - input.audioMs),\n )}ms > ${AV_TOLERANCE_MS}ms`,\n });\n }\n }\n\n // B. Placement sanity — no NaN, inside frame, and inside safeBox when required.\n for (const p of input.placements ?? []) {\n if (!Number.isFinite(p.x) || !Number.isFinite(p.y)) {\n errors.push({ check: 'placement', message: `${p.label}: NaN/∞ position` });\n continue;\n }\n if (p.x < 0 || p.x > input.W || p.y < 0 || p.y > input.H) {\n errors.push({ check: 'placement', message: `${p.label}: (${p.x},${p.y}) outside ${input.W}×${input.H}` });\n }\n if (input.safezone && p.mustBeSafe) {\n const b = input.safeBox;\n if (p.x < b.left || p.x > b.right || p.y < b.top || p.y > b.bottom) {\n errors.push({ check: 'placement', message: `${p.label}: outside safe box` });\n }\n }\n }\n\n // C. Fonts loaded (hook; default true so headless unit runs pass).\n const ready = input.fontsReady ? await input.fontsReady() : true;\n if (!ready) errors.push({ check: 'fonts', message: 'fonts not loaded before first frame' });\n\n // E. Real output (only when a probe was supplied — typically post-capture).\n if (input.output) {\n const expectedFrames = Math.floor((input.fps ?? 30) * (input.audioMs / 1000) * 0.5); // ≥ half is generous\n if (input.output.frames < expectedFrames) {\n errors.push({\n check: 'output',\n message: `only ${input.output.frames} frames (expected ≥ ${expectedFrames})`,\n });\n }\n if (input.output.audioTracks < 1) {\n errors.push({ check: 'output', message: 'no audio track in output' });\n }\n if (\n input.output.durationMs != null &&\n Math.abs(input.output.durationMs - input.audioMs) > AV_TOLERANCE_MS\n ) {\n errors.push({\n check: 'output',\n message: `output duration ${Math.round(input.output.durationMs)}ms vs audio ${Math.round(\n input.audioMs,\n )}ms exceeds ${AV_TOLERANCE_MS}ms`,\n });\n }\n }\n\n return errors;\n}\n\n/** Convenience: run the gate and throw a single aggregated error on failure. */\nexport async function assertGate(input: GateInput): Promise<void> {\n const errors = await runGate(input);\n if (errors.length) {\n throw new Error(\n `pre-render gate failed (${errors.length}):\\n` +\n errors.map((e) => ` [${e.check}] ${e.message}`).join('\\n'),\n );\n }\n}\n","// Notation camera bridge (S2) — expresses RSR's followBox/crop/scroll as the\n// shared camera primitive (../camera frameRect/lerpCamera), so the scroll-cursor\n// follow window \"rides the camera\" and downstream layers (falling-notes, labels)\n// can pan/zoom off the SAME pose instead of re-deriving RSR's bespoke crop math.\n//\n// Equivalence (proved in tests/scene-camera-equivalence.test.ts): RSR scrolls by\n// re-cropping a canvas `src` window and fitting it to the band. The identical\n// visual move is a camera that frames the follow window (in the base world layout)\n// to fill the band. Given the base layout L (full content -> band) and a per-frame\n// follow box F (canvas coords), the follow window in WORLD/screen coords is the\n// rect L maps F to. frameRect(worldFollowRect, bandW, bandH) is the pose that\n// scrolls/zooms that window to fill the band — the same pixels RSR's re-crop draws.\n\nimport type { Box, RenderedNotation } from '../promo';\nimport { frameRect, type CameraState } from './camera';\nimport { FOLLOW_PAD, type NotationLayout } from './notationGeometry';\n\n/** Map a canvas-space box through a base notation layout into world/screen coords. */\nexport function mapBoxThroughLayout(base: NotationLayout, b: Box): Box {\n const fx = base.rect.dw / base.src.w;\n const fy = base.rect.dh / base.src.h;\n return {\n x: base.rect.dx + (b.x - base.src.x) * fx,\n y: base.rect.dy + (b.y - base.src.y) * fy,\n w: b.w * fx,\n h: b.h * fy,\n };\n}\n\n/** RSR's per-frame `src` crop from a follow box (FOLLOW_PAD expand + clamp) —\n * the exact canvas window RSR's drawNotation blits (+page.svelte:289-298). */\nexport function followSrcBox(rn: RenderedNotation, focusBoxCanvas: Box): Box {\n const px = (focusBoxCanvas.w * (FOLLOW_PAD - 1)) / 2;\n const py = (focusBoxCanvas.h * (FOLLOW_PAD - 1)) / 2;\n const src: Box = {\n x: Math.max(0, focusBoxCanvas.x - px),\n y: Math.max(0, focusBoxCanvas.y - py),\n w: focusBoxCanvas.w + 2 * px,\n h: focusBoxCanvas.h + 2 * py,\n };\n src.w = Math.min(src.w, rn.canvas.width - src.x);\n src.h = Math.min(src.h, rn.canvas.height - src.y);\n return src;\n}\n\n/**\n * The camera pose that scrolls/zooms the base (fixed) notation blit so the follow\n * window fills a `viewW`×`viewH` viewport — the camera-primitive expression of\n * RSR's per-frame re-crop. Composing this pose with the base blit reproduces RSR's\n * `notationLayout({focusBox})` exactly (proven in scene-notation-camera.test.ts).\n *\n * We frame the SAME canvas window RSR crops (FOLLOW_PAD-expanded `src`), mapped\n * into base-world coords, into the viewport. `viewW`/`viewH` are the dest-rect\n * dimensions (so the contain-fit matches RSR's band-fit).\n */\nexport function cameraForFollow(\n rn: RenderedNotation,\n base: NotationLayout,\n focusBoxCanvas: Box,\n viewW: number,\n viewH: number,\n): CameraState {\n const src = followSrcBox(rn, focusBoxCanvas);\n const world = mapBoxThroughLayout(base, src);\n return frameRect(world, viewW, viewH, 0);\n}\n","// Standalone demo (S3) — proves the headline falling-notes + keyboard capability\n// end-to-end through the runner over a REAL Score (parsed from a bundled\n// grand-staff MusicXML, so L/R hands show). Reusable as the basis of an\n// RSR/whozart variant later; no app is wired here.\n//\n// Returns a SceneSpec + the parsed Score so a host (or the golden test) can\n// buildScene() it and render/capture deterministic frames.\n\nimport type { SceneSpec } from '../runner';\nimport type { Score } from '../score';\nimport { scoreFromMusicXML, type ScoreFromMusicXMLOpts } from '../score';\n\nexport interface FallingKeyboardDemoOpts {\n /** Frame size. Default phone-portrait 1080×1920. */\n size?: [number, number];\n /** Theme key for the spec. Default \"rsr\". */\n theme?: string;\n /** falling-notes lead time (ms). Default 2200. */\n leadMs?: number;\n /** Colour mode for both layers. Default \"hand\". */\n colorBy?: 'hand' | 'pitch-class';\n /** Keyboard range. Default \"auto\" (fit the piece's pitch span). */\n range?: '88' | 'auto';\n /** Forwarded to scoreFromMusicXML (tempo fallback/override/osmdFactory). */\n scoreOpts?: ScoreFromMusicXMLOpts;\n}\n\n/** The SceneSpec for the demo (background + falling-notes + keyboard, full clip). */\nexport function fallingKeyboardDemoSpec(opts: FallingKeyboardDemoOpts = {}): SceneSpec {\n const size = opts.size ?? [1080, 1920];\n const leadMs = opts.leadMs ?? 2200;\n const colorBy = opts.colorBy ?? 'hand';\n const range = opts.range ?? 'auto';\n return {\n size,\n theme: opts.theme ?? 'rsr',\n durationMode: 'audio',\n timeline: [\n {\n at: [0, 'end'],\n layers: [\n { k: 'background', p: { style: 'ink' } },\n // keyboard first so it publishes its layout before falling-notes inits;\n // draw order: keyboard bed under the falling blocks would hide them, so\n // we draw falling-notes ON TOP of the keyboard bed (falling listed last).\n { k: 'keyboard', p: { range, colorBy } },\n { k: 'falling-notes', p: { keyboard: true, colorBy, leadMs } },\n ],\n },\n ],\n };\n}\n\n/** Parse the demo Score from MusicXML (the caller supplies the XML string so the\n * bundling/fixture path stays in the host/test, not in src). */\nexport async function fallingKeyboardDemoScore(\n xml: string,\n opts: FallingKeyboardDemoOpts = {},\n): Promise<Score> {\n return scoreFromMusicXML(xml, opts.scoreOpts);\n}\n","// Standalone demo (S4) — proves the wrapped promo-card layers (hook / reveal /\n// portrait / cta) + spectrum + branding COMPOSE through the runner over a\n// timeline, with no app dependency. A host (or the golden test) buildScene()s\n// this and renders deterministic frames.\n//\n// Timeline (totalSec configurable, default 12s):\n// [0, hookSec] background + hook + branding\n// [hookSec, revealSec] background + spectrum + portrait + branding\n// [end-ctaSec, end] background + cta + branding\n//\n// Each card wrapper gets startMs/durationMs matching its segment so its internal\n// t01 sweeps 0..1 over the segment (the wrappers can't read segment bounds from\n// the runner — see promoCards.ts).\n\nimport type { SceneSpec } from '../runner';\n\nexport interface PromoCardsDemoOpts {\n /** Frame size. Default phone-portrait 1080×1920. */\n size?: [number, number];\n /** Theme key. Default \"rsr\". */\n theme?: string;\n /** Total clip length (s). Default 12. */\n totalSec?: number;\n /** Hook segment length (s). Default 2.2. */\n hookSec?: number;\n /** Reveal/portrait segment length (s). Default 4. */\n revealSec?: number;\n /** CTA segment length (s) at the end. Default 3. */\n ctaSec?: number;\n /** Hook lines. */\n hookLines?: string[];\n /** Reveal/portrait title (composer). */\n title?: string;\n /** Reveal/portrait subtitle. */\n subtitle?: string;\n /** Optional portrait URL (loaded crossOrigin in init; null → initials badge). */\n portraitUrl?: string | null;\n /** Optional fun fact under the portrait. */\n funFact?: string;\n /** CTA lines. */\n ctaLines?: string[];\n /** Brand wordmark. Default theme.brand (resolved by the host). */\n brand?: string;\n}\n\n/** Build the demo SceneSpec wiring the S4 wrapper layers across a timeline. */\nexport function promoCardsDemoSpec(opts: PromoCardsDemoOpts = {}): SceneSpec {\n const size = opts.size ?? [1080, 1920];\n const totalSec = opts.totalSec ?? 12;\n const hookSec = opts.hookSec ?? 2.2;\n const revealSec = opts.revealSec ?? 4;\n const ctaSec = opts.ctaSec ?? 3;\n const hookLines = opts.hookLines ?? ['Can you', 'name this?'];\n const title = opts.title ?? 'Claude Debussy';\n const subtitle = opts.subtitle ?? '1862–1918';\n const ctaLines = opts.ctaLines ?? ['Train your ear', 'realeartrainer.com'];\n const brand = opts.brand;\n\n const ms = (s: number) => Math.round(s * 1000);\n const hookStart = 0;\n const revealStart = hookSec;\n const ctaStart = totalSec - ctaSec;\n\n return {\n size,\n theme: opts.theme ?? 'rsr',\n durationMode: 'fixed',\n durationSec: totalSec,\n timeline: [\n // background spans the whole clip.\n { at: [0, 'end'], layers: [{ k: 'background', p: { style: 'paper' } }] },\n // hook card.\n {\n at: [hookStart, hookSec],\n layers: [{ k: 'hook', p: { lines: hookLines, brand, startMs: ms(hookStart), durationMs: ms(hookSec) } }],\n },\n // reveal phase: audio-reactive spectrum + the portrait medallion.\n {\n at: [revealStart, revealStart + revealSec],\n layers: [\n { k: 'spectrum', p: {} },\n {\n k: 'portrait',\n p: {\n title, subtitle, url: opts.portraitUrl ?? null, funFact: opts.funFact,\n startMs: ms(revealStart), durationMs: ms(revealSec),\n },\n },\n ],\n },\n // end-card CTA.\n {\n at: [ctaStart, 'end'],\n layers: [{ k: 'cta', p: { lines: ctaLines, startMs: ms(ctaStart), durationMs: ms(ctaSec) } }],\n },\n // persistent branding across the whole clip.\n { at: [0, 'end'], layers: [{ k: 'branding', p: { logo: brand } }] },\n ],\n audio: { voicing: 'reading' },\n };\n}\n"],"mappings":"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","names":["w","STORE","keyFor"]}
|
package/dist/video.d.ts
CHANGED
|
@@ -117,7 +117,15 @@ interface RevealSceneOpts {
|
|
|
117
117
|
title: string;
|
|
118
118
|
subtitle: string;
|
|
119
119
|
initials?: string;
|
|
120
|
+
/** Composer portrait (already decoded) drawn clipped into the medallion. If
|
|
121
|
+
* omitted/null, falls back to the initials badge. Load it with `loadPortrait`
|
|
122
|
+
* so a cross-origin image (e.g. Wikimedia) doesn't taint the capture canvas. */
|
|
123
|
+
portrait?: HTMLImageElement | null;
|
|
124
|
+
/** Optional one-line fun fact wrapped below the subtitle (e.g. corpus
|
|
125
|
+
* `fun_fact`). Trimmed to two lines. */
|
|
126
|
+
funFact?: string;
|
|
120
127
|
}
|
|
128
|
+
declare function loadPortrait(url?: string | null): Promise<HTMLImageElement | null>;
|
|
121
129
|
declare function revealScene(theme: PromoTheme, opts: RevealSceneOpts): Scene;
|
|
122
130
|
interface CtaSceneOpts {
|
|
123
131
|
lines: string[];
|
|
@@ -125,4 +133,4 @@ interface CtaSceneOpts {
|
|
|
125
133
|
declare function ctaScene(theme: PromoTheme, opts: CtaSceneOpts): Scene;
|
|
126
134
|
declare function runPromoCapture(opts: PromoCaptureOpts, record?: (scenes: Scene[], o: RecordOpts) => Promise<Blob>): Promise<void>;
|
|
127
135
|
|
|
128
|
-
export { type CtaSceneOpts, type HookSceneOpts, type PromoCaptureOpts, type PromoMeta, type PromoTheme, type RecordOpts, type RevealSceneOpts, SAFE_ZONE, type SafeBox, type Scene, ctaScene, drawBottomGradient, drawSafeGuides, hookScene, initials, pickMimeType, recordScenes, revealScene, runPromoCapture, safeBox, safeTitleBaseline, truncate };
|
|
136
|
+
export { type CtaSceneOpts, type HookSceneOpts, type PromoCaptureOpts, type PromoMeta, type PromoTheme, type RecordOpts, type RevealSceneOpts, SAFE_ZONE, type SafeBox, type Scene, ctaScene, drawBottomGradient, drawSafeGuides, hookScene, initials, loadPortrait, pickMimeType, recordScenes, revealScene, runPromoCapture, safeBox, safeTitleBaseline, truncate };
|