odori 0.0.11 → 0.0.12
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/LICENSE +22 -0
- package/dist/{brand-BVk9LMs5.d.ts → brand-CGe0NYqW.d.ts} +27 -1
- package/dist/{chunk-E3W3XJHP.js → chunk-7GHTRLJ7.js} +20 -2
- package/dist/chunk-7GHTRLJ7.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/{manifest-BmD1ybOH.d.ts → manifest-2usUa-uA.d.ts} +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +1 -1
- package/package.json +26 -34
- package/dist/chunk-E3W3XJHP.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Allen Zhou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -208,6 +208,17 @@ type BrandColors = {
|
|
|
208
208
|
muted: string;
|
|
209
209
|
accent: string;
|
|
210
210
|
border: string;
|
|
211
|
+
/**
|
|
212
|
+
* The ink a raised surface casts on this brand's ground.
|
|
213
|
+
*
|
|
214
|
+
* Components used to reach for black at whatever opacity looked right on
|
|
215
|
+
* a dark stage, which is how a card that floated on near-black became a
|
|
216
|
+
* grey smear the moment the same component was rendered on white. Depth
|
|
217
|
+
* is a property of the ground, so the brand states it once and every
|
|
218
|
+
* component spends it on geometry - how far the card is lifted - rather
|
|
219
|
+
* than on guessing a colour.
|
|
220
|
+
*/
|
|
221
|
+
shadow: string;
|
|
211
222
|
};
|
|
212
223
|
type BrandTypography = {
|
|
213
224
|
sans: string;
|
|
@@ -250,7 +261,22 @@ type BrandInput = {
|
|
|
250
261
|
audio?: Partial<Brand["audio"]>;
|
|
251
262
|
};
|
|
252
263
|
declare const defaultBrand: Brand;
|
|
264
|
+
/**
|
|
265
|
+
* The same brand with the ramp turned over.
|
|
266
|
+
*
|
|
267
|
+
* A video has no viewer preference to read: an mp4 cannot answer a media
|
|
268
|
+
* query, and a headless render has no system theme to inherit. What decides
|
|
269
|
+
* whether a cut is light or dark is the brand it inherits, which is why a
|
|
270
|
+
* component that takes its colours from the brand is already both and one
|
|
271
|
+
* that hardcodes a hex is neither.
|
|
272
|
+
*
|
|
273
|
+
* So this is the light default a project can name in a layout, rather than a
|
|
274
|
+
* mode the runtime switches into. Everything but the ramp is shared with
|
|
275
|
+
* `defaultBrand`: the same faces, the same motion, the same audio target, so
|
|
276
|
+
* moving between them changes the light in the room and nothing else.
|
|
277
|
+
*/
|
|
278
|
+
declare const lightBrand: Brand;
|
|
253
279
|
declare const defineBrand: (input: BrandInput) => Brand;
|
|
254
280
|
declare const brandCssVariables: (brand: Brand) => Record<string, string>;
|
|
255
281
|
|
|
256
|
-
export {
|
|
282
|
+
export { noise as A, type Brand as B, type CueDefinition as C, type Duration as D, type Envelope as E, normalize as F, note as G, pad as H, seamless as I, seconds as J, secondsFromFrames as K, seeded as L, sequence as M, shape as N, silence as O, sine as P, step as Q, sweep as R, type Signal as S, triangle as T, type BrandColors as a, type BrandFont as b, type BrandInput as c, type BrandTypography as d, type ChordQuality as e, type CueContext as f, SAMPLE_RATE as g, type Step as h, brandCssVariables as i, chord as j, cueSamples as k, cueSignature as l, cueUrl as m, defaultBrand as n, defineBrand as o, defineCue as p, formatTimecode as q, framesFromDuration as r, framesFromOffset as s, gain as t, highPass as u, isCueDefinition as v, lightBrand as w, loop as x, lowPass as y, mix as z };
|
|
@@ -455,7 +455,9 @@ var defaultBrand = {
|
|
|
455
455
|
foreground: "#ededed",
|
|
456
456
|
muted: "#a1a1a1",
|
|
457
457
|
accent: "#ffffff",
|
|
458
|
-
border: "#1f1f1f"
|
|
458
|
+
border: "#1f1f1f",
|
|
459
|
+
/* A dark ground can take real black: the depth reads as a room. */
|
|
460
|
+
shadow: "rgba(0, 0, 0, 0.55)"
|
|
459
461
|
},
|
|
460
462
|
/**
|
|
461
463
|
* System faces, deliberately.
|
|
@@ -476,6 +478,21 @@ var defaultBrand = {
|
|
|
476
478
|
motion: { standard: [0.16, 1, 0.3, 1], staggerFrames: 4 },
|
|
477
479
|
audio: { cues: {}, targetLufs: -14 }
|
|
478
480
|
};
|
|
481
|
+
var lightBrand = {
|
|
482
|
+
...defaultBrand,
|
|
483
|
+
name: "odori-light",
|
|
484
|
+
colors: {
|
|
485
|
+
background: "#ffffff",
|
|
486
|
+
surface: "#f5f5f5",
|
|
487
|
+
foreground: "#0a0a0a",
|
|
488
|
+
muted: "#737373",
|
|
489
|
+
accent: "#0a0a0a",
|
|
490
|
+
border: "#e5e5e5",
|
|
491
|
+
/* A light ground takes a shadow tinted toward its own ink and kept
|
|
492
|
+
shallow: black at the depth a dark stage wants reads as dirt here. */
|
|
493
|
+
shadow: "rgba(10, 12, 24, 0.13)"
|
|
494
|
+
}
|
|
495
|
+
};
|
|
479
496
|
var defineBrand = (input) => ({
|
|
480
497
|
kind: "odori-brand",
|
|
481
498
|
name: input.name ?? defaultBrand.name,
|
|
@@ -925,6 +942,7 @@ export {
|
|
|
925
942
|
interpolate,
|
|
926
943
|
spring,
|
|
927
944
|
defaultBrand,
|
|
945
|
+
lightBrand,
|
|
928
946
|
defineBrand,
|
|
929
947
|
brandCssVariables,
|
|
930
948
|
defaultLayout,
|
|
@@ -944,4 +962,4 @@ export {
|
|
|
944
962
|
entryDurationInFrames,
|
|
945
963
|
OdoriRuntime
|
|
946
964
|
};
|
|
947
|
-
//# sourceMappingURL=chunk-
|
|
965
|
+
//# sourceMappingURL=chunk-7GHTRLJ7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audio.ts","../src/synth.ts","../src/cue.ts","../src/easing.ts","../src/brand.ts","../src/layout.ts","../src/runtime.tsx"],"sourcesContent":["import {type Duration} from \"./time\";\n\nexport type AudioCue = {\n /** Stable identity, derived from the source and its placement. */\n id: string;\n src: string;\n fromFrame: number;\n durationInFrames: number;\n /** Linear gain. 1 is unchanged. */\n gain: number;\n /**\n * An authored curve, sampled from a `gain` function at compile time. A\n * function cannot enter a manifest or an FFmpeg filter; its samples can.\n */\n gainPoints?: EnvelopePoint[];\n fadeInFrames: number;\n fadeOutFrames: number;\n /** Seconds skipped at the head of the source file. */\n trimStartSeconds: number;\n loop: boolean;\n /** Ducked cues are attenuated while a louder cue overlaps them. */\n duckUnder: boolean;\n};\n\nexport type AudioTrack = {cues: AudioCue[]; durationInFrames: number};\n\nexport type AudioProps = {\n src: string;\n /** Offset from the enclosing scene, or from the video when used at the top level. */\n from?: Duration;\n duration?: Duration;\n /**\n * A number, or a curve. The function is evaluated once per frame of the\n * cue's window when the timeline compiles, so preview and export read the\n * same points rather than running your code twice.\n */\n gain?: number | ((frame: number) => number);\n fadeIn?: Duration;\n fadeOut?: Duration;\n trimStart?: Duration;\n loop?: boolean;\n duckUnder?: boolean;\n};\n\nexport const cueId = (src: string, fromFrame: number): string => `${src}@${fromFrame}`;\n\n/**\n * A source is either a URL the dev server and the render worker both serve, or\n * a reference declared in `odori.config.ts`. Anything that is not obviously a\n * path is treated as a reference, so `<Audio src=\"score\" />` and\n * `useAssets().resolve(\"score\")` name the same file.\n */\nexport const isAssetReference = (src: string): boolean => !/^(https?:\\/\\/|\\/|\\.\\/|\\.\\.\\/|data:|blob:)/.test(src);\n\n/** Deterministic ordering so a manifest hash does not depend on mount order. */\nexport const sortCues = (cues: AudioCue[]): AudioCue[] =>\n [...cues].sort((left, right) => left.fromFrame - right.fromFrame || left.id.localeCompare(right.id));\n\nexport const trackDuration = (cues: AudioCue[]): number =>\n cues.reduce((total, cue) => Math.max(total, cue.fromFrame + cue.durationInFrames), 0);\n\n/**\n * Linear gain at one frame, including fades. The renderer applies the same\n * curve through FFmpeg, so preview and export agree on shape.\n */\nexport const gainAtFrame = (cue: AudioCue, frame: number): number => {\n const local = frame - cue.fromFrame;\n if (local < 0 || local >= cue.durationInFrames) return 0;\n const fadeIn = cue.fadeInFrames > 0 ? Math.min(1, local / cue.fadeInFrames) : 1;\n const remaining = cue.durationInFrames - local;\n const fadeOut = cue.fadeOutFrames > 0 ? Math.min(1, remaining / cue.fadeOutFrames) : 1;\n const authored = cue.gainPoints ? envelopeAtFrame(cue.gainPoints, frame) : 1;\n return cue.gain * authored * fadeIn * fadeOut;\n};\n\n/**\n * Sample a gain function across a window, then drop the points a straight line\n * already describes. One point per frame would compile into a filter\n * expression hundreds of branches deep for a curve the ear cannot tell from a\n * dozen segments.\n */\nexport const sampleGainCurve = (\n curve: (frame: number) => number,\n fromFrame: number,\n durationInFrames: number,\n tolerance = 0.01,\n): EnvelopePoint[] => {\n const raw: EnvelopePoint[] = [];\n for (let frame = fromFrame; frame < fromFrame + durationInFrames; frame += 1) {\n const value = curve(frame - fromFrame);\n raw.push({frame, value: Number.isFinite(value) ? value : 1});\n }\n if (raw.length <= 2) return raw;\n\n const kept: EnvelopePoint[] = [raw[0]];\n for (let index = 1; index < raw.length - 1; index += 1) {\n const previous = kept[kept.length - 1];\n const next = raw[index + 1];\n const span = next.frame - previous.frame;\n const predicted =\n span === 0\n ? previous.value\n : previous.value + ((next.value - previous.value) * (raw[index].frame - previous.frame)) / span;\n if (Math.abs(raw[index].value - predicted) > tolerance) kept.push(raw[index]);\n }\n kept.push(raw[raw.length - 1]);\n return kept;\n};\n\n/** How far a ducked cue drops while a non-ducked cue is sounding. */\nexport const DUCK_GAIN = 0.35;\n\n/** Ramp into and out of a duck, so the drop is heard as mixing, not as a cut. */\nexport const DUCK_RAMP_FRAMES = 6;\n\nexport type EnvelopePoint = {frame: number; value: number};\n\nconst clampFrame = (frame: number, durationInFrames: number) => Math.max(0, Math.min(durationInFrames, frame));\n\n/**\n * The duck envelope for one cue against the rest of the track.\n *\n * A duck lasts only while a non-ducked cue overlaps, rather than for the whole\n * file: a one second confirmation should not hold a music bed down for the\n * length of the video. Preview evaluates these points directly and the encoder\n * compiles them into a volume expression, so both paths hear one envelope.\n */\nexport const duckEnvelope = (cue: AudioCue, cues: AudioCue[]): EnvelopePoint[] => {\n const end = cue.fromFrame + cue.durationInFrames;\n if (!cue.duckUnder) return [{frame: cue.fromFrame, value: 1}, {frame: end, value: 1}];\n\n const windows = cues\n .filter((other) => other.id !== cue.id && !other.duckUnder)\n .map((other) => ({\n start: Math.max(cue.fromFrame, other.fromFrame),\n end: Math.min(end, other.fromFrame + other.durationInFrames),\n }))\n .filter((window) => window.end > window.start)\n .sort((left, right) => left.start - right.start);\n\n // Overlapping windows duck once, not once per cue.\n const merged: Array<{start: number; end: number}> = [];\n for (const window of windows) {\n const last = merged[merged.length - 1];\n if (last && window.start <= last.end) last.end = Math.max(last.end, window.end);\n else merged.push({...window});\n }\n\n const points: EnvelopePoint[] = [{frame: cue.fromFrame, value: 1}];\n for (const window of merged) {\n points.push(\n {frame: clampFrame(window.start - DUCK_RAMP_FRAMES, end), value: 1},\n {frame: clampFrame(window.start, end), value: DUCK_GAIN},\n {frame: clampFrame(window.end, end), value: DUCK_GAIN},\n {frame: clampFrame(window.end + DUCK_RAMP_FRAMES, end), value: 1},\n );\n }\n points.push({frame: end, value: 1});\n return simplify(points.sort((left, right) => left.frame - right.frame));\n};\n\n/**\n * Two points at one frame are a step, and three points at one level are a\n * straight line. Collapsing both keeps the envelope, and the volume expression\n * compiled from it, as short as the shape actually requires.\n */\nconst simplify = (points: EnvelopePoint[]): EnvelopePoint[] => {\n const stepped: EnvelopePoint[] = [];\n for (const point of points) {\n const last = stepped[stepped.length - 1];\n if (last && last.frame === point.frame) stepped[stepped.length - 1] = point;\n else stepped.push(point);\n }\n\n return stepped.filter((point, index) => {\n const previous = stepped[index - 1];\n const next = stepped[index + 1];\n return !previous || !next || previous.value !== point.value || next.value !== point.value;\n });\n};\n\n/** Linear interpolation between envelope points. */\nexport const envelopeAtFrame = (points: EnvelopePoint[], frame: number): number => {\n if (points.length === 0) return 1;\n if (frame <= points[0].frame) return points[0].value;\n for (let index = 1; index < points.length; index += 1) {\n const previous = points[index - 1];\n const current = points[index];\n if (frame > current.frame) continue;\n const span = current.frame - previous.frame;\n if (span <= 0) return current.value;\n return previous.value + ((current.value - previous.value) * (frame - previous.frame)) / span;\n }\n return points[points.length - 1].value;\n};\n\n/**\n * Everything that shapes a cue's level at one frame: its own gain and fades,\n * and the duck the rest of the track imposes on it.\n */\nexport const trackGainAtFrame = (cue: AudioCue, cues: AudioCue[], frame: number): number =>\n gainAtFrame(cue, frame) * envelopeAtFrame(duckEnvelope(cue, cues), frame);\n","/**\n * Deterministic synthesis. A cue is code that produces samples, so a sound\n * diffs, reviews, and upgrades like every other piece of source, and preview\n * and export can run the identical function.\n *\n * The rules are the frame runtime's rules: no wall clock, no unseeded\n * randomness, one sample rate. Same score in, same bytes out, on any machine.\n */\n\n/** Every cue renders at this rate, so a score never depends on the host. */\nexport const SAMPLE_RATE = 48000;\n\nexport type Signal = {\n /** One array per channel. Mono is one; the mix upmixes when it needs to. */\n channels: Float32Array[];\n sampleRate: number;\n};\n\nexport type Envelope = {\n /** Seconds from the start of the cue to full level. */\n attack?: number;\n /** Seconds from full level to the sustain level. */\n decay?: number;\n /** Level held after the decay, 0 to 1. */\n sustain?: number;\n /** Seconds to fall from the sustain level to silence. */\n release?: number;\n};\n\nconst clamp = (value: number, low: number, high: number) => Math.max(low, Math.min(high, value));\n\n/**\n * A seeded generator, so noise is reproducible. Mulberry32: small, fast, and\n * good enough for audio noise, where the requirement is \"no audible pattern\"\n * rather than cryptographic quality.\n */\nexport const seeded = (seed: number): (() => number) => {\n let state = seed >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = Math.imul(state ^ (state >>> 15), 1 | state);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n};\n\nexport const silence = (samples: number, channels = 1): Signal => ({\n channels: Array.from({length: channels}, () => new Float32Array(samples)),\n sampleRate: SAMPLE_RATE,\n});\n\n/** A sine partial. The building block for tones, ticks, and sub weight. */\nexport const sine = (samples: number, frequency: number, phase = 0): Signal => {\n const data = new Float32Array(samples);\n const increment = (Math.PI * 2 * frequency) / SAMPLE_RATE;\n for (let index = 0; index < samples; index += 1) data[index] = Math.sin(phase + increment * index);\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/** A triangle partial: softer than a square, brighter than a sine. */\nexport const triangle = (samples: number, frequency: number): Signal => {\n const data = new Float32Array(samples);\n const period = SAMPLE_RATE / frequency;\n for (let index = 0; index < samples; index += 1) {\n const position = (index % period) / period;\n data[index] = 4 * Math.abs(position - 0.5) - 1;\n }\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/** White noise from a seeded generator, so a click is the same click twice. */\nexport const noise = (samples: number, seed = 1): Signal => {\n const random = seeded(seed);\n const data = new Float32Array(samples);\n for (let index = 0; index < samples; index += 1) data[index] = random() * 2 - 1;\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/**\n * A frequency sweep, which is what a riser is. Linear in frequency rather than\n * in pitch, because the shape is easier to reason about when writing one.\n */\nexport const sweep = (samples: number, from: number, to: number): Signal => {\n const data = new Float32Array(samples);\n let phase = 0;\n for (let index = 0; index < samples; index += 1) {\n const position = samples <= 1 ? 1 : index / (samples - 1);\n const frequency = from + (to - from) * position;\n phase += (Math.PI * 2 * frequency) / SAMPLE_RATE;\n data[index] = Math.sin(phase);\n }\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/** Apply an ADSR shape. Anything omitted is skipped rather than defaulted. */\nexport const shape = (signal: Signal, envelope: Envelope): Signal => {\n const {attack = 0, decay = 0, sustain = 1, release = 0} = envelope;\n const length = signal.channels[0]?.length ?? 0;\n const attackEnd = Math.round(attack * SAMPLE_RATE);\n const decayEnd = attackEnd + Math.round(decay * SAMPLE_RATE);\n const releaseStart = Math.max(decayEnd, length - Math.round(release * SAMPLE_RATE));\n\n const level = (index: number): number => {\n if (index < attackEnd) return attackEnd === 0 ? 1 : index / attackEnd;\n if (index < decayEnd) {\n const position = (index - attackEnd) / Math.max(1, decayEnd - attackEnd);\n return 1 + (sustain - 1) * position;\n }\n if (index < releaseStart) return sustain;\n const remaining = length - releaseStart;\n return remaining <= 0 ? 0 : sustain * (1 - (index - releaseStart) / remaining);\n };\n\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(channel.length);\n for (let index = 0; index < channel.length; index += 1) out[index] = channel[index] * level(index);\n return out;\n }),\n };\n};\n\nexport const gain = (signal: Signal, amount: number): Signal => ({\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(channel.length);\n for (let index = 0; index < channel.length; index += 1) out[index] = channel[index] * amount;\n return out;\n }),\n});\n\n/** Sum signals, padding to the longest. Levels are the caller's business. */\nexport const mix = (...signals: Signal[]): Signal => {\n const length = Math.max(0, ...signals.map((signal) => signal.channels[0]?.length ?? 0));\n const width = Math.max(1, ...signals.map((signal) => signal.channels.length));\n const channels = Array.from({length: width}, () => new Float32Array(length));\n\n for (const signal of signals) {\n for (let channel = 0; channel < width; channel += 1) {\n const source = signal.channels[channel] ?? signal.channels[0];\n if (!source) continue;\n const target = channels[channel];\n for (let index = 0; index < source.length; index += 1) target[index] += source[index];\n }\n }\n\n return {channels, sampleRate: SAMPLE_RATE};\n};\n\n/**\n * One pole filters. Enough to shape a click or take the top off a pad, and\n * cheap enough to stay readable next to the sound it is making.\n */\nexport const lowPass = (signal: Signal, frequency: number): Signal => {\n const coefficient = clamp(1 - Math.exp((-2 * Math.PI * frequency) / SAMPLE_RATE), 0, 1);\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(channel.length);\n let previous = 0;\n for (let index = 0; index < channel.length; index += 1) {\n previous += coefficient * (channel[index] - previous);\n out[index] = previous;\n }\n return out;\n }),\n };\n};\n\nexport const highPass = (signal: Signal, frequency: number): Signal => {\n const low = lowPass(signal, frequency);\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel, index) => {\n const out = new Float32Array(channel.length);\n const removed = low.channels[index];\n for (let sample = 0; sample < channel.length; sample += 1) out[sample] = channel[sample] - removed[sample];\n return out;\n }),\n };\n};\n\n/**\n * Ramp the first and last few milliseconds to zero.\n *\n * A buffer that starts or ends on a non-zero sample is a step change, and a\n * step change is a click. Every cue gets this, because the alternative is a\n * score that sounds correct in isolation and glitches at its edges.\n */\nexport const declick = (signal: Signal, milliseconds = 3): Signal => {\n const ramp = Math.max(1, Math.round((milliseconds / 1000) * SAMPLE_RATE));\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = Float32Array.from(channel);\n const span = Math.min(ramp, Math.floor(out.length / 2));\n for (let index = 0; index < span; index += 1) {\n const level = index / span;\n out[index] *= level;\n out[out.length - 1 - index] *= level;\n }\n return out;\n }),\n };\n};\n\n/**\n * Join a signal's tail to its own head, so repeating it is inaudible.\n *\n * `declick` is wrong for a loop: ramping both edges to zero puts a hole at\n * every seam, once per bar for the length of the video. Instead the tail is\n * crossfaded over the head, which is the same trick a sampler uses — the last\n * few milliseconds fade out while the copy of them under the first few\n * milliseconds fades in, so the two ends already agree when they meet.\n *\n * The crossfade eats `milliseconds` from the end, which is why a looping score\n * should be written a hair long rather than exactly one bar.\n */\nexport const seamless = (signal: Signal, milliseconds = 12): Signal => {\n const fade = Math.max(1, Math.round((milliseconds / 1000) * SAMPLE_RATE));\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const span = Math.min(fade, Math.floor(channel.length / 2));\n if (span <= 1) return Float32Array.from(channel);\n // The loop body is everything but the tail the crossfade consumes.\n const out = Float32Array.from(channel.subarray(0, channel.length - span));\n for (let index = 0; index < span; index += 1) {\n const level = index / span;\n out[index] = out[index] * level + channel[channel.length - span + index] * (1 - level);\n }\n return out;\n }),\n };\n};\n\n/** Peak normalize, so a cue lands predictably before the mix's loudness pass. */\nexport const normalize = (signal: Signal, peak = 0.9): Signal => {\n let highest = 0;\n for (const channel of signal.channels) {\n for (let index = 0; index < channel.length; index += 1) {\n const value = Math.abs(channel[index]);\n if (value > highest) highest = value;\n }\n }\n return highest === 0 ? signal : gain(signal, peak / highest);\n};\n\n/** Repeat a signal until it fills `samples`, for beds under a long scene. */\nexport const loop = (signal: Signal, samples: number): Signal => ({\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(samples);\n if (channel.length === 0) return out;\n for (let index = 0; index < samples; index += 1) out[index] = channel[index % channel.length];\n return out;\n }),\n});\n\n/** Seconds to samples at the fixed rate, so scores read in musical time. */\nexport const seconds = (value: number): number => Math.max(0, Math.round(value * SAMPLE_RATE));\n\n/**\n * The musical layer.\n *\n * Everything above is a waveform. A bed is not a waveform, it is a phrase: the\n * same voice at different pitches, placed on a grid. These four keep a score\n * readable at that level, so a bed reads as \"these notes, this tempo\" rather\n * than as arithmetic over sample indices — and so two beds written by two\n * people share a vocabulary instead of each inventing one.\n *\n * The rules do not change: pure functions, seeded randomness, one sample rate.\n */\n\nconst SEMITONES = {c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11} as const;\n\n/**\n * Scientific pitch to frequency: `a4` is 440 Hz, `c#3` and `db3` are the same\n * key. A score names notes because \"a2\" survives transposition and reading,\n * and 110 does neither.\n */\nexport const note = (name: string): number => {\n const match = /^([a-gA-G])([#b]?)(-?\\d+)$/.exec(name.trim());\n if (!match) throw new Error(`Not a note name: \"${name}\". Use scientific pitch, for example \"a4\" or \"c#3\".`);\n const [, letter, accidental, octave] = match;\n const semitone =\n SEMITONES[letter.toLowerCase() as keyof typeof SEMITONES] +\n (accidental === \"#\" ? 1 : accidental === \"b\" ? -1 : 0) +\n (Number(octave) + 1) * 12;\n // MIDI 69 is a4 at 440 Hz; equal temperament from there.\n return 440 * 2 ** ((semitone - 69) / 12);\n};\n\n/** Samples in one step of a grid, so a score can size its own voices. */\nexport const step = (bpm: number, division = 4): number =>\n Math.max(1, Math.round((60 / bpm / (division / 4)) * SAMPLE_RATE));\n\nexport type Step = {\n /** Grid position, in steps from the start. Fractions are allowed. */\n at: number;\n /** The voice to place there, built at whatever length it needs. */\n play: (samples: number) => Signal;\n /** Length in steps. Defaults to one step. */\n length?: number;\n /** Linear gain for this hit. */\n gain?: number;\n};\n\n/**\n * Place voices on a tempo grid.\n *\n * A step's `play` receives the samples its length works out to, so one voice\n * definition serves a sixteenth and a whole bar. Swing delays every odd step\n * by a fraction of a step, which is the difference between a drum machine and\n * a groove; it is off by default because a bed under narration should not\n * shuffle unless it was asked to.\n */\nexport const sequence = (\n steps: Step[],\n options: {bpm: number; division?: number; swing?: number; samples?: number},\n): Signal => {\n const {bpm, division = 4, swing = 0, samples} = options;\n const width = step(bpm, division);\n const placed = steps.map((item) => {\n const length = Math.max(1, Math.round((item.length ?? 1) * width));\n const shuffle = swing > 0 && Math.round(item.at) % 2 === 1 ? swing * width : 0;\n const offset = Math.round(item.at * width + shuffle);\n return {offset, signal: item.gain === undefined ? item.play(length) : gain(item.play(length), item.gain)};\n });\n\n const total =\n samples ?? Math.max(0, ...placed.map(({offset, signal}) => offset + (signal.channels[0]?.length ?? 0)));\n const channelCount = Math.max(1, ...placed.map(({signal}) => signal.channels.length));\n const channels = Array.from({length: channelCount}, () => new Float32Array(total));\n\n for (const {offset, signal} of placed) {\n for (let channel = 0; channel < channelCount; channel += 1) {\n const source = signal.channels[channel] ?? signal.channels[0];\n if (!source) continue;\n const target = channels[channel];\n // A voice that runs past the end is cut, not wrapped: a phrase that\n // needs its tail should be written long enough to hold it.\n for (let index = 0; index < source.length && offset + index < total; index += 1) {\n target[offset + index] += source[index];\n }\n }\n }\n\n return {channels, sampleRate: SAMPLE_RATE};\n};\n\n/** Intervals from the root, in semitones. Enough colour for product scoring. */\nconst QUALITIES = {\n major: [0, 4, 7],\n minor: [0, 3, 7],\n sus2: [0, 2, 7],\n sus4: [0, 5, 7],\n major7: [0, 4, 7, 11],\n minor7: [0, 3, 7, 10],\n add9: [0, 4, 7, 14],\n} as const;\n\nexport type ChordQuality = keyof typeof QUALITIES;\n\n/**\n * The frequencies of a chord, root first. Voicing is left to the caller: what\n * makes a pad sound like a pad is which of these you hand to which oscillator,\n * and at what level.\n */\nexport const chord = (root: string | number, quality: ChordQuality = \"major\"): number[] => {\n const base = typeof root === \"number\" ? root : note(root);\n return QUALITIES[quality].map((interval) => base * 2 ** (interval / 12));\n};\n\n/**\n * Sustained tones under one envelope: the sound of a bed that is holding\n * rather than playing. Each voice is detuned by a few cents against the last\n * so the stack beats slowly instead of standing still, which is what separates\n * a pad from a test tone.\n */\nexport const pad = (\n frequencies: number[],\n samples: number,\n options: {envelope?: Envelope; detuneCents?: number; level?: number} = {},\n): Signal => {\n const {envelope = {attack: 0.4, sustain: 1, release: 0.6}, detuneCents = 4, level = 1 / Math.max(1, frequencies.length)} =\n options;\n const voices = frequencies.flatMap((frequency, index) => {\n const detune = 2 ** ((detuneCents * (index % 2 === 0 ? 1 : -1)) / 1200);\n return [gain(sine(samples, frequency), level), gain(sine(samples, frequency * detune), level * 0.6)];\n });\n return shape(mix(...voices), envelope);\n};\n","import {hashValue} from \"./hash\";\nimport {declick, SAMPLE_RATE, seamless, type Signal} from \"./synth\";\n\nexport type CueContext = {\n /** How many samples the cue must fill. */\n samples: number;\n sampleRate: number;\n};\n\nexport type CueDefinition = {\n readonly kind: \"odori-cue\";\n /** The name a brand registers and a video asks for. */\n name: string;\n /** Length at 30fps, which is how a contract states it. */\n durationInFrames: number;\n /**\n * Whether this score is one repeatable phrase rather than a single event.\n *\n * A looping cue renders its phrase once, at `durationInFrames`, and the\n * placement repeats it for as long as the window runs: one render, cached by\n * content, however long the scene. It also changes how the edges are\n * treated — see `defineCue` — and makes `<Audio>` loop by default, so a bed\n * fills its scene without the author restating what the cue already knows.\n */\n loops: boolean;\n /**\n * Anything the score reads. Held separately from the function so the\n * manifest can hash what a cue depends on: two cues that differ only in a\n * parameter are two different sounds and must not share a cached render.\n */\n params: Record<string, number | string | boolean>;\n render(context: CueContext): Signal;\n};\n\nexport type CueInput = Omit<CueDefinition, \"kind\" | \"loops\" | \"params\"> & {\n loops?: boolean;\n params?: Record<string, number | string | boolean>;\n};\n\n/**\n * A sound as source. The function runs unchanged in the browser for preview and\n * in Node for export, so the cue a reviewer approves is the cue that ships.\n */\nexport const defineCue = (input: CueInput): CueDefinition => ({\n kind: \"odori-cue\",\n loops: false,\n params: {},\n ...input,\n // A score is about the sound, not about remembering what a buffer does at\n // its edges. A one shot is declicked, because a buffer that stops\n // mid-waveform is a click. A loop is made seamless instead, because\n // declicking it would put that same silence at every seam.\n render: (context) => (input.loops ? seamless(input.render(context)) : declick(input.render(context))),\n});\n\nexport const isCueDefinition = (value: unknown): value is CueDefinition =>\n typeof value === \"object\" && value !== null && (value as CueDefinition).kind === \"odori-cue\";\n\n/**\n * Identity of a rendered cue. The name alone is not enough, because a project\n * can redefine what a name means; the params and the duration are what change\n * the samples, so they are what the cache and the manifest key on.\n */\nexport const cueSignature = (cue: CueDefinition): string =>\n hashValue({\n name: cue.name,\n durationInFrames: cue.durationInFrames,\n loops: cue.loops,\n params: cue.params,\n }).slice(0, 16);\n\n/** The samples a cue occupies at the given frame rate. */\nexport const cueSamples = (cue: CueDefinition, fps: number): number =>\n Math.max(1, Math.round((cue.durationInFrames / fps) * SAMPLE_RATE));\n\n/**\n * A generated cue resolves to a URL that names its own content, so the render\n * worker can serve it, the mix can find it on disk, and an unchanged cue is\n * never rendered twice.\n */\nexport const cueUrl = (cue: CueDefinition): string => `/__odori/cue/${cue.name}-${cueSignature(cue)}.wav`;\n","export type EasingFunction = (value: number) => number;\n\nconst solveBezierCoordinate = (time: number, a1: number, a2: number) => {\n const inverse = 1 - time;\n return 3 * inverse * inverse * time * a1 + 3 * inverse * time * time * a2 + time ** 3;\n};\n\nconst solveBezierSlope = (time: number, a1: number, a2: number) =>\n 3 * (1 - time) ** 2 * a1 + 6 * (1 - time) * time * (a2 - a1) + 3 * time ** 2 * (1 - a2);\n\nconst cubicBezier =\n (x1: number, y1: number, x2: number, y2: number): EasingFunction =>\n (value) => {\n if (value <= 0 || value >= 1) return value;\n let time = value;\n for (let index = 0; index < 8; index += 1) {\n const error = solveBezierCoordinate(time, x1, x2) - value;\n const slope = solveBezierSlope(time, x1, x2);\n if (Math.abs(error) < 0.000001 || Math.abs(slope) < 0.000001) break;\n time -= error / slope;\n }\n return solveBezierCoordinate(Math.min(1, Math.max(0, time)), y1, y2);\n };\n\nexport const Easing = {\n linear: (value: number) => value,\n bezier: cubicBezier,\n standard: cubicBezier(0.16, 1, 0.3, 1),\n quad: (value: number) => value * value,\n cubic: (value: number) => value ** 3,\n in: (easing: EasingFunction): EasingFunction => easing,\n out:\n (easing: EasingFunction): EasingFunction =>\n (value) =>\n 1 - easing(1 - value),\n inOut:\n (easing: EasingFunction): EasingFunction =>\n (value) =>\n value < 0.5 ? easing(value * 2) / 2 : 1 - easing((1 - value) * 2) / 2,\n};\n\nexport type InterpolateOptions = {\n easing?: EasingFunction;\n extrapolateLeft?: \"extend\" | \"clamp\";\n extrapolateRight?: \"extend\" | \"clamp\";\n};\n\nexport function interpolate(value: number, input: number[], output: number[], options?: InterpolateOptions): number;\nexport function interpolate(value: number, input: number[], output: string[], options?: InterpolateOptions): string;\nexport function interpolate(\n value: number,\n input: number[],\n output: Array<number | string>,\n options: InterpolateOptions = {},\n): number | string {\n if (input.length !== output.length || input.length < 2) {\n throw new Error(\"interpolate() requires matching input and output ranges.\");\n }\n\n let resolved = value;\n if (options.extrapolateLeft !== \"extend\") resolved = Math.max(input[0], resolved);\n if (options.extrapolateRight !== \"extend\") resolved = Math.min(input[input.length - 1], resolved);\n\n let rangeIndex = input.length - 2;\n for (let index = 0; index < input.length - 1; index += 1) {\n if (resolved <= input[index + 1]) {\n rangeIndex = index;\n break;\n }\n }\n\n const inputStart = input[rangeIndex];\n const inputEnd = input[rangeIndex + 1];\n const progress = inputEnd === inputStart ? 0 : (resolved - inputStart) / (inputEnd - inputStart);\n const eased = (options.easing ?? Easing.linear)(progress);\n const startValue = output[rangeIndex];\n const endValue = output[rangeIndex + 1];\n\n if (typeof startValue === \"number\" && typeof endValue === \"number\") {\n return startValue + (endValue - startValue) * eased;\n }\n if (typeof startValue === \"string\" && typeof endValue === \"string\") {\n const startNumbers = startValue.match(/-?\\d*\\.?\\d+/g)?.map(Number) ?? [];\n const endNumbers = endValue.match(/-?\\d*\\.?\\d+/g)?.map(Number) ?? [];\n if (startNumbers.length !== endNumbers.length) {\n throw new Error(\"String interpolation requires matching numeric segments.\");\n }\n let segment = 0;\n return startValue.replace(/-?\\d*\\.?\\d+/g, () => {\n const result = startNumbers[segment] + (endNumbers[segment] - startNumbers[segment]) * eased;\n segment += 1;\n return Number(result.toFixed(4)).toString();\n });\n }\n throw new Error(\"interpolate() output values must share a type.\");\n}\n\nexport type SpringOptions = {\n frame: number;\n fps: number;\n from?: number;\n to?: number;\n stiffness?: number;\n damping?: number;\n mass?: number;\n delayInFrames?: number;\n};\n\n/**\n * A deterministic damped-spring solve. The value depends only on the frame\n * index, so preview and render always agree.\n */\nexport const spring = ({\n frame,\n fps,\n from = 0,\n to = 1,\n stiffness = 120,\n damping = 20,\n mass = 1,\n delayInFrames = 0,\n}: SpringOptions): number => {\n const elapsed = Math.max(0, frame - delayInFrames);\n const step = 1 / fps;\n let position = 0;\n let velocity = 0;\n for (let index = 0; index < elapsed; index += 1) {\n const force = -stiffness * (position - 1) - damping * velocity;\n velocity += (force / mass) * step;\n position += velocity * step;\n }\n return from + (to - from) * position;\n};\n","import {type CueDefinition} from \"./cue\";\n\nexport type BrandColors = {\n background: string;\n surface: string;\n foreground: string;\n muted: string;\n accent: string;\n border: string;\n /**\n * The ink a raised surface casts on this brand's ground.\n *\n * Components used to reach for black at whatever opacity looked right on\n * a dark stage, which is how a card that floated on near-black became a\n * grey smear the moment the same component was rendered on white. Depth\n * is a property of the ground, so the brand states it once and every\n * component spends it on geometry - how far the card is lifted - rather\n * than on guessing a colour.\n */\n shadow: string;\n};\n\nexport type BrandTypography = {\n sans: string;\n mono: string;\n display?: string;\n};\n\nexport type BrandFont = {family: string; url: string; weight?: string; integrity?: string};\n\nexport type Brand = {\n readonly kind: \"odori-brand\";\n name: string;\n colors: BrandColors;\n typography: BrandTypography;\n fonts: BrandFont[];\n logos: Record<string, string>;\n motion: {standard: [number, number, number, number]; staggerFrames: number};\n audio: {\n /**\n * Symbolic cue names, so a video says what a sound means and the brand\n * decides which file that is. Values are asset references or URLs.\n */\n cues: Record<string, string | CueDefinition>;\n targetLufs: number;\n };\n};\n\nexport type BrandInput = {\n name?: string;\n colors?: Partial<BrandColors>;\n typography?: Partial<BrandTypography>;\n fonts?: BrandFont[];\n logos?: Record<string, string>;\n motion?: Partial<Brand[\"motion\"]>;\n audio?: Partial<Brand[\"audio\"]>;\n};\n\nexport const defaultBrand: Brand = {\n kind: \"odori-brand\",\n name: \"odori\",\n colors: {\n background: \"#000000\",\n surface: \"#0a0a0a\",\n foreground: \"#ededed\",\n muted: \"#a1a1a1\",\n accent: \"#ffffff\",\n border: \"#1f1f1f\",\n /* A dark ground can take real black: the depth reads as a room. */\n shadow: \"rgba(0, 0, 0, 0.55)\",\n },\n /**\n * System faces, deliberately.\n *\n * A default that leads with a licensed family it does not ship is a default\n * that renders through a fallback on every machine while claiming not to —\n * and `odori test` is right to fail it, which meant a freshly scaffolded\n * project failed its own contract check the first time it installed a\n * component. A brand that wants Geist names it and adds the file, and the\n * check then protects that choice instead of punishing the default.\n */\n typography: {\n sans: 'system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n mono: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace',\n },\n fonts: [],\n logos: {},\n motion: {standard: [0.16, 1, 0.3, 1], staggerFrames: 4},\n audio: {cues: {}, targetLufs: -14},\n};\n\n/**\n * The same brand with the ramp turned over.\n *\n * A video has no viewer preference to read: an mp4 cannot answer a media\n * query, and a headless render has no system theme to inherit. What decides\n * whether a cut is light or dark is the brand it inherits, which is why a\n * component that takes its colours from the brand is already both and one\n * that hardcodes a hex is neither.\n *\n * So this is the light default a project can name in a layout, rather than a\n * mode the runtime switches into. Everything but the ramp is shared with\n * `defaultBrand`: the same faces, the same motion, the same audio target, so\n * moving between them changes the light in the room and nothing else.\n */\nexport const lightBrand: Brand = {\n ...defaultBrand,\n name: \"odori-light\",\n colors: {\n background: \"#ffffff\",\n surface: \"#f5f5f5\",\n foreground: \"#0a0a0a\",\n muted: \"#737373\",\n accent: \"#0a0a0a\",\n border: \"#e5e5e5\",\n /* A light ground takes a shadow tinted toward its own ink and kept\n shallow: black at the depth a dark stage wants reads as dirt here. */\n shadow: \"rgba(10, 12, 24, 0.13)\",\n },\n};\n\nexport const defineBrand = (input: BrandInput): Brand => ({\n kind: \"odori-brand\",\n name: input.name ?? defaultBrand.name,\n colors: {...defaultBrand.colors, ...input.colors},\n typography: {...defaultBrand.typography, ...input.typography},\n fonts: input.fonts ?? [],\n logos: {...input.logos},\n motion: {...defaultBrand.motion, ...input.motion},\n audio: {...defaultBrand.audio, ...input.audio, cues: {...defaultBrand.audio.cues, ...input.audio?.cues}},\n});\n\nexport const brandCssVariables = (brand: Brand): Record<string, string> => ({\n \"--odori-background\": brand.colors.background,\n \"--odori-surface\": brand.colors.surface,\n \"--odori-foreground\": brand.colors.foreground,\n \"--odori-muted\": brand.colors.muted,\n \"--odori-accent\": brand.colors.accent,\n \"--odori-border\": brand.colors.border,\n \"--odori-font-sans\": brand.typography.sans,\n \"--odori-font-mono\": brand.typography.mono,\n});\n","import {type Brand, defaultBrand} from \"./brand\";\n\nexport type VideoFormat = {width: number; height: number; fps: number};\nexport type SafeArea = {x: number; y: number};\nexport type MotionPolicy = {enter: [number, number, number, number]; staggerFrames: number; reducedMotion?: boolean};\nexport type AudioPolicy = {palette: string; targetLufs: number};\n\nexport type VideoLayout = {\n readonly kind: \"odori-layout\";\n format: VideoFormat;\n brand: Brand;\n safeArea: SafeArea;\n motion: MotionPolicy;\n audio: AudioPolicy;\n transition: {crossfadeFrames: number};\n};\n\nexport type VideoLayoutInput = {\n extends?: VideoLayout;\n format?: Partial<VideoFormat>;\n brand?: Brand;\n safeArea?: SafeArea;\n motion?: Partial<MotionPolicy>;\n audio?: Partial<AudioPolicy>;\n transition?: {crossfadeFrames: number};\n};\n\nexport const defaultLayout: VideoLayout = {\n kind: \"odori-layout\",\n format: {width: 1920, height: 1080, fps: 30},\n brand: defaultBrand,\n safeArea: {x: 96, y: 72},\n motion: {enter: [0.16, 1, 0.3, 1], staggerFrames: 4},\n audio: {palette: \"product-cinematic\", targetLufs: -14},\n transition: {crossfadeFrames: 8},\n};\n\n/**\n * Merge rules from the docs: scalar format values replace, safe areas replace\n * as a unit, motion and audio policies merge onto the inherited policy, and\n * brand replaces because a brand is itself a resolved policy object.\n */\nexport const defineVideoLayout = (input: VideoLayoutInput = {}): VideoLayout => {\n const base = input.extends ?? defaultLayout;\n const brand = input.brand ?? base.brand;\n /**\n * Loudness is stated on the brand. That is where the docs put it, where the\n * scaffold writes it, and where `odori add` edits it, so a brand that asks\n * for a quiet mix has to get one: reading a layout default instead meant the\n * encoder normalised every project to the same number and the brand's own\n * figure was silently discarded. A layout that names a target still wins,\n * and so does one inherited from a layout that named one.\n */\n const inheritedTarget =\n base.audio.targetLufs === defaultLayout.audio.targetLufs ? brand.audio.targetLufs : base.audio.targetLufs;\n return {\n kind: \"odori-layout\",\n format: {...base.format, ...input.format},\n brand,\n safeArea: input.safeArea ?? base.safeArea,\n motion: {...base.motion, ...input.motion},\n audio: {...base.audio, targetLufs: inheritedTarget, ...input.audio},\n transition: input.transition ?? base.transition,\n };\n};\n","\"use client\";\n\nimport {\n Children,\n cloneElement,\n createContext,\n isValidElement,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ComponentPropsWithoutRef,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport {\n AssetContext,\n AudioSinkContext,\n BrandContext,\n ForceMountContext,\n FrameContext,\n LayoutContext,\n ReadinessContext,\n SceneContext,\n createAssetRegistry,\n useAssets,\n useFrame,\n useVideo,\n type Readiness,\n} from \"./context\";\nimport {\n cueId,\n isAssetReference,\n sampleGainCurve,\n sortCues,\n trackDuration,\n type AudioCue,\n type AudioProps,\n type AudioTrack,\n} from \"./audio\";\nimport {cueUrl, isCueDefinition, type CueDefinition} from \"./cue\";\nimport {Easing, interpolate} from \"./easing\";\nimport {brandCssVariables, defaultBrand, type Brand} from \"./brand\";\nimport {type VideoLayout, defaultLayout} from \"./layout\";\nimport {framesFromDuration, framesFromOffset, type Duration} from \"./time\";\n\nexport type CompiledScene = {\n id: string;\n name?: string;\n index: number;\n start: number;\n durationInFrames: number;\n /** Frames this scene starts before the previous one ends. Zero is a cut. */\n overlap: number;\n};\n/**\n * A clip on the video's own clock. Clips placed inside a scene belong to that\n * scene's internals and are not listed here: this is the video timeline.\n */\nexport type CompiledClip = {\n id: string;\n index: number;\n start: number;\n durationInFrames: number;\n};\n\nexport type CompiledTimeline = {\n scenes: CompiledScene[];\n /** Absolutely placed children of `<Video>`, in source order. */\n clips: CompiledClip[];\n durationInFrames: number;\n};\n\ntype TimelineSink = {report(timeline: CompiledTimeline): void};\n\nconst TimelineContext = createContext<TimelineSink | null>(null);\n\n/**\n * The active audio variant's cue overrides, provided by whatever mounted the\n * video: Studio's player when auditioning, the render surface when exporting.\n */\nexport const AudioVariantContext = createContext<Record<string, string | CueDefinition> | null>(null);\n\nexport const Fill = ({style, ...props}: ComponentPropsWithoutRef<\"div\">) => (\n <div\n {...props}\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n overflow: \"hidden\",\n ...style,\n }}\n />\n);\n\n/** Content inset by the inherited layout safe area. */\nexport const SafeArea = ({children, style}: {children: ReactNode; style?: CSSProperties}) => {\n const layout = useContext(LayoutContext) ?? defaultLayout;\n return (\n <Fill style={{padding: `${layout.safeArea.y}px ${layout.safeArea.x}px`, ...style}}>{children}</Fill>\n );\n};\n\ntype SceneProps = {\n children: ReactNode;\n duration: Duration;\n id?: string;\n name?: string;\n /**\n * Start this many frames before the previous scene ends, so both are on\n * screen together and a transition can span the cut. The video gets shorter\n * by the same amount: an overlap is a join, not an extra beat.\n */\n overlap?: Duration;\n /**\n * How this scene meets its neighbours. `fade` dissolves it in and out, which\n * is forgiving when a scene has no entrance of its own. `cut` shows it whole\n * from its first frame, which is what most film actually does and the only\n * way to reproduce a reference that cuts.\n */\n transition?: \"fade\" | \"cut\";\n /** Injected by <Video>. Authors never pass these. */\n __start?: number;\n __index?: number;\n __durationInFrames?: number;\n __enterOverlap?: number;\n __exitOverlap?: number;\n __videoFrame?: number;\n};\n\nexport type SceneTransition = {\n /** 0 to 1 while this scene is arriving over its predecessor. */\n entering: number;\n /** 0 to 1 while its successor is arriving over it. */\n leaving: number;\n /** Frames of overlap at each end, for a component that needs the raw counts. */\n enterFrames: number;\n exitFrames: number;\n};\n\nconst SceneTransitionContext = createContext<SceneTransition>({\n entering: 1,\n leaving: 0,\n enterFrames: 0,\n exitFrames: 0,\n});\n\n/**\n * How far this scene is through the joins at its edges.\n *\n * A transition that spans a cut needs to know two things the scene itself\n * cannot see: that it is arriving over something, and that something is\n * arriving over it. Both are numbers rather than a reference to the other\n * scene — a component that could reach into its neighbour's tree would couple\n * two scenes together, and the point of the timeline is that they compose.\n *\n * Outside an overlap this reports a scene fully present and nothing leaving,\n * so a component written against it also works on an ordinary cut.\n */\nexport const useSceneTransition = (): SceneTransition => useContext(SceneTransitionContext);\n\nconst SceneEnvelope = ({children, durationInFrames}: {children: ReactNode; durationInFrames: number}) => {\n const frame = useFrame();\n const edge = Math.max(1, Math.min(10, Math.floor(durationInFrames / 6)));\n const opacity = interpolate(\n frame,\n [0, edge, Math.max(edge + 1, durationInFrames - edge), Math.max(edge + 2, durationInFrames - 1)],\n [0, 1, 1, 0],\n {easing: Easing.standard},\n );\n return <Fill style={{opacity}}>{children}</Fill>;\n};\n\n/**\n * A timeline segment with a local frame clock starting at zero.\n */\nexport const Scene = ({\n children,\n id,\n name,\n transition: treatment = \"fade\",\n __start = 0,\n __index = 0,\n __durationInFrames = 1,\n __enterOverlap = 0,\n __exitOverlap = 0,\n __videoFrame = 0,\n}: SceneProps) => {\n const config = useVideo();\n const forceMount = useContext(ForceMountContext);\n const sceneId = id ?? `scene-${__index + 1}`;\n const localFrame = __videoFrame - __start;\n const active = localFrame >= 0 && localFrame < __durationInFrames;\n if (!active && !forceMount) return null;\n\n const joined = __enterOverlap > 0 || __exitOverlap > 0;\n const transition: SceneTransition = {\n entering: __enterOverlap > 0 ? Math.min(1, Math.max(0, localFrame / __enterOverlap)) : 1,\n leaving:\n __exitOverlap > 0\n ? Math.min(1, Math.max(0, (localFrame - (__durationInFrames - __exitOverlap)) / __exitOverlap))\n : 0,\n enterFrames: __enterOverlap,\n exitFrames: __exitOverlap,\n };\n\n return (\n <SceneContext.Provider value={{id: sceneId, name, index: __index, start: __start, durationInFrames: __durationInFrames}}>\n <FrameContext.Provider\n value={{\n frame: Math.max(0, Math.min(__durationInFrames - 1, localFrame)),\n fps: config.fps,\n width: config.width,\n height: config.height,\n durationInFrames: __durationInFrames,\n }}\n >\n <SceneTransitionContext.Provider value={transition}>\n <Fill data-odori-scene={sceneId}>\n {/* Two scenes own their own edges: a joined one, because the\n envelope would fade it out underneath whatever the transition\n is doing, and one that asked to cut, because a dissolve is\n exactly what it said it did not want. */}\n {joined || treatment === \"cut\" ? (\n <Fill>{children}</Fill>\n ) : (\n <SceneEnvelope durationInFrames={__durationInFrames}>{children}</SceneEnvelope>\n )}\n </Fill>\n </SceneTransitionContext.Provider>\n </FrameContext.Provider>\n </SceneContext.Provider>\n );\n};\nScene.__odoriScene = true as const;\n\n/** An explicit offset window inside a scene, for staggered layers. */\nexport const Stagger = ({children, from = 0, duration}: {children: ReactNode; from?: Duration; duration?: Duration}) => {\n const config = useVideo();\n const frame = useFrame();\n const start = typeof from === \"number\" && Number.isInteger(from) && from > 1 ? from : framesFromDuration(from, config.fps);\n const window = duration === undefined ? config.durationInFrames : framesFromDuration(duration, config.fps);\n const local = frame - start;\n if (local < 0 || local >= window) return null;\n return (\n <FrameContext.Provider value={{...config, frame: local, durationInFrames: window}}>\n <Fill>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\n/**\n * Repeat children on a local clock.\n *\n * The frame inside is the frame within one pass, so a component written for a\n * two second window needs no idea it is being repeated. Everything stays a\n * pure function of the frame — the tenth repetition is not the first one\n * mutated ten times, it is the same computation with the same input, which is\n * why scrubbing backwards into a loop lands exactly where playing forwards\n * did.\n */\nexport const Repeat = ({\n children,\n duration,\n times,\n}: {\n children: ReactNode;\n /** Length of one pass. */\n duration: Duration;\n /** How many passes before it stops. Unlimited by default. */\n times?: number;\n}) => {\n const config = useVideo();\n const frame = useFrame();\n const window = Math.max(1, framesFromDuration(duration, config.fps));\n const pass = Math.floor(frame / window);\n if (times !== undefined && pass >= times) return null;\n\n return (\n <FrameContext.Provider value={{...config, frame: frame % window, durationInFrames: window}}>\n <Fill>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\n/**\n * Content placed at a time, rather than after the thing before it.\n *\n * A scene is a length in a sequence: it starts where the previous one ended,\n * so its position is a consequence of its siblings. That is the right model\n * for the spine of a cut, and the wrong one for anything that has to appear\n * over a scene already running — a lower third, a callout, a caption, a mark\n * that outlives the shot it started in. A clip is placed instead: it names its\n * own start, takes no space in the sequence, and moves nothing when its length\n * changes.\n *\n * It reads its clock from wherever it sits, which is what makes it compose. A\n * direct child of `<Video>` is placed against the whole cut; the same clip\n * inside a `<Scene>` is placed against that scene and disappears with it. Its\n * children see a clock starting at zero either way, so a component written for\n * a scene behaves identically inside a clip.\n *\n * Without a `duration` it runs to the end of whatever it is placed in, which\n * is what a watermark wants.\n */\nexport const Clip = ({\n children,\n id,\n from = 0,\n duration,\n}: {\n children: ReactNode;\n /** Names it on the timeline. Defaults to its position among the clips. */\n id?: string;\n /** When it starts, on the clock of whatever contains it. */\n from?: Duration;\n /** How long it lasts. To the end of the container by default. */\n duration?: Duration;\n}) => {\n const config = useVideo();\n const frame = useFrame();\n const forceMount = useContext(ForceMountContext);\n const start = framesFromOffset(from, config.fps);\n const window =\n duration === undefined\n ? Math.max(1, config.durationInFrames - start)\n : framesFromDuration(duration, config.fps);\n const local = frame - start;\n const active = local >= 0 && local < window;\n // A still of one frame mounts everything, so a contact sheet and the\n // component catalog can show a clip that this frame is not inside.\n if (!active && !forceMount) return null;\n\n return (\n <FrameContext.Provider\n value={{...config, frame: Math.max(0, Math.min(window - 1, local)), durationInFrames: window}}\n >\n <Fill data-odori-clip={id ?? true}>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\nClip.__odoriClip = true as const;\n\n/**\n * Hold children at one frame.\n *\n * Useful for the still half of a comparison, for a poster frame, and for\n * pausing an animation on the moment that makes the point. It is the frame\n * clock that stops, not the render: children still mount and lay out\n * normally, they simply always see the same number.\n */\nexport const Hold = ({children, at = 0}: {children: ReactNode; at?: Duration}) => {\n const config = useVideo();\n const held = framesFromDuration(at, config.fps);\n return (\n <FrameContext.Provider value={{...config, frame: Math.max(0, held)}}>\n <Fill>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\n/**\n * A sound placed on the timeline. Audio is declarative like a scene: it\n * registers a cue rather than starting playback, so the player, a still, and\n * the encoder all read the same track.\n */\nexport const Audio = ({\n src,\n from = 0,\n duration,\n gain = 1,\n fadeIn = 0,\n fadeOut = 0,\n trimStart = 0,\n loop,\n duckUnder = false,\n}: AudioProps) => {\n const config = useVideo();\n const sink = useContext(AudioSinkContext);\n const scene = useContext(SceneContext);\n const assets = useAssets();\n const brand = useContext(BrandContext) ?? defaultBrand;\n const frames = (value: Duration) => framesFromDuration(value, config.fps);\n // A path is a path. A name the brand knows is whatever the brand points it\n // at, which is how one edit re-scores every video. Anything else is an\n // asset reference.\n const variant = useContext(AudioVariantContext);\n // A variant renames nothing: it re-points cue names the composition\n // already uses, so the same <Audio src> plays a different file and the\n // video itself never knows which voice it has.\n const named = variant?.[src] ?? brand.audio.cues[src];\n // A generated cue names itself by content, so the same score is fetched,\n // decoded, and mixed once however many videos ask for it.\n const source = isCueDefinition(named) ? cueUrl(named) : (named ?? src);\n const url = isAssetReference(source) ? assets.resolve(source) : source;\n // A looping cue is one phrase that repeats, and it says so. Taking the\n // default from the score means a bed fills its scene without every\n // composition restating what the cue already declares; `loop` on the element\n // is still the override when a video wants the other behaviour.\n const repeats = loop ?? (isCueDefinition(named) ? named.loops : false);\n\n if (sink) {\n const offset = (from === 0 ? 0 : frames(from)) + (scene?.start ?? 0);\n const window = duration === undefined ? (scene?.durationInFrames ?? config.durationInFrames) : frames(duration);\n sink.register({\n id: cueId(url, offset),\n src: url,\n fromFrame: offset,\n durationInFrames: window,\n // A curve is sampled here, once, where the window is known. Downstream\n // only ever sees points.\n gain: typeof gain === \"function\" ? 1 : gain,\n gainPoints: typeof gain === \"function\" ? sampleGainCurve(gain, offset, window) : undefined,\n fadeInFrames: fadeIn === 0 ? 0 : frames(fadeIn),\n fadeOutFrames: fadeOut === 0 ? 0 : frames(fadeOut),\n trimStartSeconds: trimStart === 0 ? 0 : frames(trimStart) / config.fps,\n loop: repeats,\n duckUnder,\n });\n }\n\n return null;\n};\nAudio.__odoriAudio = true as const;\n\nconst isSceneElement = (node: ReactNode): node is ReactElement<SceneProps> =>\n isValidElement(node) && (node.type as {__odoriScene?: boolean})?.__odoriScene === true;\n\ntype ClipProps = {children: ReactNode; id?: string; from?: Duration; duration?: Duration};\n\nconst isClipElement = (node: ReactNode): node is ReactElement<ClipProps> =>\n isValidElement(node) && (node.type as {__odoriClip?: boolean})?.__odoriClip === true;\n\n/**\n * Provides timeline structure. `<Video>` adds no duration of its own: scene\n * children are laid out in order, other children render for the whole video.\n */\nexport const Video = ({children, style}: {children: ReactNode; style?: CSSProperties}) => {\n const config = useVideo();\n const frame = useFrame();\n const sink = useContext(TimelineContext);\n const reported = useRef(\"\");\n const nodes = Children.toArray(children);\n\n const {laidOut, timeline} = useMemo(() => {\n let cursor = 0;\n let sceneIndex = 0;\n const scenes: CompiledScene[] = [];\n const overlaps: number[] = [];\n\n // Two passes: the first places every scene, because a scene's exit overlap\n // is the next scene's entry overlap and is therefore not known until the\n // next scene has been read.\n const placed = nodes.map((node) => {\n if (!isSceneElement(node)) return null;\n const durationInFrames = Math.max(1, framesFromDuration(node.props.duration, config.fps));\n const requested = node.props.overlap === undefined ? 0 : framesFromDuration(node.props.overlap, config.fps);\n // An overlap cannot be longer than either scene, or the two would cross\n // and the timeline would run backwards.\n const previous = scenes[scenes.length - 1];\n const overlap =\n sceneIndex === 0 ? 0 : Math.max(0, Math.min(requested, durationInFrames, previous?.durationInFrames ?? 0));\n\n const start = Math.max(0, cursor - overlap);\n const index = sceneIndex;\n scenes.push({id: node.props.id ?? `scene-${index + 1}`, name: node.props.name, index, start, durationInFrames, overlap});\n overlaps.push(overlap);\n cursor = start + durationInFrames;\n sceneIndex += 1;\n return {start, index, durationInFrames};\n });\n\n let placedIndex = 0;\n const output = nodes.map((node, key) => {\n if (!isSceneElement(node)) return node;\n const entry = placed[key] ?? placed.find((item) => item !== null);\n const index = placedIndex;\n placedIndex += 1;\n if (!entry) return node;\n return cloneElement(node, {\n key: `odori-scene-${key}`,\n __start: entry.start,\n __index: entry.index,\n __durationInFrames: entry.durationInFrames,\n __enterOverlap: overlaps[index] ?? 0,\n __exitOverlap: overlaps[index + 1] ?? 0,\n });\n });\n // Clips are placed, so they are read after the spine is known: an open\n // ended clip runs to the end of the video, and the video is as long as\n // its scenes unless its metadata says otherwise.\n const total = Math.max(cursor, config.durationInFrames);\n const clips: CompiledClip[] = [];\n for (const node of nodes) {\n if (!isClipElement(node)) continue;\n const index = clips.length;\n const start = framesFromOffset(node.props.from ?? 0, config.fps);\n const durationInFrames =\n node.props.duration === undefined\n ? Math.max(1, total - start)\n : framesFromDuration(node.props.duration, config.fps);\n clips.push({id: node.props.id ?? `clip-${index + 1}`, index, start, durationInFrames});\n }\n\n return {laidOut: output, timeline: {scenes, clips, durationInFrames: cursor}};\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [children, config.fps]);\n\n // The compiled timeline is recomputed whenever children change identity,\n // which happens on every frame. Report only when it actually differs, so a\n // consumer that stores it in state cannot be driven into a render loop.\n const publish = () => {\n const signature = JSON.stringify(timeline);\n if (signature === reported.current) return;\n reported.current = signature;\n sink?.report(timeline);\n };\n\n // On the server there is no effect phase, so report inline. In the browser\n // the effect keeps the report out of the render pass.\n if (typeof window === \"undefined\") publish();\n useEffect(publish);\n\n return (\n <Fill data-odori-video style={style}>\n {laidOut.map((node) =>\n isSceneElement(node) ? cloneElement(node, {__videoFrame: frame}) : node,\n )}\n </Fill>\n );\n};\n\nexport type VideoEntry = {\n component: (props: any) => ReactNode;\n metadata: {\n id: string;\n title: string;\n description?: string;\n duration?: Duration;\n layout?: VideoLayout;\n schema?: {parse(input: unknown): unknown};\n defaultProps?: Record<string, unknown>;\n tags?: string[];\n thumbnailFrame?: number;\n audio?: {variants?: Record<string, Record<string, string | CueDefinition>>};\n };\n};\n\nexport type OdoriRuntimeProps = {\n entry: VideoEntry;\n frame: number;\n input?: Record<string, unknown>;\n prepared?: unknown;\n assets?: Array<{reference: string; url: string}>;\n layout?: VideoLayout;\n onTimeline?: (timeline: CompiledTimeline) => void;\n onAudio?: (track: AudioTrack) => void;\n};\n\nexport const resolveEntryLayout = (entry: VideoEntry, override?: VideoLayout): VideoLayout =>\n override ?? entry.metadata.layout ?? defaultLayout;\n\nexport const entryDurationInFrames = (entry: VideoEntry, layout: VideoLayout): number =>\n entry.metadata.duration === undefined ? 0 : framesFromDuration(entry.metadata.duration, layout.format.fps);\n\nconst fontFaceCss = (fonts: Brand[\"fonts\"]) =>\n fonts\n .map(\n (font) =>\n `@font-face{font-family:\"${font.family}\";src:url(\"${font.url}\") format(\"woff2\");font-weight:${\n font.weight ?? \"100 900\"\n };font-display:block;font-style:normal;}`,\n )\n .join(\"\");\n\n/** Faces already installed in this document, so a remount never reloads them. */\nconst installedFonts = new Set<string>();\n\n/**\n * Brand fonts are declared as policy, not imported by components, so preview,\n * stills, and the export worker all load the same faces before the first frame\n * is captured.\n *\n * In the browser the rules are installed once at the document level and never\n * removed. Rendering them inside the tree would drop every face each time a\n * video unmounts, which reloads the fonts and reflows the whole page.\n */\nconst FontFaces = ({fonts}: {fonts: Brand[\"fonts\"]}) => {\n const css = useMemo(() => fontFaceCss(fonts), [fonts]);\n\n useEffect(() => {\n if (!css || installedFonts.has(css)) return;\n installedFonts.add(css);\n const style = document.createElement(\"style\");\n style.dataset.odoriFonts = \"\";\n style.textContent = css;\n document.head.append(style);\n }, [css]);\n\n // Server rendering has no effect phase, so the faces stay inline there.\n return typeof window === \"undefined\" && css ? <style data-odori-fonts>{css}</style> : null;\n};\n\n/**\n * Mounts every scene at once, invisibly, so `<Audio>` cues can be collected\n * without seeking through the whole timeline first.\n */\nconst AudioPass = ({\n children,\n onAudio,\n durationInFrames,\n}: {\n children: ReactNode;\n onAudio: (track: AudioTrack) => void;\n durationInFrames: number;\n}) => {\n const collected = useRef(new Map<string, AudioCue>());\n const reported = useRef(\"\");\n const sink = useMemo(\n () => ({\n register: (cue: AudioCue) => {\n collected.current.set(cue.id, cue);\n },\n }),\n [],\n );\n\n collected.current.clear();\n const tree = (\n <AudioSinkContext.Provider value={sink}>\n <ForceMountContext.Provider value>{children}</ForceMountContext.Provider>\n </AudioSinkContext.Provider>\n );\n\n const publish = () => {\n const cues = sortCues([...collected.current.values()]);\n const signature = JSON.stringify(cues);\n if (signature === reported.current) return;\n reported.current = signature;\n onAudio({cues, durationInFrames: Math.max(durationInFrames, trackDuration(cues))});\n };\n\n useEffect(publish);\n\n return (\n <div aria-hidden data-odori-audio-pass style={{display: \"none\"}}>\n {tree}\n {/* Rendered after the tree, so on the server every cue has registered\n by the time this publishes. */}\n <Publisher publish={publish} />\n </div>\n );\n};\n\nconst Publisher = ({publish}: {publish: () => void}) => {\n if (typeof window === \"undefined\") publish();\n return null;\n};\n\nexport const OdoriRuntime = ({entry, frame, input, prepared, assets, layout, onTimeline, onAudio}: OdoriRuntimeProps) => {\n const resolvedLayout = resolveEntryLayout(entry, layout);\n const {format, brand} = resolvedLayout;\n const registry = useMemo(() => createAssetRegistry(assets ?? []), [assets]);\n const sink = useMemo<TimelineSink>(() => ({report: (timeline) => onTimeline?.(timeline)}), [onTimeline]);\n\n const props = useMemo(() => {\n const merged = {...entry.metadata.defaultProps, ...input};\n return entry.metadata.schema ? (entry.metadata.schema.parse(merged) as Record<string, unknown>) : merged;\n }, [entry, input]);\n\n const durationInFrames = entryDurationInFrames(entry, resolvedLayout);\n const Composition = entry.component;\n\n // The frame attribute is the readiness handshake the render worker waits on,\n // so it is written only once every held handle has been released.\n const [pending, setPending] = useState(0);\n const readiness = useMemo<Readiness>(\n () => ({\n hold: () => {\n setPending((count) => count + 1);\n let released = false;\n return () => {\n if (released) return;\n released = true;\n setPending((count) => Math.max(0, count - 1));\n };\n },\n }),\n [],\n );\n\n return (\n <LayoutContext.Provider value={resolvedLayout}>\n <BrandContext.Provider value={brand}>\n <AssetContext.Provider value={registry}>\n <ReadinessContext.Provider value={readiness}>\n <TimelineContext.Provider value={sink}>\n <FrameContext.Provider\n value={{\n frame,\n fps: format.fps,\n width: format.width,\n height: format.height,\n durationInFrames: durationInFrames || 1,\n }}\n >\n <Fill\n data-odori-frame={pending === 0 ? frame : undefined}\n style={\n {\n backgroundColor: brand.colors.background,\n color: brand.colors.foreground,\n fontFamily: brand.typography.sans,\n ...brandCssVariables(brand),\n } as CSSProperties\n }\n >\n <FontFaces fonts={brand.fonts} />\n <Composition {...props} prepared={prepared} />\n {/* The pass is keyed by composition: it dedupes against what it\n last published, so a consumer that clears its track when the\n selection changes would otherwise never be handed the cues\n again for two videos that share one cue signature. */}\n {onAudio ? (\n <AudioPass key={entry.metadata.id} onAudio={onAudio} durationInFrames={durationInFrames}>\n <Composition {...props} prepared={prepared} />\n </AudioPass>\n ) : null}\n </Fill>\n </FrameContext.Provider>\n </TimelineContext.Provider>\n </ReadinessContext.Provider>\n </AssetContext.Provider>\n </BrandContext.Provider>\n </LayoutContext.Provider>\n );\n};\n"],"mappings":"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","names":["window","step","window","gain","loop"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { V as VideoEntry, a as VideoLayout, C as CompiledTimeline, A as AudioTrack } from './manifest-
|
|
2
|
-
export { b as Audio, c as AudioCue, d as AudioPolicy, e as AudioProps, f as AudioVariantContext, g as Clip, h as CompiledScene, i as CreateManifestOptions, D as DUCK_GAIN, j as DUCK_RAMP_FRAMES, E as EnvelopePoint, k as ExportJob, F as Fill, H as Hold, M as ManifestAsset, l as ManifestAudioCue, m as ManifestFont, n as MotionPolicy, O as OdoriRuntime, o as OdoriRuntimeProps, R as RenderManifest, p as Repeat, S as SafeArea, q as SafeAreaPolicy, r as Scene, s as SceneTransition, t as Stagger, u as Video, v as VideoFormat, w as VideoLayoutInput, x as createRenderManifest, y as cueId, z as defaultLayout, B as defineVideoLayout, G as duckEnvelope, I as entryDurationInFrames, J as envelopeAtFrame, K as gainAtFrame, L as isAssetReference, N as resolveEntryLayout, P as sampleGainCurve, Q as sortCues, T as trackDuration, U as trackGainAtFrame, W as useSceneTransition } from './manifest-
|
|
1
|
+
import { V as VideoEntry, a as VideoLayout, C as CompiledTimeline, A as AudioTrack } from './manifest-2usUa-uA.js';
|
|
2
|
+
export { b as Audio, c as AudioCue, d as AudioPolicy, e as AudioProps, f as AudioVariantContext, g as Clip, h as CompiledScene, i as CreateManifestOptions, D as DUCK_GAIN, j as DUCK_RAMP_FRAMES, E as EnvelopePoint, k as ExportJob, F as Fill, H as Hold, M as ManifestAsset, l as ManifestAudioCue, m as ManifestFont, n as MotionPolicy, O as OdoriRuntime, o as OdoriRuntimeProps, R as RenderManifest, p as Repeat, S as SafeArea, q as SafeAreaPolicy, r as Scene, s as SceneTransition, t as Stagger, u as Video, v as VideoFormat, w as VideoLayoutInput, x as createRenderManifest, y as cueId, z as defaultLayout, B as defineVideoLayout, G as duckEnvelope, I as entryDurationInFrames, J as envelopeAtFrame, K as gainAtFrame, L as isAssetReference, N as resolveEntryLayout, P as sampleGainCurve, Q as sortCues, T as trackDuration, U as trackGainAtFrame, W as useSceneTransition } from './manifest-2usUa-uA.js';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import { CSSProperties, RefObject } from 'react';
|
|
5
|
-
import { B as Brand, S as Signal, D as Duration, C as CueDefinition } from './brand-
|
|
6
|
-
export { a as BrandColors, b as BrandFont, c as BrandInput, d as BrandTypography, e as ChordQuality, f as CueContext, E as Envelope, g as SAMPLE_RATE, h as Step, i as brandCssVariables, j as chord, k as cueSamples, l as cueSignature, m as cueUrl, n as defaultBrand, o as defineBrand, p as defineCue, q as formatTimecode, r as framesFromDuration, s as framesFromOffset, t as gain, u as highPass, v as isCueDefinition, w as
|
|
5
|
+
import { B as Brand, S as Signal, D as Duration, C as CueDefinition } from './brand-CGe0NYqW.js';
|
|
6
|
+
export { a as BrandColors, b as BrandFont, c as BrandInput, d as BrandTypography, e as ChordQuality, f as CueContext, E as Envelope, g as SAMPLE_RATE, h as Step, i as brandCssVariables, j as chord, k as cueSamples, l as cueSignature, m as cueUrl, n as defaultBrand, o as defineBrand, p as defineCue, q as formatTimecode, r as framesFromDuration, s as framesFromOffset, t as gain, u as highPass, v as isCueDefinition, w as lightBrand, x as loop, y as lowPass, z as mix, A as noise, F as normalize, G as note, H as pad, I as seamless, J as seconds, K as secondsFromFrames, L as seeded, M as sequence, N as shape, O as silence, P as sine, Q as step, R as sweep, T as triangle } from './brand-CGe0NYqW.js';
|
|
7
7
|
import { P as ParsableSchema } from './schema-DXxRezrk.js';
|
|
8
8
|
export { F as FieldDescriptor, I as InputSchema, d as defineInputSchema, i as isOdoriSchema, p as parseWithSchema } from './schema-DXxRezrk.js';
|
|
9
9
|
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
interpolate,
|
|
36
36
|
isAssetReference,
|
|
37
37
|
isCueDefinition,
|
|
38
|
+
lightBrand,
|
|
38
39
|
loop,
|
|
39
40
|
lowPass,
|
|
40
41
|
mix,
|
|
@@ -59,7 +60,7 @@ import {
|
|
|
59
60
|
trackGainAtFrame,
|
|
60
61
|
triangle,
|
|
61
62
|
useSceneTransition
|
|
62
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-7GHTRLJ7.js";
|
|
63
64
|
import {
|
|
64
65
|
createAssetRegistry,
|
|
65
66
|
useAssets,
|
|
@@ -920,6 +921,7 @@ export {
|
|
|
920
921
|
isCueDefinition,
|
|
921
922
|
isOdoriSchema,
|
|
922
923
|
isValidVideoId,
|
|
924
|
+
lightBrand,
|
|
923
925
|
loop,
|
|
924
926
|
lowPass,
|
|
925
927
|
mix,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loudness.ts","../src/viewer.tsx","../src/playback.ts","../src/audio-playback.ts","../src/narration.ts","../src/render-surface.tsx","../src/wav.ts","../src/cursor.ts","../src/typing.ts","../src/random.ts","../src/canvas.ts","../src/placeholder.ts","../src/metadata.ts","../src/schema.ts"],"sourcesContent":["/**\n * Integrated loudness, ITU-R BS.1770-4.\n *\n * The number a mix is judged by is not peak or RMS: it is K-weighted, gated\n * loudness. Studio measures what it is about to hand the encoder so the\n * brand's target is something you can mix toward rather than discover after an\n * export.\n */\n\nexport type Biquad = {b0: number; b1: number; b2: number; a1: number; a2: number};\n\n/** Stage 1: the head shelf, and stage 2: the high pass, from the spec's filter table. */\nconst SHELF = {frequency: 1681.974450955533, gainDb: 3.999843853973347, q: 0.7071752369554196};\nconst HIGH_PASS = {frequency: 38.13547087602444, q: 0.5003270373238773};\n\n/**\n * The spec tabulates coefficients at 48 kHz. Deriving them per rate keeps a\n * 44.1 kHz source from being measured with the wrong filter.\n */\nexport const shelfCoefficients = (sampleRate: number): Biquad => {\n const amplitude = 10 ** (SHELF.gainDb / 40);\n const omega = (2 * Math.PI * SHELF.frequency) / sampleRate;\n const alpha = Math.sin(omega) / (2 * SHELF.q);\n const cos = Math.cos(omega);\n const shared = 2 * Math.sqrt(amplitude) * alpha;\n const a0 = amplitude + 1 - (amplitude - 1) * cos + shared;\n return {\n b0: (amplitude * (amplitude + 1 + (amplitude - 1) * cos + shared)) / a0,\n b1: (-2 * amplitude * (amplitude - 1 + (amplitude + 1) * cos)) / a0,\n b2: (amplitude * (amplitude + 1 + (amplitude - 1) * cos - shared)) / a0,\n a1: (2 * (amplitude - 1 - (amplitude + 1) * cos)) / a0,\n a2: (amplitude + 1 - (amplitude - 1) * cos - shared) / a0,\n };\n};\n\nexport const highPassCoefficients = (sampleRate: number): Biquad => {\n const omega = (2 * Math.PI * HIGH_PASS.frequency) / sampleRate;\n const alpha = Math.sin(omega) / (2 * HIGH_PASS.q);\n const cos = Math.cos(omega);\n const a0 = 1 + alpha;\n return {\n b0: (1 + cos) / 2 / a0,\n b1: (-(1 + cos)) / a0,\n b2: (1 + cos) / 2 / a0,\n a1: (-2 * cos) / a0,\n a2: (1 - alpha) / a0,\n };\n};\n\nconst filter = (samples: Float32Array, {b0, b1, b2, a1, a2}: Biquad): Float32Array => {\n const output = new Float32Array(samples.length);\n let x1 = 0;\n let x2 = 0;\n let y1 = 0;\n let y2 = 0;\n for (let index = 0; index < samples.length; index += 1) {\n const x0 = samples[index];\n const y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;\n output[index] = y0;\n x2 = x1;\n x1 = x0;\n y2 = y1;\n y1 = y0;\n }\n return output;\n};\n\nconst BLOCK_SECONDS = 0.4;\n/** Blocks overlap by 75%, so a short transient cannot hide between them. */\nconst STEP = 0.25;\nconst ABSOLUTE_GATE = -70;\nconst RELATIVE_GATE = -10;\nconst OFFSET = -0.691;\n\nconst loudnessOf = (meanSquares: number[]) =>\n OFFSET + 10 * Math.log10(meanSquares.reduce((total, value) => total + value, 0) || Number.MIN_VALUE);\n\n/**\n * Channel weights for stereo. Surround weights the surround channels higher;\n * a video mix is stereo, so both channels count equally.\n */\nexport const integratedLufs = (channels: Float32Array[], sampleRate: number): number | null => {\n if (channels.length === 0 || channels[0].length === 0) return null;\n const weighted = channels.map((channel) => filter(filter(channel, shelfCoefficients(sampleRate)), highPassCoefficients(sampleRate)));\n\n const blockSize = Math.round(BLOCK_SECONDS * sampleRate);\n const hop = Math.round(BLOCK_SECONDS * STEP * sampleRate);\n if (weighted[0].length < blockSize) return null;\n\n // One mean square per channel per block, kept apart so gating can sum them.\n const blocks: number[][] = [];\n for (let start = 0; start + blockSize <= weighted[0].length; start += hop) {\n blocks.push(\n weighted.map((channel) => {\n let sum = 0;\n for (let index = start; index < start + blockSize; index += 1) sum += channel[index] * channel[index];\n return sum / blockSize;\n }),\n );\n }\n if (blocks.length === 0) return null;\n\n const above = blocks.filter((block) => loudnessOf(block) > ABSOLUTE_GATE);\n if (above.length === 0) return null;\n\n // The relative gate is measured against the ungated mean of what survived.\n const mean = above[0].map((_, channel) => above.reduce((total, block) => total + block[channel], 0) / above.length);\n const threshold = loudnessOf(mean) + RELATIVE_GATE;\n const gated = above.filter((block) => loudnessOf(block) > threshold);\n if (gated.length === 0) return null;\n\n const integrated = gated[0].map((_, channel) => gated.reduce((total, block) => total + block[channel], 0) / gated.length);\n return loudnessOf(integrated);\n};\n","\"use client\";\n\nimport {useCallback, useMemo, useState, type CSSProperties} from \"react\";\nimport {OdoriRuntime, entryDurationInFrames, resolveEntryLayout, type CompiledTimeline, type VideoEntry} from \"./runtime\";\nimport {type VideoLayout} from \"./layout\";\nimport {formatTimecode} from \"./time\";\nimport {usePlayback} from \"./playback\";\nimport {useAudioPlayback} from \"./audio-playback\";\nimport {type AudioTrack} from \"./audio\";\n\nexport type ViewerProps = {\n entry: VideoEntry;\n input?: Record<string, unknown>;\n prepared?: unknown;\n assets?: Array<{reference: string; url: string}>;\n layout?: VideoLayout;\n initialFrame?: number;\n autoPlay?: boolean;\n loop?: boolean;\n controls?: boolean;\n style?: CSSProperties;\n muted?: boolean;\n onFrame?: (frame: number) => void;\n onTimeline?: (timeline: CompiledTimeline) => void;\n onAudio?: (track: AudioTrack) => void;\n};\n\n/**\n * A composition, embeddable and seekable, for a product surface rather than\n * for Studio.\n *\n * Playback advances a fractional frame counter from wall-clock deltas, but\n * React only ever sees an integer frame, so a paused viewer and a render\n * worker produce identical output: what somebody watches in your app is the\n * file you would export.\n *\n * The controls here are the plain ones. A surface that wants its own transport\n * imports `usePlayback` instead and keeps this out of it, which is what Studio\n * and the documentation site both do.\n */\nexport const Viewer = ({\n entry,\n input,\n prepared,\n assets,\n layout,\n initialFrame = 0,\n autoPlay = false,\n loop = true,\n controls = true,\n muted = false,\n style,\n onFrame,\n onTimeline,\n onAudio,\n}: ViewerProps) => {\n const resolvedLayout = resolveEntryLayout(entry, layout);\n const {fps, width, height} = resolvedLayout.format;\n const [timeline, setTimeline] = useState<CompiledTimeline | null>(null);\n const [track, setTrack] = useState<AudioTrack | null>(null);\n const declared = entryDurationInFrames(entry, resolvedLayout);\n const durationInFrames = Math.max(1, declared || timeline?.durationInFrames || fps);\n\n const playback = usePlayback({fps, durationInFrames, initialFrame, autoPlay, loop, onFrame});\n const {frame} = playback;\n\n useAudioPlayback({track, frame: playback.frame, fps, playing: playback.playing, muted});\n\n const handleAudio = useCallback(\n (next: AudioTrack) => {\n setTrack(next);\n onAudio?.(next);\n },\n [onAudio],\n );\n\n const handleTimeline = useCallback(\n (next: CompiledTimeline) => {\n // Comparing the count and total would hold a stale timeline when two\n // scenes trade frames between them: same length, same total, different\n // boundaries.\n setTimeline((current) => (JSON.stringify(current) === JSON.stringify(next) ? current : next));\n onTimeline?.(next);\n },\n [onTimeline],\n );\n\n const aspectRatio = useMemo(() => `${width} / ${height}`, [height, width]);\n const activeScene = timeline?.scenes.find(\n (scene) => frame >= scene.start && frame < scene.start + scene.durationInFrames,\n );\n\n return (\n <div className=\"odori-viewer\" style={{display: \"grid\", gap: 12, width: \"100%\", ...style}}>\n <div\n data-odori-viewer\n style={{\n aspectRatio,\n background: resolvedLayout.brand.colors.background,\n borderRadius: 10,\n overflow: \"hidden\",\n position: \"relative\",\n width: \"100%\",\n }}\n >\n <OdoriRuntime\n entry={entry}\n frame={frame}\n input={input}\n prepared={prepared}\n assets={assets}\n layout={layout}\n onTimeline={handleTimeline}\n onAudio={handleAudio}\n />\n </div>\n {controls ? (\n <div className=\"odori-viewer-controls\" style={{alignItems: \"center\", display: \"flex\", gap: 10}}>\n <button type=\"button\" onClick={playback.toggle}>\n {playback.playing ? \"Pause\" : \"Play\"}\n </button>\n <button type=\"button\" onClick={() => playback.step(-1)} aria-label=\"Previous frame\">\n {\"\\u2039\"}\n </button>\n <button type=\"button\" onClick={() => playback.step(1)} aria-label=\"Next frame\">\n {\"\\u203a\"}\n </button>\n <input\n aria-label=\"Timeline\"\n type=\"range\"\n min={0}\n max={durationInFrames - 1}\n value={frame}\n onChange={(event) => {\n playback.pause();\n playback.seek(Number(event.currentTarget.value));\n }}\n style={{flex: 1}}\n />\n <output style={{fontVariantNumeric: \"tabular-nums\", minWidth: 132, textAlign: \"right\"}}>\n {formatTimecode(frame, fps)} {\"·\"} {frame}/{durationInFrames - 1}\n {activeScene ? ` · ${activeScene.name ?? activeScene.id}` : \"\"}\n </output>\n </div>\n ) : null}\n </div>\n );\n};\n","\"use client\";\n\nimport {useCallback, useEffect, useRef, useState} from \"react\";\n\nexport type PlaybackOptions = {\n fps: number;\n durationInFrames: number;\n initialFrame?: number;\n autoPlay?: boolean;\n loop?: boolean;\n /** Wall-clock multiplier. The frame clock keeps its rate; only time moves. */\n rate?: number;\n onFrame?: (frame: number) => void;\n};\n\nexport type Playback = {\n frame: number;\n playing: boolean;\n rate: number;\n play(): void;\n pause(): void;\n toggle(): void;\n seek(frame: number): void;\n step(delta: number): void;\n restart(): void;\n};\n\n/**\n * The seekable frame clock. Playback advances a fractional counter from\n * wall-clock deltas, but React only ever sees an integer frame, so a paused\n * player, a still, and the export worker agree by construction.\n */\n/**\n * How far the clock moves for a wall-clock delta.\n *\n * Rate scales elapsed time, never the frame index, so frame 90 is the same\n * image at 0.25x, 1x, and 4x, and an export ignores rate entirely.\n */\nexport const advanceFrames = (fractional: number, deltaMs: number, fps: number, rate = 1): number =>\n fractional + (deltaMs / 1000) * fps * rate;\n\n/**\n * The longest wall-clock gap the clock will believe in one tick.\n *\n * requestAnimationFrame stops firing when the tab is hidden, the window goes\n * to the background, or an embedded webview loses the cursor, and it resumes\n * with a single delta covering the entire gap. Handed to advanceFrames that is\n * a jump of hundreds of frames: a video that does not loop lands on its last\n * frame and stops, which is what a hang looks like from the outside, and one\n * that loops wraps to somewhere arbitrary.\n *\n * A gap this long is a suspended clock, not a slow frame, so it is capped\n * rather than trusted, and playback picks up a few frames on from where it\n * stopped. The cost is that a renderer slower than four frames a second falls\n * behind wall clock. That is the right way round for a preview: this clock\n * never touches an export, which the encoder drives frame by frame.\n */\nexport const MAX_TICK_MS = 250;\n\nexport const usePlayback = ({\n fps,\n durationInFrames,\n initialFrame = 0,\n autoPlay = false,\n loop = true,\n rate = 1,\n onFrame,\n}: PlaybackOptions): Playback => {\n const [frame, setFrame] = useState(initialFrame);\n const [playing, setPlaying] = useState(autoPlay);\n const animation = useRef<number | null>(null);\n const previousTime = useRef<number | null>(null);\n const fractional = useRef(initialFrame);\n const frameRef = useRef(initialFrame);\n\n const commit = useCallback(\n (next: number) => {\n const clamped = Math.max(0, Math.min(Math.round(next), Math.max(0, durationInFrames - 1)));\n frameRef.current = clamped;\n setFrame(clamped);\n onFrame?.(clamped);\n },\n [durationInFrames, onFrame],\n );\n\n useEffect(() => {\n if (!playing) {\n previousTime.current = null;\n fractional.current = frameRef.current;\n return;\n }\n const tick = (now: number) => {\n const previous = previousTime.current ?? now;\n previousTime.current = now;\n fractional.current = advanceFrames(fractional.current, Math.min(now - previous, MAX_TICK_MS), fps, rate);\n if (fractional.current >= durationInFrames) {\n if (!loop) {\n commit(durationInFrames - 1);\n setPlaying(false);\n return;\n }\n fractional.current %= durationInFrames;\n }\n commit(Math.floor(fractional.current));\n animation.current = requestAnimationFrame(tick);\n };\n animation.current = requestAnimationFrame(tick);\n return () => {\n if (animation.current !== null) cancelAnimationFrame(animation.current);\n };\n }, [commit, durationInFrames, fps, loop, playing, rate]);\n\n const seek = useCallback(\n (next: number) => {\n fractional.current = next;\n commit(next);\n },\n [commit],\n );\n\n return {\n frame,\n playing,\n rate,\n play: () => setPlaying(true),\n pause: () => setPlaying(false),\n toggle: () => setPlaying((value) => !value),\n seek,\n step: (delta: number) => {\n setPlaying(false);\n seek(frameRef.current + delta);\n },\n restart: () => {\n seek(0);\n setPlaying(true);\n },\n };\n};\n","\"use client\";\n\nimport {useEffect, useRef} from \"react\";\nimport {trackGainAtFrame, type AudioTrack} from \"./audio\";\n\nexport type AudioPlaybackOptions = {\n track: AudioTrack | null;\n frame: number;\n fps: number;\n playing: boolean;\n muted?: boolean;\n masterGain?: number;\n /** Cue id to hear alone, or null for the whole mix. */\n soloCue?: string | null;\n /**\n * True while the playhead is being moved by hand. Cues go quiet: auditioning\n * under the cursor sounds like a stuck record, because every frame reseeks\n * the element and you hear the same few milliseconds over and over.\n */\n scrubbing?: boolean;\n /** Playback rate, so cues stay with the frame clock when it is sped up. */\n rate?: number;\n /**\n * Called when the browser refuses to start a cue without a user gesture, and\n * again when it relents. Autoplay policy is the difference between a silent\n * preview and a broken one, so it is reported rather than swallowed.\n */\n onBlocked?: (blocked: boolean) => void;\n /**\n * Called with the cues whose files failed to load or decode. A cue pointing\n * at a missing or wrong file is silence with no other symptom, so it is\n * reported rather than swallowed.\n */\n onFailed?: (sources: string[]) => void;\n};\n\n/**\n * Drives one HTMLAudioElement per cue from the frame clock.\n *\n * Audio is the one thing that cannot be derived from a frame index, so preview\n * playback resyncs whenever the element drifts more than a frame from where the\n * timeline says it should be. The exported mix is built separately by the\n * encoder from the same cues, which keeps the file frame accurate.\n */\nexport const useAudioPlayback = ({\n track,\n frame,\n fps,\n playing,\n muted = false,\n masterGain = 1,\n soloCue = null,\n scrubbing = false,\n rate = 1,\n onBlocked,\n onFailed,\n}: AudioPlaybackOptions) => {\n const elements = useRef(new Map<string, HTMLAudioElement>());\n // The sync pass, reachable from listeners that fire when the frame has not\n // changed: the clock can stall (a hidden tab throttles rAF, a slow render\n // drops frames) while an audio element keeps running at its own pace.\n const sync = useRef<() => void>(() => {});\n const failed = useRef(new Set<string>());\n\n useEffect(() => {\n const table = elements.current;\n const live = new Set((track?.cues ?? []).map((cue) => cue.id));\n for (const [id, element] of table) {\n if (live.has(id)) continue;\n element.pause();\n table.delete(id);\n }\n return () => {\n for (const element of table.values()) element.pause();\n };\n }, [track]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const table = elements.current;\n\n const onTimeUpdate = () => run();\n\n const run = () => {\n /*\n * A hidden tab is silent, and this is the check that makes it so. The\n * visibility listener below used to pause every element on its own, but\n * a hidden tab still ticks its throttled clock, so the very next frame\n * ran this pass, found the cue audible, and called play() again. What\n * came out was a cue restarted and reseeked once or twice a second for\n * as long as the tab stayed in the background, which is the glitching.\n * Deciding it here means the pause cannot be undone by the next tick.\n */\n const audible = playing && !scrubbing && !document.hidden;\n\n for (const cue of track?.cues ?? []) {\n let element = table.get(cue.id);\n if (!element) {\n element = new window.Audio(cue.src);\n element.preload = \"auto\";\n element.loop = cue.loop;\n // An element that drifts while the clock is stalled corrects itself\n // on its own time updates, so audio can never run away from a\n // frozen picture.\n element.addEventListener(\"timeupdate\", onTimeUpdate);\n element.addEventListener(\"error\", () => {\n failed.current.add(cue.src);\n onFailed?.([...failed.current]);\n });\n table.set(cue.id, element);\n }\n\n const local = frame - cue.fromFrame;\n const inside = local >= 0 && local < cue.durationInFrames;\n /**\n * Where in the file this frame sounds. A looping cue's window is\n * longer than its file, so the position wraps: seeking straight to\n * `local / fps` would land past the end and the browser would clamp\n * it, leaving the bed stuck on its final sample. The wrap needs the\n * file's real length, which only exists once metadata has loaded.\n */\n const span = element.duration;\n const elapsed = local / fps;\n const position =\n cue.loop && Number.isFinite(span) && span > 0 ? elapsed % span : elapsed;\n // Set every pass, not only at creation: an edit that turns looping on\n // does not change the cue's id, so the element it reuses would keep\n // the old behaviour.\n if (element.loop !== cue.loop) element.loop = cue.loop;\n element.volume = Math.max(0, Math.min(1, trackGainAtFrame(cue, track?.cues ?? [], frame) * masterGain));\n element.muted = muted || (soloCue !== null && soloCue !== cue.id);\n\n if (!inside || !audible) {\n if (!element.paused) element.pause();\n if (inside && !audible) {\n const target = cue.trimStartSeconds + position;\n if (Math.abs(element.currentTime - target) > 1 / fps) element.currentTime = target;\n }\n continue;\n }\n\n const target = cue.trimStartSeconds + position;\n // A rate change is a new playbackRate, not a reseek: the element keeps\n // playing and the drift check below catches it if it falls behind.\n if (element.playbackRate !== rate) element.playbackRate = rate;\n if (Math.abs(element.currentTime - target) > (2 / fps) * Math.max(1, rate)) element.currentTime = target;\n if (element.paused) {\n void element.play().then(\n () => onBlocked?.(false),\n (error: unknown) => onBlocked?.((error as Error)?.name === \"NotAllowedError\"),\n );\n }\n }\n };\n\n sync.current = run;\n run();\n\n return () => {\n for (const element of table.values()) element.removeEventListener(\"timeupdate\", onTimeUpdate);\n };\n }, [fps, frame, masterGain, muted, onBlocked, onFailed, playing, rate, scrubbing, soloCue, track]);\n\n // Visibility is not something React re-renders for, so the change is what\n // re-runs the pass. Which way it went does not matter: run() reads\n // document.hidden itself and either holds every cue or resyncs them to the\n // frame the timeline is actually showing.\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const onVisibility = () => sync.current();\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => document.removeEventListener(\"visibilitychange\", onVisibility);\n }, []);\n};\n","/**\n * Narration as source.\n *\n * A recorded voice is the one part of a video that usually lives outside the\n * project: a file somebody exported once, with captions transcribed after the\n * fact and timed by hand. Odori keeps it in the repository instead. The\n * artifact is a small JSON document holding the script, the voice, and the\n * time every word starts and ends, written by `odori narrate` in the same\n * call that writes the audio.\n *\n * Everything downstream derives from that document. Captions are computed\n * from the word timings at the project's fps, so they cannot drift from the\n * recording; a scene can hold until a sentence lands, because the sentence's\n * end is data; re-recording is one command, and the diff shows exactly which\n * words moved. The render never talks to a provider, because by then the\n * narration is just a file and some numbers.\n */\n\n/** One spoken word and when it happens, in seconds from the start. */\nexport type NarrationWord = {\n word: string;\n startSeconds: number;\n endSeconds: number;\n};\n\nexport type Narration = {\n /** The script as given, so a re-record can be diffed against it. */\n script: string;\n /** Provider and voice that read it, so a re-record sounds the same. */\n provider: string;\n voice: string;\n /** The audio file, as a path under public/ or a brand cue role. */\n audio: string;\n words: NarrationWord[];\n};\n\n/**\n * Character-level timing, the shape speech APIs return: three parallel\n * arrays, one entry per character of the spoken text.\n */\nexport type CharacterAlignment = {\n characters: string[];\n startSeconds: number[];\n endSeconds: number[];\n};\n\n/**\n * Fold character timings into word timings.\n *\n * A word runs from its first character's start to its last character's end.\n * Whitespace separates; punctuation stays attached to the word it follows,\n * because captions read better with it than without.\n */\nexport const wordsFromCharacters = (alignment: CharacterAlignment): NarrationWord[] => {\n const words: NarrationWord[] = [];\n let current = \"\";\n let start = 0;\n let end = 0;\n\n alignment.characters.forEach((character, index) => {\n if (/\\s/.test(character)) {\n if (current) words.push({word: current, startSeconds: start, endSeconds: end});\n current = \"\";\n return;\n }\n if (!current) start = alignment.startSeconds[index];\n current += character;\n end = alignment.endSeconds[index];\n });\n if (current) words.push({word: current, startSeconds: start, endSeconds: end});\n return words;\n};\n\nexport type CaptionCueTiming = {\n text: string;\n fromFrame: number;\n durationInFrames: number;\n};\n\nexport type CaptionOptions = {\n /** Upper bound per caption. Fewer arrive when punctuation or a pause ends one. */\n maxWords?: number;\n /** A silence at least this long ends the caption, because the speaker did. */\n breakOnGapSeconds?: number;\n /** Hold the last caption of a group this long after its final word. */\n hangSeconds?: number;\n};\n\n/**\n * Group word timings into caption cues at a given fps.\n *\n * Deterministic on purpose: the same narration and fps produce the same cues\n * on every machine, so captions are computed where they are used rather than\n * stored, and a change of fps or grouping is a re-render rather than a\n * re-transcription.\n *\n * A caption ends at a sentence mark, at a real pause, or at `maxWords`,\n * whichever comes first. It stays on screen until the next one starts or its\n * hang runs out, so text never vanishes mid-phrase.\n */\nexport const captionCues = (\n narration: Pick<Narration, \"words\">,\n fps: number,\n options: CaptionOptions = {},\n): CaptionCueTiming[] => {\n const {maxWords = 7, breakOnGapSeconds = 0.6, hangSeconds = 0.8} = options;\n const groups: NarrationWord[][] = [];\n let group: NarrationWord[] = [];\n\n narration.words.forEach((word, index) => {\n group.push(word);\n const next = narration.words[index + 1];\n const sentence = /[.!?]$/.test(word.word);\n const clause = /[,;:]$/.test(word.word) && group.length >= Math.ceil(maxWords / 2);\n const pause = next !== undefined && next.startSeconds - word.endSeconds >= breakOnGapSeconds;\n if (sentence || clause || pause || group.length >= maxWords || next === undefined) {\n groups.push(group);\n group = [];\n }\n });\n\n return groups.map((words, index) => {\n const start = words[0].startSeconds;\n const spoken = words[words.length - 1].endSeconds;\n const nextStart = groups[index + 1]?.[0].startSeconds;\n // Held to the next caption or the hang, but never past the next caption.\n const end = nextStart !== undefined ? Math.min(spoken + hangSeconds, nextStart) : spoken + hangSeconds;\n const fromFrame = Math.round(start * fps);\n return {\n text: words.map((entry) => entry.word).join(\" \"),\n fromFrame,\n durationInFrames: Math.max(1, Math.round(end * fps) - fromFrame),\n };\n });\n};\n\n/** The narration's last spoken moment, for sizing a scene to it. */\nexport const narrationEndSeconds = (narration: Pick<Narration, \"words\">): number =>\n narration.words.length > 0 ? narration.words[narration.words.length - 1].endSeconds : 0;\n","\"use client\";\n\nimport {useEffect, useState} from \"react\";\nimport {AudioVariantContext, OdoriRuntime, type CompiledTimeline, type VideoEntry} from \"./runtime\";\nimport {type AudioTrack} from \"./audio\";\nimport {type VideoLayout} from \"./layout\";\n\ndeclare global {\n interface Window {\n __ODORI_SET_FRAME__?: (frame: number) => void;\n __ODORI_TIMELINE__?: CompiledTimeline;\n __ODORI_AUDIO__?: AudioTrack;\n __ODORI_READY__?: boolean;\n }\n}\n\n/**\n * The surface the render worker drives. It exposes an explicit frame setter\n * and a readiness handshake instead of relying on timing heuristics.\n */\nexport const RenderSurface = ({\n entry,\n initialFrame = 0,\n input,\n prepared,\n assets,\n layout,\n audioVariant,\n}: {\n entry: VideoEntry;\n initialFrame?: number;\n input?: Record<string, unknown>;\n prepared?: unknown;\n assets?: Array<{reference: string; url: string}>;\n layout?: VideoLayout;\n /** A declared audio variant's name, so exports render the chosen voice. */\n audioVariant?: string;\n}) => {\n const [frame, setFrame] = useState(initialFrame);\n\n useEffect(() => {\n window.__ODORI_SET_FRAME__ = setFrame;\n window.__ODORI_READY__ = true;\n return () => {\n delete window.__ODORI_SET_FRAME__;\n delete window.__ODORI_READY__;\n };\n }, []);\n\n return (\n <AudioVariantContext.Provider value={(audioVariant && entry.metadata.audio?.variants?.[audioVariant]) || null}>\n <OdoriRuntime\n entry={entry}\n frame={frame}\n input={input}\n prepared={prepared}\n assets={assets}\n layout={layout}\n onTimeline={(timeline) => {\n window.__ODORI_TIMELINE__ = timeline;\n }}\n onAudio={(track) => {\n window.__ODORI_AUDIO__ = track;\n }}\n />\n </AudioVariantContext.Provider>\n );\n};\n","import {type Signal} from \"./synth\";\n\n/**\n * Signal to a 16 bit PCM WAV. Small, lossless, and readable by FFmpeg without\n * a decoder, which is all the mix needs from a generated cue.\n *\n * Encoding lives in the runtime rather than the CLI so preview and export\n * share it: the browser can hand the same bytes to an AudioContext that the\n * render worker writes to disk.\n */\nexport const encodeWav = (signal: Signal): Uint8Array => {\n const channels = signal.channels.length || 1;\n const frames = signal.channels[0]?.length ?? 0;\n const bytesPerSample = 2;\n const dataBytes = frames * channels * bytesPerSample;\n const buffer = new ArrayBuffer(44 + dataBytes);\n const view = new DataView(buffer);\n\n const ascii = (offset: number, text: string) => {\n for (let index = 0; index < text.length; index += 1) view.setUint8(offset + index, text.charCodeAt(index));\n };\n\n ascii(0, \"RIFF\");\n view.setUint32(4, 36 + dataBytes, true);\n ascii(8, \"WAVE\");\n ascii(12, \"fmt \");\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true); // PCM\n view.setUint16(22, channels, true);\n view.setUint32(24, signal.sampleRate, true);\n view.setUint32(28, signal.sampleRate * channels * bytesPerSample, true);\n view.setUint16(32, channels * bytesPerSample, true);\n view.setUint16(34, 8 * bytesPerSample, true);\n ascii(36, \"data\");\n view.setUint32(40, dataBytes, true);\n\n let offset = 44;\n for (let frame = 0; frame < frames; frame += 1) {\n for (let channel = 0; channel < channels; channel += 1) {\n const sample = signal.channels[channel]?.[frame] ?? 0;\n // Clamp before quantizing, so a hot score distorts predictably instead\n // of wrapping into noise.\n const clamped = Math.max(-1, Math.min(1, sample));\n view.setInt16(offset, Math.round(clamped * 32767), true);\n offset += bytesPerSample;\n }\n }\n\n return new Uint8Array(buffer);\n};\n","import {Easing, interpolate} from \"./easing\";\n\nexport type CursorStop = {\n /** Frame this stop is reached, from the start of the enclosing scene. */\n frame: number;\n /** Canvas coordinates, in the composition's own pixels. */\n x: number;\n y: number;\n /**\n * A click landing on this stop. The press is drawn at the stop's frame and\n * decays over a few frames, so the pointer visibly does the thing the UI is\n * about to react to.\n */\n click?: boolean;\n /** Hold here until this many frames have passed before moving on. */\n hold?: number;\n};\n\nexport type CursorState = {\n x: number;\n y: number;\n /** 0 before the path starts and after it ends, 1 while it is on screen. */\n visible: number;\n /** 1 at the instant of a click, decaying to 0. Drives the press ring. */\n pressed: number;\n /** True while a click is within its press window, for a UI to react to. */\n clicking: boolean;\n};\n\n/** Frames a press ring takes to expand and fade. */\nconst PRESS_FRAMES = 9;\n\n/**\n * Where an authored pointer is at this frame.\n *\n * Recording a real cursor would make a video that cannot be re-rendered: the\n * path would live in a file, not in the composition, and a change of copy or\n * canvas would leave it pointing at nothing. An authored path is source — it\n * diffs, it survives a reflow, and it produces the same pixels every run.\n *\n * Movement eases between stops rather than running linearly, because a pointer\n * that travels at constant speed reads as a machine. A `hold` keeps the\n * pointer still without needing a duplicate stop at the same coordinates.\n */\nexport const cursorAt = (stops: CursorStop[], frame: number): CursorState => {\n if (stops.length === 0) return {x: 0, y: 0, visible: 0, pressed: 0, clicking: false};\n\n // A hold extends the stop it is on, which shifts everything after it.\n const timed: CursorStop[] = [];\n let shift = 0;\n for (const stop of stops) {\n const start = stop.frame + shift;\n timed.push({...stop, frame: start});\n if (stop.hold) {\n timed.push({...stop, frame: start + stop.hold, click: false});\n shift += stop.hold;\n }\n }\n\n const first = timed[0];\n const last = timed[timed.length - 1];\n if (frame <= first.frame) return {x: first.x, y: first.y, visible: 0, pressed: 0, clicking: false};\n\n const frames = timed.map((stop) => stop.frame);\n const x = interpolate(frame, frames, timed.map((stop) => stop.x), {easing: Easing.standard});\n const y = interpolate(frame, frames, timed.map((stop) => stop.y), {easing: Easing.standard});\n\n // The most recent click at or before this frame owns the press ring.\n let pressed = 0;\n for (const stop of timed) {\n if (!stop.click || stop.frame > frame) continue;\n const age = frame - stop.frame;\n if (age <= PRESS_FRAMES) pressed = Math.max(pressed, 1 - age / PRESS_FRAMES);\n }\n\n return {\n x,\n y,\n // Fade in as it arrives and out after the last stop, so a pointer never\n // pops onto a frame it was not part of.\n visible: interpolate(\n frame,\n [first.frame, first.frame + 6, last.frame + 12, last.frame + 20],\n [0, 1, 1, 0],\n {easing: Easing.standard},\n ),\n pressed,\n clicking: pressed > 0,\n };\n};\n\n/** The last frame an authored path is still on screen, for sizing a scene. */\nexport const cursorDuration = (stops: CursorStop[]): number => {\n const hold = stops.reduce((total, stop) => total + (stop.hold ?? 0), 0);\n return (stops[stops.length - 1]?.frame ?? 0) + hold + 20;\n};\n","import {useFrame} from \"./context\";\n\nexport type TypingOptions = {\n /** Frame the first character lands on. */\n from?: number;\n /** Characters revealed per second. */\n charactersPerSecond?: number;\n /**\n * Characters revealed per step. Typing one character at a time reads as a\n * machine at high speeds; two or three at a time reads as hands, because\n * that is roughly what a fast typist does between glances at the screen.\n */\n chunk?: number;\n /** Frames the caret stays solid after the last character before it blinks. */\n settle?: number;\n};\n\nexport type TypingState = {\n /** What is on screen at this frame. */\n text: string;\n /** How many characters of the source are revealed. */\n length: number;\n /** True once every character is on screen. */\n done: boolean;\n /**\n * Whether the caret is drawn this frame: solid while typing and for a beat\n * after, blinking once the line is finished, the way a terminal waits.\n */\n caret: boolean;\n /** 0 before the first character, 1 at the last. */\n progress: number;\n};\n\nconst DEFAULTS = {from: 0, charactersPerSecond: 22, chunk: 1, settle: 12};\n\n/** Frames the typing itself occupies, for laying out what comes after it. */\nexport const typingFrames = (text: string, options: TypingOptions = {}, fps = 30): number => {\n const {charactersPerSecond, chunk} = {...DEFAULTS, ...options};\n const steps = Math.ceil(text.length / Math.max(1, chunk));\n return Math.ceil((steps * Math.max(1, chunk) * fps) / Math.max(1, charactersPerSecond));\n};\n\n/**\n * What a line of typed text looks like at one frame.\n *\n * A pure function of the frame, so scrubbing backwards untypes the line\n * exactly and two render workers on either side of a chunk boundary agree\n * character for character. The caret is part of the state rather than a\n * separate blink timer for the same reason.\n */\nexport const typedAt = (text: string, frame: number, options: TypingOptions = {}, fps = 30): TypingState => {\n const {from, charactersPerSecond, chunk, settle} = {...DEFAULTS, ...options};\n const step = Math.max(1, chunk);\n const elapsed = frame - from;\n const revealed = Math.floor((elapsed / fps) * charactersPerSecond);\n const length = Math.max(0, Math.min(text.length, Math.floor(revealed / step) * step));\n const done = elapsed >= 0 && length >= text.length;\n const finishedAt = from + typingFrames(text, options, fps);\n // Solid while there is more to type and through the settle, then a one\n // second blink: on for the first half of each cycle.\n const caret = !done || frame < finishedAt + settle ? elapsed >= 0 : (frame - finishedAt - settle) % fps < fps / 2;\n\n return {\n text: text.slice(0, length),\n length,\n done,\n caret,\n progress: text.length === 0 ? 1 : length / text.length,\n };\n};\n\n/** `typedAt` bound to the current frame. */\nexport const useTyping = (text: string, options: TypingOptions = {}): TypingState =>\n typedAt(text, useFrame(), options);\n","/**\n * Randomness that survives a re-render.\n *\n * A frame is a pure function of its number. `Math.random()` breaks that in the\n * quietest possible way: the preview looks fine, every export looks fine, and\n * the two are different — and so are two chunks of the same export, because a\n * render is parallel and each worker rolls its own numbers. Fifty particles\n * that jump between chunk boundaries is the usual symptom, found late.\n *\n * So a composition asks for a number by name instead. The same seed always\n * gives the same value, on every machine and in every worker, and a seed that\n * includes the frame gives motion that is random-looking and reproducible.\n */\n\n/**\n * A 32-bit hash of a string, so a seed can be written as a readable name\n * rather than a magic integer. FNV-1a: small, well distributed for short keys,\n * and stable across engines, which matters because two workers must agree.\n */\nconst hashSeed = (value: string): number => {\n let hash = 0x811c9dc5;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193);\n }\n return hash >>> 0;\n};\n\nconst toSeed = (seed: number | string): number =>\n typeof seed === \"number\" ? Math.floor(seed) >>> 0 : hashSeed(seed);\n\n/**\n * A number in `[0, 1)` for a seed. The same seed always returns the same\n * number, which is the whole point.\n *\n * ```tsx\n * const drift = random(`particle-${index}`) * 40;\n * const jitter = random([frame, index]) - 0.5;\n * ```\n *\n * An array seed is joined, which is the convenient way to say \"this thing, on\n * this frame\" without building the string by hand.\n */\nexport const random = (seed: number | string | Array<number | string>): number => {\n const key = Array.isArray(seed) ? seed.join(\":\") : seed;\n // Mulberry32, the same generator the audio synthesis uses, so a project has\n // one notion of \"seeded\" rather than two that disagree.\n let state = (toSeed(key) + 0x6d2b79f5) >>> 0;\n let t = Math.imul(state ^ (state >>> 15), 1 | state);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n};\n\n/** A number in `[min, max)`, for a seed. */\nexport const randomBetween = (seed: number | string | Array<number | string>, min: number, max: number): number =>\n min + random(seed) * (max - min);\n\n/** One item from a list, for a seed. Empty lists return undefined. */\nexport const randomPick = <T,>(seed: number | string | Array<number | string>, items: readonly T[]): T | undefined =>\n items.length === 0 ? undefined : items[Math.floor(random(seed) * items.length)];\n\n/**\n * A shuffled copy, for a seed. Fisher-Yates driven by the same generator, so\n * the order is arbitrary but fixed — a list that reshuffles every frame is an\n * animation nobody asked for.\n */\nexport const randomOrder = <T,>(seed: number | string | Array<number | string>, items: readonly T[]): T[] => {\n const key = Array.isArray(seed) ? seed.join(\":\") : String(seed);\n const out = [...items];\n for (let index = out.length - 1; index > 0; index -= 1) {\n const swap = Math.floor(random(`${key}:${index}`) * (index + 1));\n [out[index], out[swap]] = [out[swap], out[index]];\n }\n return out;\n};\n","\"use client\";\n\nimport {useEffect, useLayoutEffect, useRef, type RefObject} from \"react\";\nimport {useFrame, useReadiness, useVideo} from \"./context\";\n\nexport type CanvasDraw = (context: CanvasRenderingContext2D, state: {frame: number; width: number; height: number}) => void;\n\n/** `useLayoutEffect` warns during server rendering, where there is no canvas. */\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/**\n * Draw to a canvas from the frame clock.\n *\n * The contract a video runs on is that frame N produces the same pixels every\n * time. A canvas is where that is easiest to lose: the obvious way to animate\n * one is `requestAnimationFrame`, which is wall time, and wall time means the\n * export samples wherever the loop happened to be. Two workers rendering\n * neighbouring chunks then disagree, and the seam shows.\n *\n * So the draw is a pure function of the frame, called synchronously before the\n * browser paints, and the frame is held until it has run. The capture waits on\n * the same readiness handshake an image decode uses, which is what makes the\n * screenshot see finished pixels rather than an empty buffer.\n */\nexport const useCanvas = (draw: CanvasDraw, dependencies: readonly unknown[] = []): RefObject<HTMLCanvasElement | null> => {\n const canvas = useRef<HTMLCanvasElement | null>(null);\n const frame = useFrame();\n const {width, height} = useVideo();\n const readiness = useReadiness();\n // The draw is called with the current closure but must not re-run the effect\n // when an inline function identity changes, or every render would repaint.\n const latest = useRef(draw);\n latest.current = draw;\n\n useIsomorphicLayoutEffect(() => {\n const element = canvas.current;\n if (!element) return;\n\n // Held across the draw, so a frame is never captured mid-paint.\n const release = readiness.hold();\n try {\n const context = element.getContext(\"2d\", {alpha: true});\n if (!context) return;\n\n // Reset rather than accumulate: a frame is drawn from nothing, so\n // scrubbing backwards produces the same image as playing forwards.\n context.setTransform(1, 0, 0, 1, 0, 0);\n context.clearRect(0, 0, element.width, element.height);\n latest.current(context, {frame, width: element.width, height: element.height});\n } finally {\n release();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [frame, width, height, ...dependencies]);\n\n return canvas;\n};\n\n/**\n * Rasterize HTML into a canvas, deterministically.\n *\n * The browser will draw an SVG containing a `foreignObject` onto a canvas, and\n * a `foreignObject` can hold ordinary markup. That is the whole trick, and the\n * reason it needs care: the image decode is asynchronous, so the frame has to\n * be held until it lands, and the markup has to carry its own styles because\n * nothing outside the SVG reaches into it.\n *\n * Fonts are the sharp edge. A face that is not loaded when this runs will fall\n * back, and the fallback is what gets baked into the pixels — which is why the\n * caller waits on `document.fonts.ready` before drawing.\n */\nexport const drawHtml = async (\n context: CanvasRenderingContext2D,\n html: string,\n options: {width: number; height: number; style?: string},\n): Promise<void> => {\n const {width, height, style = \"\"} = options;\n if (typeof document !== \"undefined\" && document.fonts?.ready) await document.fonts.ready;\n\n const svg = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">`,\n `<foreignObject width=\"100%\" height=\"100%\">`,\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px;${style}\">`,\n html,\n `</div></foreignObject></svg>`,\n ].join(\"\");\n\n // A data URL rather than a blob URL: a blob URL has to be revoked, and a\n // leak here is a leak once per frame for the length of the video.\n const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\n await new Promise<void>((done, fail) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n done();\n };\n image.onerror = () =>\n fail(\n new Error(\n \"The HTML could not be rasterized. Every element inside must carry inline styles, and images must be data URLs: an SVG foreignObject cannot reach outside itself.\",\n ),\n );\n image.src = encoded;\n });\n};\n","import {random} from \"./random\";\n\nexport type PlaceholderOptions = {\n width?: number;\n height?: number;\n /** Drawn across the middle, so a fixture says what it is standing in for. */\n label?: string;\n /** Two colours the gradient runs between. */\n from?: string;\n to?: string;\n /** Seed for the scatter, so two placeholders differ without differing runs. */\n seed?: string;\n};\n\n/**\n * A picture that ships as code.\n *\n * A component that shows media needs media to show, and a fixture that ships a\n * JPEG cannot be reviewed in a diff, cannot be recoloured by a brand, and adds\n * a binary to a repository forever. Generating an SVG instead keeps the\n * registry's rule intact — install copies source — and makes the picture do\n * something a file cannot: describe itself.\n *\n * It is deliberately obviously a placeholder. A fixture that looks like real\n * photography invites someone to ship it.\n */\nexport const placeholderSvg = ({\n width = 1600,\n height = 900,\n label,\n from = \"#1a1a1a\",\n to = \"#0a0a0a\",\n seed = \"placeholder\",\n}: PlaceholderOptions = {}): string => {\n const shapes = Array.from({length: 14}, (_, index) => {\n const x = random([seed, \"x\", index]) * width;\n const y = random([seed, \"y\", index]) * height;\n const radius = (random([seed, \"r\", index]) * 0.16 + 0.03) * Math.min(width, height);\n const opacity = (random([seed, \"o\", index]) * 0.06 + 0.02).toFixed(3);\n return `<circle cx=\"${x.toFixed(1)}\" cy=\"${y.toFixed(1)}\" r=\"${radius.toFixed(1)}\" fill=\"#ffffff\" opacity=\"${opacity}\"/>`;\n }).join(\"\");\n\n const caption = label\n ? `<text x=\"50%\" y=\"50%\" fill=\"#ffffff\" fill-opacity=\"0.42\" font-family=\"ui-monospace, monospace\" font-size=\"${Math.round(\n Math.min(width, height) * 0.06,\n )}\" text-anchor=\"middle\" dominant-baseline=\"middle\">${label.replace(/[<>&]/g, \"\")}</text>`\n : \"\";\n\n return [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n `<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">`,\n `<stop offset=\"0\" stop-color=\"${from}\"/><stop offset=\"1\" stop-color=\"${to}\"/>`,\n `</linearGradient></defs>`,\n `<rect width=\"${width}\" height=\"${height}\" fill=\"url(#g)\"/>`,\n shapes,\n `<rect x=\"1\" y=\"1\" width=\"${width - 2}\" height=\"${height - 2}\" fill=\"none\" stroke=\"#ffffff\" stroke-opacity=\"0.08\"/>`,\n caption,\n `</svg>`,\n ].join(\"\");\n};\n\n/**\n * The same picture as a data URL, which is what an `<img>` or a canvas draw\n * wants. Inline rather than fetched: a fixture that needs the network is a\n * fixture that fails on a plane, in CI, and in a sandboxed render.\n */\nexport const placeholderImage = (options: PlaceholderOptions = {}): string =>\n `data:image/svg+xml;charset=utf-8,${encodeURIComponent(placeholderSvg(options))}`;\n\n/**\n * A frame of a placeholder \"clip\": the same picture with a moving marker and a\n * timecode, so a component that plays media has something to play that visibly\n * advances and is still a pure function of the frame.\n */\nexport const placeholderFrame = (frame: number, options: PlaceholderOptions & {fps?: number} = {}): string => {\n const {width = 1600, height = 900, fps = 30, ...rest} = options;\n const seconds = frame / fps;\n const timecode = `${String(Math.floor(seconds / 60)).padStart(2, \"0\")}:${String(Math.floor(seconds % 60)).padStart(2, \"0\")}:${String(\n frame % fps,\n ).padStart(2, \"0\")}`;\n\n const base = placeholderSvg({...rest, width, height, label: undefined});\n const progress = (frame % (fps * 4)) / (fps * 4);\n const marker = [\n `<rect x=\"0\" y=\"${height - 12}\" width=\"${(width * progress).toFixed(1)}\" height=\"12\" fill=\"#ffffff\" fill-opacity=\"0.5\"/>`,\n `<text x=\"${width / 2}\" y=\"${height / 2}\" fill=\"#ffffff\" fill-opacity=\"0.5\" font-family=\"ui-monospace, monospace\" font-size=\"${Math.round(\n Math.min(width, height) * 0.08,\n )}\" text-anchor=\"middle\" dominant-baseline=\"middle\">${timecode}</text>`,\n ].join(\"\");\n\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(base.replace(\"</svg>\", `${marker}</svg>`))}`;\n};\n","import {type CueDefinition} from \"./cue\";\nimport {type Duration} from \"./time\";\nimport {type VideoLayout} from \"./layout\";\nimport {type ParsableSchema} from \"./schema\";\n\nexport type VideoMetadata<Input = Record<string, unknown>> = {\n readonly kind: \"odori-video-metadata\";\n id: string;\n title: string;\n description?: string;\n duration?: Duration;\n layout?: VideoLayout;\n schema?: ParsableSchema<Input>;\n defaultProps?: Partial<Input>;\n tags?: string[];\n thumbnailFrame?: number;\n /**\n * Named alternatives for the video's sound: each variant overrides brand\n * cues by name, and everything else about the film stays identical. This\n * is what a narration audition or a localized voice track is, and it is\n * one video with a switch rather than a copy of the video per option.\n * Studio offers the variants beside the player, and `odori export` takes\n * one with `--audio <name>`. Absent, or absent a name, the brand's own\n * cues play.\n */\n audio?: {variants?: Record<string, Record<string, string | CueDefinition>>};\n};\n\nexport type VideoMetadataInput<Input = Record<string, unknown>> = Omit<VideoMetadata<Input>, \"kind\" | \"id\"> & {\n /**\n * Defaults to the entry's path under `videos/`, so the directory names a\n * video the way a route names a page. Set it to keep an id stable across a\n * directory move.\n */\n id?: string;\n};\n\n/** A segment of an id: alphanumeric with dashes, the way a directory is named. */\nconst SEGMENT = /^[a-z0-9][a-z0-9-]*$/i;\n\nexport const isValidVideoId = (id: string): boolean =>\n id.length > 0 && id.split(\"/\").every((segment) => SEGMENT.test(segment));\n\n/**\n * An id left unset is resolved from the filesystem by discovery. The empty\n * string is the unresolved state: no entry reaches a manifest, a render, or\n * Studio without an id stamped in.\n */\nexport const resolveVideoId = (id: string | undefined, pathId: string): string => id || pathId;\n\nexport const defineVideoMetadata = <Input = Record<string, unknown>>(\n metadata: VideoMetadataInput<Input>,\n): VideoMetadata<Input> => {\n if (metadata.id !== undefined && !isValidVideoId(metadata.id)) {\n throw new Error(`Video id must be alphanumeric path segments with dashes: ${metadata.id}`);\n }\n return {kind: \"odori-video-metadata\", ...metadata, id: metadata.id ?? \"\"};\n};\n\nexport type PrepareContext<Input> = {\n input: Input;\n assets: {resolve(reference: string): Promise<string>};\n cache: {getOrSet<Value>(key: string, factory: () => Promise<Value>): Promise<Value>};\n signal?: AbortSignal;\n};\n\nexport type PrepareFunction<Input = Record<string, unknown>, Prepared = unknown> = {\n readonly kind: \"odori-prepare\";\n version: string;\n run(context: PrepareContext<Input>): Promise<Prepared>;\n};\n\nexport const definePrepare = <Input = Record<string, unknown>, Prepared = unknown>(\n run: (context: PrepareContext<Input>) => Promise<Prepared>,\n options: {version?: string} = {},\n): PrepareFunction<Input, Prepared> => ({\n kind: \"odori-prepare\",\n version: options.version ?? \"1\",\n run,\n});\n","/**\n * A tiny serializable input contract.\n *\n * Odori needs three things from a schema: validation with defaults, a JSON\n * description Studio can turn into controls, and zero runtime dependencies.\n * Any zod-compatible object with `parse()` is also accepted.\n */\nexport type FieldDescriptor =\n | {type: \"text\"; defaultValue: string; maxLength?: number; multiline?: boolean}\n | {type: \"number\"; defaultValue: number; min?: number; max?: number; step?: number}\n | {type: \"boolean\"; defaultValue: boolean}\n | {type: \"select\"; defaultValue: string; options: string[]}\n | {type: \"color\"; defaultValue: string}\n | {type: \"json\"; defaultValue: unknown};\n\nexport type InputSchema<Value = Record<string, unknown>> = {\n readonly kind: \"odori-schema\";\n readonly fields: Record<string, FieldDescriptor>;\n parse(input: unknown): Value;\n safeParse(input: unknown): {success: true; data: Value} | {success: false; issues: string[]};\n defaults(): Value;\n describe(): Record<string, FieldDescriptor>;\n};\n\nexport type ParsableSchema<Value = unknown> = InputSchema<Value> | {parse(input: unknown): Value};\n\nconst validateField = (name: string, field: FieldDescriptor, value: unknown, issues: string[]): unknown => {\n if (value === undefined) return field.defaultValue;\n switch (field.type) {\n case \"text\":\n case \"color\": {\n if (typeof value !== \"string\") {\n issues.push(`${name} must be a string`);\n return field.defaultValue;\n }\n if (field.type === \"text\" && field.maxLength !== undefined && value.length > field.maxLength) {\n issues.push(`${name} exceeds ${field.maxLength} characters`);\n }\n return value;\n }\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n issues.push(`${name} must be a finite number`);\n return field.defaultValue;\n }\n if (field.min !== undefined && value < field.min) issues.push(`${name} is below ${field.min}`);\n if (field.max !== undefined && value > field.max) issues.push(`${name} is above ${field.max}`);\n return value;\n }\n case \"boolean\": {\n if (typeof value !== \"boolean\") {\n issues.push(`${name} must be a boolean`);\n return field.defaultValue;\n }\n return value;\n }\n case \"select\": {\n if (typeof value !== \"string\" || !field.options.includes(value)) {\n issues.push(`${name} must be one of ${field.options.join(\", \")}`);\n return field.defaultValue;\n }\n return value;\n }\n default:\n return value;\n }\n};\n\nexport const defineInputSchema = <Fields extends Record<string, FieldDescriptor>>(\n fields: Fields,\n): InputSchema<Record<string, unknown>> => {\n const defaults = () =>\n Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.defaultValue]));\n\n const safeParse = (input: unknown) => {\n if (input !== undefined && input !== null && typeof input !== \"object\") {\n return {success: false as const, issues: [\"input must be an object\"]};\n }\n const source = (input ?? {}) as Record<string, unknown>;\n const issues: string[] = [];\n const data: Record<string, unknown> = {};\n for (const [name, field] of Object.entries(fields)) {\n data[name] = validateField(name, field, source[name], issues);\n }\n for (const key of Object.keys(source)) {\n if (!(key in fields)) data[key] = source[key];\n }\n return issues.length > 0\n ? {success: false as const, issues}\n : {success: true as const, data};\n };\n\n return {\n kind: \"odori-schema\",\n fields,\n defaults,\n describe: () => fields,\n safeParse,\n parse(input) {\n const result = safeParse(input);\n if (!result.success) throw new Error(`Invalid video input: ${result.issues.join(\"; \")}`);\n return result.data;\n },\n };\n};\n\nexport const isOdoriSchema = (schema: unknown): schema is InputSchema =>\n typeof schema === \"object\" && schema !== null && (schema as {kind?: string}).kind === \"odori-schema\";\n\nexport const parseWithSchema = <Value>(schema: ParsableSchema<Value> | undefined, input: unknown): Value =>\n schema ? schema.parse(input) : ((input ?? {}) as Value);\n"],"mappings":"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","names":["useCallback","useState","loop","useEffect","useRef","useRef","useEffect","target","loop","useState","useCallback","useEffect","useState","jsx","useState","useEffect","offset","step","useEffect","useRef","useEffect","useRef","seconds"]}
|
|
1
|
+
{"version":3,"sources":["../src/loudness.ts","../src/viewer.tsx","../src/playback.ts","../src/audio-playback.ts","../src/narration.ts","../src/render-surface.tsx","../src/wav.ts","../src/cursor.ts","../src/typing.ts","../src/random.ts","../src/canvas.ts","../src/placeholder.ts","../src/metadata.ts","../src/schema.ts"],"sourcesContent":["/**\n * Integrated loudness, ITU-R BS.1770-4.\n *\n * The number a mix is judged by is not peak or RMS: it is K-weighted, gated\n * loudness. Studio measures what it is about to hand the encoder so the\n * brand's target is something you can mix toward rather than discover after an\n * export.\n */\n\nexport type Biquad = {b0: number; b1: number; b2: number; a1: number; a2: number};\n\n/** Stage 1: the head shelf, and stage 2: the high pass, from the spec's filter table. */\nconst SHELF = {frequency: 1681.974450955533, gainDb: 3.999843853973347, q: 0.7071752369554196};\nconst HIGH_PASS = {frequency: 38.13547087602444, q: 0.5003270373238773};\n\n/**\n * The spec tabulates coefficients at 48 kHz. Deriving them per rate keeps a\n * 44.1 kHz source from being measured with the wrong filter.\n */\nexport const shelfCoefficients = (sampleRate: number): Biquad => {\n const amplitude = 10 ** (SHELF.gainDb / 40);\n const omega = (2 * Math.PI * SHELF.frequency) / sampleRate;\n const alpha = Math.sin(omega) / (2 * SHELF.q);\n const cos = Math.cos(omega);\n const shared = 2 * Math.sqrt(amplitude) * alpha;\n const a0 = amplitude + 1 - (amplitude - 1) * cos + shared;\n return {\n b0: (amplitude * (amplitude + 1 + (amplitude - 1) * cos + shared)) / a0,\n b1: (-2 * amplitude * (amplitude - 1 + (amplitude + 1) * cos)) / a0,\n b2: (amplitude * (amplitude + 1 + (amplitude - 1) * cos - shared)) / a0,\n a1: (2 * (amplitude - 1 - (amplitude + 1) * cos)) / a0,\n a2: (amplitude + 1 - (amplitude - 1) * cos - shared) / a0,\n };\n};\n\nexport const highPassCoefficients = (sampleRate: number): Biquad => {\n const omega = (2 * Math.PI * HIGH_PASS.frequency) / sampleRate;\n const alpha = Math.sin(omega) / (2 * HIGH_PASS.q);\n const cos = Math.cos(omega);\n const a0 = 1 + alpha;\n return {\n b0: (1 + cos) / 2 / a0,\n b1: (-(1 + cos)) / a0,\n b2: (1 + cos) / 2 / a0,\n a1: (-2 * cos) / a0,\n a2: (1 - alpha) / a0,\n };\n};\n\nconst filter = (samples: Float32Array, {b0, b1, b2, a1, a2}: Biquad): Float32Array => {\n const output = new Float32Array(samples.length);\n let x1 = 0;\n let x2 = 0;\n let y1 = 0;\n let y2 = 0;\n for (let index = 0; index < samples.length; index += 1) {\n const x0 = samples[index];\n const y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;\n output[index] = y0;\n x2 = x1;\n x1 = x0;\n y2 = y1;\n y1 = y0;\n }\n return output;\n};\n\nconst BLOCK_SECONDS = 0.4;\n/** Blocks overlap by 75%, so a short transient cannot hide between them. */\nconst STEP = 0.25;\nconst ABSOLUTE_GATE = -70;\nconst RELATIVE_GATE = -10;\nconst OFFSET = -0.691;\n\nconst loudnessOf = (meanSquares: number[]) =>\n OFFSET + 10 * Math.log10(meanSquares.reduce((total, value) => total + value, 0) || Number.MIN_VALUE);\n\n/**\n * Channel weights for stereo. Surround weights the surround channels higher;\n * a video mix is stereo, so both channels count equally.\n */\nexport const integratedLufs = (channels: Float32Array[], sampleRate: number): number | null => {\n if (channels.length === 0 || channels[0].length === 0) return null;\n const weighted = channels.map((channel) => filter(filter(channel, shelfCoefficients(sampleRate)), highPassCoefficients(sampleRate)));\n\n const blockSize = Math.round(BLOCK_SECONDS * sampleRate);\n const hop = Math.round(BLOCK_SECONDS * STEP * sampleRate);\n if (weighted[0].length < blockSize) return null;\n\n // One mean square per channel per block, kept apart so gating can sum them.\n const blocks: number[][] = [];\n for (let start = 0; start + blockSize <= weighted[0].length; start += hop) {\n blocks.push(\n weighted.map((channel) => {\n let sum = 0;\n for (let index = start; index < start + blockSize; index += 1) sum += channel[index] * channel[index];\n return sum / blockSize;\n }),\n );\n }\n if (blocks.length === 0) return null;\n\n const above = blocks.filter((block) => loudnessOf(block) > ABSOLUTE_GATE);\n if (above.length === 0) return null;\n\n // The relative gate is measured against the ungated mean of what survived.\n const mean = above[0].map((_, channel) => above.reduce((total, block) => total + block[channel], 0) / above.length);\n const threshold = loudnessOf(mean) + RELATIVE_GATE;\n const gated = above.filter((block) => loudnessOf(block) > threshold);\n if (gated.length === 0) return null;\n\n const integrated = gated[0].map((_, channel) => gated.reduce((total, block) => total + block[channel], 0) / gated.length);\n return loudnessOf(integrated);\n};\n","\"use client\";\n\nimport {useCallback, useMemo, useState, type CSSProperties} from \"react\";\nimport {OdoriRuntime, entryDurationInFrames, resolveEntryLayout, type CompiledTimeline, type VideoEntry} from \"./runtime\";\nimport {type VideoLayout} from \"./layout\";\nimport {formatTimecode} from \"./time\";\nimport {usePlayback} from \"./playback\";\nimport {useAudioPlayback} from \"./audio-playback\";\nimport {type AudioTrack} from \"./audio\";\n\nexport type ViewerProps = {\n entry: VideoEntry;\n input?: Record<string, unknown>;\n prepared?: unknown;\n assets?: Array<{reference: string; url: string}>;\n layout?: VideoLayout;\n initialFrame?: number;\n autoPlay?: boolean;\n loop?: boolean;\n controls?: boolean;\n style?: CSSProperties;\n muted?: boolean;\n onFrame?: (frame: number) => void;\n onTimeline?: (timeline: CompiledTimeline) => void;\n onAudio?: (track: AudioTrack) => void;\n};\n\n/**\n * A composition, embeddable and seekable, for a product surface rather than\n * for Studio.\n *\n * Playback advances a fractional frame counter from wall-clock deltas, but\n * React only ever sees an integer frame, so a paused viewer and a render\n * worker produce identical output: what somebody watches in your app is the\n * file you would export.\n *\n * The controls here are the plain ones. A surface that wants its own transport\n * imports `usePlayback` instead and keeps this out of it, which is what Studio\n * and the documentation site both do.\n */\nexport const Viewer = ({\n entry,\n input,\n prepared,\n assets,\n layout,\n initialFrame = 0,\n autoPlay = false,\n loop = true,\n controls = true,\n muted = false,\n style,\n onFrame,\n onTimeline,\n onAudio,\n}: ViewerProps) => {\n const resolvedLayout = resolveEntryLayout(entry, layout);\n const {fps, width, height} = resolvedLayout.format;\n const [timeline, setTimeline] = useState<CompiledTimeline | null>(null);\n const [track, setTrack] = useState<AudioTrack | null>(null);\n const declared = entryDurationInFrames(entry, resolvedLayout);\n const durationInFrames = Math.max(1, declared || timeline?.durationInFrames || fps);\n\n const playback = usePlayback({fps, durationInFrames, initialFrame, autoPlay, loop, onFrame});\n const {frame} = playback;\n\n useAudioPlayback({track, frame: playback.frame, fps, playing: playback.playing, muted});\n\n const handleAudio = useCallback(\n (next: AudioTrack) => {\n setTrack(next);\n onAudio?.(next);\n },\n [onAudio],\n );\n\n const handleTimeline = useCallback(\n (next: CompiledTimeline) => {\n // Comparing the count and total would hold a stale timeline when two\n // scenes trade frames between them: same length, same total, different\n // boundaries.\n setTimeline((current) => (JSON.stringify(current) === JSON.stringify(next) ? current : next));\n onTimeline?.(next);\n },\n [onTimeline],\n );\n\n const aspectRatio = useMemo(() => `${width} / ${height}`, [height, width]);\n const activeScene = timeline?.scenes.find(\n (scene) => frame >= scene.start && frame < scene.start + scene.durationInFrames,\n );\n\n return (\n <div className=\"odori-viewer\" style={{display: \"grid\", gap: 12, width: \"100%\", ...style}}>\n <div\n data-odori-viewer\n style={{\n aspectRatio,\n background: resolvedLayout.brand.colors.background,\n borderRadius: 10,\n overflow: \"hidden\",\n position: \"relative\",\n width: \"100%\",\n }}\n >\n <OdoriRuntime\n entry={entry}\n frame={frame}\n input={input}\n prepared={prepared}\n assets={assets}\n layout={layout}\n onTimeline={handleTimeline}\n onAudio={handleAudio}\n />\n </div>\n {controls ? (\n <div className=\"odori-viewer-controls\" style={{alignItems: \"center\", display: \"flex\", gap: 10}}>\n <button type=\"button\" onClick={playback.toggle}>\n {playback.playing ? \"Pause\" : \"Play\"}\n </button>\n <button type=\"button\" onClick={() => playback.step(-1)} aria-label=\"Previous frame\">\n {\"\\u2039\"}\n </button>\n <button type=\"button\" onClick={() => playback.step(1)} aria-label=\"Next frame\">\n {\"\\u203a\"}\n </button>\n <input\n aria-label=\"Timeline\"\n type=\"range\"\n min={0}\n max={durationInFrames - 1}\n value={frame}\n onChange={(event) => {\n playback.pause();\n playback.seek(Number(event.currentTarget.value));\n }}\n style={{flex: 1}}\n />\n <output style={{fontVariantNumeric: \"tabular-nums\", minWidth: 132, textAlign: \"right\"}}>\n {formatTimecode(frame, fps)} {\"·\"} {frame}/{durationInFrames - 1}\n {activeScene ? ` · ${activeScene.name ?? activeScene.id}` : \"\"}\n </output>\n </div>\n ) : null}\n </div>\n );\n};\n","\"use client\";\n\nimport {useCallback, useEffect, useRef, useState} from \"react\";\n\nexport type PlaybackOptions = {\n fps: number;\n durationInFrames: number;\n initialFrame?: number;\n autoPlay?: boolean;\n loop?: boolean;\n /** Wall-clock multiplier. The frame clock keeps its rate; only time moves. */\n rate?: number;\n onFrame?: (frame: number) => void;\n};\n\nexport type Playback = {\n frame: number;\n playing: boolean;\n rate: number;\n play(): void;\n pause(): void;\n toggle(): void;\n seek(frame: number): void;\n step(delta: number): void;\n restart(): void;\n};\n\n/**\n * The seekable frame clock. Playback advances a fractional counter from\n * wall-clock deltas, but React only ever sees an integer frame, so a paused\n * player, a still, and the export worker agree by construction.\n */\n/**\n * How far the clock moves for a wall-clock delta.\n *\n * Rate scales elapsed time, never the frame index, so frame 90 is the same\n * image at 0.25x, 1x, and 4x, and an export ignores rate entirely.\n */\nexport const advanceFrames = (fractional: number, deltaMs: number, fps: number, rate = 1): number =>\n fractional + (deltaMs / 1000) * fps * rate;\n\n/**\n * The longest wall-clock gap the clock will believe in one tick.\n *\n * requestAnimationFrame stops firing when the tab is hidden, the window goes\n * to the background, or an embedded webview loses the cursor, and it resumes\n * with a single delta covering the entire gap. Handed to advanceFrames that is\n * a jump of hundreds of frames: a video that does not loop lands on its last\n * frame and stops, which is what a hang looks like from the outside, and one\n * that loops wraps to somewhere arbitrary.\n *\n * A gap this long is a suspended clock, not a slow frame, so it is capped\n * rather than trusted, and playback picks up a few frames on from where it\n * stopped. The cost is that a renderer slower than four frames a second falls\n * behind wall clock. That is the right way round for a preview: this clock\n * never touches an export, which the encoder drives frame by frame.\n */\nexport const MAX_TICK_MS = 250;\n\nexport const usePlayback = ({\n fps,\n durationInFrames,\n initialFrame = 0,\n autoPlay = false,\n loop = true,\n rate = 1,\n onFrame,\n}: PlaybackOptions): Playback => {\n const [frame, setFrame] = useState(initialFrame);\n const [playing, setPlaying] = useState(autoPlay);\n const animation = useRef<number | null>(null);\n const previousTime = useRef<number | null>(null);\n const fractional = useRef(initialFrame);\n const frameRef = useRef(initialFrame);\n\n const commit = useCallback(\n (next: number) => {\n const clamped = Math.max(0, Math.min(Math.round(next), Math.max(0, durationInFrames - 1)));\n frameRef.current = clamped;\n setFrame(clamped);\n onFrame?.(clamped);\n },\n [durationInFrames, onFrame],\n );\n\n useEffect(() => {\n if (!playing) {\n previousTime.current = null;\n fractional.current = frameRef.current;\n return;\n }\n const tick = (now: number) => {\n const previous = previousTime.current ?? now;\n previousTime.current = now;\n fractional.current = advanceFrames(fractional.current, Math.min(now - previous, MAX_TICK_MS), fps, rate);\n if (fractional.current >= durationInFrames) {\n if (!loop) {\n commit(durationInFrames - 1);\n setPlaying(false);\n return;\n }\n fractional.current %= durationInFrames;\n }\n commit(Math.floor(fractional.current));\n animation.current = requestAnimationFrame(tick);\n };\n animation.current = requestAnimationFrame(tick);\n return () => {\n if (animation.current !== null) cancelAnimationFrame(animation.current);\n };\n }, [commit, durationInFrames, fps, loop, playing, rate]);\n\n const seek = useCallback(\n (next: number) => {\n fractional.current = next;\n commit(next);\n },\n [commit],\n );\n\n return {\n frame,\n playing,\n rate,\n play: () => setPlaying(true),\n pause: () => setPlaying(false),\n toggle: () => setPlaying((value) => !value),\n seek,\n step: (delta: number) => {\n setPlaying(false);\n seek(frameRef.current + delta);\n },\n restart: () => {\n seek(0);\n setPlaying(true);\n },\n };\n};\n","\"use client\";\n\nimport {useEffect, useRef} from \"react\";\nimport {trackGainAtFrame, type AudioTrack} from \"./audio\";\n\nexport type AudioPlaybackOptions = {\n track: AudioTrack | null;\n frame: number;\n fps: number;\n playing: boolean;\n muted?: boolean;\n masterGain?: number;\n /** Cue id to hear alone, or null for the whole mix. */\n soloCue?: string | null;\n /**\n * True while the playhead is being moved by hand. Cues go quiet: auditioning\n * under the cursor sounds like a stuck record, because every frame reseeks\n * the element and you hear the same few milliseconds over and over.\n */\n scrubbing?: boolean;\n /** Playback rate, so cues stay with the frame clock when it is sped up. */\n rate?: number;\n /**\n * Called when the browser refuses to start a cue without a user gesture, and\n * again when it relents. Autoplay policy is the difference between a silent\n * preview and a broken one, so it is reported rather than swallowed.\n */\n onBlocked?: (blocked: boolean) => void;\n /**\n * Called with the cues whose files failed to load or decode. A cue pointing\n * at a missing or wrong file is silence with no other symptom, so it is\n * reported rather than swallowed.\n */\n onFailed?: (sources: string[]) => void;\n};\n\n/**\n * Drives one HTMLAudioElement per cue from the frame clock.\n *\n * Audio is the one thing that cannot be derived from a frame index, so preview\n * playback resyncs whenever the element drifts more than a frame from where the\n * timeline says it should be. The exported mix is built separately by the\n * encoder from the same cues, which keeps the file frame accurate.\n */\nexport const useAudioPlayback = ({\n track,\n frame,\n fps,\n playing,\n muted = false,\n masterGain = 1,\n soloCue = null,\n scrubbing = false,\n rate = 1,\n onBlocked,\n onFailed,\n}: AudioPlaybackOptions) => {\n const elements = useRef(new Map<string, HTMLAudioElement>());\n // The sync pass, reachable from listeners that fire when the frame has not\n // changed: the clock can stall (a hidden tab throttles rAF, a slow render\n // drops frames) while an audio element keeps running at its own pace.\n const sync = useRef<() => void>(() => {});\n const failed = useRef(new Set<string>());\n\n useEffect(() => {\n const table = elements.current;\n const live = new Set((track?.cues ?? []).map((cue) => cue.id));\n for (const [id, element] of table) {\n if (live.has(id)) continue;\n element.pause();\n table.delete(id);\n }\n return () => {\n for (const element of table.values()) element.pause();\n };\n }, [track]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const table = elements.current;\n\n const onTimeUpdate = () => run();\n\n const run = () => {\n /*\n * A hidden tab is silent, and this is the check that makes it so. The\n * visibility listener below used to pause every element on its own, but\n * a hidden tab still ticks its throttled clock, so the very next frame\n * ran this pass, found the cue audible, and called play() again. What\n * came out was a cue restarted and reseeked once or twice a second for\n * as long as the tab stayed in the background, which is the glitching.\n * Deciding it here means the pause cannot be undone by the next tick.\n */\n const audible = playing && !scrubbing && !document.hidden;\n\n for (const cue of track?.cues ?? []) {\n let element = table.get(cue.id);\n if (!element) {\n element = new window.Audio(cue.src);\n element.preload = \"auto\";\n element.loop = cue.loop;\n // An element that drifts while the clock is stalled corrects itself\n // on its own time updates, so audio can never run away from a\n // frozen picture.\n element.addEventListener(\"timeupdate\", onTimeUpdate);\n element.addEventListener(\"error\", () => {\n failed.current.add(cue.src);\n onFailed?.([...failed.current]);\n });\n table.set(cue.id, element);\n }\n\n const local = frame - cue.fromFrame;\n const inside = local >= 0 && local < cue.durationInFrames;\n /**\n * Where in the file this frame sounds. A looping cue's window is\n * longer than its file, so the position wraps: seeking straight to\n * `local / fps` would land past the end and the browser would clamp\n * it, leaving the bed stuck on its final sample. The wrap needs the\n * file's real length, which only exists once metadata has loaded.\n */\n const span = element.duration;\n const elapsed = local / fps;\n const position =\n cue.loop && Number.isFinite(span) && span > 0 ? elapsed % span : elapsed;\n // Set every pass, not only at creation: an edit that turns looping on\n // does not change the cue's id, so the element it reuses would keep\n // the old behaviour.\n if (element.loop !== cue.loop) element.loop = cue.loop;\n element.volume = Math.max(0, Math.min(1, trackGainAtFrame(cue, track?.cues ?? [], frame) * masterGain));\n element.muted = muted || (soloCue !== null && soloCue !== cue.id);\n\n if (!inside || !audible) {\n if (!element.paused) element.pause();\n if (inside && !audible) {\n const target = cue.trimStartSeconds + position;\n if (Math.abs(element.currentTime - target) > 1 / fps) element.currentTime = target;\n }\n continue;\n }\n\n const target = cue.trimStartSeconds + position;\n // A rate change is a new playbackRate, not a reseek: the element keeps\n // playing and the drift check below catches it if it falls behind.\n if (element.playbackRate !== rate) element.playbackRate = rate;\n if (Math.abs(element.currentTime - target) > (2 / fps) * Math.max(1, rate)) element.currentTime = target;\n if (element.paused) {\n void element.play().then(\n () => onBlocked?.(false),\n (error: unknown) => onBlocked?.((error as Error)?.name === \"NotAllowedError\"),\n );\n }\n }\n };\n\n sync.current = run;\n run();\n\n return () => {\n for (const element of table.values()) element.removeEventListener(\"timeupdate\", onTimeUpdate);\n };\n }, [fps, frame, masterGain, muted, onBlocked, onFailed, playing, rate, scrubbing, soloCue, track]);\n\n // Visibility is not something React re-renders for, so the change is what\n // re-runs the pass. Which way it went does not matter: run() reads\n // document.hidden itself and either holds every cue or resyncs them to the\n // frame the timeline is actually showing.\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const onVisibility = () => sync.current();\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => document.removeEventListener(\"visibilitychange\", onVisibility);\n }, []);\n};\n","/**\n * Narration as source.\n *\n * A recorded voice is the one part of a video that usually lives outside the\n * project: a file somebody exported once, with captions transcribed after the\n * fact and timed by hand. Odori keeps it in the repository instead. The\n * artifact is a small JSON document holding the script, the voice, and the\n * time every word starts and ends, written by `odori narrate` in the same\n * call that writes the audio.\n *\n * Everything downstream derives from that document. Captions are computed\n * from the word timings at the project's fps, so they cannot drift from the\n * recording; a scene can hold until a sentence lands, because the sentence's\n * end is data; re-recording is one command, and the diff shows exactly which\n * words moved. The render never talks to a provider, because by then the\n * narration is just a file and some numbers.\n */\n\n/** One spoken word and when it happens, in seconds from the start. */\nexport type NarrationWord = {\n word: string;\n startSeconds: number;\n endSeconds: number;\n};\n\nexport type Narration = {\n /** The script as given, so a re-record can be diffed against it. */\n script: string;\n /** Provider and voice that read it, so a re-record sounds the same. */\n provider: string;\n voice: string;\n /** The audio file, as a path under public/ or a brand cue role. */\n audio: string;\n words: NarrationWord[];\n};\n\n/**\n * Character-level timing, the shape speech APIs return: three parallel\n * arrays, one entry per character of the spoken text.\n */\nexport type CharacterAlignment = {\n characters: string[];\n startSeconds: number[];\n endSeconds: number[];\n};\n\n/**\n * Fold character timings into word timings.\n *\n * A word runs from its first character's start to its last character's end.\n * Whitespace separates; punctuation stays attached to the word it follows,\n * because captions read better with it than without.\n */\nexport const wordsFromCharacters = (alignment: CharacterAlignment): NarrationWord[] => {\n const words: NarrationWord[] = [];\n let current = \"\";\n let start = 0;\n let end = 0;\n\n alignment.characters.forEach((character, index) => {\n if (/\\s/.test(character)) {\n if (current) words.push({word: current, startSeconds: start, endSeconds: end});\n current = \"\";\n return;\n }\n if (!current) start = alignment.startSeconds[index];\n current += character;\n end = alignment.endSeconds[index];\n });\n if (current) words.push({word: current, startSeconds: start, endSeconds: end});\n return words;\n};\n\nexport type CaptionCueTiming = {\n text: string;\n fromFrame: number;\n durationInFrames: number;\n};\n\nexport type CaptionOptions = {\n /** Upper bound per caption. Fewer arrive when punctuation or a pause ends one. */\n maxWords?: number;\n /** A silence at least this long ends the caption, because the speaker did. */\n breakOnGapSeconds?: number;\n /** Hold the last caption of a group this long after its final word. */\n hangSeconds?: number;\n};\n\n/**\n * Group word timings into caption cues at a given fps.\n *\n * Deterministic on purpose: the same narration and fps produce the same cues\n * on every machine, so captions are computed where they are used rather than\n * stored, and a change of fps or grouping is a re-render rather than a\n * re-transcription.\n *\n * A caption ends at a sentence mark, at a real pause, or at `maxWords`,\n * whichever comes first. It stays on screen until the next one starts or its\n * hang runs out, so text never vanishes mid-phrase.\n */\nexport const captionCues = (\n narration: Pick<Narration, \"words\">,\n fps: number,\n options: CaptionOptions = {},\n): CaptionCueTiming[] => {\n const {maxWords = 7, breakOnGapSeconds = 0.6, hangSeconds = 0.8} = options;\n const groups: NarrationWord[][] = [];\n let group: NarrationWord[] = [];\n\n narration.words.forEach((word, index) => {\n group.push(word);\n const next = narration.words[index + 1];\n const sentence = /[.!?]$/.test(word.word);\n const clause = /[,;:]$/.test(word.word) && group.length >= Math.ceil(maxWords / 2);\n const pause = next !== undefined && next.startSeconds - word.endSeconds >= breakOnGapSeconds;\n if (sentence || clause || pause || group.length >= maxWords || next === undefined) {\n groups.push(group);\n group = [];\n }\n });\n\n return groups.map((words, index) => {\n const start = words[0].startSeconds;\n const spoken = words[words.length - 1].endSeconds;\n const nextStart = groups[index + 1]?.[0].startSeconds;\n // Held to the next caption or the hang, but never past the next caption.\n const end = nextStart !== undefined ? Math.min(spoken + hangSeconds, nextStart) : spoken + hangSeconds;\n const fromFrame = Math.round(start * fps);\n return {\n text: words.map((entry) => entry.word).join(\" \"),\n fromFrame,\n durationInFrames: Math.max(1, Math.round(end * fps) - fromFrame),\n };\n });\n};\n\n/** The narration's last spoken moment, for sizing a scene to it. */\nexport const narrationEndSeconds = (narration: Pick<Narration, \"words\">): number =>\n narration.words.length > 0 ? narration.words[narration.words.length - 1].endSeconds : 0;\n","\"use client\";\n\nimport {useEffect, useState} from \"react\";\nimport {AudioVariantContext, OdoriRuntime, type CompiledTimeline, type VideoEntry} from \"./runtime\";\nimport {type AudioTrack} from \"./audio\";\nimport {type VideoLayout} from \"./layout\";\n\ndeclare global {\n interface Window {\n __ODORI_SET_FRAME__?: (frame: number) => void;\n __ODORI_TIMELINE__?: CompiledTimeline;\n __ODORI_AUDIO__?: AudioTrack;\n __ODORI_READY__?: boolean;\n }\n}\n\n/**\n * The surface the render worker drives. It exposes an explicit frame setter\n * and a readiness handshake instead of relying on timing heuristics.\n */\nexport const RenderSurface = ({\n entry,\n initialFrame = 0,\n input,\n prepared,\n assets,\n layout,\n audioVariant,\n}: {\n entry: VideoEntry;\n initialFrame?: number;\n input?: Record<string, unknown>;\n prepared?: unknown;\n assets?: Array<{reference: string; url: string}>;\n layout?: VideoLayout;\n /** A declared audio variant's name, so exports render the chosen voice. */\n audioVariant?: string;\n}) => {\n const [frame, setFrame] = useState(initialFrame);\n\n useEffect(() => {\n window.__ODORI_SET_FRAME__ = setFrame;\n window.__ODORI_READY__ = true;\n return () => {\n delete window.__ODORI_SET_FRAME__;\n delete window.__ODORI_READY__;\n };\n }, []);\n\n return (\n <AudioVariantContext.Provider value={(audioVariant && entry.metadata.audio?.variants?.[audioVariant]) || null}>\n <OdoriRuntime\n entry={entry}\n frame={frame}\n input={input}\n prepared={prepared}\n assets={assets}\n layout={layout}\n onTimeline={(timeline) => {\n window.__ODORI_TIMELINE__ = timeline;\n }}\n onAudio={(track) => {\n window.__ODORI_AUDIO__ = track;\n }}\n />\n </AudioVariantContext.Provider>\n );\n};\n","import {type Signal} from \"./synth\";\n\n/**\n * Signal to a 16 bit PCM WAV. Small, lossless, and readable by FFmpeg without\n * a decoder, which is all the mix needs from a generated cue.\n *\n * Encoding lives in the runtime rather than the CLI so preview and export\n * share it: the browser can hand the same bytes to an AudioContext that the\n * render worker writes to disk.\n */\nexport const encodeWav = (signal: Signal): Uint8Array => {\n const channels = signal.channels.length || 1;\n const frames = signal.channels[0]?.length ?? 0;\n const bytesPerSample = 2;\n const dataBytes = frames * channels * bytesPerSample;\n const buffer = new ArrayBuffer(44 + dataBytes);\n const view = new DataView(buffer);\n\n const ascii = (offset: number, text: string) => {\n for (let index = 0; index < text.length; index += 1) view.setUint8(offset + index, text.charCodeAt(index));\n };\n\n ascii(0, \"RIFF\");\n view.setUint32(4, 36 + dataBytes, true);\n ascii(8, \"WAVE\");\n ascii(12, \"fmt \");\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true); // PCM\n view.setUint16(22, channels, true);\n view.setUint32(24, signal.sampleRate, true);\n view.setUint32(28, signal.sampleRate * channels * bytesPerSample, true);\n view.setUint16(32, channels * bytesPerSample, true);\n view.setUint16(34, 8 * bytesPerSample, true);\n ascii(36, \"data\");\n view.setUint32(40, dataBytes, true);\n\n let offset = 44;\n for (let frame = 0; frame < frames; frame += 1) {\n for (let channel = 0; channel < channels; channel += 1) {\n const sample = signal.channels[channel]?.[frame] ?? 0;\n // Clamp before quantizing, so a hot score distorts predictably instead\n // of wrapping into noise.\n const clamped = Math.max(-1, Math.min(1, sample));\n view.setInt16(offset, Math.round(clamped * 32767), true);\n offset += bytesPerSample;\n }\n }\n\n return new Uint8Array(buffer);\n};\n","import {Easing, interpolate} from \"./easing\";\n\nexport type CursorStop = {\n /** Frame this stop is reached, from the start of the enclosing scene. */\n frame: number;\n /** Canvas coordinates, in the composition's own pixels. */\n x: number;\n y: number;\n /**\n * A click landing on this stop. The press is drawn at the stop's frame and\n * decays over a few frames, so the pointer visibly does the thing the UI is\n * about to react to.\n */\n click?: boolean;\n /** Hold here until this many frames have passed before moving on. */\n hold?: number;\n};\n\nexport type CursorState = {\n x: number;\n y: number;\n /** 0 before the path starts and after it ends, 1 while it is on screen. */\n visible: number;\n /** 1 at the instant of a click, decaying to 0. Drives the press ring. */\n pressed: number;\n /** True while a click is within its press window, for a UI to react to. */\n clicking: boolean;\n};\n\n/** Frames a press ring takes to expand and fade. */\nconst PRESS_FRAMES = 9;\n\n/**\n * Where an authored pointer is at this frame.\n *\n * Recording a real cursor would make a video that cannot be re-rendered: the\n * path would live in a file, not in the composition, and a change of copy or\n * canvas would leave it pointing at nothing. An authored path is source — it\n * diffs, it survives a reflow, and it produces the same pixels every run.\n *\n * Movement eases between stops rather than running linearly, because a pointer\n * that travels at constant speed reads as a machine. A `hold` keeps the\n * pointer still without needing a duplicate stop at the same coordinates.\n */\nexport const cursorAt = (stops: CursorStop[], frame: number): CursorState => {\n if (stops.length === 0) return {x: 0, y: 0, visible: 0, pressed: 0, clicking: false};\n\n // A hold extends the stop it is on, which shifts everything after it.\n const timed: CursorStop[] = [];\n let shift = 0;\n for (const stop of stops) {\n const start = stop.frame + shift;\n timed.push({...stop, frame: start});\n if (stop.hold) {\n timed.push({...stop, frame: start + stop.hold, click: false});\n shift += stop.hold;\n }\n }\n\n const first = timed[0];\n const last = timed[timed.length - 1];\n if (frame <= first.frame) return {x: first.x, y: first.y, visible: 0, pressed: 0, clicking: false};\n\n const frames = timed.map((stop) => stop.frame);\n const x = interpolate(frame, frames, timed.map((stop) => stop.x), {easing: Easing.standard});\n const y = interpolate(frame, frames, timed.map((stop) => stop.y), {easing: Easing.standard});\n\n // The most recent click at or before this frame owns the press ring.\n let pressed = 0;\n for (const stop of timed) {\n if (!stop.click || stop.frame > frame) continue;\n const age = frame - stop.frame;\n if (age <= PRESS_FRAMES) pressed = Math.max(pressed, 1 - age / PRESS_FRAMES);\n }\n\n return {\n x,\n y,\n // Fade in as it arrives and out after the last stop, so a pointer never\n // pops onto a frame it was not part of.\n visible: interpolate(\n frame,\n [first.frame, first.frame + 6, last.frame + 12, last.frame + 20],\n [0, 1, 1, 0],\n {easing: Easing.standard},\n ),\n pressed,\n clicking: pressed > 0,\n };\n};\n\n/** The last frame an authored path is still on screen, for sizing a scene. */\nexport const cursorDuration = (stops: CursorStop[]): number => {\n const hold = stops.reduce((total, stop) => total + (stop.hold ?? 0), 0);\n return (stops[stops.length - 1]?.frame ?? 0) + hold + 20;\n};\n","import {useFrame} from \"./context\";\n\nexport type TypingOptions = {\n /** Frame the first character lands on. */\n from?: number;\n /** Characters revealed per second. */\n charactersPerSecond?: number;\n /**\n * Characters revealed per step. Typing one character at a time reads as a\n * machine at high speeds; two or three at a time reads as hands, because\n * that is roughly what a fast typist does between glances at the screen.\n */\n chunk?: number;\n /** Frames the caret stays solid after the last character before it blinks. */\n settle?: number;\n};\n\nexport type TypingState = {\n /** What is on screen at this frame. */\n text: string;\n /** How many characters of the source are revealed. */\n length: number;\n /** True once every character is on screen. */\n done: boolean;\n /**\n * Whether the caret is drawn this frame: solid while typing and for a beat\n * after, blinking once the line is finished, the way a terminal waits.\n */\n caret: boolean;\n /** 0 before the first character, 1 at the last. */\n progress: number;\n};\n\nconst DEFAULTS = {from: 0, charactersPerSecond: 22, chunk: 1, settle: 12};\n\n/** Frames the typing itself occupies, for laying out what comes after it. */\nexport const typingFrames = (text: string, options: TypingOptions = {}, fps = 30): number => {\n const {charactersPerSecond, chunk} = {...DEFAULTS, ...options};\n const steps = Math.ceil(text.length / Math.max(1, chunk));\n return Math.ceil((steps * Math.max(1, chunk) * fps) / Math.max(1, charactersPerSecond));\n};\n\n/**\n * What a line of typed text looks like at one frame.\n *\n * A pure function of the frame, so scrubbing backwards untypes the line\n * exactly and two render workers on either side of a chunk boundary agree\n * character for character. The caret is part of the state rather than a\n * separate blink timer for the same reason.\n */\nexport const typedAt = (text: string, frame: number, options: TypingOptions = {}, fps = 30): TypingState => {\n const {from, charactersPerSecond, chunk, settle} = {...DEFAULTS, ...options};\n const step = Math.max(1, chunk);\n const elapsed = frame - from;\n const revealed = Math.floor((elapsed / fps) * charactersPerSecond);\n const length = Math.max(0, Math.min(text.length, Math.floor(revealed / step) * step));\n const done = elapsed >= 0 && length >= text.length;\n const finishedAt = from + typingFrames(text, options, fps);\n // Solid while there is more to type and through the settle, then a one\n // second blink: on for the first half of each cycle.\n const caret = !done || frame < finishedAt + settle ? elapsed >= 0 : (frame - finishedAt - settle) % fps < fps / 2;\n\n return {\n text: text.slice(0, length),\n length,\n done,\n caret,\n progress: text.length === 0 ? 1 : length / text.length,\n };\n};\n\n/** `typedAt` bound to the current frame. */\nexport const useTyping = (text: string, options: TypingOptions = {}): TypingState =>\n typedAt(text, useFrame(), options);\n","/**\n * Randomness that survives a re-render.\n *\n * A frame is a pure function of its number. `Math.random()` breaks that in the\n * quietest possible way: the preview looks fine, every export looks fine, and\n * the two are different — and so are two chunks of the same export, because a\n * render is parallel and each worker rolls its own numbers. Fifty particles\n * that jump between chunk boundaries is the usual symptom, found late.\n *\n * So a composition asks for a number by name instead. The same seed always\n * gives the same value, on every machine and in every worker, and a seed that\n * includes the frame gives motion that is random-looking and reproducible.\n */\n\n/**\n * A 32-bit hash of a string, so a seed can be written as a readable name\n * rather than a magic integer. FNV-1a: small, well distributed for short keys,\n * and stable across engines, which matters because two workers must agree.\n */\nconst hashSeed = (value: string): number => {\n let hash = 0x811c9dc5;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193);\n }\n return hash >>> 0;\n};\n\nconst toSeed = (seed: number | string): number =>\n typeof seed === \"number\" ? Math.floor(seed) >>> 0 : hashSeed(seed);\n\n/**\n * A number in `[0, 1)` for a seed. The same seed always returns the same\n * number, which is the whole point.\n *\n * ```tsx\n * const drift = random(`particle-${index}`) * 40;\n * const jitter = random([frame, index]) - 0.5;\n * ```\n *\n * An array seed is joined, which is the convenient way to say \"this thing, on\n * this frame\" without building the string by hand.\n */\nexport const random = (seed: number | string | Array<number | string>): number => {\n const key = Array.isArray(seed) ? seed.join(\":\") : seed;\n // Mulberry32, the same generator the audio synthesis uses, so a project has\n // one notion of \"seeded\" rather than two that disagree.\n let state = (toSeed(key) + 0x6d2b79f5) >>> 0;\n let t = Math.imul(state ^ (state >>> 15), 1 | state);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n};\n\n/** A number in `[min, max)`, for a seed. */\nexport const randomBetween = (seed: number | string | Array<number | string>, min: number, max: number): number =>\n min + random(seed) * (max - min);\n\n/** One item from a list, for a seed. Empty lists return undefined. */\nexport const randomPick = <T,>(seed: number | string | Array<number | string>, items: readonly T[]): T | undefined =>\n items.length === 0 ? undefined : items[Math.floor(random(seed) * items.length)];\n\n/**\n * A shuffled copy, for a seed. Fisher-Yates driven by the same generator, so\n * the order is arbitrary but fixed — a list that reshuffles every frame is an\n * animation nobody asked for.\n */\nexport const randomOrder = <T,>(seed: number | string | Array<number | string>, items: readonly T[]): T[] => {\n const key = Array.isArray(seed) ? seed.join(\":\") : String(seed);\n const out = [...items];\n for (let index = out.length - 1; index > 0; index -= 1) {\n const swap = Math.floor(random(`${key}:${index}`) * (index + 1));\n [out[index], out[swap]] = [out[swap], out[index]];\n }\n return out;\n};\n","\"use client\";\n\nimport {useEffect, useLayoutEffect, useRef, type RefObject} from \"react\";\nimport {useFrame, useReadiness, useVideo} from \"./context\";\n\nexport type CanvasDraw = (context: CanvasRenderingContext2D, state: {frame: number; width: number; height: number}) => void;\n\n/** `useLayoutEffect` warns during server rendering, where there is no canvas. */\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/**\n * Draw to a canvas from the frame clock.\n *\n * The contract a video runs on is that frame N produces the same pixels every\n * time. A canvas is where that is easiest to lose: the obvious way to animate\n * one is `requestAnimationFrame`, which is wall time, and wall time means the\n * export samples wherever the loop happened to be. Two workers rendering\n * neighbouring chunks then disagree, and the seam shows.\n *\n * So the draw is a pure function of the frame, called synchronously before the\n * browser paints, and the frame is held until it has run. The capture waits on\n * the same readiness handshake an image decode uses, which is what makes the\n * screenshot see finished pixels rather than an empty buffer.\n */\nexport const useCanvas = (draw: CanvasDraw, dependencies: readonly unknown[] = []): RefObject<HTMLCanvasElement | null> => {\n const canvas = useRef<HTMLCanvasElement | null>(null);\n const frame = useFrame();\n const {width, height} = useVideo();\n const readiness = useReadiness();\n // The draw is called with the current closure but must not re-run the effect\n // when an inline function identity changes, or every render would repaint.\n const latest = useRef(draw);\n latest.current = draw;\n\n useIsomorphicLayoutEffect(() => {\n const element = canvas.current;\n if (!element) return;\n\n // Held across the draw, so a frame is never captured mid-paint.\n const release = readiness.hold();\n try {\n const context = element.getContext(\"2d\", {alpha: true});\n if (!context) return;\n\n // Reset rather than accumulate: a frame is drawn from nothing, so\n // scrubbing backwards produces the same image as playing forwards.\n context.setTransform(1, 0, 0, 1, 0, 0);\n context.clearRect(0, 0, element.width, element.height);\n latest.current(context, {frame, width: element.width, height: element.height});\n } finally {\n release();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [frame, width, height, ...dependencies]);\n\n return canvas;\n};\n\n/**\n * Rasterize HTML into a canvas, deterministically.\n *\n * The browser will draw an SVG containing a `foreignObject` onto a canvas, and\n * a `foreignObject` can hold ordinary markup. That is the whole trick, and the\n * reason it needs care: the image decode is asynchronous, so the frame has to\n * be held until it lands, and the markup has to carry its own styles because\n * nothing outside the SVG reaches into it.\n *\n * Fonts are the sharp edge. A face that is not loaded when this runs will fall\n * back, and the fallback is what gets baked into the pixels — which is why the\n * caller waits on `document.fonts.ready` before drawing.\n */\nexport const drawHtml = async (\n context: CanvasRenderingContext2D,\n html: string,\n options: {width: number; height: number; style?: string},\n): Promise<void> => {\n const {width, height, style = \"\"} = options;\n if (typeof document !== \"undefined\" && document.fonts?.ready) await document.fonts.ready;\n\n const svg = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">`,\n `<foreignObject width=\"100%\" height=\"100%\">`,\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px;${style}\">`,\n html,\n `</div></foreignObject></svg>`,\n ].join(\"\");\n\n // A data URL rather than a blob URL: a blob URL has to be revoked, and a\n // leak here is a leak once per frame for the length of the video.\n const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\n await new Promise<void>((done, fail) => {\n const image = new Image();\n image.onload = () => {\n context.drawImage(image, 0, 0, width, height);\n done();\n };\n image.onerror = () =>\n fail(\n new Error(\n \"The HTML could not be rasterized. Every element inside must carry inline styles, and images must be data URLs: an SVG foreignObject cannot reach outside itself.\",\n ),\n );\n image.src = encoded;\n });\n};\n","import {random} from \"./random\";\n\nexport type PlaceholderOptions = {\n width?: number;\n height?: number;\n /** Drawn across the middle, so a fixture says what it is standing in for. */\n label?: string;\n /** Two colours the gradient runs between. */\n from?: string;\n to?: string;\n /** Seed for the scatter, so two placeholders differ without differing runs. */\n seed?: string;\n};\n\n/**\n * A picture that ships as code.\n *\n * A component that shows media needs media to show, and a fixture that ships a\n * JPEG cannot be reviewed in a diff, cannot be recoloured by a brand, and adds\n * a binary to a repository forever. Generating an SVG instead keeps the\n * registry's rule intact — install copies source — and makes the picture do\n * something a file cannot: describe itself.\n *\n * It is deliberately obviously a placeholder. A fixture that looks like real\n * photography invites someone to ship it.\n */\nexport const placeholderSvg = ({\n width = 1600,\n height = 900,\n label,\n from = \"#1a1a1a\",\n to = \"#0a0a0a\",\n seed = \"placeholder\",\n}: PlaceholderOptions = {}): string => {\n const shapes = Array.from({length: 14}, (_, index) => {\n const x = random([seed, \"x\", index]) * width;\n const y = random([seed, \"y\", index]) * height;\n const radius = (random([seed, \"r\", index]) * 0.16 + 0.03) * Math.min(width, height);\n const opacity = (random([seed, \"o\", index]) * 0.06 + 0.02).toFixed(3);\n return `<circle cx=\"${x.toFixed(1)}\" cy=\"${y.toFixed(1)}\" r=\"${radius.toFixed(1)}\" fill=\"#ffffff\" opacity=\"${opacity}\"/>`;\n }).join(\"\");\n\n const caption = label\n ? `<text x=\"50%\" y=\"50%\" fill=\"#ffffff\" fill-opacity=\"0.42\" font-family=\"ui-monospace, monospace\" font-size=\"${Math.round(\n Math.min(width, height) * 0.06,\n )}\" text-anchor=\"middle\" dominant-baseline=\"middle\">${label.replace(/[<>&]/g, \"\")}</text>`\n : \"\";\n\n return [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n `<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">`,\n `<stop offset=\"0\" stop-color=\"${from}\"/><stop offset=\"1\" stop-color=\"${to}\"/>`,\n `</linearGradient></defs>`,\n `<rect width=\"${width}\" height=\"${height}\" fill=\"url(#g)\"/>`,\n shapes,\n `<rect x=\"1\" y=\"1\" width=\"${width - 2}\" height=\"${height - 2}\" fill=\"none\" stroke=\"#ffffff\" stroke-opacity=\"0.08\"/>`,\n caption,\n `</svg>`,\n ].join(\"\");\n};\n\n/**\n * The same picture as a data URL, which is what an `<img>` or a canvas draw\n * wants. Inline rather than fetched: a fixture that needs the network is a\n * fixture that fails on a plane, in CI, and in a sandboxed render.\n */\nexport const placeholderImage = (options: PlaceholderOptions = {}): string =>\n `data:image/svg+xml;charset=utf-8,${encodeURIComponent(placeholderSvg(options))}`;\n\n/**\n * A frame of a placeholder \"clip\": the same picture with a moving marker and a\n * timecode, so a component that plays media has something to play that visibly\n * advances and is still a pure function of the frame.\n */\nexport const placeholderFrame = (frame: number, options: PlaceholderOptions & {fps?: number} = {}): string => {\n const {width = 1600, height = 900, fps = 30, ...rest} = options;\n const seconds = frame / fps;\n const timecode = `${String(Math.floor(seconds / 60)).padStart(2, \"0\")}:${String(Math.floor(seconds % 60)).padStart(2, \"0\")}:${String(\n frame % fps,\n ).padStart(2, \"0\")}`;\n\n const base = placeholderSvg({...rest, width, height, label: undefined});\n const progress = (frame % (fps * 4)) / (fps * 4);\n const marker = [\n `<rect x=\"0\" y=\"${height - 12}\" width=\"${(width * progress).toFixed(1)}\" height=\"12\" fill=\"#ffffff\" fill-opacity=\"0.5\"/>`,\n `<text x=\"${width / 2}\" y=\"${height / 2}\" fill=\"#ffffff\" fill-opacity=\"0.5\" font-family=\"ui-monospace, monospace\" font-size=\"${Math.round(\n Math.min(width, height) * 0.08,\n )}\" text-anchor=\"middle\" dominant-baseline=\"middle\">${timecode}</text>`,\n ].join(\"\");\n\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(base.replace(\"</svg>\", `${marker}</svg>`))}`;\n};\n","import {type CueDefinition} from \"./cue\";\nimport {type Duration} from \"./time\";\nimport {type VideoLayout} from \"./layout\";\nimport {type ParsableSchema} from \"./schema\";\n\nexport type VideoMetadata<Input = Record<string, unknown>> = {\n readonly kind: \"odori-video-metadata\";\n id: string;\n title: string;\n description?: string;\n duration?: Duration;\n layout?: VideoLayout;\n schema?: ParsableSchema<Input>;\n defaultProps?: Partial<Input>;\n tags?: string[];\n thumbnailFrame?: number;\n /**\n * Named alternatives for the video's sound: each variant overrides brand\n * cues by name, and everything else about the film stays identical. This\n * is what a narration audition or a localized voice track is, and it is\n * one video with a switch rather than a copy of the video per option.\n * Studio offers the variants beside the player, and `odori export` takes\n * one with `--audio <name>`. Absent, or absent a name, the brand's own\n * cues play.\n */\n audio?: {variants?: Record<string, Record<string, string | CueDefinition>>};\n};\n\nexport type VideoMetadataInput<Input = Record<string, unknown>> = Omit<VideoMetadata<Input>, \"kind\" | \"id\"> & {\n /**\n * Defaults to the entry's path under `videos/`, so the directory names a\n * video the way a route names a page. Set it to keep an id stable across a\n * directory move.\n */\n id?: string;\n};\n\n/** A segment of an id: alphanumeric with dashes, the way a directory is named. */\nconst SEGMENT = /^[a-z0-9][a-z0-9-]*$/i;\n\nexport const isValidVideoId = (id: string): boolean =>\n id.length > 0 && id.split(\"/\").every((segment) => SEGMENT.test(segment));\n\n/**\n * An id left unset is resolved from the filesystem by discovery. The empty\n * string is the unresolved state: no entry reaches a manifest, a render, or\n * Studio without an id stamped in.\n */\nexport const resolveVideoId = (id: string | undefined, pathId: string): string => id || pathId;\n\nexport const defineVideoMetadata = <Input = Record<string, unknown>>(\n metadata: VideoMetadataInput<Input>,\n): VideoMetadata<Input> => {\n if (metadata.id !== undefined && !isValidVideoId(metadata.id)) {\n throw new Error(`Video id must be alphanumeric path segments with dashes: ${metadata.id}`);\n }\n return {kind: \"odori-video-metadata\", ...metadata, id: metadata.id ?? \"\"};\n};\n\nexport type PrepareContext<Input> = {\n input: Input;\n assets: {resolve(reference: string): Promise<string>};\n cache: {getOrSet<Value>(key: string, factory: () => Promise<Value>): Promise<Value>};\n signal?: AbortSignal;\n};\n\nexport type PrepareFunction<Input = Record<string, unknown>, Prepared = unknown> = {\n readonly kind: \"odori-prepare\";\n version: string;\n run(context: PrepareContext<Input>): Promise<Prepared>;\n};\n\nexport const definePrepare = <Input = Record<string, unknown>, Prepared = unknown>(\n run: (context: PrepareContext<Input>) => Promise<Prepared>,\n options: {version?: string} = {},\n): PrepareFunction<Input, Prepared> => ({\n kind: \"odori-prepare\",\n version: options.version ?? \"1\",\n run,\n});\n","/**\n * A tiny serializable input contract.\n *\n * Odori needs three things from a schema: validation with defaults, a JSON\n * description Studio can turn into controls, and zero runtime dependencies.\n * Any zod-compatible object with `parse()` is also accepted.\n */\nexport type FieldDescriptor =\n | {type: \"text\"; defaultValue: string; maxLength?: number; multiline?: boolean}\n | {type: \"number\"; defaultValue: number; min?: number; max?: number; step?: number}\n | {type: \"boolean\"; defaultValue: boolean}\n | {type: \"select\"; defaultValue: string; options: string[]}\n | {type: \"color\"; defaultValue: string}\n | {type: \"json\"; defaultValue: unknown};\n\nexport type InputSchema<Value = Record<string, unknown>> = {\n readonly kind: \"odori-schema\";\n readonly fields: Record<string, FieldDescriptor>;\n parse(input: unknown): Value;\n safeParse(input: unknown): {success: true; data: Value} | {success: false; issues: string[]};\n defaults(): Value;\n describe(): Record<string, FieldDescriptor>;\n};\n\nexport type ParsableSchema<Value = unknown> = InputSchema<Value> | {parse(input: unknown): Value};\n\nconst validateField = (name: string, field: FieldDescriptor, value: unknown, issues: string[]): unknown => {\n if (value === undefined) return field.defaultValue;\n switch (field.type) {\n case \"text\":\n case \"color\": {\n if (typeof value !== \"string\") {\n issues.push(`${name} must be a string`);\n return field.defaultValue;\n }\n if (field.type === \"text\" && field.maxLength !== undefined && value.length > field.maxLength) {\n issues.push(`${name} exceeds ${field.maxLength} characters`);\n }\n return value;\n }\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n issues.push(`${name} must be a finite number`);\n return field.defaultValue;\n }\n if (field.min !== undefined && value < field.min) issues.push(`${name} is below ${field.min}`);\n if (field.max !== undefined && value > field.max) issues.push(`${name} is above ${field.max}`);\n return value;\n }\n case \"boolean\": {\n if (typeof value !== \"boolean\") {\n issues.push(`${name} must be a boolean`);\n return field.defaultValue;\n }\n return value;\n }\n case \"select\": {\n if (typeof value !== \"string\" || !field.options.includes(value)) {\n issues.push(`${name} must be one of ${field.options.join(\", \")}`);\n return field.defaultValue;\n }\n return value;\n }\n default:\n return value;\n }\n};\n\nexport const defineInputSchema = <Fields extends Record<string, FieldDescriptor>>(\n fields: Fields,\n): InputSchema<Record<string, unknown>> => {\n const defaults = () =>\n Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.defaultValue]));\n\n const safeParse = (input: unknown) => {\n if (input !== undefined && input !== null && typeof input !== \"object\") {\n return {success: false as const, issues: [\"input must be an object\"]};\n }\n const source = (input ?? {}) as Record<string, unknown>;\n const issues: string[] = [];\n const data: Record<string, unknown> = {};\n for (const [name, field] of Object.entries(fields)) {\n data[name] = validateField(name, field, source[name], issues);\n }\n for (const key of Object.keys(source)) {\n if (!(key in fields)) data[key] = source[key];\n }\n return issues.length > 0\n ? {success: false as const, issues}\n : {success: true as const, data};\n };\n\n return {\n kind: \"odori-schema\",\n fields,\n defaults,\n describe: () => fields,\n safeParse,\n parse(input) {\n const result = safeParse(input);\n if (!result.success) throw new Error(`Invalid video input: ${result.issues.join(\"; \")}`);\n return result.data;\n },\n };\n};\n\nexport const isOdoriSchema = (schema: unknown): schema is InputSchema =>\n typeof schema === \"object\" && schema !== null && (schema as {kind?: string}).kind === \"odori-schema\";\n\nexport const parseWithSchema = <Value>(schema: ParsableSchema<Value> | undefined, input: unknown): Value =>\n schema ? schema.parse(input) : ((input ?? {}) as Value);\n"],"mappings":"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","names":["useCallback","useState","loop","useEffect","useRef","useRef","useEffect","target","loop","useState","useCallback","useEffect","useState","jsx","useState","useEffect","offset","step","useEffect","useRef","useEffect","useRef","seconds"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as Duration, B as Brand, C as CueDefinition } from './brand-
|
|
1
|
+
import { D as Duration, B as Brand, C as CueDefinition } from './brand-CGe0NYqW.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { ReactNode, ComponentPropsWithoutRef, CSSProperties } from 'react';
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { i as CreateManifestOptions, k as ExportJob, M as ManifestAsset, l as ManifestAudioCue, m as ManifestFont, X as ManifestToolchain, R as RenderManifest, x as createRenderManifest } from './manifest-
|
|
2
|
-
import './brand-
|
|
1
|
+
export { i as CreateManifestOptions, k as ExportJob, M as ManifestAsset, l as ManifestAudioCue, m as ManifestFont, X as ManifestToolchain, R as RenderManifest, x as createRenderManifest } from './manifest-2usUa-uA.js';
|
|
2
|
+
import './brand-CGe0NYqW.js';
|
|
3
3
|
import 'react/jsx-runtime';
|
|
4
4
|
import 'react';
|
package/dist/preview.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { Component, ReactNode, ErrorInfo, ComponentType } from 'react';
|
|
3
|
-
import { C as CueDefinition, D as Duration, B as Brand } from './brand-
|
|
3
|
+
import { C as CueDefinition, D as Duration, B as Brand } from './brand-CGe0NYqW.js';
|
|
4
4
|
import { F as FieldDescriptor } from './schema-DXxRezrk.js';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
6
|
|
package/dist/preview.js
CHANGED
package/package.json
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odori",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An independent React video framework with a first-class videos source root, deterministic frame runtime, timeline compiler, player, and render manifest.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./effects": {
|
|
13
|
+
"types": "./dist/effects.d.ts",
|
|
14
|
+
"import": "./dist/effects.js"
|
|
15
|
+
},
|
|
16
|
+
"./preview": {
|
|
17
|
+
"types": "./dist/preview.d.ts",
|
|
18
|
+
"import": "./dist/preview.js"
|
|
19
|
+
},
|
|
20
|
+
"./manifest": {
|
|
21
|
+
"types": "./dist/manifest.d.ts",
|
|
22
|
+
"import": "./dist/manifest.js"
|
|
23
|
+
},
|
|
12
24
|
"./package.json": "./package.json"
|
|
13
25
|
},
|
|
14
26
|
"files": [
|
|
15
27
|
"dist"
|
|
16
28
|
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsup",
|
|
19
|
-
"typecheck": "tsc --noEmit",
|
|
20
|
-
"prepack": "pnpm build"
|
|
21
|
-
},
|
|
22
29
|
"peerDependencies": {
|
|
23
30
|
"react": "^19.0.0"
|
|
24
31
|
},
|
|
@@ -41,27 +48,12 @@
|
|
|
41
48
|
],
|
|
42
49
|
"sideEffects": false,
|
|
43
50
|
"publishConfig": {
|
|
44
|
-
"access": "public"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"types": "./dist/effects.d.ts",
|
|
54
|
-
"import": "./dist/effects.js"
|
|
55
|
-
},
|
|
56
|
-
"./preview": {
|
|
57
|
-
"types": "./dist/preview.d.ts",
|
|
58
|
-
"import": "./dist/preview.js"
|
|
59
|
-
},
|
|
60
|
-
"./manifest": {
|
|
61
|
-
"types": "./dist/manifest.d.ts",
|
|
62
|
-
"import": "./dist/manifest.js"
|
|
63
|
-
},
|
|
64
|
-
"./package.json": "./package.json"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
},
|
|
57
|
+
"main": "./dist/index.js",
|
|
58
|
+
"types": "./dist/index.d.ts"
|
|
59
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audio.ts","../src/synth.ts","../src/cue.ts","../src/easing.ts","../src/brand.ts","../src/layout.ts","../src/runtime.tsx"],"sourcesContent":["import {type Duration} from \"./time\";\n\nexport type AudioCue = {\n /** Stable identity, derived from the source and its placement. */\n id: string;\n src: string;\n fromFrame: number;\n durationInFrames: number;\n /** Linear gain. 1 is unchanged. */\n gain: number;\n /**\n * An authored curve, sampled from a `gain` function at compile time. A\n * function cannot enter a manifest or an FFmpeg filter; its samples can.\n */\n gainPoints?: EnvelopePoint[];\n fadeInFrames: number;\n fadeOutFrames: number;\n /** Seconds skipped at the head of the source file. */\n trimStartSeconds: number;\n loop: boolean;\n /** Ducked cues are attenuated while a louder cue overlaps them. */\n duckUnder: boolean;\n};\n\nexport type AudioTrack = {cues: AudioCue[]; durationInFrames: number};\n\nexport type AudioProps = {\n src: string;\n /** Offset from the enclosing scene, or from the video when used at the top level. */\n from?: Duration;\n duration?: Duration;\n /**\n * A number, or a curve. The function is evaluated once per frame of the\n * cue's window when the timeline compiles, so preview and export read the\n * same points rather than running your code twice.\n */\n gain?: number | ((frame: number) => number);\n fadeIn?: Duration;\n fadeOut?: Duration;\n trimStart?: Duration;\n loop?: boolean;\n duckUnder?: boolean;\n};\n\nexport const cueId = (src: string, fromFrame: number): string => `${src}@${fromFrame}`;\n\n/**\n * A source is either a URL the dev server and the render worker both serve, or\n * a reference declared in `odori.config.ts`. Anything that is not obviously a\n * path is treated as a reference, so `<Audio src=\"score\" />` and\n * `useAssets().resolve(\"score\")` name the same file.\n */\nexport const isAssetReference = (src: string): boolean => !/^(https?:\\/\\/|\\/|\\.\\/|\\.\\.\\/|data:|blob:)/.test(src);\n\n/** Deterministic ordering so a manifest hash does not depend on mount order. */\nexport const sortCues = (cues: AudioCue[]): AudioCue[] =>\n [...cues].sort((left, right) => left.fromFrame - right.fromFrame || left.id.localeCompare(right.id));\n\nexport const trackDuration = (cues: AudioCue[]): number =>\n cues.reduce((total, cue) => Math.max(total, cue.fromFrame + cue.durationInFrames), 0);\n\n/**\n * Linear gain at one frame, including fades. The renderer applies the same\n * curve through FFmpeg, so preview and export agree on shape.\n */\nexport const gainAtFrame = (cue: AudioCue, frame: number): number => {\n const local = frame - cue.fromFrame;\n if (local < 0 || local >= cue.durationInFrames) return 0;\n const fadeIn = cue.fadeInFrames > 0 ? Math.min(1, local / cue.fadeInFrames) : 1;\n const remaining = cue.durationInFrames - local;\n const fadeOut = cue.fadeOutFrames > 0 ? Math.min(1, remaining / cue.fadeOutFrames) : 1;\n const authored = cue.gainPoints ? envelopeAtFrame(cue.gainPoints, frame) : 1;\n return cue.gain * authored * fadeIn * fadeOut;\n};\n\n/**\n * Sample a gain function across a window, then drop the points a straight line\n * already describes. One point per frame would compile into a filter\n * expression hundreds of branches deep for a curve the ear cannot tell from a\n * dozen segments.\n */\nexport const sampleGainCurve = (\n curve: (frame: number) => number,\n fromFrame: number,\n durationInFrames: number,\n tolerance = 0.01,\n): EnvelopePoint[] => {\n const raw: EnvelopePoint[] = [];\n for (let frame = fromFrame; frame < fromFrame + durationInFrames; frame += 1) {\n const value = curve(frame - fromFrame);\n raw.push({frame, value: Number.isFinite(value) ? value : 1});\n }\n if (raw.length <= 2) return raw;\n\n const kept: EnvelopePoint[] = [raw[0]];\n for (let index = 1; index < raw.length - 1; index += 1) {\n const previous = kept[kept.length - 1];\n const next = raw[index + 1];\n const span = next.frame - previous.frame;\n const predicted =\n span === 0\n ? previous.value\n : previous.value + ((next.value - previous.value) * (raw[index].frame - previous.frame)) / span;\n if (Math.abs(raw[index].value - predicted) > tolerance) kept.push(raw[index]);\n }\n kept.push(raw[raw.length - 1]);\n return kept;\n};\n\n/** How far a ducked cue drops while a non-ducked cue is sounding. */\nexport const DUCK_GAIN = 0.35;\n\n/** Ramp into and out of a duck, so the drop is heard as mixing, not as a cut. */\nexport const DUCK_RAMP_FRAMES = 6;\n\nexport type EnvelopePoint = {frame: number; value: number};\n\nconst clampFrame = (frame: number, durationInFrames: number) => Math.max(0, Math.min(durationInFrames, frame));\n\n/**\n * The duck envelope for one cue against the rest of the track.\n *\n * A duck lasts only while a non-ducked cue overlaps, rather than for the whole\n * file: a one second confirmation should not hold a music bed down for the\n * length of the video. Preview evaluates these points directly and the encoder\n * compiles them into a volume expression, so both paths hear one envelope.\n */\nexport const duckEnvelope = (cue: AudioCue, cues: AudioCue[]): EnvelopePoint[] => {\n const end = cue.fromFrame + cue.durationInFrames;\n if (!cue.duckUnder) return [{frame: cue.fromFrame, value: 1}, {frame: end, value: 1}];\n\n const windows = cues\n .filter((other) => other.id !== cue.id && !other.duckUnder)\n .map((other) => ({\n start: Math.max(cue.fromFrame, other.fromFrame),\n end: Math.min(end, other.fromFrame + other.durationInFrames),\n }))\n .filter((window) => window.end > window.start)\n .sort((left, right) => left.start - right.start);\n\n // Overlapping windows duck once, not once per cue.\n const merged: Array<{start: number; end: number}> = [];\n for (const window of windows) {\n const last = merged[merged.length - 1];\n if (last && window.start <= last.end) last.end = Math.max(last.end, window.end);\n else merged.push({...window});\n }\n\n const points: EnvelopePoint[] = [{frame: cue.fromFrame, value: 1}];\n for (const window of merged) {\n points.push(\n {frame: clampFrame(window.start - DUCK_RAMP_FRAMES, end), value: 1},\n {frame: clampFrame(window.start, end), value: DUCK_GAIN},\n {frame: clampFrame(window.end, end), value: DUCK_GAIN},\n {frame: clampFrame(window.end + DUCK_RAMP_FRAMES, end), value: 1},\n );\n }\n points.push({frame: end, value: 1});\n return simplify(points.sort((left, right) => left.frame - right.frame));\n};\n\n/**\n * Two points at one frame are a step, and three points at one level are a\n * straight line. Collapsing both keeps the envelope, and the volume expression\n * compiled from it, as short as the shape actually requires.\n */\nconst simplify = (points: EnvelopePoint[]): EnvelopePoint[] => {\n const stepped: EnvelopePoint[] = [];\n for (const point of points) {\n const last = stepped[stepped.length - 1];\n if (last && last.frame === point.frame) stepped[stepped.length - 1] = point;\n else stepped.push(point);\n }\n\n return stepped.filter((point, index) => {\n const previous = stepped[index - 1];\n const next = stepped[index + 1];\n return !previous || !next || previous.value !== point.value || next.value !== point.value;\n });\n};\n\n/** Linear interpolation between envelope points. */\nexport const envelopeAtFrame = (points: EnvelopePoint[], frame: number): number => {\n if (points.length === 0) return 1;\n if (frame <= points[0].frame) return points[0].value;\n for (let index = 1; index < points.length; index += 1) {\n const previous = points[index - 1];\n const current = points[index];\n if (frame > current.frame) continue;\n const span = current.frame - previous.frame;\n if (span <= 0) return current.value;\n return previous.value + ((current.value - previous.value) * (frame - previous.frame)) / span;\n }\n return points[points.length - 1].value;\n};\n\n/**\n * Everything that shapes a cue's level at one frame: its own gain and fades,\n * and the duck the rest of the track imposes on it.\n */\nexport const trackGainAtFrame = (cue: AudioCue, cues: AudioCue[], frame: number): number =>\n gainAtFrame(cue, frame) * envelopeAtFrame(duckEnvelope(cue, cues), frame);\n","/**\n * Deterministic synthesis. A cue is code that produces samples, so a sound\n * diffs, reviews, and upgrades like every other piece of source, and preview\n * and export can run the identical function.\n *\n * The rules are the frame runtime's rules: no wall clock, no unseeded\n * randomness, one sample rate. Same score in, same bytes out, on any machine.\n */\n\n/** Every cue renders at this rate, so a score never depends on the host. */\nexport const SAMPLE_RATE = 48000;\n\nexport type Signal = {\n /** One array per channel. Mono is one; the mix upmixes when it needs to. */\n channels: Float32Array[];\n sampleRate: number;\n};\n\nexport type Envelope = {\n /** Seconds from the start of the cue to full level. */\n attack?: number;\n /** Seconds from full level to the sustain level. */\n decay?: number;\n /** Level held after the decay, 0 to 1. */\n sustain?: number;\n /** Seconds to fall from the sustain level to silence. */\n release?: number;\n};\n\nconst clamp = (value: number, low: number, high: number) => Math.max(low, Math.min(high, value));\n\n/**\n * A seeded generator, so noise is reproducible. Mulberry32: small, fast, and\n * good enough for audio noise, where the requirement is \"no audible pattern\"\n * rather than cryptographic quality.\n */\nexport const seeded = (seed: number): (() => number) => {\n let state = seed >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = Math.imul(state ^ (state >>> 15), 1 | state);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n};\n\nexport const silence = (samples: number, channels = 1): Signal => ({\n channels: Array.from({length: channels}, () => new Float32Array(samples)),\n sampleRate: SAMPLE_RATE,\n});\n\n/** A sine partial. The building block for tones, ticks, and sub weight. */\nexport const sine = (samples: number, frequency: number, phase = 0): Signal => {\n const data = new Float32Array(samples);\n const increment = (Math.PI * 2 * frequency) / SAMPLE_RATE;\n for (let index = 0; index < samples; index += 1) data[index] = Math.sin(phase + increment * index);\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/** A triangle partial: softer than a square, brighter than a sine. */\nexport const triangle = (samples: number, frequency: number): Signal => {\n const data = new Float32Array(samples);\n const period = SAMPLE_RATE / frequency;\n for (let index = 0; index < samples; index += 1) {\n const position = (index % period) / period;\n data[index] = 4 * Math.abs(position - 0.5) - 1;\n }\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/** White noise from a seeded generator, so a click is the same click twice. */\nexport const noise = (samples: number, seed = 1): Signal => {\n const random = seeded(seed);\n const data = new Float32Array(samples);\n for (let index = 0; index < samples; index += 1) data[index] = random() * 2 - 1;\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/**\n * A frequency sweep, which is what a riser is. Linear in frequency rather than\n * in pitch, because the shape is easier to reason about when writing one.\n */\nexport const sweep = (samples: number, from: number, to: number): Signal => {\n const data = new Float32Array(samples);\n let phase = 0;\n for (let index = 0; index < samples; index += 1) {\n const position = samples <= 1 ? 1 : index / (samples - 1);\n const frequency = from + (to - from) * position;\n phase += (Math.PI * 2 * frequency) / SAMPLE_RATE;\n data[index] = Math.sin(phase);\n }\n return {channels: [data], sampleRate: SAMPLE_RATE};\n};\n\n/** Apply an ADSR shape. Anything omitted is skipped rather than defaulted. */\nexport const shape = (signal: Signal, envelope: Envelope): Signal => {\n const {attack = 0, decay = 0, sustain = 1, release = 0} = envelope;\n const length = signal.channels[0]?.length ?? 0;\n const attackEnd = Math.round(attack * SAMPLE_RATE);\n const decayEnd = attackEnd + Math.round(decay * SAMPLE_RATE);\n const releaseStart = Math.max(decayEnd, length - Math.round(release * SAMPLE_RATE));\n\n const level = (index: number): number => {\n if (index < attackEnd) return attackEnd === 0 ? 1 : index / attackEnd;\n if (index < decayEnd) {\n const position = (index - attackEnd) / Math.max(1, decayEnd - attackEnd);\n return 1 + (sustain - 1) * position;\n }\n if (index < releaseStart) return sustain;\n const remaining = length - releaseStart;\n return remaining <= 0 ? 0 : sustain * (1 - (index - releaseStart) / remaining);\n };\n\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(channel.length);\n for (let index = 0; index < channel.length; index += 1) out[index] = channel[index] * level(index);\n return out;\n }),\n };\n};\n\nexport const gain = (signal: Signal, amount: number): Signal => ({\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(channel.length);\n for (let index = 0; index < channel.length; index += 1) out[index] = channel[index] * amount;\n return out;\n }),\n});\n\n/** Sum signals, padding to the longest. Levels are the caller's business. */\nexport const mix = (...signals: Signal[]): Signal => {\n const length = Math.max(0, ...signals.map((signal) => signal.channels[0]?.length ?? 0));\n const width = Math.max(1, ...signals.map((signal) => signal.channels.length));\n const channels = Array.from({length: width}, () => new Float32Array(length));\n\n for (const signal of signals) {\n for (let channel = 0; channel < width; channel += 1) {\n const source = signal.channels[channel] ?? signal.channels[0];\n if (!source) continue;\n const target = channels[channel];\n for (let index = 0; index < source.length; index += 1) target[index] += source[index];\n }\n }\n\n return {channels, sampleRate: SAMPLE_RATE};\n};\n\n/**\n * One pole filters. Enough to shape a click or take the top off a pad, and\n * cheap enough to stay readable next to the sound it is making.\n */\nexport const lowPass = (signal: Signal, frequency: number): Signal => {\n const coefficient = clamp(1 - Math.exp((-2 * Math.PI * frequency) / SAMPLE_RATE), 0, 1);\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(channel.length);\n let previous = 0;\n for (let index = 0; index < channel.length; index += 1) {\n previous += coefficient * (channel[index] - previous);\n out[index] = previous;\n }\n return out;\n }),\n };\n};\n\nexport const highPass = (signal: Signal, frequency: number): Signal => {\n const low = lowPass(signal, frequency);\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel, index) => {\n const out = new Float32Array(channel.length);\n const removed = low.channels[index];\n for (let sample = 0; sample < channel.length; sample += 1) out[sample] = channel[sample] - removed[sample];\n return out;\n }),\n };\n};\n\n/**\n * Ramp the first and last few milliseconds to zero.\n *\n * A buffer that starts or ends on a non-zero sample is a step change, and a\n * step change is a click. Every cue gets this, because the alternative is a\n * score that sounds correct in isolation and glitches at its edges.\n */\nexport const declick = (signal: Signal, milliseconds = 3): Signal => {\n const ramp = Math.max(1, Math.round((milliseconds / 1000) * SAMPLE_RATE));\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = Float32Array.from(channel);\n const span = Math.min(ramp, Math.floor(out.length / 2));\n for (let index = 0; index < span; index += 1) {\n const level = index / span;\n out[index] *= level;\n out[out.length - 1 - index] *= level;\n }\n return out;\n }),\n };\n};\n\n/**\n * Join a signal's tail to its own head, so repeating it is inaudible.\n *\n * `declick` is wrong for a loop: ramping both edges to zero puts a hole at\n * every seam, once per bar for the length of the video. Instead the tail is\n * crossfaded over the head, which is the same trick a sampler uses — the last\n * few milliseconds fade out while the copy of them under the first few\n * milliseconds fades in, so the two ends already agree when they meet.\n *\n * The crossfade eats `milliseconds` from the end, which is why a looping score\n * should be written a hair long rather than exactly one bar.\n */\nexport const seamless = (signal: Signal, milliseconds = 12): Signal => {\n const fade = Math.max(1, Math.round((milliseconds / 1000) * SAMPLE_RATE));\n return {\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const span = Math.min(fade, Math.floor(channel.length / 2));\n if (span <= 1) return Float32Array.from(channel);\n // The loop body is everything but the tail the crossfade consumes.\n const out = Float32Array.from(channel.subarray(0, channel.length - span));\n for (let index = 0; index < span; index += 1) {\n const level = index / span;\n out[index] = out[index] * level + channel[channel.length - span + index] * (1 - level);\n }\n return out;\n }),\n };\n};\n\n/** Peak normalize, so a cue lands predictably before the mix's loudness pass. */\nexport const normalize = (signal: Signal, peak = 0.9): Signal => {\n let highest = 0;\n for (const channel of signal.channels) {\n for (let index = 0; index < channel.length; index += 1) {\n const value = Math.abs(channel[index]);\n if (value > highest) highest = value;\n }\n }\n return highest === 0 ? signal : gain(signal, peak / highest);\n};\n\n/** Repeat a signal until it fills `samples`, for beds under a long scene. */\nexport const loop = (signal: Signal, samples: number): Signal => ({\n sampleRate: signal.sampleRate,\n channels: signal.channels.map((channel) => {\n const out = new Float32Array(samples);\n if (channel.length === 0) return out;\n for (let index = 0; index < samples; index += 1) out[index] = channel[index % channel.length];\n return out;\n }),\n});\n\n/** Seconds to samples at the fixed rate, so scores read in musical time. */\nexport const seconds = (value: number): number => Math.max(0, Math.round(value * SAMPLE_RATE));\n\n/**\n * The musical layer.\n *\n * Everything above is a waveform. A bed is not a waveform, it is a phrase: the\n * same voice at different pitches, placed on a grid. These four keep a score\n * readable at that level, so a bed reads as \"these notes, this tempo\" rather\n * than as arithmetic over sample indices — and so two beds written by two\n * people share a vocabulary instead of each inventing one.\n *\n * The rules do not change: pure functions, seeded randomness, one sample rate.\n */\n\nconst SEMITONES = {c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11} as const;\n\n/**\n * Scientific pitch to frequency: `a4` is 440 Hz, `c#3` and `db3` are the same\n * key. A score names notes because \"a2\" survives transposition and reading,\n * and 110 does neither.\n */\nexport const note = (name: string): number => {\n const match = /^([a-gA-G])([#b]?)(-?\\d+)$/.exec(name.trim());\n if (!match) throw new Error(`Not a note name: \"${name}\". Use scientific pitch, for example \"a4\" or \"c#3\".`);\n const [, letter, accidental, octave] = match;\n const semitone =\n SEMITONES[letter.toLowerCase() as keyof typeof SEMITONES] +\n (accidental === \"#\" ? 1 : accidental === \"b\" ? -1 : 0) +\n (Number(octave) + 1) * 12;\n // MIDI 69 is a4 at 440 Hz; equal temperament from there.\n return 440 * 2 ** ((semitone - 69) / 12);\n};\n\n/** Samples in one step of a grid, so a score can size its own voices. */\nexport const step = (bpm: number, division = 4): number =>\n Math.max(1, Math.round((60 / bpm / (division / 4)) * SAMPLE_RATE));\n\nexport type Step = {\n /** Grid position, in steps from the start. Fractions are allowed. */\n at: number;\n /** The voice to place there, built at whatever length it needs. */\n play: (samples: number) => Signal;\n /** Length in steps. Defaults to one step. */\n length?: number;\n /** Linear gain for this hit. */\n gain?: number;\n};\n\n/**\n * Place voices on a tempo grid.\n *\n * A step's `play` receives the samples its length works out to, so one voice\n * definition serves a sixteenth and a whole bar. Swing delays every odd step\n * by a fraction of a step, which is the difference between a drum machine and\n * a groove; it is off by default because a bed under narration should not\n * shuffle unless it was asked to.\n */\nexport const sequence = (\n steps: Step[],\n options: {bpm: number; division?: number; swing?: number; samples?: number},\n): Signal => {\n const {bpm, division = 4, swing = 0, samples} = options;\n const width = step(bpm, division);\n const placed = steps.map((item) => {\n const length = Math.max(1, Math.round((item.length ?? 1) * width));\n const shuffle = swing > 0 && Math.round(item.at) % 2 === 1 ? swing * width : 0;\n const offset = Math.round(item.at * width + shuffle);\n return {offset, signal: item.gain === undefined ? item.play(length) : gain(item.play(length), item.gain)};\n });\n\n const total =\n samples ?? Math.max(0, ...placed.map(({offset, signal}) => offset + (signal.channels[0]?.length ?? 0)));\n const channelCount = Math.max(1, ...placed.map(({signal}) => signal.channels.length));\n const channels = Array.from({length: channelCount}, () => new Float32Array(total));\n\n for (const {offset, signal} of placed) {\n for (let channel = 0; channel < channelCount; channel += 1) {\n const source = signal.channels[channel] ?? signal.channels[0];\n if (!source) continue;\n const target = channels[channel];\n // A voice that runs past the end is cut, not wrapped: a phrase that\n // needs its tail should be written long enough to hold it.\n for (let index = 0; index < source.length && offset + index < total; index += 1) {\n target[offset + index] += source[index];\n }\n }\n }\n\n return {channels, sampleRate: SAMPLE_RATE};\n};\n\n/** Intervals from the root, in semitones. Enough colour for product scoring. */\nconst QUALITIES = {\n major: [0, 4, 7],\n minor: [0, 3, 7],\n sus2: [0, 2, 7],\n sus4: [0, 5, 7],\n major7: [0, 4, 7, 11],\n minor7: [0, 3, 7, 10],\n add9: [0, 4, 7, 14],\n} as const;\n\nexport type ChordQuality = keyof typeof QUALITIES;\n\n/**\n * The frequencies of a chord, root first. Voicing is left to the caller: what\n * makes a pad sound like a pad is which of these you hand to which oscillator,\n * and at what level.\n */\nexport const chord = (root: string | number, quality: ChordQuality = \"major\"): number[] => {\n const base = typeof root === \"number\" ? root : note(root);\n return QUALITIES[quality].map((interval) => base * 2 ** (interval / 12));\n};\n\n/**\n * Sustained tones under one envelope: the sound of a bed that is holding\n * rather than playing. Each voice is detuned by a few cents against the last\n * so the stack beats slowly instead of standing still, which is what separates\n * a pad from a test tone.\n */\nexport const pad = (\n frequencies: number[],\n samples: number,\n options: {envelope?: Envelope; detuneCents?: number; level?: number} = {},\n): Signal => {\n const {envelope = {attack: 0.4, sustain: 1, release: 0.6}, detuneCents = 4, level = 1 / Math.max(1, frequencies.length)} =\n options;\n const voices = frequencies.flatMap((frequency, index) => {\n const detune = 2 ** ((detuneCents * (index % 2 === 0 ? 1 : -1)) / 1200);\n return [gain(sine(samples, frequency), level), gain(sine(samples, frequency * detune), level * 0.6)];\n });\n return shape(mix(...voices), envelope);\n};\n","import {hashValue} from \"./hash\";\nimport {declick, SAMPLE_RATE, seamless, type Signal} from \"./synth\";\n\nexport type CueContext = {\n /** How many samples the cue must fill. */\n samples: number;\n sampleRate: number;\n};\n\nexport type CueDefinition = {\n readonly kind: \"odori-cue\";\n /** The name a brand registers and a video asks for. */\n name: string;\n /** Length at 30fps, which is how a contract states it. */\n durationInFrames: number;\n /**\n * Whether this score is one repeatable phrase rather than a single event.\n *\n * A looping cue renders its phrase once, at `durationInFrames`, and the\n * placement repeats it for as long as the window runs: one render, cached by\n * content, however long the scene. It also changes how the edges are\n * treated — see `defineCue` — and makes `<Audio>` loop by default, so a bed\n * fills its scene without the author restating what the cue already knows.\n */\n loops: boolean;\n /**\n * Anything the score reads. Held separately from the function so the\n * manifest can hash what a cue depends on: two cues that differ only in a\n * parameter are two different sounds and must not share a cached render.\n */\n params: Record<string, number | string | boolean>;\n render(context: CueContext): Signal;\n};\n\nexport type CueInput = Omit<CueDefinition, \"kind\" | \"loops\" | \"params\"> & {\n loops?: boolean;\n params?: Record<string, number | string | boolean>;\n};\n\n/**\n * A sound as source. The function runs unchanged in the browser for preview and\n * in Node for export, so the cue a reviewer approves is the cue that ships.\n */\nexport const defineCue = (input: CueInput): CueDefinition => ({\n kind: \"odori-cue\",\n loops: false,\n params: {},\n ...input,\n // A score is about the sound, not about remembering what a buffer does at\n // its edges. A one shot is declicked, because a buffer that stops\n // mid-waveform is a click. A loop is made seamless instead, because\n // declicking it would put that same silence at every seam.\n render: (context) => (input.loops ? seamless(input.render(context)) : declick(input.render(context))),\n});\n\nexport const isCueDefinition = (value: unknown): value is CueDefinition =>\n typeof value === \"object\" && value !== null && (value as CueDefinition).kind === \"odori-cue\";\n\n/**\n * Identity of a rendered cue. The name alone is not enough, because a project\n * can redefine what a name means; the params and the duration are what change\n * the samples, so they are what the cache and the manifest key on.\n */\nexport const cueSignature = (cue: CueDefinition): string =>\n hashValue({\n name: cue.name,\n durationInFrames: cue.durationInFrames,\n loops: cue.loops,\n params: cue.params,\n }).slice(0, 16);\n\n/** The samples a cue occupies at the given frame rate. */\nexport const cueSamples = (cue: CueDefinition, fps: number): number =>\n Math.max(1, Math.round((cue.durationInFrames / fps) * SAMPLE_RATE));\n\n/**\n * A generated cue resolves to a URL that names its own content, so the render\n * worker can serve it, the mix can find it on disk, and an unchanged cue is\n * never rendered twice.\n */\nexport const cueUrl = (cue: CueDefinition): string => `/__odori/cue/${cue.name}-${cueSignature(cue)}.wav`;\n","export type EasingFunction = (value: number) => number;\n\nconst solveBezierCoordinate = (time: number, a1: number, a2: number) => {\n const inverse = 1 - time;\n return 3 * inverse * inverse * time * a1 + 3 * inverse * time * time * a2 + time ** 3;\n};\n\nconst solveBezierSlope = (time: number, a1: number, a2: number) =>\n 3 * (1 - time) ** 2 * a1 + 6 * (1 - time) * time * (a2 - a1) + 3 * time ** 2 * (1 - a2);\n\nconst cubicBezier =\n (x1: number, y1: number, x2: number, y2: number): EasingFunction =>\n (value) => {\n if (value <= 0 || value >= 1) return value;\n let time = value;\n for (let index = 0; index < 8; index += 1) {\n const error = solveBezierCoordinate(time, x1, x2) - value;\n const slope = solveBezierSlope(time, x1, x2);\n if (Math.abs(error) < 0.000001 || Math.abs(slope) < 0.000001) break;\n time -= error / slope;\n }\n return solveBezierCoordinate(Math.min(1, Math.max(0, time)), y1, y2);\n };\n\nexport const Easing = {\n linear: (value: number) => value,\n bezier: cubicBezier,\n standard: cubicBezier(0.16, 1, 0.3, 1),\n quad: (value: number) => value * value,\n cubic: (value: number) => value ** 3,\n in: (easing: EasingFunction): EasingFunction => easing,\n out:\n (easing: EasingFunction): EasingFunction =>\n (value) =>\n 1 - easing(1 - value),\n inOut:\n (easing: EasingFunction): EasingFunction =>\n (value) =>\n value < 0.5 ? easing(value * 2) / 2 : 1 - easing((1 - value) * 2) / 2,\n};\n\nexport type InterpolateOptions = {\n easing?: EasingFunction;\n extrapolateLeft?: \"extend\" | \"clamp\";\n extrapolateRight?: \"extend\" | \"clamp\";\n};\n\nexport function interpolate(value: number, input: number[], output: number[], options?: InterpolateOptions): number;\nexport function interpolate(value: number, input: number[], output: string[], options?: InterpolateOptions): string;\nexport function interpolate(\n value: number,\n input: number[],\n output: Array<number | string>,\n options: InterpolateOptions = {},\n): number | string {\n if (input.length !== output.length || input.length < 2) {\n throw new Error(\"interpolate() requires matching input and output ranges.\");\n }\n\n let resolved = value;\n if (options.extrapolateLeft !== \"extend\") resolved = Math.max(input[0], resolved);\n if (options.extrapolateRight !== \"extend\") resolved = Math.min(input[input.length - 1], resolved);\n\n let rangeIndex = input.length - 2;\n for (let index = 0; index < input.length - 1; index += 1) {\n if (resolved <= input[index + 1]) {\n rangeIndex = index;\n break;\n }\n }\n\n const inputStart = input[rangeIndex];\n const inputEnd = input[rangeIndex + 1];\n const progress = inputEnd === inputStart ? 0 : (resolved - inputStart) / (inputEnd - inputStart);\n const eased = (options.easing ?? Easing.linear)(progress);\n const startValue = output[rangeIndex];\n const endValue = output[rangeIndex + 1];\n\n if (typeof startValue === \"number\" && typeof endValue === \"number\") {\n return startValue + (endValue - startValue) * eased;\n }\n if (typeof startValue === \"string\" && typeof endValue === \"string\") {\n const startNumbers = startValue.match(/-?\\d*\\.?\\d+/g)?.map(Number) ?? [];\n const endNumbers = endValue.match(/-?\\d*\\.?\\d+/g)?.map(Number) ?? [];\n if (startNumbers.length !== endNumbers.length) {\n throw new Error(\"String interpolation requires matching numeric segments.\");\n }\n let segment = 0;\n return startValue.replace(/-?\\d*\\.?\\d+/g, () => {\n const result = startNumbers[segment] + (endNumbers[segment] - startNumbers[segment]) * eased;\n segment += 1;\n return Number(result.toFixed(4)).toString();\n });\n }\n throw new Error(\"interpolate() output values must share a type.\");\n}\n\nexport type SpringOptions = {\n frame: number;\n fps: number;\n from?: number;\n to?: number;\n stiffness?: number;\n damping?: number;\n mass?: number;\n delayInFrames?: number;\n};\n\n/**\n * A deterministic damped-spring solve. The value depends only on the frame\n * index, so preview and render always agree.\n */\nexport const spring = ({\n frame,\n fps,\n from = 0,\n to = 1,\n stiffness = 120,\n damping = 20,\n mass = 1,\n delayInFrames = 0,\n}: SpringOptions): number => {\n const elapsed = Math.max(0, frame - delayInFrames);\n const step = 1 / fps;\n let position = 0;\n let velocity = 0;\n for (let index = 0; index < elapsed; index += 1) {\n const force = -stiffness * (position - 1) - damping * velocity;\n velocity += (force / mass) * step;\n position += velocity * step;\n }\n return from + (to - from) * position;\n};\n","import {type CueDefinition} from \"./cue\";\n\nexport type BrandColors = {\n background: string;\n surface: string;\n foreground: string;\n muted: string;\n accent: string;\n border: string;\n};\n\nexport type BrandTypography = {\n sans: string;\n mono: string;\n display?: string;\n};\n\nexport type BrandFont = {family: string; url: string; weight?: string; integrity?: string};\n\nexport type Brand = {\n readonly kind: \"odori-brand\";\n name: string;\n colors: BrandColors;\n typography: BrandTypography;\n fonts: BrandFont[];\n logos: Record<string, string>;\n motion: {standard: [number, number, number, number]; staggerFrames: number};\n audio: {\n /**\n * Symbolic cue names, so a video says what a sound means and the brand\n * decides which file that is. Values are asset references or URLs.\n */\n cues: Record<string, string | CueDefinition>;\n targetLufs: number;\n };\n};\n\nexport type BrandInput = {\n name?: string;\n colors?: Partial<BrandColors>;\n typography?: Partial<BrandTypography>;\n fonts?: BrandFont[];\n logos?: Record<string, string>;\n motion?: Partial<Brand[\"motion\"]>;\n audio?: Partial<Brand[\"audio\"]>;\n};\n\nexport const defaultBrand: Brand = {\n kind: \"odori-brand\",\n name: \"odori\",\n colors: {\n background: \"#000000\",\n surface: \"#0a0a0a\",\n foreground: \"#ededed\",\n muted: \"#a1a1a1\",\n accent: \"#ffffff\",\n border: \"#1f1f1f\",\n },\n /**\n * System faces, deliberately.\n *\n * A default that leads with a licensed family it does not ship is a default\n * that renders through a fallback on every machine while claiming not to —\n * and `odori test` is right to fail it, which meant a freshly scaffolded\n * project failed its own contract check the first time it installed a\n * component. A brand that wants Geist names it and adds the file, and the\n * check then protects that choice instead of punishing the default.\n */\n typography: {\n sans: 'system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n mono: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace',\n },\n fonts: [],\n logos: {},\n motion: {standard: [0.16, 1, 0.3, 1], staggerFrames: 4},\n audio: {cues: {}, targetLufs: -14},\n};\n\nexport const defineBrand = (input: BrandInput): Brand => ({\n kind: \"odori-brand\",\n name: input.name ?? defaultBrand.name,\n colors: {...defaultBrand.colors, ...input.colors},\n typography: {...defaultBrand.typography, ...input.typography},\n fonts: input.fonts ?? [],\n logos: {...input.logos},\n motion: {...defaultBrand.motion, ...input.motion},\n audio: {...defaultBrand.audio, ...input.audio, cues: {...defaultBrand.audio.cues, ...input.audio?.cues}},\n});\n\nexport const brandCssVariables = (brand: Brand): Record<string, string> => ({\n \"--odori-background\": brand.colors.background,\n \"--odori-surface\": brand.colors.surface,\n \"--odori-foreground\": brand.colors.foreground,\n \"--odori-muted\": brand.colors.muted,\n \"--odori-accent\": brand.colors.accent,\n \"--odori-border\": brand.colors.border,\n \"--odori-font-sans\": brand.typography.sans,\n \"--odori-font-mono\": brand.typography.mono,\n});\n","import {type Brand, defaultBrand} from \"./brand\";\n\nexport type VideoFormat = {width: number; height: number; fps: number};\nexport type SafeArea = {x: number; y: number};\nexport type MotionPolicy = {enter: [number, number, number, number]; staggerFrames: number; reducedMotion?: boolean};\nexport type AudioPolicy = {palette: string; targetLufs: number};\n\nexport type VideoLayout = {\n readonly kind: \"odori-layout\";\n format: VideoFormat;\n brand: Brand;\n safeArea: SafeArea;\n motion: MotionPolicy;\n audio: AudioPolicy;\n transition: {crossfadeFrames: number};\n};\n\nexport type VideoLayoutInput = {\n extends?: VideoLayout;\n format?: Partial<VideoFormat>;\n brand?: Brand;\n safeArea?: SafeArea;\n motion?: Partial<MotionPolicy>;\n audio?: Partial<AudioPolicy>;\n transition?: {crossfadeFrames: number};\n};\n\nexport const defaultLayout: VideoLayout = {\n kind: \"odori-layout\",\n format: {width: 1920, height: 1080, fps: 30},\n brand: defaultBrand,\n safeArea: {x: 96, y: 72},\n motion: {enter: [0.16, 1, 0.3, 1], staggerFrames: 4},\n audio: {palette: \"product-cinematic\", targetLufs: -14},\n transition: {crossfadeFrames: 8},\n};\n\n/**\n * Merge rules from the docs: scalar format values replace, safe areas replace\n * as a unit, motion and audio policies merge onto the inherited policy, and\n * brand replaces because a brand is itself a resolved policy object.\n */\nexport const defineVideoLayout = (input: VideoLayoutInput = {}): VideoLayout => {\n const base = input.extends ?? defaultLayout;\n const brand = input.brand ?? base.brand;\n /**\n * Loudness is stated on the brand. That is where the docs put it, where the\n * scaffold writes it, and where `odori add` edits it, so a brand that asks\n * for a quiet mix has to get one: reading a layout default instead meant the\n * encoder normalised every project to the same number and the brand's own\n * figure was silently discarded. A layout that names a target still wins,\n * and so does one inherited from a layout that named one.\n */\n const inheritedTarget =\n base.audio.targetLufs === defaultLayout.audio.targetLufs ? brand.audio.targetLufs : base.audio.targetLufs;\n return {\n kind: \"odori-layout\",\n format: {...base.format, ...input.format},\n brand,\n safeArea: input.safeArea ?? base.safeArea,\n motion: {...base.motion, ...input.motion},\n audio: {...base.audio, targetLufs: inheritedTarget, ...input.audio},\n transition: input.transition ?? base.transition,\n };\n};\n","\"use client\";\n\nimport {\n Children,\n cloneElement,\n createContext,\n isValidElement,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ComponentPropsWithoutRef,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport {\n AssetContext,\n AudioSinkContext,\n BrandContext,\n ForceMountContext,\n FrameContext,\n LayoutContext,\n ReadinessContext,\n SceneContext,\n createAssetRegistry,\n useAssets,\n useFrame,\n useVideo,\n type Readiness,\n} from \"./context\";\nimport {\n cueId,\n isAssetReference,\n sampleGainCurve,\n sortCues,\n trackDuration,\n type AudioCue,\n type AudioProps,\n type AudioTrack,\n} from \"./audio\";\nimport {cueUrl, isCueDefinition, type CueDefinition} from \"./cue\";\nimport {Easing, interpolate} from \"./easing\";\nimport {brandCssVariables, defaultBrand, type Brand} from \"./brand\";\nimport {type VideoLayout, defaultLayout} from \"./layout\";\nimport {framesFromDuration, framesFromOffset, type Duration} from \"./time\";\n\nexport type CompiledScene = {\n id: string;\n name?: string;\n index: number;\n start: number;\n durationInFrames: number;\n /** Frames this scene starts before the previous one ends. Zero is a cut. */\n overlap: number;\n};\n/**\n * A clip on the video's own clock. Clips placed inside a scene belong to that\n * scene's internals and are not listed here: this is the video timeline.\n */\nexport type CompiledClip = {\n id: string;\n index: number;\n start: number;\n durationInFrames: number;\n};\n\nexport type CompiledTimeline = {\n scenes: CompiledScene[];\n /** Absolutely placed children of `<Video>`, in source order. */\n clips: CompiledClip[];\n durationInFrames: number;\n};\n\ntype TimelineSink = {report(timeline: CompiledTimeline): void};\n\nconst TimelineContext = createContext<TimelineSink | null>(null);\n\n/**\n * The active audio variant's cue overrides, provided by whatever mounted the\n * video: Studio's player when auditioning, the render surface when exporting.\n */\nexport const AudioVariantContext = createContext<Record<string, string | CueDefinition> | null>(null);\n\nexport const Fill = ({style, ...props}: ComponentPropsWithoutRef<\"div\">) => (\n <div\n {...props}\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n overflow: \"hidden\",\n ...style,\n }}\n />\n);\n\n/** Content inset by the inherited layout safe area. */\nexport const SafeArea = ({children, style}: {children: ReactNode; style?: CSSProperties}) => {\n const layout = useContext(LayoutContext) ?? defaultLayout;\n return (\n <Fill style={{padding: `${layout.safeArea.y}px ${layout.safeArea.x}px`, ...style}}>{children}</Fill>\n );\n};\n\ntype SceneProps = {\n children: ReactNode;\n duration: Duration;\n id?: string;\n name?: string;\n /**\n * Start this many frames before the previous scene ends, so both are on\n * screen together and a transition can span the cut. The video gets shorter\n * by the same amount: an overlap is a join, not an extra beat.\n */\n overlap?: Duration;\n /**\n * How this scene meets its neighbours. `fade` dissolves it in and out, which\n * is forgiving when a scene has no entrance of its own. `cut` shows it whole\n * from its first frame, which is what most film actually does and the only\n * way to reproduce a reference that cuts.\n */\n transition?: \"fade\" | \"cut\";\n /** Injected by <Video>. Authors never pass these. */\n __start?: number;\n __index?: number;\n __durationInFrames?: number;\n __enterOverlap?: number;\n __exitOverlap?: number;\n __videoFrame?: number;\n};\n\nexport type SceneTransition = {\n /** 0 to 1 while this scene is arriving over its predecessor. */\n entering: number;\n /** 0 to 1 while its successor is arriving over it. */\n leaving: number;\n /** Frames of overlap at each end, for a component that needs the raw counts. */\n enterFrames: number;\n exitFrames: number;\n};\n\nconst SceneTransitionContext = createContext<SceneTransition>({\n entering: 1,\n leaving: 0,\n enterFrames: 0,\n exitFrames: 0,\n});\n\n/**\n * How far this scene is through the joins at its edges.\n *\n * A transition that spans a cut needs to know two things the scene itself\n * cannot see: that it is arriving over something, and that something is\n * arriving over it. Both are numbers rather than a reference to the other\n * scene — a component that could reach into its neighbour's tree would couple\n * two scenes together, and the point of the timeline is that they compose.\n *\n * Outside an overlap this reports a scene fully present and nothing leaving,\n * so a component written against it also works on an ordinary cut.\n */\nexport const useSceneTransition = (): SceneTransition => useContext(SceneTransitionContext);\n\nconst SceneEnvelope = ({children, durationInFrames}: {children: ReactNode; durationInFrames: number}) => {\n const frame = useFrame();\n const edge = Math.max(1, Math.min(10, Math.floor(durationInFrames / 6)));\n const opacity = interpolate(\n frame,\n [0, edge, Math.max(edge + 1, durationInFrames - edge), Math.max(edge + 2, durationInFrames - 1)],\n [0, 1, 1, 0],\n {easing: Easing.standard},\n );\n return <Fill style={{opacity}}>{children}</Fill>;\n};\n\n/**\n * A timeline segment with a local frame clock starting at zero.\n */\nexport const Scene = ({\n children,\n id,\n name,\n transition: treatment = \"fade\",\n __start = 0,\n __index = 0,\n __durationInFrames = 1,\n __enterOverlap = 0,\n __exitOverlap = 0,\n __videoFrame = 0,\n}: SceneProps) => {\n const config = useVideo();\n const forceMount = useContext(ForceMountContext);\n const sceneId = id ?? `scene-${__index + 1}`;\n const localFrame = __videoFrame - __start;\n const active = localFrame >= 0 && localFrame < __durationInFrames;\n if (!active && !forceMount) return null;\n\n const joined = __enterOverlap > 0 || __exitOverlap > 0;\n const transition: SceneTransition = {\n entering: __enterOverlap > 0 ? Math.min(1, Math.max(0, localFrame / __enterOverlap)) : 1,\n leaving:\n __exitOverlap > 0\n ? Math.min(1, Math.max(0, (localFrame - (__durationInFrames - __exitOverlap)) / __exitOverlap))\n : 0,\n enterFrames: __enterOverlap,\n exitFrames: __exitOverlap,\n };\n\n return (\n <SceneContext.Provider value={{id: sceneId, name, index: __index, start: __start, durationInFrames: __durationInFrames}}>\n <FrameContext.Provider\n value={{\n frame: Math.max(0, Math.min(__durationInFrames - 1, localFrame)),\n fps: config.fps,\n width: config.width,\n height: config.height,\n durationInFrames: __durationInFrames,\n }}\n >\n <SceneTransitionContext.Provider value={transition}>\n <Fill data-odori-scene={sceneId}>\n {/* Two scenes own their own edges: a joined one, because the\n envelope would fade it out underneath whatever the transition\n is doing, and one that asked to cut, because a dissolve is\n exactly what it said it did not want. */}\n {joined || treatment === \"cut\" ? (\n <Fill>{children}</Fill>\n ) : (\n <SceneEnvelope durationInFrames={__durationInFrames}>{children}</SceneEnvelope>\n )}\n </Fill>\n </SceneTransitionContext.Provider>\n </FrameContext.Provider>\n </SceneContext.Provider>\n );\n};\nScene.__odoriScene = true as const;\n\n/** An explicit offset window inside a scene, for staggered layers. */\nexport const Stagger = ({children, from = 0, duration}: {children: ReactNode; from?: Duration; duration?: Duration}) => {\n const config = useVideo();\n const frame = useFrame();\n const start = typeof from === \"number\" && Number.isInteger(from) && from > 1 ? from : framesFromDuration(from, config.fps);\n const window = duration === undefined ? config.durationInFrames : framesFromDuration(duration, config.fps);\n const local = frame - start;\n if (local < 0 || local >= window) return null;\n return (\n <FrameContext.Provider value={{...config, frame: local, durationInFrames: window}}>\n <Fill>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\n/**\n * Repeat children on a local clock.\n *\n * The frame inside is the frame within one pass, so a component written for a\n * two second window needs no idea it is being repeated. Everything stays a\n * pure function of the frame — the tenth repetition is not the first one\n * mutated ten times, it is the same computation with the same input, which is\n * why scrubbing backwards into a loop lands exactly where playing forwards\n * did.\n */\nexport const Repeat = ({\n children,\n duration,\n times,\n}: {\n children: ReactNode;\n /** Length of one pass. */\n duration: Duration;\n /** How many passes before it stops. Unlimited by default. */\n times?: number;\n}) => {\n const config = useVideo();\n const frame = useFrame();\n const window = Math.max(1, framesFromDuration(duration, config.fps));\n const pass = Math.floor(frame / window);\n if (times !== undefined && pass >= times) return null;\n\n return (\n <FrameContext.Provider value={{...config, frame: frame % window, durationInFrames: window}}>\n <Fill>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\n/**\n * Content placed at a time, rather than after the thing before it.\n *\n * A scene is a length in a sequence: it starts where the previous one ended,\n * so its position is a consequence of its siblings. That is the right model\n * for the spine of a cut, and the wrong one for anything that has to appear\n * over a scene already running — a lower third, a callout, a caption, a mark\n * that outlives the shot it started in. A clip is placed instead: it names its\n * own start, takes no space in the sequence, and moves nothing when its length\n * changes.\n *\n * It reads its clock from wherever it sits, which is what makes it compose. A\n * direct child of `<Video>` is placed against the whole cut; the same clip\n * inside a `<Scene>` is placed against that scene and disappears with it. Its\n * children see a clock starting at zero either way, so a component written for\n * a scene behaves identically inside a clip.\n *\n * Without a `duration` it runs to the end of whatever it is placed in, which\n * is what a watermark wants.\n */\nexport const Clip = ({\n children,\n id,\n from = 0,\n duration,\n}: {\n children: ReactNode;\n /** Names it on the timeline. Defaults to its position among the clips. */\n id?: string;\n /** When it starts, on the clock of whatever contains it. */\n from?: Duration;\n /** How long it lasts. To the end of the container by default. */\n duration?: Duration;\n}) => {\n const config = useVideo();\n const frame = useFrame();\n const forceMount = useContext(ForceMountContext);\n const start = framesFromOffset(from, config.fps);\n const window =\n duration === undefined\n ? Math.max(1, config.durationInFrames - start)\n : framesFromDuration(duration, config.fps);\n const local = frame - start;\n const active = local >= 0 && local < window;\n // A still of one frame mounts everything, so a contact sheet and the\n // component catalog can show a clip that this frame is not inside.\n if (!active && !forceMount) return null;\n\n return (\n <FrameContext.Provider\n value={{...config, frame: Math.max(0, Math.min(window - 1, local)), durationInFrames: window}}\n >\n <Fill data-odori-clip={id ?? true}>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\nClip.__odoriClip = true as const;\n\n/**\n * Hold children at one frame.\n *\n * Useful for the still half of a comparison, for a poster frame, and for\n * pausing an animation on the moment that makes the point. It is the frame\n * clock that stops, not the render: children still mount and lay out\n * normally, they simply always see the same number.\n */\nexport const Hold = ({children, at = 0}: {children: ReactNode; at?: Duration}) => {\n const config = useVideo();\n const held = framesFromDuration(at, config.fps);\n return (\n <FrameContext.Provider value={{...config, frame: Math.max(0, held)}}>\n <Fill>{children}</Fill>\n </FrameContext.Provider>\n );\n};\n\n/**\n * A sound placed on the timeline. Audio is declarative like a scene: it\n * registers a cue rather than starting playback, so the player, a still, and\n * the encoder all read the same track.\n */\nexport const Audio = ({\n src,\n from = 0,\n duration,\n gain = 1,\n fadeIn = 0,\n fadeOut = 0,\n trimStart = 0,\n loop,\n duckUnder = false,\n}: AudioProps) => {\n const config = useVideo();\n const sink = useContext(AudioSinkContext);\n const scene = useContext(SceneContext);\n const assets = useAssets();\n const brand = useContext(BrandContext) ?? defaultBrand;\n const frames = (value: Duration) => framesFromDuration(value, config.fps);\n // A path is a path. A name the brand knows is whatever the brand points it\n // at, which is how one edit re-scores every video. Anything else is an\n // asset reference.\n const variant = useContext(AudioVariantContext);\n // A variant renames nothing: it re-points cue names the composition\n // already uses, so the same <Audio src> plays a different file and the\n // video itself never knows which voice it has.\n const named = variant?.[src] ?? brand.audio.cues[src];\n // A generated cue names itself by content, so the same score is fetched,\n // decoded, and mixed once however many videos ask for it.\n const source = isCueDefinition(named) ? cueUrl(named) : (named ?? src);\n const url = isAssetReference(source) ? assets.resolve(source) : source;\n // A looping cue is one phrase that repeats, and it says so. Taking the\n // default from the score means a bed fills its scene without every\n // composition restating what the cue already declares; `loop` on the element\n // is still the override when a video wants the other behaviour.\n const repeats = loop ?? (isCueDefinition(named) ? named.loops : false);\n\n if (sink) {\n const offset = (from === 0 ? 0 : frames(from)) + (scene?.start ?? 0);\n const window = duration === undefined ? (scene?.durationInFrames ?? config.durationInFrames) : frames(duration);\n sink.register({\n id: cueId(url, offset),\n src: url,\n fromFrame: offset,\n durationInFrames: window,\n // A curve is sampled here, once, where the window is known. Downstream\n // only ever sees points.\n gain: typeof gain === \"function\" ? 1 : gain,\n gainPoints: typeof gain === \"function\" ? sampleGainCurve(gain, offset, window) : undefined,\n fadeInFrames: fadeIn === 0 ? 0 : frames(fadeIn),\n fadeOutFrames: fadeOut === 0 ? 0 : frames(fadeOut),\n trimStartSeconds: trimStart === 0 ? 0 : frames(trimStart) / config.fps,\n loop: repeats,\n duckUnder,\n });\n }\n\n return null;\n};\nAudio.__odoriAudio = true as const;\n\nconst isSceneElement = (node: ReactNode): node is ReactElement<SceneProps> =>\n isValidElement(node) && (node.type as {__odoriScene?: boolean})?.__odoriScene === true;\n\ntype ClipProps = {children: ReactNode; id?: string; from?: Duration; duration?: Duration};\n\nconst isClipElement = (node: ReactNode): node is ReactElement<ClipProps> =>\n isValidElement(node) && (node.type as {__odoriClip?: boolean})?.__odoriClip === true;\n\n/**\n * Provides timeline structure. `<Video>` adds no duration of its own: scene\n * children are laid out in order, other children render for the whole video.\n */\nexport const Video = ({children, style}: {children: ReactNode; style?: CSSProperties}) => {\n const config = useVideo();\n const frame = useFrame();\n const sink = useContext(TimelineContext);\n const reported = useRef(\"\");\n const nodes = Children.toArray(children);\n\n const {laidOut, timeline} = useMemo(() => {\n let cursor = 0;\n let sceneIndex = 0;\n const scenes: CompiledScene[] = [];\n const overlaps: number[] = [];\n\n // Two passes: the first places every scene, because a scene's exit overlap\n // is the next scene's entry overlap and is therefore not known until the\n // next scene has been read.\n const placed = nodes.map((node) => {\n if (!isSceneElement(node)) return null;\n const durationInFrames = Math.max(1, framesFromDuration(node.props.duration, config.fps));\n const requested = node.props.overlap === undefined ? 0 : framesFromDuration(node.props.overlap, config.fps);\n // An overlap cannot be longer than either scene, or the two would cross\n // and the timeline would run backwards.\n const previous = scenes[scenes.length - 1];\n const overlap =\n sceneIndex === 0 ? 0 : Math.max(0, Math.min(requested, durationInFrames, previous?.durationInFrames ?? 0));\n\n const start = Math.max(0, cursor - overlap);\n const index = sceneIndex;\n scenes.push({id: node.props.id ?? `scene-${index + 1}`, name: node.props.name, index, start, durationInFrames, overlap});\n overlaps.push(overlap);\n cursor = start + durationInFrames;\n sceneIndex += 1;\n return {start, index, durationInFrames};\n });\n\n let placedIndex = 0;\n const output = nodes.map((node, key) => {\n if (!isSceneElement(node)) return node;\n const entry = placed[key] ?? placed.find((item) => item !== null);\n const index = placedIndex;\n placedIndex += 1;\n if (!entry) return node;\n return cloneElement(node, {\n key: `odori-scene-${key}`,\n __start: entry.start,\n __index: entry.index,\n __durationInFrames: entry.durationInFrames,\n __enterOverlap: overlaps[index] ?? 0,\n __exitOverlap: overlaps[index + 1] ?? 0,\n });\n });\n // Clips are placed, so they are read after the spine is known: an open\n // ended clip runs to the end of the video, and the video is as long as\n // its scenes unless its metadata says otherwise.\n const total = Math.max(cursor, config.durationInFrames);\n const clips: CompiledClip[] = [];\n for (const node of nodes) {\n if (!isClipElement(node)) continue;\n const index = clips.length;\n const start = framesFromOffset(node.props.from ?? 0, config.fps);\n const durationInFrames =\n node.props.duration === undefined\n ? Math.max(1, total - start)\n : framesFromDuration(node.props.duration, config.fps);\n clips.push({id: node.props.id ?? `clip-${index + 1}`, index, start, durationInFrames});\n }\n\n return {laidOut: output, timeline: {scenes, clips, durationInFrames: cursor}};\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [children, config.fps]);\n\n // The compiled timeline is recomputed whenever children change identity,\n // which happens on every frame. Report only when it actually differs, so a\n // consumer that stores it in state cannot be driven into a render loop.\n const publish = () => {\n const signature = JSON.stringify(timeline);\n if (signature === reported.current) return;\n reported.current = signature;\n sink?.report(timeline);\n };\n\n // On the server there is no effect phase, so report inline. In the browser\n // the effect keeps the report out of the render pass.\n if (typeof window === \"undefined\") publish();\n useEffect(publish);\n\n return (\n <Fill data-odori-video style={style}>\n {laidOut.map((node) =>\n isSceneElement(node) ? cloneElement(node, {__videoFrame: frame}) : node,\n )}\n </Fill>\n );\n};\n\nexport type VideoEntry = {\n component: (props: any) => ReactNode;\n metadata: {\n id: string;\n title: string;\n description?: string;\n duration?: Duration;\n layout?: VideoLayout;\n schema?: {parse(input: unknown): unknown};\n defaultProps?: Record<string, unknown>;\n tags?: string[];\n thumbnailFrame?: number;\n audio?: {variants?: Record<string, Record<string, string | CueDefinition>>};\n };\n};\n\nexport type OdoriRuntimeProps = {\n entry: VideoEntry;\n frame: number;\n input?: Record<string, unknown>;\n prepared?: unknown;\n assets?: Array<{reference: string; url: string}>;\n layout?: VideoLayout;\n onTimeline?: (timeline: CompiledTimeline) => void;\n onAudio?: (track: AudioTrack) => void;\n};\n\nexport const resolveEntryLayout = (entry: VideoEntry, override?: VideoLayout): VideoLayout =>\n override ?? entry.metadata.layout ?? defaultLayout;\n\nexport const entryDurationInFrames = (entry: VideoEntry, layout: VideoLayout): number =>\n entry.metadata.duration === undefined ? 0 : framesFromDuration(entry.metadata.duration, layout.format.fps);\n\nconst fontFaceCss = (fonts: Brand[\"fonts\"]) =>\n fonts\n .map(\n (font) =>\n `@font-face{font-family:\"${font.family}\";src:url(\"${font.url}\") format(\"woff2\");font-weight:${\n font.weight ?? \"100 900\"\n };font-display:block;font-style:normal;}`,\n )\n .join(\"\");\n\n/** Faces already installed in this document, so a remount never reloads them. */\nconst installedFonts = new Set<string>();\n\n/**\n * Brand fonts are declared as policy, not imported by components, so preview,\n * stills, and the export worker all load the same faces before the first frame\n * is captured.\n *\n * In the browser the rules are installed once at the document level and never\n * removed. Rendering them inside the tree would drop every face each time a\n * video unmounts, which reloads the fonts and reflows the whole page.\n */\nconst FontFaces = ({fonts}: {fonts: Brand[\"fonts\"]}) => {\n const css = useMemo(() => fontFaceCss(fonts), [fonts]);\n\n useEffect(() => {\n if (!css || installedFonts.has(css)) return;\n installedFonts.add(css);\n const style = document.createElement(\"style\");\n style.dataset.odoriFonts = \"\";\n style.textContent = css;\n document.head.append(style);\n }, [css]);\n\n // Server rendering has no effect phase, so the faces stay inline there.\n return typeof window === \"undefined\" && css ? <style data-odori-fonts>{css}</style> : null;\n};\n\n/**\n * Mounts every scene at once, invisibly, so `<Audio>` cues can be collected\n * without seeking through the whole timeline first.\n */\nconst AudioPass = ({\n children,\n onAudio,\n durationInFrames,\n}: {\n children: ReactNode;\n onAudio: (track: AudioTrack) => void;\n durationInFrames: number;\n}) => {\n const collected = useRef(new Map<string, AudioCue>());\n const reported = useRef(\"\");\n const sink = useMemo(\n () => ({\n register: (cue: AudioCue) => {\n collected.current.set(cue.id, cue);\n },\n }),\n [],\n );\n\n collected.current.clear();\n const tree = (\n <AudioSinkContext.Provider value={sink}>\n <ForceMountContext.Provider value>{children}</ForceMountContext.Provider>\n </AudioSinkContext.Provider>\n );\n\n const publish = () => {\n const cues = sortCues([...collected.current.values()]);\n const signature = JSON.stringify(cues);\n if (signature === reported.current) return;\n reported.current = signature;\n onAudio({cues, durationInFrames: Math.max(durationInFrames, trackDuration(cues))});\n };\n\n useEffect(publish);\n\n return (\n <div aria-hidden data-odori-audio-pass style={{display: \"none\"}}>\n {tree}\n {/* Rendered after the tree, so on the server every cue has registered\n by the time this publishes. */}\n <Publisher publish={publish} />\n </div>\n );\n};\n\nconst Publisher = ({publish}: {publish: () => void}) => {\n if (typeof window === \"undefined\") publish();\n return null;\n};\n\nexport const OdoriRuntime = ({entry, frame, input, prepared, assets, layout, onTimeline, onAudio}: OdoriRuntimeProps) => {\n const resolvedLayout = resolveEntryLayout(entry, layout);\n const {format, brand} = resolvedLayout;\n const registry = useMemo(() => createAssetRegistry(assets ?? []), [assets]);\n const sink = useMemo<TimelineSink>(() => ({report: (timeline) => onTimeline?.(timeline)}), [onTimeline]);\n\n const props = useMemo(() => {\n const merged = {...entry.metadata.defaultProps, ...input};\n return entry.metadata.schema ? (entry.metadata.schema.parse(merged) as Record<string, unknown>) : merged;\n }, [entry, input]);\n\n const durationInFrames = entryDurationInFrames(entry, resolvedLayout);\n const Composition = entry.component;\n\n // The frame attribute is the readiness handshake the render worker waits on,\n // so it is written only once every held handle has been released.\n const [pending, setPending] = useState(0);\n const readiness = useMemo<Readiness>(\n () => ({\n hold: () => {\n setPending((count) => count + 1);\n let released = false;\n return () => {\n if (released) return;\n released = true;\n setPending((count) => Math.max(0, count - 1));\n };\n },\n }),\n [],\n );\n\n return (\n <LayoutContext.Provider value={resolvedLayout}>\n <BrandContext.Provider value={brand}>\n <AssetContext.Provider value={registry}>\n <ReadinessContext.Provider value={readiness}>\n <TimelineContext.Provider value={sink}>\n <FrameContext.Provider\n value={{\n frame,\n fps: format.fps,\n width: format.width,\n height: format.height,\n durationInFrames: durationInFrames || 1,\n }}\n >\n <Fill\n data-odori-frame={pending === 0 ? frame : undefined}\n style={\n {\n backgroundColor: brand.colors.background,\n color: brand.colors.foreground,\n fontFamily: brand.typography.sans,\n ...brandCssVariables(brand),\n } as CSSProperties\n }\n >\n <FontFaces fonts={brand.fonts} />\n <Composition {...props} prepared={prepared} />\n {/* The pass is keyed by composition: it dedupes against what it\n last published, so a consumer that clears its track when the\n selection changes would otherwise never be handed the cues\n again for two videos that share one cue signature. */}\n {onAudio ? (\n <AudioPass key={entry.metadata.id} onAudio={onAudio} durationInFrames={durationInFrames}>\n <Composition {...props} prepared={prepared} />\n </AudioPass>\n ) : null}\n </Fill>\n </FrameContext.Provider>\n </TimelineContext.Provider>\n </ReadinessContext.Provider>\n </AssetContext.Provider>\n </BrandContext.Provider>\n </LayoutContext.Provider>\n );\n};\n"],"mappings":"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","names":["window","step","window","gain","loop"]}
|