pixi-effects 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -1
- package/README.md +11 -6
- package/ai/SKILL.md +7 -4
- package/ai/reference/cheatsheet.md +18 -5
- package/ai/reference/pitfalls.md +16 -11
- package/ai/reference/recipes.md +153 -1
- package/ai/template.html +6 -3
- package/ai/tools/check.mjs +385 -0
- package/dist/{Base-DhY9oMxr.d.ts → Base-4vNLl4tH.d.cts} +6 -1
- package/dist/{Base-5R7C5pHR.d.cts → Base-CD4AoT5-.d.ts} +6 -1
- package/dist/Composition-7SO5WYHU.js +4 -0
- package/dist/{Composition-RGTRZ2JX.js.map → Composition-7SO5WYHU.js.map} +1 -1
- package/dist/Composition-Y5VZSDDP.cjs +13 -0
- package/dist/{Composition-D7RCAGEA.cjs.map → Composition-Y5VZSDDP.cjs.map} +1 -1
- package/dist/Controller.cjs +47 -15
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +5 -2
- package/dist/Controller.d.ts +5 -2
- package/dist/Controller.js +47 -15
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-DRZtIGU7.d.ts → Movie-CADDBfpl.d.cts} +58 -2
- package/dist/{Movie-CghFXprP.d.cts → Movie-Ch2SUI1h.d.ts} +58 -2
- package/dist/{chunk-QGBOC5YL.cjs → chunk-4AGJF5RK.cjs} +33 -21
- package/dist/chunk-4AGJF5RK.cjs.map +1 -0
- package/dist/{chunk-ZQX7WIYL.js → chunk-7IJHJXDI.js} +380 -64
- package/dist/chunk-7IJHJXDI.js.map +1 -0
- package/dist/{chunk-A574IA4F.js → chunk-B2FTJLNB.js} +18 -6
- package/dist/chunk-B2FTJLNB.js.map +1 -0
- package/dist/chunk-DKGG7WD4.js +61 -0
- package/dist/chunk-DKGG7WD4.js.map +1 -0
- package/dist/{chunk-6BQ6IEW2.cjs → chunk-KQM2ZZMK.cjs} +380 -63
- package/dist/chunk-KQM2ZZMK.cjs.map +1 -0
- package/dist/chunk-UQBGW5DO.cjs +64 -0
- package/dist/chunk-UQBGW5DO.cjs.map +1 -0
- package/dist/index.cjs +333 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -5
- package/dist/index.d.ts +54 -5
- package/dist/index.js +317 -75
- package/dist/index.js.map +1 -1
- package/dist/sfx-MZJZXYY3.cjs +446 -0
- package/dist/sfx-MZJZXYY3.cjs.map +1 -0
- package/dist/sfx-S37DDC66.js +434 -0
- package/dist/sfx-S37DDC66.js.map +1 -0
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-CRE9WKd4.d.ts → types-DrW9QeKU.d.cts} +118 -12
- package/dist/{types-CRE9WKd4.d.cts → types-DrW9QeKU.d.ts} +118 -12
- package/docs/api.md +37 -0
- package/docs/dsl.md +70 -10
- package/llms-full.txt +300 -30
- package/llms.txt +1 -1
- package/package.json +2 -1
- package/dist/Composition-D7RCAGEA.cjs +0 -13
- package/dist/Composition-RGTRZ2JX.js +0 -4
- package/dist/chunk-6BQ6IEW2.cjs.map +0 -1
- package/dist/chunk-A574IA4F.js.map +0 -1
- package/dist/chunk-QGBOC5YL.cjs.map +0 -1
- package/dist/chunk-ZQX7WIYL.js.map +0 -1
|
@@ -212,12 +212,17 @@ interface TextSequenceSpec extends SequenceCommon {
|
|
|
212
212
|
* The text. May contain `{value}`, replaced by the layer's animatable number
|
|
213
213
|
* `value` (a counter): `text: '{value} users'`, `initial: { value: 0 }`,
|
|
214
214
|
* keyframe `to: { value: 2480 }`. Format it with `format`.
|
|
215
|
+
*
|
|
216
|
+
* The text can change over time: a keyframe `set: { text: 'Two' }` swaps the string at that time (and back when
|
|
217
|
+
* you seek back; a string cannot be tweened). `visibleChars` is a number that shows only the first N characters
|
|
218
|
+
* (a typewriter): `initial: { visibleChars: 0 }`, keyframe `to: { visibleChars: 20 }`. It starts at 0 once it is animated.
|
|
215
219
|
*/
|
|
216
220
|
text?: string;
|
|
217
221
|
/** How `{value}` is printed: `decimals` (default 0) and thousands `grouping` (default false). */
|
|
218
222
|
format?: {
|
|
219
223
|
decimals?: number;
|
|
220
|
-
grouping?: boolean;
|
|
224
|
+
grouping?: boolean; /** Zero-pad the whole part to this many digits (`pad: 2` → `05`; a clock `18:42:05`). */
|
|
225
|
+
pad?: number;
|
|
221
226
|
};
|
|
222
227
|
/** Subset of PIXI v8 TextStyleOptions. String values may be exprs (e.g. fontSize: 'GW * 0.05'). */
|
|
223
228
|
style?: Record<string, PropValue | {
|
|
@@ -232,12 +237,71 @@ interface TextSequenceSpec extends SequenceCommon {
|
|
|
232
237
|
*/
|
|
233
238
|
colorSpace?: 'rgb' | 'oklab' | 'oklch';
|
|
234
239
|
}
|
|
235
|
-
|
|
240
|
+
type SfxPreset = 'click' | 'pop' | 'swoosh' | 'swipe' | 'hit' | 'riser' | 'chime' | 'beep' | 'coin' | 'glitch' | 'typewriter';
|
|
241
|
+
/** The intent-level knobs shared by presets and custom sounds. */
|
|
242
|
+
interface SfxKnobs {
|
|
243
|
+
/** Semitones up (+) or down (−); 12 = one octave. Default 0, range ±24. */
|
|
244
|
+
pitch?: number;
|
|
245
|
+
/** −1 = dark / muffled … 0 = as designed … 1 = bright / crisp. Default 0. */
|
|
246
|
+
brightness?: number;
|
|
247
|
+
/** Another take of the random parts (noise grain, glitch pattern, typewriter key). Same seed ⇒ same sound. Default 0. */
|
|
248
|
+
seed?: number;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* One layer of a custom sound. Most sounds are one or two voices: noise through a moving band-pass
|
|
252
|
+
* (whooshes, air, impacts) or a tone with a pitch sweep (blips, zaps, drops).
|
|
253
|
+
*/
|
|
254
|
+
interface SfxVoice {
|
|
255
|
+
/** `'noise'` for air, hiss and impacts; a waveform for tones. */
|
|
256
|
+
wave: 'sine' | 'triangle' | 'square' | 'saw' | 'noise';
|
|
257
|
+
/** Tone frequency in Hz, or `[from, to]` to sweep (exponentially). Ignored for noise. Default 440. */
|
|
258
|
+
freq?: number | [number, number];
|
|
259
|
+
/** Keeps part of the spectrum; `freq` may sweep: `{ type: 'bandpass', freq: [500, 5000] }` = a band of noise rising. `q` default 0.7 (≥ 0.7). */
|
|
260
|
+
filter?: {
|
|
261
|
+
type: 'lowpass' | 'highpass' | 'bandpass';
|
|
262
|
+
freq: number | [number, number];
|
|
263
|
+
q?: number;
|
|
264
|
+
};
|
|
265
|
+
/** Level over the voice: `'fall'` (default) hit then decay · `'bell'` swell then fade · `'swell'` grow to the end · `'hold'` steady. */
|
|
266
|
+
envelope?: 'fall' | 'bell' | 'swell' | 'hold';
|
|
267
|
+
/** −1 left … 1 right, or `[from, to]` to move. Default 0. */
|
|
268
|
+
pan?: number | [number, number];
|
|
269
|
+
/** The part of the sound this voice plays, as fractions 0–1 of its length. Default 0 and 1. */
|
|
270
|
+
from?: number;
|
|
271
|
+
to?: number;
|
|
272
|
+
/** Level relative to the other voices. Default 1. */
|
|
273
|
+
gain?: number;
|
|
274
|
+
}
|
|
275
|
+
/** A preset with knobs, or a custom sound made of `voices` (length = the layer's duration, default 0.5 s). */
|
|
276
|
+
type SfxOptions = (SfxKnobs & {
|
|
277
|
+
preset: SfxPreset;
|
|
278
|
+
voices?: never;
|
|
279
|
+
}) | (SfxKnobs & {
|
|
280
|
+
voices: SfxVoice[];
|
|
281
|
+
preset?: never;
|
|
282
|
+
});
|
|
283
|
+
interface AudioBase extends SequenceCommon {
|
|
236
284
|
type: 'audio';
|
|
285
|
+
/** Level. 1 = the file as recorded, or the sfx preset's standard level (peaks at −12 dBFS). Animatable. */
|
|
286
|
+
volume?: number;
|
|
287
|
+
}
|
|
288
|
+
/** Plays an audio file from `assets`. `duration` defaults to the file's length (with `loop`: to the composition's end). */
|
|
289
|
+
interface AudioAssetSpec extends AudioBase {
|
|
237
290
|
asset: string;
|
|
291
|
+
sfx?: never;
|
|
292
|
+
/** Repeat the file for the whole `duration`. Without it the layer goes silent when the file ends. */
|
|
238
293
|
loop?: boolean;
|
|
239
|
-
volume?: number;
|
|
240
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Plays a synthesised sound effect. `duration` IS the sound's length: omitted, it is the preset's own
|
|
297
|
+
* length; given, the sound is stretched or shortened to it. `at` is where the sound starts.
|
|
298
|
+
*/
|
|
299
|
+
interface AudioSfxSpec extends AudioBase {
|
|
300
|
+
sfx: SfxPreset | SfxOptions;
|
|
301
|
+
asset?: never;
|
|
302
|
+
loop?: never;
|
|
303
|
+
}
|
|
304
|
+
type AudioSequenceSpec = AudioAssetSpec | AudioSfxSpec;
|
|
241
305
|
interface CompositionSequenceSpec extends SequenceCommon {
|
|
242
306
|
type: 'composition';
|
|
243
307
|
width?: number;
|
|
@@ -276,6 +340,18 @@ interface GradientSpec {
|
|
|
276
340
|
/** radial: outer radius where the last stop applies, 0–1. Default 0.5. */
|
|
277
341
|
radius?: number;
|
|
278
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Stroke trim (draw-on): the part of the outline that is stroked, as fractions 0–1 of its length. Animate `trimEnd`
|
|
345
|
+
* from 0 to 1 to draw a line / path / outline on; animate both for a travelling dash. The outline of a rect starts at
|
|
346
|
+
* its top-left and goes clockwise, of a circle / ellipse at 12 o'clock and goes clockwise; a polygon's closing edge counts.
|
|
347
|
+
* The fill is not trimmed. Not for `arc` (animate its `endAngle`).
|
|
348
|
+
*/
|
|
349
|
+
interface Trimmable {
|
|
350
|
+
/** Where the stroke starts, 0–1 of the outline. Default 0. Animatable. */
|
|
351
|
+
trimStart?: PropValue;
|
|
352
|
+
/** Where the stroke ends, 0–1 of the outline. Default 1. Animatable. */
|
|
353
|
+
trimEnd?: PropValue;
|
|
354
|
+
}
|
|
279
355
|
interface ShapeBase extends SequenceCommon {
|
|
280
356
|
type: 'shape';
|
|
281
357
|
/** Fill with a gradient instead of `fillColor` (top level or in `initial`). */
|
|
@@ -293,8 +369,12 @@ interface ShapeBase extends SequenceCommon {
|
|
|
293
369
|
* sweeps; `oklab` is straight-line in the chromaticity plane.
|
|
294
370
|
*/
|
|
295
371
|
colorSpace?: 'rgb' | 'oklab' | 'oklch';
|
|
372
|
+
/** How an open stroke ends: `'butt'` (default), `'round'` (a friendly progress-ring end) or `'square'`. Top level or in `initial`. */
|
|
373
|
+
strokeCap?: 'butt' | 'round' | 'square';
|
|
374
|
+
/** How stroke corners join: `'miter'` (default; sharp points can poke out), `'round'` or `'bevel'`. Top level or in `initial`. */
|
|
375
|
+
strokeJoin?: 'miter' | 'round' | 'bevel';
|
|
296
376
|
}
|
|
297
|
-
interface RectShapeSpec extends ShapeBase {
|
|
377
|
+
interface RectShapeSpec extends ShapeBase, Trimmable {
|
|
298
378
|
shape: 'rect';
|
|
299
379
|
width: PropValue;
|
|
300
380
|
height: PropValue;
|
|
@@ -309,14 +389,14 @@ interface RectShapeSpec extends ShapeBase {
|
|
|
309
389
|
anchorX?: PropValue;
|
|
310
390
|
anchorY?: PropValue;
|
|
311
391
|
}
|
|
312
|
-
interface CircleShapeSpec extends ShapeBase {
|
|
392
|
+
interface CircleShapeSpec extends ShapeBase, Trimmable {
|
|
313
393
|
shape: 'circle';
|
|
314
394
|
radius: PropValue;
|
|
315
395
|
/** See RectShapeSpec.anchorX. Default 0.5 (centre). */
|
|
316
396
|
anchorX?: PropValue;
|
|
317
397
|
anchorY?: PropValue;
|
|
318
398
|
}
|
|
319
|
-
interface EllipseShapeSpec extends ShapeBase {
|
|
399
|
+
interface EllipseShapeSpec extends ShapeBase, Trimmable {
|
|
320
400
|
shape: 'ellipse';
|
|
321
401
|
radiusX: PropValue;
|
|
322
402
|
radiusY: PropValue;
|
|
@@ -324,25 +404,41 @@ interface EllipseShapeSpec extends ShapeBase {
|
|
|
324
404
|
anchorX?: PropValue;
|
|
325
405
|
anchorY?: PropValue;
|
|
326
406
|
}
|
|
327
|
-
|
|
407
|
+
/**
|
|
408
|
+
* An arc around the local origin (its centre). Angles are in degrees: 0 = 3 o'clock, positive = clockwise,
|
|
409
|
+
* so a progress ring starts at `startAngle: -90` (12 o'clock) and animates `endAngle` from -90 to 270.
|
|
410
|
+
* With a stroke and no fill it is an open arc line (`strokeCap: 'round'` for rounded ends); with a fill it is a
|
|
411
|
+
* sector (a pie slice), and with `innerRadius` a ring segment (a donut slice). A sweep of 360° or more is a full circle.
|
|
412
|
+
*/
|
|
413
|
+
interface ArcShapeSpec extends ShapeBase {
|
|
414
|
+
shape: 'arc';
|
|
415
|
+
radius: PropValue;
|
|
416
|
+
/** Hole radius of a filled ring segment. Default 0 (a pie slice to the centre). Animatable. */
|
|
417
|
+
innerRadius?: PropValue;
|
|
418
|
+
/** Degrees. Default 0. Animatable. */
|
|
419
|
+
startAngle?: PropValue;
|
|
420
|
+
/** Degrees. Default 360. Animatable. */
|
|
421
|
+
endAngle?: PropValue;
|
|
422
|
+
}
|
|
423
|
+
interface LineShapeSpec extends ShapeBase, Trimmable {
|
|
328
424
|
shape: 'line';
|
|
329
425
|
/** Line endpoints relative to the shape's local origin. */
|
|
330
426
|
from: [PropValue, PropValue];
|
|
331
427
|
to: [PropValue, PropValue];
|
|
332
428
|
}
|
|
333
|
-
interface PolygonShapeSpec extends ShapeBase {
|
|
429
|
+
interface PolygonShapeSpec extends ShapeBase, Trimmable {
|
|
334
430
|
shape: 'polygon';
|
|
335
431
|
/** Vertices in local space. The path is auto-closed. */
|
|
336
432
|
points: Array<[PropValue, PropValue]>;
|
|
337
433
|
/** When true, draw an open polyline instead of a closed polygon. Default false. */
|
|
338
434
|
open?: boolean;
|
|
339
435
|
}
|
|
340
|
-
interface PathShapeSpec extends ShapeBase {
|
|
436
|
+
interface PathShapeSpec extends ShapeBase, Trimmable {
|
|
341
437
|
shape: 'path';
|
|
342
438
|
/** SVG path data (`d` attribute). Goes through PIXI's GraphicsContext.svg(). */
|
|
343
439
|
d: string;
|
|
344
440
|
}
|
|
345
|
-
type ShapeSequenceSpec = RectShapeSpec | CircleShapeSpec | EllipseShapeSpec | LineShapeSpec | PolygonShapeSpec | PathShapeSpec;
|
|
441
|
+
type ShapeSequenceSpec = RectShapeSpec | CircleShapeSpec | EllipseShapeSpec | ArcShapeSpec | LineShapeSpec | PolygonShapeSpec | PathShapeSpec;
|
|
346
442
|
type SequenceSpec = VideoSequenceSpec | ImageSequenceSpec | TextSequenceSpec | AudioSequenceSpec | CompositionSequenceSpec | CameraSequenceSpec | ShapeSequenceSpec;
|
|
347
443
|
/** Top-level composition (root node) — same as `CompositionSequenceSpec` minus the discriminant. */
|
|
348
444
|
interface CompositionSpec extends SequenceCommon {
|
|
@@ -359,7 +455,17 @@ interface CompositionShape {
|
|
|
359
455
|
}
|
|
360
456
|
/** Audio descriptor pushed to the mixdown queue by AudioSequence/VideoSequence. */
|
|
361
457
|
interface AudioDescriptor {
|
|
362
|
-
buffer
|
|
458
|
+
/** Decoded audio (a file or a video's soundtrack). Exactly one of `buffer` / `synth` is set. */
|
|
459
|
+
buffer?: AudioBuffer;
|
|
460
|
+
/** Stereo samples (left, right) made on demand at the mix's sample rate (a synthesised sfx). Equal keys render equal samples. */
|
|
461
|
+
synth?: {
|
|
462
|
+
key: string;
|
|
463
|
+
render: (sampleRate: number) => [Float32Array, Float32Array];
|
|
464
|
+
};
|
|
465
|
+
/** Who made it, for warnings and inspectAudio: `layer "pop-1"`. */
|
|
466
|
+
layer?: string;
|
|
467
|
+
/** What it plays: `sfx "pop"`, `asset "bgm"`, `video "green"`. */
|
|
468
|
+
source?: string;
|
|
363
469
|
loop: boolean;
|
|
364
470
|
start: number;
|
|
365
471
|
end: number;
|
|
@@ -370,4 +476,4 @@ interface AudioDescriptor {
|
|
|
370
476
|
}[];
|
|
371
477
|
}
|
|
372
478
|
|
|
373
|
-
export type {
|
|
479
|
+
export type { ArcShapeSpec as A, AudioDescriptor as B, CompositionShape as C, DipTransition as D, EllipseShapeSpec as E, FilterSpec as F, GradientSpec as G, ImageSequenceSpec as I, Keyframe as K, LineShapeSpec as L, PathShapeSpec as P, RectShapeSpec as R, SequenceSpec as S, TextSequenceSpec as T, VideoSequenceSpec as V, WipeTransition as W, ZoomTransition as Z, CameraSequenceSpec as a, SequenceCommon as b, AssetSpec as c, AudioAssetSpec as d, AudioSequenceSpec as e, AudioSfxSpec as f, ChromaKeyFilterSpec as g, CircleShapeSpec as h, CompositionSequenceSpec as i, CompositionSpec as j, CrossfadeTransition as k, CustomFilterSpec as l, DissolveTransition as m, Expr as n, IrisTransition as o, PolygonShapeSpec as p, PropValue as q, Props as r, SfxKnobs as s, SfxOptions as t, SfxPreset as u, SfxVoice as v, ShapeSequenceSpec as w, SlideTransition as x, TransitionCommon as y, TransitionSpec as z };
|
|
@@ -212,12 +212,17 @@ interface TextSequenceSpec extends SequenceCommon {
|
|
|
212
212
|
* The text. May contain `{value}`, replaced by the layer's animatable number
|
|
213
213
|
* `value` (a counter): `text: '{value} users'`, `initial: { value: 0 }`,
|
|
214
214
|
* keyframe `to: { value: 2480 }`. Format it with `format`.
|
|
215
|
+
*
|
|
216
|
+
* The text can change over time: a keyframe `set: { text: 'Two' }` swaps the string at that time (and back when
|
|
217
|
+
* you seek back; a string cannot be tweened). `visibleChars` is a number that shows only the first N characters
|
|
218
|
+
* (a typewriter): `initial: { visibleChars: 0 }`, keyframe `to: { visibleChars: 20 }`. It starts at 0 once it is animated.
|
|
215
219
|
*/
|
|
216
220
|
text?: string;
|
|
217
221
|
/** How `{value}` is printed: `decimals` (default 0) and thousands `grouping` (default false). */
|
|
218
222
|
format?: {
|
|
219
223
|
decimals?: number;
|
|
220
|
-
grouping?: boolean;
|
|
224
|
+
grouping?: boolean; /** Zero-pad the whole part to this many digits (`pad: 2` → `05`; a clock `18:42:05`). */
|
|
225
|
+
pad?: number;
|
|
221
226
|
};
|
|
222
227
|
/** Subset of PIXI v8 TextStyleOptions. String values may be exprs (e.g. fontSize: 'GW * 0.05'). */
|
|
223
228
|
style?: Record<string, PropValue | {
|
|
@@ -232,12 +237,71 @@ interface TextSequenceSpec extends SequenceCommon {
|
|
|
232
237
|
*/
|
|
233
238
|
colorSpace?: 'rgb' | 'oklab' | 'oklch';
|
|
234
239
|
}
|
|
235
|
-
|
|
240
|
+
type SfxPreset = 'click' | 'pop' | 'swoosh' | 'swipe' | 'hit' | 'riser' | 'chime' | 'beep' | 'coin' | 'glitch' | 'typewriter';
|
|
241
|
+
/** The intent-level knobs shared by presets and custom sounds. */
|
|
242
|
+
interface SfxKnobs {
|
|
243
|
+
/** Semitones up (+) or down (−); 12 = one octave. Default 0, range ±24. */
|
|
244
|
+
pitch?: number;
|
|
245
|
+
/** −1 = dark / muffled … 0 = as designed … 1 = bright / crisp. Default 0. */
|
|
246
|
+
brightness?: number;
|
|
247
|
+
/** Another take of the random parts (noise grain, glitch pattern, typewriter key). Same seed ⇒ same sound. Default 0. */
|
|
248
|
+
seed?: number;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* One layer of a custom sound. Most sounds are one or two voices: noise through a moving band-pass
|
|
252
|
+
* (whooshes, air, impacts) or a tone with a pitch sweep (blips, zaps, drops).
|
|
253
|
+
*/
|
|
254
|
+
interface SfxVoice {
|
|
255
|
+
/** `'noise'` for air, hiss and impacts; a waveform for tones. */
|
|
256
|
+
wave: 'sine' | 'triangle' | 'square' | 'saw' | 'noise';
|
|
257
|
+
/** Tone frequency in Hz, or `[from, to]` to sweep (exponentially). Ignored for noise. Default 440. */
|
|
258
|
+
freq?: number | [number, number];
|
|
259
|
+
/** Keeps part of the spectrum; `freq` may sweep: `{ type: 'bandpass', freq: [500, 5000] }` = a band of noise rising. `q` default 0.7 (≥ 0.7). */
|
|
260
|
+
filter?: {
|
|
261
|
+
type: 'lowpass' | 'highpass' | 'bandpass';
|
|
262
|
+
freq: number | [number, number];
|
|
263
|
+
q?: number;
|
|
264
|
+
};
|
|
265
|
+
/** Level over the voice: `'fall'` (default) hit then decay · `'bell'` swell then fade · `'swell'` grow to the end · `'hold'` steady. */
|
|
266
|
+
envelope?: 'fall' | 'bell' | 'swell' | 'hold';
|
|
267
|
+
/** −1 left … 1 right, or `[from, to]` to move. Default 0. */
|
|
268
|
+
pan?: number | [number, number];
|
|
269
|
+
/** The part of the sound this voice plays, as fractions 0–1 of its length. Default 0 and 1. */
|
|
270
|
+
from?: number;
|
|
271
|
+
to?: number;
|
|
272
|
+
/** Level relative to the other voices. Default 1. */
|
|
273
|
+
gain?: number;
|
|
274
|
+
}
|
|
275
|
+
/** A preset with knobs, or a custom sound made of `voices` (length = the layer's duration, default 0.5 s). */
|
|
276
|
+
type SfxOptions = (SfxKnobs & {
|
|
277
|
+
preset: SfxPreset;
|
|
278
|
+
voices?: never;
|
|
279
|
+
}) | (SfxKnobs & {
|
|
280
|
+
voices: SfxVoice[];
|
|
281
|
+
preset?: never;
|
|
282
|
+
});
|
|
283
|
+
interface AudioBase extends SequenceCommon {
|
|
236
284
|
type: 'audio';
|
|
285
|
+
/** Level. 1 = the file as recorded, or the sfx preset's standard level (peaks at −12 dBFS). Animatable. */
|
|
286
|
+
volume?: number;
|
|
287
|
+
}
|
|
288
|
+
/** Plays an audio file from `assets`. `duration` defaults to the file's length (with `loop`: to the composition's end). */
|
|
289
|
+
interface AudioAssetSpec extends AudioBase {
|
|
237
290
|
asset: string;
|
|
291
|
+
sfx?: never;
|
|
292
|
+
/** Repeat the file for the whole `duration`. Without it the layer goes silent when the file ends. */
|
|
238
293
|
loop?: boolean;
|
|
239
|
-
volume?: number;
|
|
240
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Plays a synthesised sound effect. `duration` IS the sound's length: omitted, it is the preset's own
|
|
297
|
+
* length; given, the sound is stretched or shortened to it. `at` is where the sound starts.
|
|
298
|
+
*/
|
|
299
|
+
interface AudioSfxSpec extends AudioBase {
|
|
300
|
+
sfx: SfxPreset | SfxOptions;
|
|
301
|
+
asset?: never;
|
|
302
|
+
loop?: never;
|
|
303
|
+
}
|
|
304
|
+
type AudioSequenceSpec = AudioAssetSpec | AudioSfxSpec;
|
|
241
305
|
interface CompositionSequenceSpec extends SequenceCommon {
|
|
242
306
|
type: 'composition';
|
|
243
307
|
width?: number;
|
|
@@ -276,6 +340,18 @@ interface GradientSpec {
|
|
|
276
340
|
/** radial: outer radius where the last stop applies, 0–1. Default 0.5. */
|
|
277
341
|
radius?: number;
|
|
278
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Stroke trim (draw-on): the part of the outline that is stroked, as fractions 0–1 of its length. Animate `trimEnd`
|
|
345
|
+
* from 0 to 1 to draw a line / path / outline on; animate both for a travelling dash. The outline of a rect starts at
|
|
346
|
+
* its top-left and goes clockwise, of a circle / ellipse at 12 o'clock and goes clockwise; a polygon's closing edge counts.
|
|
347
|
+
* The fill is not trimmed. Not for `arc` (animate its `endAngle`).
|
|
348
|
+
*/
|
|
349
|
+
interface Trimmable {
|
|
350
|
+
/** Where the stroke starts, 0–1 of the outline. Default 0. Animatable. */
|
|
351
|
+
trimStart?: PropValue;
|
|
352
|
+
/** Where the stroke ends, 0–1 of the outline. Default 1. Animatable. */
|
|
353
|
+
trimEnd?: PropValue;
|
|
354
|
+
}
|
|
279
355
|
interface ShapeBase extends SequenceCommon {
|
|
280
356
|
type: 'shape';
|
|
281
357
|
/** Fill with a gradient instead of `fillColor` (top level or in `initial`). */
|
|
@@ -293,8 +369,12 @@ interface ShapeBase extends SequenceCommon {
|
|
|
293
369
|
* sweeps; `oklab` is straight-line in the chromaticity plane.
|
|
294
370
|
*/
|
|
295
371
|
colorSpace?: 'rgb' | 'oklab' | 'oklch';
|
|
372
|
+
/** How an open stroke ends: `'butt'` (default), `'round'` (a friendly progress-ring end) or `'square'`. Top level or in `initial`. */
|
|
373
|
+
strokeCap?: 'butt' | 'round' | 'square';
|
|
374
|
+
/** How stroke corners join: `'miter'` (default; sharp points can poke out), `'round'` or `'bevel'`. Top level or in `initial`. */
|
|
375
|
+
strokeJoin?: 'miter' | 'round' | 'bevel';
|
|
296
376
|
}
|
|
297
|
-
interface RectShapeSpec extends ShapeBase {
|
|
377
|
+
interface RectShapeSpec extends ShapeBase, Trimmable {
|
|
298
378
|
shape: 'rect';
|
|
299
379
|
width: PropValue;
|
|
300
380
|
height: PropValue;
|
|
@@ -309,14 +389,14 @@ interface RectShapeSpec extends ShapeBase {
|
|
|
309
389
|
anchorX?: PropValue;
|
|
310
390
|
anchorY?: PropValue;
|
|
311
391
|
}
|
|
312
|
-
interface CircleShapeSpec extends ShapeBase {
|
|
392
|
+
interface CircleShapeSpec extends ShapeBase, Trimmable {
|
|
313
393
|
shape: 'circle';
|
|
314
394
|
radius: PropValue;
|
|
315
395
|
/** See RectShapeSpec.anchorX. Default 0.5 (centre). */
|
|
316
396
|
anchorX?: PropValue;
|
|
317
397
|
anchorY?: PropValue;
|
|
318
398
|
}
|
|
319
|
-
interface EllipseShapeSpec extends ShapeBase {
|
|
399
|
+
interface EllipseShapeSpec extends ShapeBase, Trimmable {
|
|
320
400
|
shape: 'ellipse';
|
|
321
401
|
radiusX: PropValue;
|
|
322
402
|
radiusY: PropValue;
|
|
@@ -324,25 +404,41 @@ interface EllipseShapeSpec extends ShapeBase {
|
|
|
324
404
|
anchorX?: PropValue;
|
|
325
405
|
anchorY?: PropValue;
|
|
326
406
|
}
|
|
327
|
-
|
|
407
|
+
/**
|
|
408
|
+
* An arc around the local origin (its centre). Angles are in degrees: 0 = 3 o'clock, positive = clockwise,
|
|
409
|
+
* so a progress ring starts at `startAngle: -90` (12 o'clock) and animates `endAngle` from -90 to 270.
|
|
410
|
+
* With a stroke and no fill it is an open arc line (`strokeCap: 'round'` for rounded ends); with a fill it is a
|
|
411
|
+
* sector (a pie slice), and with `innerRadius` a ring segment (a donut slice). A sweep of 360° or more is a full circle.
|
|
412
|
+
*/
|
|
413
|
+
interface ArcShapeSpec extends ShapeBase {
|
|
414
|
+
shape: 'arc';
|
|
415
|
+
radius: PropValue;
|
|
416
|
+
/** Hole radius of a filled ring segment. Default 0 (a pie slice to the centre). Animatable. */
|
|
417
|
+
innerRadius?: PropValue;
|
|
418
|
+
/** Degrees. Default 0. Animatable. */
|
|
419
|
+
startAngle?: PropValue;
|
|
420
|
+
/** Degrees. Default 360. Animatable. */
|
|
421
|
+
endAngle?: PropValue;
|
|
422
|
+
}
|
|
423
|
+
interface LineShapeSpec extends ShapeBase, Trimmable {
|
|
328
424
|
shape: 'line';
|
|
329
425
|
/** Line endpoints relative to the shape's local origin. */
|
|
330
426
|
from: [PropValue, PropValue];
|
|
331
427
|
to: [PropValue, PropValue];
|
|
332
428
|
}
|
|
333
|
-
interface PolygonShapeSpec extends ShapeBase {
|
|
429
|
+
interface PolygonShapeSpec extends ShapeBase, Trimmable {
|
|
334
430
|
shape: 'polygon';
|
|
335
431
|
/** Vertices in local space. The path is auto-closed. */
|
|
336
432
|
points: Array<[PropValue, PropValue]>;
|
|
337
433
|
/** When true, draw an open polyline instead of a closed polygon. Default false. */
|
|
338
434
|
open?: boolean;
|
|
339
435
|
}
|
|
340
|
-
interface PathShapeSpec extends ShapeBase {
|
|
436
|
+
interface PathShapeSpec extends ShapeBase, Trimmable {
|
|
341
437
|
shape: 'path';
|
|
342
438
|
/** SVG path data (`d` attribute). Goes through PIXI's GraphicsContext.svg(). */
|
|
343
439
|
d: string;
|
|
344
440
|
}
|
|
345
|
-
type ShapeSequenceSpec = RectShapeSpec | CircleShapeSpec | EllipseShapeSpec | LineShapeSpec | PolygonShapeSpec | PathShapeSpec;
|
|
441
|
+
type ShapeSequenceSpec = RectShapeSpec | CircleShapeSpec | EllipseShapeSpec | ArcShapeSpec | LineShapeSpec | PolygonShapeSpec | PathShapeSpec;
|
|
346
442
|
type SequenceSpec = VideoSequenceSpec | ImageSequenceSpec | TextSequenceSpec | AudioSequenceSpec | CompositionSequenceSpec | CameraSequenceSpec | ShapeSequenceSpec;
|
|
347
443
|
/** Top-level composition (root node) — same as `CompositionSequenceSpec` minus the discriminant. */
|
|
348
444
|
interface CompositionSpec extends SequenceCommon {
|
|
@@ -359,7 +455,17 @@ interface CompositionShape {
|
|
|
359
455
|
}
|
|
360
456
|
/** Audio descriptor pushed to the mixdown queue by AudioSequence/VideoSequence. */
|
|
361
457
|
interface AudioDescriptor {
|
|
362
|
-
buffer
|
|
458
|
+
/** Decoded audio (a file or a video's soundtrack). Exactly one of `buffer` / `synth` is set. */
|
|
459
|
+
buffer?: AudioBuffer;
|
|
460
|
+
/** Stereo samples (left, right) made on demand at the mix's sample rate (a synthesised sfx). Equal keys render equal samples. */
|
|
461
|
+
synth?: {
|
|
462
|
+
key: string;
|
|
463
|
+
render: (sampleRate: number) => [Float32Array, Float32Array];
|
|
464
|
+
};
|
|
465
|
+
/** Who made it, for warnings and inspectAudio: `layer "pop-1"`. */
|
|
466
|
+
layer?: string;
|
|
467
|
+
/** What it plays: `sfx "pop"`, `asset "bgm"`, `video "green"`. */
|
|
468
|
+
source?: string;
|
|
363
469
|
loop: boolean;
|
|
364
470
|
start: number;
|
|
365
471
|
end: number;
|
|
@@ -370,4 +476,4 @@ interface AudioDescriptor {
|
|
|
370
476
|
}[];
|
|
371
477
|
}
|
|
372
478
|
|
|
373
|
-
export type {
|
|
479
|
+
export type { ArcShapeSpec as A, AudioDescriptor as B, CompositionShape as C, DipTransition as D, EllipseShapeSpec as E, FilterSpec as F, GradientSpec as G, ImageSequenceSpec as I, Keyframe as K, LineShapeSpec as L, PathShapeSpec as P, RectShapeSpec as R, SequenceSpec as S, TextSequenceSpec as T, VideoSequenceSpec as V, WipeTransition as W, ZoomTransition as Z, CameraSequenceSpec as a, SequenceCommon as b, AssetSpec as c, AudioAssetSpec as d, AudioSequenceSpec as e, AudioSfxSpec as f, ChromaKeyFilterSpec as g, CircleShapeSpec as h, CompositionSequenceSpec as i, CompositionSpec as j, CrossfadeTransition as k, CustomFilterSpec as l, DissolveTransition as m, Expr as n, IrisTransition as o, PolygonShapeSpec as p, PropValue as q, Props as r, SfxKnobs as s, SfxOptions as t, SfxPreset as u, SfxVoice as v, ShapeSequenceSpec as w, SlideTransition as x, TransitionCommon as y, TransitionSpec as z };
|
package/docs/api.md
CHANGED
|
@@ -89,6 +89,21 @@ contactSheet(options?: {
|
|
|
89
89
|
|
|
90
90
|
Many frames on **one** PNG, each labelled `frame N · T s`. The cheapest way to check a whole animation by eye (include the middle of every transition and the last second). Restores the current frame afterwards.
|
|
91
91
|
|
|
92
|
+
### `measureText(text, style?): { width, height, lines }` and `splitText(text, style?, options?): TextPiece[]`
|
|
93
|
+
|
|
94
|
+
Text layout helpers, exported from `pixi-effects`. They measure with the renderer's own font metrics (the same numbers a text layer with that `style` has), so a layout built from them matches what is drawn. `style` is a text layer's `style` with numbers (no expressions). A web font must be loaded first.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
measureText('Hello', { fontSize: 72 }); // { width: 187.4, height: 81.3, lines: 1 }
|
|
98
|
+
|
|
99
|
+
interface SplitOptions { by?: 'chars' | 'words' | 'lines'; x?: number; y?: number; align?: 'left' | 'center' | 'right' }
|
|
100
|
+
interface TextPiece { text: string; x: number; y: number; width: number; index: number; line: number }
|
|
101
|
+
splitText('Hello world', style, { by: 'words', x: 640, y: 300, align: 'center' });
|
|
102
|
+
// → [{ text: 'Hello', x, y, width, index: 0, line: 0 }, { text: 'world', ... }]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`x` is the line's left edge, or its centre / right edge with `align` (each line of a `\n` text is aligned on its own width); `y` is the top of the first line, and further lines go down by the line height. Whitespace takes its room but is not returned as a piece. Use one text layer per piece with the same `style` and the default top-left anchor: `{ type: 'text', text: p.text, style, initial: { x: p.x, y: p.y } }`. Kerning is kept (a piece's x is the measured width of everything before it).
|
|
106
|
+
|
|
92
107
|
### `movie.inspect(frame?, options?): Promise<InspectReport>`
|
|
93
108
|
|
|
94
109
|
Where every layer is drawn at `frame`, as JSON — for checking layout without eyes. Seeks there and stays there. `options.layers`: `'visible'` (default, only layers drawn at this frame), `'all'`, or `'none'` (just `summary` and `issues`). Faint layers (alpha < 0.3) and text whose x / y is animated (tickers) are not reported.
|
|
@@ -109,6 +124,27 @@ interface InspectReport {
|
|
|
109
124
|
}
|
|
110
125
|
```
|
|
111
126
|
|
|
127
|
+
### `movie.inspectAudio(options?): AudioReport`
|
|
128
|
+
|
|
129
|
+
The soundtrack as numbers — for checking sound without listening. Every sound is also measured on its own, so overlaps do not blur it. `options.window`: seconds per row of `windows` (default 0.1).
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
interface AudioReport {
|
|
133
|
+
duration: number; sampleRate: number;
|
|
134
|
+
peakDb: number; peakAt: number; // the mix before limiting; > 0 dBFS means it was limited
|
|
135
|
+
sources: Array<{
|
|
136
|
+
layer: string; source: string; // 'layer "hit"', 'sfx "hit"' | 'asset "bgm"' | 'video "clip"'
|
|
137
|
+
start: number; end: number; // movie time
|
|
138
|
+
peakDb: number; // the MIX while it plays
|
|
139
|
+
sound: { length: number; peakDb: number; loudestAt: number; brightnessHz: number }; // the sound alone
|
|
140
|
+
}>;
|
|
141
|
+
windows: Array<{ t: number; rmsDb: number; peakDb: number; brightnessHz: number }>;
|
|
142
|
+
issues: string[]; // limited mix, inaudible layer, sfx cut off by the end, no audio — read first
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`render()` encodes this same mix: AAC in mp4 / mov (the encoder delay is trimmed with an edit list), Opus in webm / mkv.
|
|
147
|
+
|
|
112
148
|
### `movie.render(options?): Promise<Blob>`
|
|
113
149
|
|
|
114
150
|
Renders the entire timeline to a single video file. Pauses playback first.
|
|
@@ -146,6 +182,7 @@ movie.off(event, fn): this
|
|
|
146
182
|
| ----------- | ------------------------------------------------- | ----------------------------------------------------- |
|
|
147
183
|
| `'ready'` | none | once, after `init()` resolves |
|
|
148
184
|
| `'frame'` | `{ frame: number; totalFrames: number }` | every `gotoFrame` (so once per playback frame too) |
|
|
185
|
+
| `'play'` | none | when `play()` actually transitions from paused |
|
|
149
186
|
| `'pause'` | none | when `pause()` actually transitions from playing |
|
|
150
187
|
| `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
|
|
151
188
|
|
package/docs/dsl.md
CHANGED
|
@@ -59,7 +59,7 @@ interface SequenceCommon {
|
|
|
59
59
|
| Layer | Where `x, y` land by default | To change it |
|
|
60
60
|
|---|---|---|
|
|
61
61
|
| `shape` rect / circle / ellipse | the **centre** of the shape | `anchorX` / `anchorY` (0–1). A bar growing from its base: `anchorY: 1`; left to right: `anchorX: 0` |
|
|
62
|
-
| `shape` line / polygon / path | the centre of the shape's bounds |
|
|
62
|
+
| `shape` line / polygon / path | the centre of the shape's bounds | `pivotX` / `pivotY` (in the shape's own canvas coordinates): the point that sits at `x, y` and that rotation / scale turn about. **`anchorX` / `anchorY` do nothing on these.** |
|
|
63
63
|
| `text`, `image`, `video` | the **top-left** corner | `anchorX` / `anchorY: 0.5` to centre |
|
|
64
64
|
| `composition` | its top-left corner | `pivotX` / `pivotY` = the point that sits at `x, y` and that rotation / scale turn about |
|
|
65
65
|
|
|
@@ -71,7 +71,7 @@ A layer is **not removed** when its lifespan `[at, at + duration)` ends: it is o
|
|
|
71
71
|
|
|
72
72
|
### `text`
|
|
73
73
|
|
|
74
|
-
Renders a [PIXI.Text](https://pixijs.com/8.x/guides/components/scene-objects/text/text). Animatable position, rotation, scale, opacity and `fill`. `style` accepts any PixiJS `TextStyle` field and is applied once at build time — notably `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding` (numbers may be expressions). A `dropShadow` glow is clipped unless `padding` is at least twice its `blur`. Text content
|
|
74
|
+
Renders a [PIXI.Text](https://pixijs.com/8.x/guides/components/scene-objects/text/text). Animatable position, rotation, scale, opacity and `fill`. `style` accepts any PixiJS `TextStyle` field and is applied once at build time — notably `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding` (numbers may be expressions). A `dropShadow` glow is clipped unless `padding` is at least twice its `blur`. Text content can change over time (see *Changing text* below): a `{value}` placeholder prints an animatable number (counters), a keyframe `set: { text: '…' }` swaps the string, and `visibleChars` types it out. Multi-line text is centre-aligned unless `style.align` says otherwise. In expressions `w` / `h` are the size of the styled text, so `x: '-w'` places it just off the left edge.
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
77
|
{
|
|
@@ -103,6 +103,17 @@ The text's `fill` is also animatable via keyframes (under the `to`/`from`/`set`
|
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
#### Changing text: `set: { text }` and `visibleChars`
|
|
107
|
+
|
|
108
|
+
A keyframe `set: { text: 'Two' }` swaps the string at that time (layer-local `at`, undone when you seek back; a string cannot be tweened, so `to` / `from` with `text` warn). A countdown is one layer:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
{ type: 'text', text: '3', duration: 4, style: { fontSize: 150 }, initial: { x: 'GW/2', y: 'GH/2', anchorX: 0.5, anchorY: 0.5 },
|
|
112
|
+
keyframes: [{ at: 1, set: { text: '2' } }, { at: 2, set: { text: '1' } }, { at: 3, set: { text: 'Go!' } }] }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`visibleChars` is a number that shows only the first N characters (counted as you see them: an emoji is one): a typewriter. `initial: { visibleChars: 0 }` and a keyframe `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` types 30 characters over 2.4 s (it starts at 0 as soon as it is animated). Use a left-aligned layer (`anchorX: 0`, `style.align: 'left'`) so the text grows to the right instead of re-centring each letter; the layer's `w` / `h` are those of the whole text. `set: { text }` and `visibleChars` combine (swap the string, `set: { visibleChars: 0 }`, type it again), and so does a `{value}` placeholder in the string.
|
|
116
|
+
|
|
106
117
|
#### Counters (`{value}`)
|
|
107
118
|
|
|
108
119
|
A text layer has one animatable number, `value`, printed wherever the text contains `{value}`. Animate it with the normal keyframes (any ease, `from`/`to`/`set`, `repeat`):
|
|
@@ -116,7 +127,7 @@ A text layer has one animatable number, `value`, printed wherever the text conta
|
|
|
116
127
|
}
|
|
117
128
|
```
|
|
118
129
|
|
|
119
|
-
`format`: `decimals` (default 0)
|
|
130
|
+
`format`: `decimals` (default 0), thousands `grouping` (default false) and `pad` (zero-pad the whole part to that many digits: `pad: 2` gives `05`, so a clock is `text: '18:42:{value}'` with `format: { pad: 2 }`). Put prefixes and suffixes in the text (`'${value}'`, `'{value}%'`). If `value` is animated but the text has no `{value}`, a warning says so.
|
|
120
131
|
|
|
121
132
|
### `image`
|
|
122
133
|
|
|
@@ -179,10 +190,40 @@ Audio-only sequence. No visual. Volume is animatable via keyframes.
|
|
|
179
190
|
|
|
180
191
|
Audio is mixed during `Movie.init()` and during `Movie.render()`. Volume keyframes interpolate linearly.
|
|
181
192
|
|
|
193
|
+
#### Sound effects without files: `sfx`
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
{ type: 'audio', sfx: 'swoosh', at: 2 } // a preset at its own length
|
|
197
|
+
{ type: 'audio', sfx: { preset: 'hit', pitch: -3, brightness: -0.5 }, at: 4 } // lower and darker
|
|
198
|
+
{ type: 'audio', sfx: 'riser', at: 3, duration: 1.5, volume: 0.7 } // duration IS the sound's length (stretched)
|
|
199
|
+
{ type: 'audio', at: 6, duration: 0.3, // a custom sound
|
|
200
|
+
sfx: { voices: [{ wave: 'square', freq: [1600, 200], filter: { type: 'lowpass', freq: 3000 }, envelope: 'fall' }] } }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The sound is synthesised from data at init (no asset, no network), deterministic, and the same mix in playback and in the exported file (encoded there: AAC / Opus are lossy). `at` is where it starts; `volume` and volume keyframes work as for files; `loop` is not available (one layer per hit). Knobs: `pitch` (semitones, ±24), `brightness` (−1 dark … 1 bright), `seed` (another take of the random parts). `volume: 1` is the preset's standard level, peaking at −12 … −18 dBFS depending on the preset; the finished mix is soft-limited above 0.9 with a warning naming the layers. A custom sound has up to 16 `voices`: `wave` (`sine` `triangle` `square` `saw` `noise`), `freq` (Hz or `[from, to]`), `filter` (`{ type: 'lowpass' | 'highpass' | 'bandpass', freq, q }`), `envelope` (`fall` `bell` `swell` `hold`), `pan`, `from` / `to` (part of the sound, 0–1), `gain`; its default length is 0.5 s.
|
|
204
|
+
|
|
205
|
+
| Preset | Default length | Loudest at | Brightness | Use for |
|
|
206
|
+
|---|---|---|---|---|
|
|
207
|
+
| `click` | 0.04 s | start | ≈ 12780 Hz | UI click, tick |
|
|
208
|
+
| `pop` | 0.12 s | start | ≈ 390 Hz | an element appearing |
|
|
209
|
+
| `swoosh` | 0.5 s | 0.16 s | ≈ 3070 Hz | transitions, fly-pasts (moves left → right) |
|
|
210
|
+
| `swipe` | 0.22 s | start | ≈ 13190 Hz | quick UI swipe |
|
|
211
|
+
| `hit` | 0.7 s | start | ≈ 150 Hz | impact, slam |
|
|
212
|
+
| `riser` | 1 s | its end | ≈ 11850 Hz | build-up into a hit |
|
|
213
|
+
| `chime` | 1.4 s | start | ≈ 1400 Hz | success, reveal |
|
|
214
|
+
| `beep` | 0.16 s | flat | ≈ 1030 Hz | countdown, alert |
|
|
215
|
+
| `coin` | 0.4 s | 0.07 s | ≈ 1870 Hz | reward, collect |
|
|
216
|
+
| `glitch` | 0.35 s | 0.22 s | ≈ 3970 Hz | digital error |
|
|
217
|
+
| `typewriter` | 0.06 s | start | ≈ 3260 Hz | one key (vary `seed` per key) |
|
|
218
|
+
|
|
219
|
+
Brightness is the spectral centroid that `movie.inspectAudio()` reports (`sound.brightnessHz`), at 48 kHz with no knobs.
|
|
220
|
+
|
|
182
221
|
### `composition`
|
|
183
222
|
|
|
184
223
|
Nested composition. Same shape as the root spec but with `type: 'composition'` and an explicit `width`/`height`. Children animate within the local coordinate system; the composition itself can be positioned, scaled, and rotated as a unit.
|
|
185
224
|
|
|
225
|
+
**Size, clipping and culling** (measured): a 2D composition does **not** clip its children — a child far outside `width × height` is still drawn. But the rectangle `width × height` is what the renderer uses to decide the group is on screen: when that whole rectangle is off the canvas the group is skipped even if a child is placed back on screen. So keep the rectangle around what it draws (or make it canvas-sized). A `threeD: true` composition (a card) is rendered into a texture of exactly `width × height`, so it **does** clip its children there; size it for soft shadows too.
|
|
226
|
+
|
|
186
227
|
```ts
|
|
187
228
|
{
|
|
188
229
|
type: 'composition',
|
|
@@ -220,12 +261,31 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
|
|
|
220
261
|
|
|
221
262
|
| `shape` | Required props | Optional |
|
|
222
263
|
|-----------|------------------------------------------|------------------------|
|
|
223
|
-
| `rect` | `width`, `height` | `cornerRadius`
|
|
224
|
-
| `circle` | `radius` |
|
|
225
|
-
| `ellipse` | `radiusX`, `radiusY` |
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
264
|
+
| `rect` | `width`, `height` | `cornerRadius`, `trimStart`, `trimEnd` |
|
|
265
|
+
| `circle` | `radius` | `trimStart`, `trimEnd` |
|
|
266
|
+
| `ellipse` | `radiusX`, `radiusY` | `trimStart`, `trimEnd` |
|
|
267
|
+
| `arc` | `radius` | `innerRadius`, `startAngle` (0), `endAngle` (360) |
|
|
268
|
+
| `line` | `from: [x,y]`, `to: [x,y]` (canvas coordinates; stroke in `initial`) | `trimStart`, `trimEnd` |
|
|
269
|
+
| `polygon` | `points: [[x,y], …]` | `open` (default false), `trimStart`, `trimEnd` |
|
|
270
|
+
| `path` | `d` (SVG path data) | `trimStart`, `trimEnd` |
|
|
271
|
+
|
|
272
|
+
**`arc`** is an arc around its local origin (the centre). Angles are in **degrees: 0° is 3 o'clock and positive is clockwise**, so a progress ring starts at `startAngle: -90` (12 o'clock) and animates `endAngle` from `-90` to `270`. With a stroke and no fill it is an open arc line (`strokeCap: 'round'` for rounded ends); with a fill it is a sector (a pie slice), and with `innerRadius` a ring segment (a donut slice). A sweep of 360° or more is a full circle; `endAngle < startAngle` sweeps counter-clockwise. `radius`, `innerRadius`, `startAngle` and `endAngle` are all animatable.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
{ type: 'shape', shape: 'arc', radius: 120, startAngle: -90, endAngle: -90, strokeCap: 'round',
|
|
276
|
+
initial: { x: 'GW/2', y: 'GH/2', strokeColor: '#ffd166', strokeWidth: 24 },
|
|
277
|
+
keyframes: [{ at: 0, to: { endAngle: 270 }, duration: 2, ease: 'power3.out' }] } // a progress ring filling clockwise from the top
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Draw-on (`trimStart` / `trimEnd`).** On every shape except `arc`, `trimStart` and `trimEnd` (0–1, default 0 and 1, both animatable) are the part of the outline that is stroked, as fractions of its length. Animate `trimEnd` from 0 to 1 to draw a line, border or SVG path on; animate `trimStart` after it to wipe it off the same way; animate both for a travelling dash. A rect's outline starts at its top-left and goes clockwise, a circle / ellipse's at 12 o'clock and goes clockwise, a polygon's includes its closing edge, and a path's several sub-paths count as one length in order. **The fill is not trimmed** (the whole shape stays filled): fade `fillAlpha` in after the draw-on. Use `strokeCap: 'round'` for a friendly end. For `arc`, animate `endAngle` instead (a `trim…` on an arc warns).
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
{ type: 'shape', shape: 'path', d: 'M 0 50 L 45 95 L 130 0', trimEnd: 0, strokeCap: 'round', strokeJoin: 'round',
|
|
284
|
+
initial: { x: 1040, y: 270, strokeColor: '#7bd88f', strokeWidth: 16 },
|
|
285
|
+
keyframes: [{ at: 0, to: { trimEnd: 1 }, duration: 1, ease: 'power2.inOut' }] } // a check mark drawing itself
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`strokeCap` (`'butt'`, `'round'`, `'square'`) and `strokeJoin` (`'miter'`, `'round'`, `'bevel'`) work on every shape, at the top level or in `initial`; they are not animated. Sharp corners (the apex of an M in outlined text, a thin polygon) poke out with the default `miter` join: use `'round'`.
|
|
229
289
|
|
|
230
290
|
**Style** is set on `initial` and animatable via keyframes:
|
|
231
291
|
|
|
@@ -309,7 +369,7 @@ Per-shape choice of how colour keyframes are interpolated. Default `'rgb'` (line
|
|
|
309
369
|
|
|
310
370
|
**Scalar geometry is animatable.** `width`, `height`, `cornerRadius` (rect), `radius` (circle), `radiusX` / `radiusY` (ellipse) all flow through the same per-frame redraw and can be tweened via keyframes — useful for progress bars (animate `width`), pulsing icons (animate `radius`), or shape-morph callouts. Array geometry (polygon `points`, line endpoints, path `d`) is baked at build time; use `scale` / `scaleX` / `scaleY` for those.
|
|
311
371
|
|
|
312
|
-
`anchorX` / `anchorY` (default `0.5` each) control which point on the bbox sits at the local origin — `0` is left/top, `1` is right/bottom. They're animatable too. Critical for "grow from one edge" effects:
|
|
372
|
+
For rect / circle / ellipse, `anchorX` / `anchorY` (default `0.5` each; not available on line / polygon / path, which use `pivotX` / `pivotY`) control which point on the bbox sits at the local origin — `0` is left/top, `1` is right/bottom. They're animatable too. Critical for "grow from one edge" effects:
|
|
313
373
|
|
|
314
374
|
```ts
|
|
315
375
|
// Left-anchored progress bar — width animates 0 → W, left edge stays at x
|