pixi-effects 0.19.0 → 0.20.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +6 -6
  3. package/ai/SKILL.md +1 -0
  4. package/ai/chat-template.html +3 -3
  5. package/ai/reference/cheatsheet.md +7 -6
  6. package/ai/reference/pitfalls.md +5 -0
  7. package/ai/reference/recipes.md +52 -0
  8. package/ai/template.html +5 -5
  9. package/ai/tools/check.mjs +15 -3
  10. package/dist/{Base-B4WbUJO2.d.ts → Base-CzfLGoDr.d.ts} +9 -1
  11. package/dist/{Base-i88XXaiP.d.cts → Base-wYJQ2g5Q.d.cts} +9 -1
  12. package/dist/Composition-6PA4C4TV.js +5 -0
  13. package/dist/{Composition-QBKEEJBV.js.map → Composition-6PA4C4TV.js.map} +1 -1
  14. package/dist/Composition-LL2XXU5N.cjs +14 -0
  15. package/dist/{Composition-KYNPRWK3.cjs.map → Composition-LL2XXU5N.cjs.map} +1 -1
  16. package/dist/Controller.cjs +2 -2
  17. package/dist/Controller.d.cts +2 -2
  18. package/dist/Controller.d.ts +2 -2
  19. package/dist/Controller.js +1 -1
  20. package/dist/{Movie-8IrqL3kO.d.ts → Movie-CBxGH5ms.d.ts} +8 -2
  21. package/dist/{Movie-CpmdNadg.d.cts → Movie-CzzJw8A-.d.cts} +8 -2
  22. package/dist/Presenter.cjs +2 -2
  23. package/dist/Presenter.d.cts +2 -2
  24. package/dist/Presenter.d.ts +2 -2
  25. package/dist/Presenter.js +1 -1
  26. package/dist/{chunk-YRXTKOV5.js → chunk-DQHXVNOI.js} +282 -23
  27. package/dist/chunk-DQHXVNOI.js.map +1 -0
  28. package/dist/{chunk-CTZRFSVU.cjs → chunk-FFYA3UBU.cjs} +144 -37
  29. package/dist/chunk-FFYA3UBU.cjs.map +1 -0
  30. package/dist/{chunk-DKGG7WD4.js → chunk-MNM56B3X.js} +6 -3
  31. package/dist/chunk-MNM56B3X.js.map +1 -0
  32. package/dist/{chunk-VTBT6NNE.cjs → chunk-NO26HWR7.cjs} +294 -30
  33. package/dist/chunk-NO26HWR7.cjs.map +1 -0
  34. package/dist/{chunk-B3YB2TNY.js → chunk-TSPMKV2F.js} +120 -13
  35. package/dist/chunk-TSPMKV2F.js.map +1 -0
  36. package/dist/{chunk-UQBGW5DO.cjs → chunk-WTRKDIKH.cjs} +6 -3
  37. package/dist/chunk-WTRKDIKH.cjs.map +1 -0
  38. package/dist/index.cjs +213 -90
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +4 -4
  41. package/dist/index.d.ts +4 -4
  42. package/dist/index.js +154 -31
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.css +1 -1
  45. package/dist/{music-4HDFULNR.js → music-CFEC7HKL.js} +3 -3
  46. package/dist/{music-4HDFULNR.js.map → music-CFEC7HKL.js.map} +1 -1
  47. package/dist/{music-3OF5C6X5.cjs → music-UAN7DXK4.cjs} +7 -7
  48. package/dist/{music-3OF5C6X5.cjs.map → music-UAN7DXK4.cjs.map} +1 -1
  49. package/dist/{sfx-MZJZXYY3.cjs → sfx-EX4PUBMV.cjs} +8 -8
  50. package/dist/{sfx-MZJZXYY3.cjs.map → sfx-EX4PUBMV.cjs.map} +1 -1
  51. package/dist/{sfx-S37DDC66.js → sfx-U3GM2MED.js} +3 -3
  52. package/dist/{sfx-S37DDC66.js.map → sfx-U3GM2MED.js.map} +1 -1
  53. package/dist/three.cjs +6 -6
  54. package/dist/three.d.cts +2 -2
  55. package/dist/three.d.ts +2 -2
  56. package/dist/three.js +2 -2
  57. package/dist/{types-bzLJ7aUx.d.ts → types-YmUaViL1.d.cts} +24 -0
  58. package/dist/{types-bzLJ7aUx.d.cts → types-YmUaViL1.d.ts} +24 -0
  59. package/docs/dsl.md +22 -0
  60. package/llms-full.txt +87 -6
  61. package/package.json +3 -1
  62. package/dist/Composition-KYNPRWK3.cjs +0 -14
  63. package/dist/Composition-QBKEEJBV.js +0 -5
  64. package/dist/chunk-B3YB2TNY.js.map +0 -1
  65. package/dist/chunk-CTZRFSVU.cjs.map +0 -1
  66. package/dist/chunk-DKGG7WD4.js.map +0 -1
  67. package/dist/chunk-UQBGW5DO.cjs.map +0 -1
  68. package/dist/chunk-VTBT6NNE.cjs.map +0 -1
  69. package/dist/chunk-YRXTKOV5.js.map +0 -1
package/dist/Presenter.js CHANGED
@@ -1,4 +1,4 @@
1
- import { warnUnknownOptions } from './chunk-DKGG7WD4.js';
1
+ import { warnUnknownOptions } from './chunk-MNM56B3X.js';
2
2
 
3
3
  // src/Presenter.ts
4
4
  var OPTION_KEYS = ["canvas", "keyboard", "clickToAdvance", "swipe", "indicator", "autoAdvance", "loop", "accent", "onExit"];
@@ -1,4 +1,4 @@
1
- import { suggestName, warnUnknownOptions } from './chunk-DKGG7WD4.js';
1
+ import { suggestName, warnUnknownOptions } from './chunk-MNM56B3X.js';
2
2
  import * as PIXI from 'pixi.js';
3
3
  import { Rectangle, Container, GraphicsPath, Graphics, Assets, Texture, Sprite, FillGradient, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, Color } from 'pixi.js';
4
4
  import { gsap } from 'gsap';
@@ -396,6 +396,58 @@ function looksLikeColorString(s) {
396
396
  if (s.charCodeAt(0) === 35) return true;
397
397
  return /^(rgb|hsl|oklab|oklch|lab|lch)\b/i.test(s);
398
398
  }
399
+ function cubicBezierEase(x1, y1, x2, y2) {
400
+ const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
401
+ const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
402
+ const X = (t) => ((ax * t + bx) * t + cx) * t;
403
+ const Y = (t) => ((ay * t + by) * t + cy) * t;
404
+ const dX = (t) => (3 * ax * t + 2 * bx) * t + cx;
405
+ return (p) => {
406
+ if (p <= 0) return 0;
407
+ if (p >= 1) return 1;
408
+ let t = p;
409
+ for (let i = 0; i < 8; i++) {
410
+ const err = X(t) - p;
411
+ if (Math.abs(err) < 1e-9) return Y(t);
412
+ const d = dX(t);
413
+ if (Math.abs(d) < 1e-6) break;
414
+ t -= err / d;
415
+ }
416
+ let lo = 0, hi = 1;
417
+ t = p;
418
+ for (let i = 0; i < 60; i++) {
419
+ const x = X(t);
420
+ if (Math.abs(x - p) < 1e-10) break;
421
+ if (x < p) lo = t;
422
+ else hi = t;
423
+ t = (lo + hi) / 2;
424
+ }
425
+ return Y(t);
426
+ };
427
+ }
428
+ var NUM = String.raw`\s*(-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)\s*`;
429
+ var RE = new RegExp(`^\\s*cubic-bezier\\(${NUM},${NUM},${NUM},${NUM}\\)\\s*$`, "i");
430
+ function cubicBezierProblem(name) {
431
+ const m = RE.exec(name);
432
+ if (!m) return 'expected "cubic-bezier(x1, y1, x2, y2)" with four numbers, like CSS';
433
+ const x1 = Number(m[1]), x2 = Number(m[3]);
434
+ if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) return "x1 and x2 must be between 0 and 1 (y1 and y2 may overshoot)";
435
+ return null;
436
+ }
437
+ var registered2 = false;
438
+ function registerCubicBezierEase() {
439
+ if (registered2) return;
440
+ registered2 = true;
441
+ const base = ((p) => p);
442
+ base.config = (x1, y1, x2, y2) => {
443
+ const bad = [x1, y1, x2, y2].some((v) => typeof v !== "number" || !Number.isFinite(v)) || x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1;
444
+ return bad ? (p) => p : cubicBezierEase(x1, y1, x2, y2);
445
+ };
446
+ gsap.registerEase("cubic-bezier", base);
447
+ }
448
+ registerCubicBezierEase();
449
+
450
+ // src/core/ease.ts
399
451
  var FAMILIES = ["power1", "power2", "power3", "power4", "back", "elastic", "bounce", "circ", "expo", "sine"];
400
452
  var EASE_NAMES = ["none", ...FAMILIES.flatMap((f) => [`${f}.in`, `${f}.out`, `${f}.inOut`])];
401
453
  var warned = /* @__PURE__ */ new Set();
@@ -408,6 +460,13 @@ function checkEase(ease, where) {
408
460
  console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. GSAP would run it as its default ease, power1.out.`);
409
461
  return;
410
462
  }
463
+ if (/^\s*cubic-bezier/i.test(ease)) {
464
+ const problem = cubicBezierProblem(ease);
465
+ if (!problem || warned.has(ease)) return;
466
+ warned.add(ease);
467
+ console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. It runs as 'none' (a straight line).`);
468
+ return;
469
+ }
411
470
  const found = gsap.parseEase(ease);
412
471
  if (typeof found === "function") return;
413
472
  if (warned.has(ease)) return;
@@ -1082,7 +1141,7 @@ var GrainFilter = class extends Filter {
1082
1141
  }
1083
1142
  /** The movie calls this with the time (s) of the frame it is about to draw: the grain is new every 1/fps s. */
1084
1143
  setTime(t) {
1085
- this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-6) : 0;
1144
+ this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-3) : 0;
1086
1145
  }
1087
1146
  };
1088
1147
 
@@ -1297,13 +1356,21 @@ var Sequence = class {
1297
1356
  pathRouters() {
1298
1357
  return {};
1299
1358
  }
1359
+ /**
1360
+ * The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
1361
+ * out of them: it is not a property of the display object.
1362
+ */
1363
+ displayProps() {
1364
+ return { initial: this.spec.initial, keyframes: this.spec.keyframes };
1365
+ }
1300
1366
  bindTimeline(timeline, offset = 0) {
1301
1367
  if (!this.target) return;
1302
1368
  const scope = this.scope();
1303
1369
  const routers = this.pathRouters();
1304
1370
  const startTime = offset + this.at;
1305
- applyInitial(this.target, this.spec.initial, scope, [], routers);
1306
- applyKeyframes(timeline, this.target, this.spec.keyframes, this.duration, scope, [], startTime, routers);
1371
+ const { initial, keyframes } = this.displayProps();
1372
+ applyInitial(this.target, initial, scope, [], routers);
1373
+ applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime, routers);
1307
1374
  const endTime = startTime + this.duration;
1308
1375
  this.absoluteStart = startTime;
1309
1376
  if (this.keepHidden) return;
@@ -1356,11 +1423,11 @@ function collectPropKeys(spec) {
1356
1423
 
1357
1424
  // src/core/layerKeys.ts
1358
1425
  var COMMON = ["type", "name", "parent", "at", "duration", "initial", "keyframes", "filters", "filterArea", "mask", "maskInverted", "blendMode", "threeD", "hideBehindCamera"];
1359
- var VIDEO = [...COMMON, "asset", "loop", "audio", "volume"];
1426
+ var VIDEO = [...COMMON, "asset", "loop", "audio", "volume", "speed"];
1360
1427
  var IMAGE = [...COMMON, "asset", "colorSpace"];
1361
1428
  var TEXT = [...COMMON, "text", "format", "style", "colorSpace", "fillGradient"];
1362
- var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music"];
1363
- var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions"];
1429
+ var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music", "speed"];
1430
+ var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions", "speed"];
1364
1431
  var CAMERA = [...COMMON];
1365
1432
  var NULL = [...COMMON];
1366
1433
  var SHAPE = [...COMMON, "shape", "fillGradient", "colorSpace", "strokeCap", "strokeJoin"];
@@ -1450,6 +1517,7 @@ var PROP_KEYS = /* @__PURE__ */ new Set([
1450
1517
  "visibleChars",
1451
1518
  "text",
1452
1519
  "volume",
1520
+ "time",
1453
1521
  // camera
1454
1522
  "lookAtX",
1455
1523
  "lookAtY",
@@ -1526,12 +1594,54 @@ function lintText(spec, warn = defaultWarn) {
1526
1594
  warn(`pixi-effects: ${describeLayer(spec)}: \`value\` is set or animated but the text has no {value} placeholder, so nothing shows it. Use text: '{value}' (or e.g. '{value} users').`);
1527
1595
  }
1528
1596
  }
1597
+ var REMAPPABLE = /* @__PURE__ */ new Set(["video", "audio", "composition"]);
1598
+ var REMAP_NAMES = /* @__PURE__ */ new Set(["time", "timeRemap", "reverse"]);
1599
+ var TIME_BAGS = ["set", "to", "from"];
1600
+ function lintRemap(spec, warn) {
1601
+ const s = spec;
1602
+ const who = describeLayer(spec);
1603
+ const kfs = s.keyframes ?? [];
1604
+ const bags = [s.initial, ...kfs.flatMap((kf) => TIME_BAGS.map((b) => kf[b]))];
1605
+ const timeInInitial = !!s.initial && s.initial.time !== void 0;
1606
+ const timeInKeyframes = kfs.some((kf) => TIME_BAGS.some((b) => kf[b] && kf[b].time !== void 0));
1607
+ const timed = timeInInitial || timeInKeyframes;
1608
+ if (bags.some((bag) => bag && bag.speed !== void 0)) {
1609
+ warn(`pixi-effects: ${who}: speed is a fixed setting, not an animatable property: write it on the layer (speed: 2); to change the speed over time, animate time (keyframes: [{ at: 0, from: { time: 0 }, to: { time: 4 }, duration: 2, ease: 'power2.in' }])`);
1610
+ }
1611
+ if (!REMAPPABLE.has(spec.type)) {
1612
+ if (timed) warn(`pixi-effects: ${who}: "time" only works on video, audio and composition layers; put this layer in a composition and remap that (it is ignored here)`);
1613
+ return;
1614
+ }
1615
+ if (s.reverse !== void 0) warn(`pixi-effects: ${who}: "reverse" is not a key: write speed: -1 to play backward`);
1616
+ if (s.timeRemap !== void 0) warn(`pixi-effects: ${who}: "timeRemap" is not a key: animate time in keyframes (from / to / set), or write speed`);
1617
+ if (s.time !== void 0) warn(`pixi-effects: ${who}: "time" belongs in initial or keyframes (initial: { time: 2 }), not on the layer`);
1618
+ if (spec.type === "audio" && (s.sfx !== void 0 || s.music !== void 0) && (s.speed !== void 0 || timed)) {
1619
+ warn(`pixi-effects: ${who}: speed and time do not apply to a synthesised sound (sfx, music); change an sfx's pitch with pitch (semitones), or put it in a composition and remap that`);
1620
+ return;
1621
+ }
1622
+ if (s.speed !== void 0 && (typeof s.speed !== "number" || !Number.isFinite(s.speed) || s.speed === 0)) {
1623
+ warn(`pixi-effects: ${who}: speed ${JSON.stringify(s.speed)} cannot be used: write a number other than 0 (negative plays backward); to hold a moment, key time to the same value twice`);
1624
+ }
1625
+ if (s.speed !== void 0 && timeInKeyframes) {
1626
+ warn(`pixi-effects: ${who}: speed and keyframed time are both set; time wins and speed is ignored. Use one of them`);
1627
+ }
1628
+ if (timeInInitial && typeof s.initial.time !== "number") {
1629
+ warn(`pixi-effects: ${who}: initial.time must be a number of seconds, got ${JSON.stringify(s.initial.time)}`);
1630
+ }
1631
+ kfs.forEach((kf, i) => {
1632
+ for (const b of TIME_BAGS) {
1633
+ const v = kf[b]?.time;
1634
+ if (v !== void 0 && typeof v !== "number" && typeof v !== "string") warn(`pixi-effects: ${who}: keyframes[${i}].${b}.time must be a number of seconds, got ${JSON.stringify(v)}`);
1635
+ }
1636
+ });
1637
+ }
1529
1638
  function lintKeys(spec, warn = defaultWarn) {
1530
1639
  const who = describeLayer(spec);
1531
1640
  const valid = layerKeys(spec);
1532
1641
  if (valid) {
1533
1642
  for (const key of Object.keys(spec)) {
1534
1643
  if (valid.includes(key) || spec[key] === void 0) continue;
1644
+ if (REMAPPABLE.has(spec.type) && REMAP_NAMES.has(key)) continue;
1535
1645
  const guess = suggestName(key, valid.filter((k) => k !== "type"));
1536
1646
  const owners = kindsWithKey(key);
1537
1647
  const belongs = owners.length ? ` (it belongs to ${owners.slice(0, 3).join(", ")})` : "";
@@ -1539,12 +1649,13 @@ function lintKeys(spec, warn = defaultWarn) {
1539
1649
  warn(`pixi-effects: ${who}: "${key}" is not a ${kindName(spec)} key${hint}. Valid keys: ${valid.join(", ")}`);
1540
1650
  }
1541
1651
  }
1652
+ lintRemap(spec, warn);
1542
1653
  const said = /* @__PURE__ */ new Set();
1543
1654
  const bags = [spec.initial, ...(spec.keyframes ?? []).flatMap((kf) => [kf.set, kf.to, kf.from])];
1544
1655
  for (const bag of bags) {
1545
1656
  for (const key of Object.keys(bag ?? {})) {
1546
1657
  if (bag[key] === void 0) continue;
1547
- if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key)) continue;
1658
+ if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key) || key === "speed") continue;
1548
1659
  said.add(key);
1549
1660
  const guess = suggestName(key, [...PROP_KEYS]);
1550
1661
  warn(`pixi-effects: ${who}: "${key}" is not an animatable property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (in initial, set, to or from). See the cheatsheet for the properties of this kind of layer`);
@@ -1558,6 +1669,116 @@ function lintKeys(spec, warn = defaultWarn) {
1558
1669
  }
1559
1670
  }
1560
1671
  }
1672
+ var BAGS = ["set", "to", "from"];
1673
+ function timeRemapOf(spec, prop = "time") {
1674
+ const hasInitial = !!spec.initial && "time" in spec.initial;
1675
+ const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
1676
+ const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
1677
+ if (!hasInitial && !hasKf && speed === void 0) return null;
1678
+ const timeKfs = [];
1679
+ const restKfs = [];
1680
+ for (const kf of spec.keyframes ?? []) {
1681
+ const t = { ...kf }, r = { ...kf };
1682
+ let touched = false, left = false;
1683
+ for (const b of BAGS) {
1684
+ const bag = kf[b];
1685
+ if (!bag) {
1686
+ delete t[b];
1687
+ delete r[b];
1688
+ continue;
1689
+ }
1690
+ const { time, ...others } = bag;
1691
+ if ("time" in bag) {
1692
+ touched = true;
1693
+ t[b] = { [prop]: time };
1694
+ } else delete t[b];
1695
+ if (Object.keys(others).length) {
1696
+ r[b] = others;
1697
+ left = true;
1698
+ } else delete r[b];
1699
+ }
1700
+ if (touched) timeKfs.push(t);
1701
+ if (!touched || left) restKfs.push(r);
1702
+ }
1703
+ let restInitial = spec.initial;
1704
+ let start;
1705
+ if (hasInitial) {
1706
+ const { time, ...others } = spec.initial;
1707
+ start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
1708
+ restInitial = others;
1709
+ }
1710
+ return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
1711
+ }
1712
+ function resolveFromEnd(remap, length, prop) {
1713
+ const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
1714
+ const timeKfs = remap.timeKfs.map((kf) => {
1715
+ const out = { ...kf };
1716
+ for (const b of BAGS) {
1717
+ const bag = kf[b];
1718
+ if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
1719
+ }
1720
+ return out;
1721
+ });
1722
+ return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
1723
+ }
1724
+ function remapOf(spec, prop, length) {
1725
+ const r = timeRemapOf(spec, prop);
1726
+ return r && length !== null ? resolveFromEnd(r, length, prop) : r;
1727
+ }
1728
+ function contentLength(remap, duration) {
1729
+ let m = duration;
1730
+ if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
1731
+ if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
1732
+ for (const kf of remap.timeKfs) {
1733
+ for (const b of BAGS) {
1734
+ const bag = kf[b];
1735
+ const v = bag && Object.values(bag)[0];
1736
+ if (typeof v === "number") m = Math.max(m, v);
1737
+ }
1738
+ }
1739
+ return m;
1740
+ }
1741
+ function bindClock(timeline, clock, prop, remap, duration, at, scope) {
1742
+ if (remap.timeKfs.length) {
1743
+ if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
1744
+ applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
1745
+ return;
1746
+ }
1747
+ const s = remap.speed ?? 1;
1748
+ const from = remap.start ?? (s < 0 ? -s * duration : 0);
1749
+ timeline.fromTo(clock, { [prop]: from }, { [prop]: from + s * duration, duration, ease: "none" }, at);
1750
+ }
1751
+ function clockTable(remap, prop, duration, at, scope, to) {
1752
+ const probe = gsap.timeline({ paused: true });
1753
+ const clock = { [prop]: remap.start ?? 0 };
1754
+ bindClock(probe, clock, prop, remap, duration, at, scope);
1755
+ probe.time(probe.duration());
1756
+ probe.time(0);
1757
+ const h = 1 / 1e3;
1758
+ const n = Math.max(2, Math.ceil(to / h) + 2);
1759
+ const tab = new Float64Array(n);
1760
+ let min = Infinity, max = -Infinity;
1761
+ for (let k = 0; k < n; k++) {
1762
+ probe.time(k * h);
1763
+ const v = clock[prop];
1764
+ tab[k] = v;
1765
+ const t = k * h;
1766
+ if (t >= at && t <= at + duration) {
1767
+ if (v < min) min = v;
1768
+ if (v > max) max = v;
1769
+ }
1770
+ }
1771
+ probe.kill();
1772
+ return {
1773
+ at(t) {
1774
+ const x = Math.min(Math.max(t / h, 0), n - 1.000001);
1775
+ const k = Math.floor(x), f = x - k;
1776
+ return tab[k] * (1 - f) + tab[k + 1] * f;
1777
+ },
1778
+ min: Number.isFinite(min) ? min : 0,
1779
+ max: Number.isFinite(max) ? max : 0
1780
+ };
1781
+ }
1561
1782
 
1562
1783
  // src/space/math.ts
1563
1784
  var DEG = Math.PI / 180;
@@ -2458,6 +2679,7 @@ var AudioSequence = class extends Sequence {
2458
2679
  _audioBuffer = null;
2459
2680
  _synth = null;
2460
2681
  _source = "";
2682
+ _remap = null;
2461
2683
  async build() {
2462
2684
  this.target = null;
2463
2685
  if (this.spec.music !== void 0) return this._buildMusic(this.spec);
@@ -2466,13 +2688,16 @@ var AudioSequence = class extends Sequence {
2466
2688
  this._source = `asset "${spec.asset}"`;
2467
2689
  const data = await Assets.get(spec.asset);
2468
2690
  this._audioBuffer = data.audioBuffer;
2691
+ this._remap = remapOf(spec, "time", data.duration ?? null);
2692
+ const constant = this._remap && this._remap.timeKfs.length === 0 ? Math.abs(this._remap.speed ?? 1) : 1;
2469
2693
  if (this.duration === void 0) {
2470
2694
  const rest = Math.max(0, (this.parent?.duration ?? this.root.duration) - this.at);
2471
- this.duration = spec.duration ?? (spec.loop ? rest : data.duration) ?? this.root.duration;
2695
+ this.duration = spec.duration ?? (spec.loop ? rest : data.duration !== void 0 ? data.duration / constant : void 0) ?? this.root.duration;
2472
2696
  }
2473
- if (!spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
2697
+ const playable = this._remap && this._remap.timeKfs.length > 0 ? Infinity : (data.duration ?? Infinity) / constant;
2698
+ if (!spec.loop && Number.isFinite(playable) && this.duration > playable + 0.05) {
2474
2699
  console.warn(
2475
- `pixi-effects: ${describeLayer(spec)}: audio asset "${spec.asset}" is ${data.duration.toFixed(1)}s but the layer lasts ${this.duration.toFixed(1)}s, so it goes silent after ${data.duration.toFixed(1)}s. Add loop: true, or shorten the layer's duration.`
2700
+ `pixi-effects: ${describeLayer(spec)}: audio asset "${spec.asset}" plays for ${playable.toFixed(1)}s (${data.duration.toFixed(1)}s at speed ${constant}) but the layer lasts ${this.duration.toFixed(1)}s, so it goes silent after ${playable.toFixed(1)}s. Add loop: true, or shorten the layer's duration.`
2476
2701
  );
2477
2702
  }
2478
2703
  }
@@ -2481,7 +2706,7 @@ var AudioSequence = class extends Sequence {
2481
2706
  const raw = spec;
2482
2707
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and sfx \u2014 the sfx plays; remove one`);
2483
2708
  if (raw.loop) console.warn(`pixi-effects: ${who}: loop has no effect on an sfx \u2014 to repeat it, add one audio layer per hit (a JS loop)`);
2484
- const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-S37DDC66.js');
2709
+ const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-U3GM2MED.js');
2485
2710
  const sfx = resolveSfx(spec.sfx, spec.duration, who);
2486
2711
  if (!sfx) return;
2487
2712
  this.duration = sfx.length;
@@ -2500,7 +2725,7 @@ var AudioSequence = class extends Sequence {
2500
2725
  const raw = spec;
2501
2726
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
2502
2727
  if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
2503
- const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-4HDFULNR.js');
2728
+ const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-CFEC7HKL.js');
2504
2729
  const music = resolveMusic(spec.music, who);
2505
2730
  if (!music) return;
2506
2731
  const natural = musicLength(music);
@@ -2526,7 +2751,9 @@ var AudioSequence = class extends Sequence {
2526
2751
  const initialVolume = this.spec.volume ?? this.spec.initial?.volume ?? 1;
2527
2752
  const t0 = baseTime + this.at;
2528
2753
  const dur = this.duration;
2754
+ const sourceMap = this._remap ? clockTable(this._remap, "time", dur, t0, this.scope(), t0 + dur).at : void 0;
2529
2755
  out.push({
2756
+ ...sourceMap ? { sourceMap } : {},
2530
2757
  ...this._audioBuffer ? { buffer: this._audioBuffer } : { synth: this._synth },
2531
2758
  layer: describeLayer(this.spec),
2532
2759
  source: this._source,
@@ -2585,7 +2812,7 @@ var FrameCache = class {
2585
2812
  this.capacity = options.capacity ?? 30;
2586
2813
  }
2587
2814
  _key(time) {
2588
- return Math.round(time * 1e3);
2815
+ return Math.round(time * 1e6);
2589
2816
  }
2590
2817
  async getFrameAt(time) {
2591
2818
  const key = this._key(time);
@@ -2650,6 +2877,15 @@ var FrameCache = class {
2650
2877
  }
2651
2878
  };
2652
2879
 
2880
+ // src/core/sourceTime.ts
2881
+ var FRAME_EPS = 5e-5;
2882
+ function sourceLookup(v, sourceDuration, loop) {
2883
+ if (!(sourceDuration > 0)) return Math.max(0, v);
2884
+ const w = v + FRAME_EPS;
2885
+ if (loop) return (w % sourceDuration + sourceDuration) % sourceDuration;
2886
+ return Math.min(Math.max(w, 0), sourceDuration);
2887
+ }
2888
+
2653
2889
  // src/sequences/Video.ts
2654
2890
  var VideoSequence = class extends Sequence {
2655
2891
  _sourceDuration = 0;
@@ -2658,9 +2894,15 @@ var VideoSequence = class extends Sequence {
2658
2894
  _canvas = null;
2659
2895
  _ctx = null;
2660
2896
  _drawSeq = 0;
2897
+ /** The playhead value (seconds in the file) the timeline last wrote: already remapped, and local inside a remapped composition. */
2898
+ _cur = 0;
2899
+ _remap = null;
2900
+ /** Movie asks this layer to draw the frame its own timeline is at, not the one for a time it computed. */
2901
+ selfTimed = true;
2661
2902
  async build() {
2662
2903
  const data = await Assets.get(this.spec.asset);
2663
2904
  this._sourceDuration = data.duration;
2905
+ this._remap = remapOf(this.spec, "currentTime", this._sourceDuration);
2664
2906
  this._audioBuffer = this.spec.audio !== false ? data.audioBuffer : null;
2665
2907
  this._cache = new FrameCache(data.sink, { capacity: 30 });
2666
2908
  const probeFrame = await this._cache.getFrameAt(0);
@@ -2679,7 +2921,8 @@ var VideoSequence = class extends Sequence {
2679
2921
  this.intrinsicWidth = w;
2680
2922
  this.intrinsicHeight = h;
2681
2923
  if (this.duration === void 0) {
2682
- this.duration = this.spec.duration ?? data.duration ?? this.root.duration;
2924
+ const constant = this._remap && this._remap.timeKfs.length === 0 ? Math.abs(this._remap.speed ?? 1) : 1;
2925
+ this.duration = this.spec.duration ?? (data.duration ?? this.root.duration * constant) / constant;
2683
2926
  }
2684
2927
  let currentTime = 0;
2685
2928
  const seq = this;
@@ -2689,8 +2932,9 @@ var VideoSequence = class extends Sequence {
2689
2932
  },
2690
2933
  set(v) {
2691
2934
  currentTime = v;
2935
+ seq._cur = v;
2692
2936
  const mySeq = ++seq._drawSeq;
2693
- const lookup = seq.spec.loop && seq._sourceDuration > 0 ? v % seq._sourceDuration : v;
2937
+ const lookup = sourceLookup(v, seq._sourceDuration, !!seq.spec.loop);
2694
2938
  seq._cache.getFrameAt(lookup).then((frame) => {
2695
2939
  if (mySeq !== seq._drawSeq) return;
2696
2940
  if (!frame || !seq._ctx) return;
@@ -2703,21 +2947,33 @@ var VideoSequence = class extends Sequence {
2703
2947
  });
2704
2948
  this.buildFilters();
2705
2949
  }
2950
+ displayProps() {
2951
+ const r = this._remap;
2952
+ return r ? { initial: r.restInitial, keyframes: r.restKfs } : super.displayProps();
2953
+ }
2706
2954
  bindTimeline(timeline, offset = 0) {
2707
2955
  super.bindTimeline(timeline, offset);
2956
+ const at = offset + this.at;
2957
+ if (this._remap) {
2958
+ bindClock(timeline, this.target, "currentTime", this._remap, this.duration, at, this.scope());
2959
+ return;
2960
+ }
2708
2961
  const playDuration = this.spec.loop ? this.duration : Math.min(this.duration, this._sourceDuration);
2709
2962
  timeline.fromTo(
2710
2963
  this.target,
2711
2964
  { currentTime: 0 },
2712
2965
  { currentTime: playDuration, duration: playDuration, ease: "none" },
2713
- offset + this.at
2966
+ at
2714
2967
  // offset = start of the enclosing composition(s); dropping it misplaced videos inside nested compositions
2715
2968
  );
2716
2969
  }
2717
2970
  collectAudio(out, baseTime) {
2718
2971
  if (!this._audioBuffer) return;
2719
2972
  const initialVolume = this.spec.volume ?? 1;
2973
+ const t0 = baseTime + this.at;
2974
+ const sourceMap = this._remap ? clockTable(this._remap, "currentTime", this.duration, t0, this.scope(), t0 + this.duration).at : void 0;
2720
2975
  out.push({
2976
+ ...sourceMap ? { sourceMap } : {},
2721
2977
  buffer: this._audioBuffer,
2722
2978
  layer: describeLayer(this.spec),
2723
2979
  source: `video "${this.spec.asset}"`,
@@ -2728,9 +2984,12 @@ var VideoSequence = class extends Sequence {
2728
2984
  volumeKeyframes: []
2729
2985
  });
2730
2986
  }
2731
- async awaitFrameAt(time) {
2987
+ /** Draw the frame the playhead is at NOW (`_cur`). The argument (a time Movie worked out for layers that need it) is not used. */
2988
+ async awaitFrameAt(_time) {
2989
+ const time = this._cur;
2990
+ if (!this.keepHidden && !this.target?.renderable) return;
2732
2991
  const mySeq = ++this._drawSeq;
2733
- const lookup = this.spec.loop && this._sourceDuration > 0 ? time % this._sourceDuration : time;
2992
+ const lookup = sourceLookup(time, this._sourceDuration, !!this.spec.loop);
2734
2993
  let frame = null;
2735
2994
  try {
2736
2995
  frame = await this._cache.getFrameAt(lookup);
@@ -3460,7 +3719,7 @@ function numOrZero(v, scope) {
3460
3719
  var _CompositionSequence = null;
3461
3720
  async function getCompositionSequence() {
3462
3721
  if (!_CompositionSequence) {
3463
- _CompositionSequence = (await import('./Composition-QBKEEJBV.js')).CompositionSequence;
3722
+ _CompositionSequence = (await import('./Composition-6PA4C4TV.js')).CompositionSequence;
3464
3723
  }
3465
3724
  return _CompositionSequence;
3466
3725
  }
@@ -3498,6 +3757,6 @@ async function buildSequenceTree(specs, parent, root) {
3498
3757
  return out;
3499
3758
  }
3500
3759
 
3501
- export { CameraSequence, DEFAULT_FOV, DEG, NEAR, NullSequence, Sequence, buildScope, buildSequenceTree, cameraBasis, checkEase, collectPropKeys, describeLayer, ensureFilterLibrary, evaluateExpr, flattenSvgPath, homeCamera, homeDistance, lintKeys, lintText, lintTiming, noise, projectLayer, rand, random, registerSequenceType, resolveAt, summarizeWarnings };
3502
- //# sourceMappingURL=chunk-YRXTKOV5.js.map
3503
- //# sourceMappingURL=chunk-YRXTKOV5.js.map
3760
+ export { CameraSequence, DEFAULT_FOV, DEG, NEAR, NullSequence, Sequence, bindClock, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, collectPropKeys, contentLength, describeLayer, ensureFilterLibrary, evaluateExpr, flattenSvgPath, homeCamera, homeDistance, lintKeys, lintText, lintTiming, noise, projectLayer, rand, random, registerSequenceType, remapOf, resolveAt, summarizeWarnings, timeRemapOf };
3761
+ //# sourceMappingURL=chunk-DQHXVNOI.js.map
3762
+ //# sourceMappingURL=chunk-DQHXVNOI.js.map