pixi-effects 0.47.1 → 0.47.3

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 +23 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/dist/Composition-6TXRN65C.js +7 -0
  5. package/dist/{Composition-ME3L3EFT.js.map → Composition-6TXRN65C.js.map} +1 -1
  6. package/dist/Composition-IC274CKO.cjs +16 -0
  7. package/dist/{Composition-AZEATHJB.cjs.map → Composition-IC274CKO.cjs.map} +1 -1
  8. package/dist/{Spans-YV7K4X5I.js → Spans-3T6ZEEN4.js} +6 -6
  9. package/dist/{Spans-YV7K4X5I.js.map → Spans-3T6ZEEN4.js.map} +1 -1
  10. package/dist/{Spans-LTKEUHDN.cjs → Spans-M5KMHT6K.cjs} +11 -11
  11. package/dist/{Spans-LTKEUHDN.cjs.map → Spans-M5KMHT6K.cjs.map} +1 -1
  12. package/dist/{blendModes-56NHK2MP.js → blendModes-S72MBYKY.js} +4 -4
  13. package/dist/{blendModes-56NHK2MP.js.map → blendModes-S72MBYKY.js.map} +1 -1
  14. package/dist/{blendModes-ONOPMMAB.cjs → blendModes-Y56QAMC4.cjs} +5 -5
  15. package/dist/{blendModes-ONOPMMAB.cjs.map → blendModes-Y56QAMC4.cjs.map} +1 -1
  16. package/dist/{chunk-4NOTVQ3I.cjs → chunk-33OVRSXF.cjs} +29 -29
  17. package/dist/{chunk-4NOTVQ3I.cjs.map → chunk-33OVRSXF.cjs.map} +1 -1
  18. package/dist/{chunk-OVTTCX2E.js → chunk-6LA7W362.js} +136 -64
  19. package/dist/chunk-6LA7W362.js.map +1 -0
  20. package/dist/{chunk-7IDEWQPH.js → chunk-G4PXCV2B.js} +12 -8
  21. package/dist/chunk-G4PXCV2B.js.map +1 -0
  22. package/dist/{chunk-BFERTUXK.cjs → chunk-GDY56V74.cjs} +92 -92
  23. package/dist/{chunk-BFERTUXK.cjs.map → chunk-GDY56V74.cjs.map} +1 -1
  24. package/dist/{chunk-6MHJKCXO.js → chunk-HGKXEPDJ.js} +5 -5
  25. package/dist/{chunk-6MHJKCXO.js.map → chunk-HGKXEPDJ.js.map} +1 -1
  26. package/dist/{chunk-QMVK55LC.js → chunk-HX73PCVU.js} +5 -5
  27. package/dist/{chunk-QMVK55LC.js.map → chunk-HX73PCVU.js.map} +1 -1
  28. package/dist/{chunk-NVKHUAHU.js → chunk-JVO2W77G.js} +5 -5
  29. package/dist/chunk-JVO2W77G.js.map +1 -0
  30. package/dist/{chunk-UTDELFOU.js → chunk-PC7DFDCV.js} +4 -4
  31. package/dist/{chunk-UTDELFOU.js.map → chunk-PC7DFDCV.js.map} +1 -1
  32. package/dist/{chunk-53DXJEO7.cjs → chunk-RU4N3B5B.cjs} +5 -5
  33. package/dist/{chunk-53DXJEO7.cjs.map → chunk-RU4N3B5B.cjs.map} +1 -1
  34. package/dist/{chunk-YXIEPTFG.cjs → chunk-VTGJRKW4.cjs} +182 -110
  35. package/dist/chunk-VTGJRKW4.cjs.map +1 -0
  36. package/dist/{chunk-NEMWRJQS.cjs → chunk-YSTOH5PC.cjs} +5 -5
  37. package/dist/chunk-YSTOH5PC.cjs.map +1 -0
  38. package/dist/{chunk-SMHTOWXG.cjs → chunk-ZAHCGFGC.cjs} +12 -8
  39. package/dist/chunk-ZAHCGFGC.cjs.map +1 -0
  40. package/dist/index.cjs +37 -37
  41. package/dist/index.d.cts +4 -4
  42. package/dist/index.d.ts +4 -4
  43. package/dist/index.js +6 -6
  44. package/dist/loader.css +1 -1
  45. package/dist/present.cjs +6 -6
  46. package/dist/present.js +6 -6
  47. package/dist/sound.cjs +2 -2
  48. package/dist/sound.js +1 -1
  49. package/dist/three.cjs +93 -26
  50. package/dist/three.cjs.map +1 -1
  51. package/dist/three.d.cts +19 -4
  52. package/dist/three.d.ts +19 -4
  53. package/dist/three.js +89 -22
  54. package/dist/three.js.map +1 -1
  55. package/docs/dsl.md +9 -4
  56. package/llms-full.txt +12 -7
  57. package/package.json +1 -1
  58. package/skills/pixi-effects/SKILL.md +1 -1
  59. package/skills/pixi-effects/reference/cheatsheet.md +1 -1
  60. package/skills/pixi-effects/reference/recipes.md +1 -1
  61. package/skills/pixi-effects/template.html +6 -6
  62. package/dist/Composition-AZEATHJB.cjs +0 -16
  63. package/dist/Composition-ME3L3EFT.js +0 -7
  64. package/dist/chunk-7IDEWQPH.js.map +0 -1
  65. package/dist/chunk-NEMWRJQS.cjs.map +0 -1
  66. package/dist/chunk-NVKHUAHU.js.map +0 -1
  67. package/dist/chunk-OVTTCX2E.js.map +0 -1
  68. package/dist/chunk-SMHTOWXG.cjs.map +0 -1
  69. package/dist/chunk-YXIEPTFG.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
- import { CompositionSequence, maskSourceOf } from './chunk-QMVK55LC.js';
2
- import { EXPRESSION_NAMES, createTimeline, lengthen, ensureFilterLibrary, checkEase, parseEase, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, trimPolylines, rand, DEG, timeRemapOf, contentLength, resolveAt, parseSpring, springDuration, spansMeta } from './chunk-6MHJKCXO.js';
3
- import { analyzeAudio, fft, resampleThrough, measureLoudness, playSpan } from './chunk-7IDEWQPH.js';
4
- import { usesAdvancedBlend, enableAdvancedBlend } from './chunk-UTDELFOU.js';
5
- import { describeLayer } from './chunk-NVKHUAHU.js';
1
+ import { CompositionSequence, maskSourceOf } from './chunk-HX73PCVU.js';
2
+ import { EXPRESSION_NAMES, createTimeline, lengthen, ensureFilterLibrary, checkEase, parseEase, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, trimPolylines, rand, DEG, timeRemapOf, contentLength, resolveAt, parseSpring, springDuration, spansMeta } from './chunk-HGKXEPDJ.js';
3
+ import { analyzeAudio, fft, resampleThrough, playSpan, measureLoudness } from './chunk-G4PXCV2B.js';
4
+ import { usesAdvancedBlend, enableAdvancedBlend } from './chunk-PC7DFDCV.js';
5
+ import { describeLayer } from './chunk-JVO2W77G.js';
6
6
  import { warnUnknownOptions, suggestName } from './chunk-BXOCEZ4U.js';
7
7
  import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, Texture, UniformGroup, GpuProgram, GlProgram, defaultFilterVert, Text, CanvasTextGenerator, CanvasPool } from 'pixi.js';
8
8
  import { Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, OggOutputFormat, QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat, canEncodeVideo, getFirstEncodableAudioCodec } from 'mediabunny';
@@ -144,81 +144,124 @@ var LOUDNESS_CEILING_DB = -1;
144
144
  var MAX_LIMIT_DB = 10;
145
145
  function limitInto(source, out, gain, ceil, sampleRate) {
146
146
  const n = source[0]?.length ?? 0;
147
- const req = new Float32Array(n);
148
- for (let i = 0; i < n; i++) {
147
+ const L = Math.max(1, Math.round(3e-3 * sampleRate));
148
+ const release = 1 - Math.exp(-1 / (0.08 * sampleRate));
149
+ const reqAt = (i) => {
149
150
  let p = 0;
150
151
  for (const c of source) {
151
152
  const a = Math.abs(c[i]) * gain;
152
153
  if (a > p) p = a;
153
154
  }
154
- req[i] = p > ceil ? ceil / p : 1;
155
- }
156
- const L = Math.max(1, Math.round(3e-3 * sampleRate));
157
- const mn = new Float32Array(n), dq = new Int32Array(n);
158
- let head = 0, tail = 0;
159
- for (let i = n - 1; i >= 0; i--) {
160
- while (tail > head && req[dq[tail - 1]] >= req[i]) tail--;
161
- dq[tail++] = i;
162
- while (dq[head] > i + L) head++;
163
- mn[i] = req[dq[head]];
164
- }
165
- const release = 1 - Math.exp(-1 / (0.08 * sampleRate));
166
- let run = 0, prev = 1, lowest = 1, lowestAt = 0;
167
- const gains = new Float32Array(n);
155
+ return p > ceil ? ceil / p : 1;
156
+ };
157
+ const size = L + 2, dqIdx = new Int32Array(size), dqVal = new Float32Array(size);
158
+ let head = 0, count = 0, added = -1;
159
+ const ring = new Float32Array(L + 1);
160
+ let run = 0;
161
+ let prev = 1, lowest = 1, lowestAt = 0;
168
162
  for (let i = 0; i < n; i++) {
169
- run += mn[i];
170
- if (i > L) run -= mn[i - L - 1];
163
+ for (; added < Math.min(n - 1, i + L); ) {
164
+ added++;
165
+ const v = reqAt(added);
166
+ while (count > 0 && dqVal[(head + count - 1) % size] >= v) count--;
167
+ const at = (head + count) % size;
168
+ dqIdx[at] = added;
169
+ dqVal[at] = v;
170
+ count++;
171
+ }
172
+ while (dqIdx[head] < i) {
173
+ head = (head + 1) % size;
174
+ count--;
175
+ }
176
+ const mn = dqVal[head];
177
+ const slot = i % (L + 1);
178
+ if (i > L) run -= ring[slot];
179
+ ring[slot] = mn;
180
+ run += mn;
171
181
  const avg = run / Math.min(i + 1, L + 1);
172
182
  const g = Math.min(avg, prev + (1 - prev) * release);
173
- gains[i] = g;
174
183
  prev = g;
175
184
  if (g < lowest) {
176
185
  lowest = g;
177
186
  lowestAt = i;
178
187
  }
179
- }
180
- for (let ch = 0; ch < source.length; ch++) {
181
- const s = source[ch], o = out[ch];
182
- for (let i = 0; i < n; i++) {
183
- const v = s[i] * gain * gains[i];
184
- o[i] = v > ceil ? ceil : v < -ceil ? -ceil : v;
188
+ for (let ch = 0; ch < source.length; ch++) {
189
+ const v = source[ch][i] * gain * g;
190
+ out[ch][i] = v > ceil ? ceil : v < -ceil ? -ceil : v;
185
191
  }
186
192
  }
187
193
  return { reductionDb: lowest < 1 ? -20 * Math.log10(lowest) : 0, at: lowestAt / sampleRate };
188
194
  }
189
- function normalizeMix(buffer, target) {
195
+ function cleanMix(buffer, audios) {
196
+ let count = 0, first = -1;
197
+ for (let ch = 0; ch < buffer.numberOfChannels; ch++) {
198
+ const d = buffer.getChannelData(ch);
199
+ for (let i = 0; i < d.length; i++) if (!Number.isFinite(d[i])) {
200
+ d[i] = 0;
201
+ count++;
202
+ if (first < 0 || i < first) first = i;
203
+ }
204
+ }
205
+ const firstAt = first < 0 ? 0 : first / buffer.sampleRate;
206
+ if (count > 0 && audios) {
207
+ const playing = audios.map((a) => ({ a, ...playSpan(a, buffer.duration) })).filter((s) => s.start <= firstAt && firstAt < s.end).map((s) => s.a.layer ?? "an audio layer");
208
+ console.warn(`pixi-effects: ${count} sample(s) of the audio mix were not numbers (NaN or Infinity), first at ${firstAt.toFixed(2)}s, and were made silent. Playing there: ${[...new Set(playing)].join(", ") || "nothing that is listed"}. A sound that produces these (a custom synth, a division by zero in a volume) is broken.`);
209
+ }
210
+ return { count, firstAt };
211
+ }
212
+ async function normalizeMix(buffer, target, between) {
190
213
  const chans = [];
191
214
  for (let ch = 0; ch < buffer.numberOfChannels; ch++) chans.push(buffer.getChannelData(ch));
192
215
  const sr = buffer.sampleRate;
216
+ const pause = async () => {
217
+ if (between) await between();
218
+ };
193
219
  const m = measureLoudness(chans, sr, { truePeak: false });
194
220
  if (m.integratedLufs === null) return { target, gainDb: 0, reachedLufs: null, limited: false };
195
- const original = chans.map((c) => Float32Array.from(c));
221
+ await pause();
196
222
  let gainDb = target - m.integratedLufs;
197
223
  let ceilDb = LOUDNESS_CEILING_DB;
198
- let applied = { reductionDb: 0, at: 0 }, measured = m;
199
- for (let round2 = 0; round2 < 8; round2++) {
200
- const cap = MAX_LIMIT_DB + ceilDb - m.samplePeakDb;
224
+ let applied = { reductionDb: 0, at: 0 };
225
+ let final = m, original = null, cap = Infinity;
226
+ if (gainDb + m.samplePeakDb <= ceilDb) {
227
+ const gain = 10 ** (gainDb / 20);
228
+ for (const d of chans) for (let i = 0; i < d.length; i++) d[i] = d[i] * gain;
229
+ await pause();
230
+ final = measureLoudness(chans, sr);
231
+ } else {
232
+ original = chans.map((c) => Float32Array.from(c));
233
+ cap = MAX_LIMIT_DB + ceilDb - m.samplePeakDb;
201
234
  if (gainDb > cap) gainDb = cap;
202
- applied = limitInto(original, chans, 10 ** (gainDb / 20), 10 ** (ceilDb / 20), sr);
203
- measured = measureLoudness(chans, sr);
204
- const overTp = measured.truePeakDb - LOUDNESS_CEILING_DB;
205
- if (overTp > 0.02) {
206
- ceilDb -= overTp + 0.02;
207
- continue;
235
+ for (let round2 = 0; round2 < 3; round2++) {
236
+ cap = MAX_LIMIT_DB + ceilDb - m.samplePeakDb;
237
+ if (gainDb > cap) gainDb = cap;
238
+ applied = limitInto(original, chans, 10 ** (gainDb / 20), 10 ** (ceilDb / 20), sr);
239
+ await pause();
240
+ final = measureLoudness(chans, sr);
241
+ await pause();
242
+ const overTp = final.truePeakDb - LOUDNESS_CEILING_DB;
243
+ const deficit = target - final.integratedLufs;
244
+ if (round2 === 2 || overTp <= 0.02 && Math.abs(deficit) <= 0.15) break;
245
+ if (overTp > 0.02) ceilDb -= overTp + 0.02;
246
+ const next = Math.min(gainDb + deficit, MAX_LIMIT_DB + ceilDb - m.samplePeakDb);
247
+ if (overTp <= 0.02 && Math.abs(next - gainDb) < 0.05) break;
248
+ gainDb = next;
208
249
  }
209
- const deficit = target - measured.integratedLufs;
210
- if (Math.abs(deficit) <= 0.15) break;
211
- const next = Math.min(gainDb + deficit, cap);
212
- if (Math.abs(next - gainDb) < 0.05) break;
213
- gainDb = next;
214
250
  }
215
- const limited = measured.integratedLufs < target - 0.15;
251
+ let trimDb = 0;
252
+ if (final.truePeakDb > LOUDNESS_CEILING_DB) {
253
+ trimDb = final.truePeakDb - LOUDNESS_CEILING_DB + 0.01;
254
+ const k = 10 ** (-trimDb / 20);
255
+ for (const d of chans) for (let i = 0; i < d.length; i++) d[i] = d[i] * k;
256
+ final = { ...final, integratedLufs: final.integratedLufs === null ? null : final.integratedLufs - trimDb };
257
+ }
258
+ const reached = final.integratedLufs === null ? null : Math.round(final.integratedLufs * 10) / 10;
216
259
  const reduction = Math.round(applied.reductionDb * 10) / 10;
217
260
  return {
218
261
  target,
219
- gainDb: Math.round(gainDb * 10) / 10,
220
- reachedLufs: measured.integratedLufs,
221
- limited,
262
+ gainDb: Math.round((gainDb - trimDb) * 10) / 10,
263
+ reachedLufs: reached,
264
+ limited: reached !== null && reached < target - 0.15,
222
265
  ...reduction >= 0.1 ? { limiterDb: reduction, limiterAt: Math.round(applied.at * 100) / 100 } : {}
223
266
  };
224
267
  }
@@ -1880,6 +1923,22 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1880
1923
  return true;
1881
1924
  };
1882
1925
  const inside = (b, p) => p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
1926
+ const boxOf = (d, masked) => {
1927
+ const v = d.bounds;
1928
+ if (masked && v && Number.isFinite(v.minX) && Number.isFinite(v.maxX)) return { x: v.minX, y: v.minY, width: v.maxX - v.minX, height: v.maxY - v.minY };
1929
+ return d.getLocalBounds();
1930
+ };
1931
+ const inMask = (maskTarget, p) => {
1932
+ const c = maskTarget;
1933
+ if (typeof c.containsPoint === "function") return c.containsPoint(c.toLocal(p));
1934
+ const was = c.measurable;
1935
+ c.measurable = true;
1936
+ try {
1937
+ return inside(c.getBounds(), p);
1938
+ } finally {
1939
+ c.measurable = was;
1940
+ }
1941
+ };
1883
1942
  const hiddenSources = /* @__PURE__ */ new Map();
1884
1943
  const forwards = [];
1885
1944
  const rays = /* @__PURE__ */ new Map();
@@ -1893,7 +1952,7 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1893
1952
  if (!shows || !inside(lb, local)) return;
1894
1953
  const r = rayOf(seq, d, local, lb);
1895
1954
  const shown = r?.uv ? shows.call(seq, r.material) : null;
1896
- if (r?.uv && shown) forwards.push({ source: shown.source, uv: r.uv, via: { path, object: String(r.object) }, order: orderOf(d) });
1955
+ if (r?.uv && shown) forwards.push({ source: shown.source, at: shown.at(r.uv.u, r.uv.v), via: { path, object: String(r.object) }, order: orderOf(d) });
1897
1956
  };
1898
1957
  const walk3 = (comp, prefix, parentVisible, at = { x, y }, orderBase, via) => {
1899
1958
  comp.layers().forEach(({ seq, display, threeD, carriers, quad }, i) => {
@@ -1911,6 +1970,8 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1911
1970
  }
1912
1971
  const isComposition = seq instanceof CompositionSequence;
1913
1972
  if (isComposition && !threeD) {
1973
+ const gm = seq.maskSequence?.target, gs = maskSourceOf(seq.spec.mask);
1974
+ if (gm && gs.kind === "inline" && !gs.invert && !inMask(gm, at)) return;
1914
1975
  walk3(seq, path + "/", visible, at, orderBase, via);
1915
1976
  return;
1916
1977
  }
@@ -1927,7 +1988,7 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1927
1988
  if (quad && pointInQuad(quad, d.toLocal(point).x, d.toLocal(point).y)) hit = { name, path, type: seq.spec.type, kind: "card", point };
1928
1989
  } else {
1929
1990
  const local = d.toLocal(point);
1930
- const lb = d.getLocalBounds();
1991
+ const lb = boxOf(d, !!seq.maskSequence);
1931
1992
  if (inside(lb, local)) {
1932
1993
  const isThree = typeof seq.pickAt === "function";
1933
1994
  const ray = isThree ? rayOf(seq, d, local, lb) : void 0;
@@ -1966,7 +2027,7 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1966
2027
  if (!hit) return;
1967
2028
  const maskTarget = seq.maskSequence?.target;
1968
2029
  const maskSrc = maskSourceOf(seq.spec.mask);
1969
- if (maskTarget && !(maskSrc.kind === "inline" && maskSrc.invert) && !inside(maskTarget.getBounds(), point)) return;
2030
+ if (maskTarget && !(maskSrc.kind === "inline" && maskSrc.invert) && !(maskSrc.kind === "inline" ? inMask(maskTarget, point) : inside(maskTarget.getBounds(), point))) return;
1970
2031
  found.push({ hit: via ? { ...hit, point: { x, y }, via } : hit, order: orderBase ? [...orderBase, ...orderOf(d)] : orderOf(d) });
1971
2032
  });
1972
2033
  };
@@ -1974,7 +2035,9 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1974
2035
  for (const f of forwards) {
1975
2036
  const path = hiddenSources.get(f.source);
1976
2037
  if (path === void 0) continue;
1977
- const g = f.source.target.toGlobal({ x: f.uv.u * f.source.intrinsicWidth, y: (1 - f.uv.v) * f.source.intrinsicHeight });
2038
+ const g = f.source.target.toGlobal({ x: f.at.x * f.source.intrinsicWidth, y: f.at.y * f.source.intrinsicHeight });
2039
+ const sm = f.source.maskSequence?.target, ss = maskSourceOf(f.source.spec.mask);
2040
+ if (sm && ss.kind === "inline" && !ss.invert && !inMask(sm, g)) continue;
1978
2041
  walk3(f.source, path + "/", true, g, f.order, f.via);
1979
2042
  }
1980
2043
  return found.sort((a, b) => above(a.order, b.order)).map((f) => f.hit);
@@ -2975,7 +3038,8 @@ var Movie = class {
2975
3038
  lp.part(done);
2976
3039
  await lp.yieldIfDue();
2977
3040
  } : void 0);
2978
- const loudness = this.audioBuffer && this._loudness !== null ? normalizeMix(this.audioBuffer, this._loudness) : void 0;
3041
+ if (this.audioBuffer) cleanMix(this.audioBuffer, audios);
3042
+ const loudness = this.audioBuffer && this._loudness !== null ? await normalizeMix(this.audioBuffer, this._loudness, lp ? () => lp.yieldIfDue() : void 0) : void 0;
2979
3043
  this._mixStats = this.audioBuffer ? { ...limitMix(this.audioBuffer, audios), ...loudness ? { loudness } : {} } : null;
2980
3044
  this._mixCache.set(key, this.audioBuffer, this._mixStats);
2981
3045
  status = "mixed";
@@ -3218,12 +3282,18 @@ var Movie = class {
3218
3282
  const collected = [];
3219
3283
  const clocks = /* @__PURE__ */ new Map();
3220
3284
  collectVideoSequences(this._rootSequence, collected, clocks);
3221
- await Promise.all(collected.map((v) => {
3285
+ const wait = (v) => {
3222
3286
  if (v.selfTimed) return v.awaitFrameAt(t);
3223
3287
  const local = (clocks.get(v)?.() ?? t) - (v.absoluteStart ?? v.at);
3224
3288
  if (local < 0 || local > (v.duration ?? 0)) return Promise.resolve();
3225
3289
  return v.awaitFrameAt(local);
3226
- }));
3290
+ };
3291
+ const late = collected.filter((v) => v.showsLayers === true);
3292
+ await Promise.all(collected.filter((v) => !late.includes(v)).map(wait));
3293
+ if (late.length > 0) {
3294
+ this._updateSpace(t);
3295
+ await Promise.all(late.map(wait));
3296
+ }
3227
3297
  }
3228
3298
  async snapshot(frame = this.currentFrame, opts = {}) {
3229
3299
  warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "quality", "as", "motionBlur"]);
@@ -3861,7 +3931,7 @@ var ALIGN = ["left", "center", "right"];
3861
3931
  var widthOf = (text, style) => CanvasTextMetrics.measureText(text, style).width;
3862
3932
  function measureText(text, style = {}) {
3863
3933
  const m = CanvasTextMetrics.measureText(text, new TextStyle(style));
3864
- return { width: m.width, height: m.height, lines: text.split("\n").length };
3934
+ return { width: m.width, height: m.height, lines: m.lines?.length ?? text.split("\n").length };
3865
3935
  }
3866
3936
  function splitText(text, style = {}, opts = {}) {
3867
3937
  warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align", "vertical"]);
@@ -4389,7 +4459,9 @@ function catmullRom(p0, p1, p2, p3, t) {
4389
4459
  // src/presets/stagger.ts
4390
4460
  var STAGGER_KEYS = ["each", "amount", "from", "grid", "ease", "seed"];
4391
4461
  var FROM_NAMES = ["start", "end", "center", "edges", "random"];
4392
- function stagger(items, options = {}) {
4462
+ function stagger(items, spec = {}) {
4463
+ if (typeof spec !== "number" && (typeof spec !== "object" || spec === null)) throw new Error(`stagger(): the second argument is { each, from, \u2026 } or a number of seconds between neighbours, got ${JSON.stringify(spec)}`);
4464
+ const options = typeof spec === "number" ? { each: spec } : spec;
4393
4465
  const isCount = typeof items === "number";
4394
4466
  const n = isCount ? items : items.length;
4395
4467
  if (!Number.isInteger(n) || n < 0) throw new Error(`stagger(): count must be a whole number of items, got ${n}`);
@@ -4553,7 +4625,7 @@ function animateText(text, style, options) {
4553
4625
  let idleEnd = len;
4554
4626
  if (outTween) {
4555
4627
  const begin = outTween.at !== void 0 ? void 0 : outTween.duration + (outWave - outDelays[i]);
4556
- const local = outTween.at !== void 0 ? Math.max(0, outTween.at + outDelays[i] - start) : -begin;
4628
+ const local = outTween.at !== void 0 ? Math.max(0, outTween.at - at + (outDelays[i] - delays[i])) : -begin;
4557
4629
  idleEnd = outTween.at !== void 0 ? local : len - begin;
4558
4630
  keyframes.push({
4559
4631
  at: local,
@@ -4568,7 +4640,7 @@ function animateText(text, style, options) {
4568
4640
  for (const k of ["x", "y", "rotation", "scale"]) if (idle[k] !== void 0) props[k] = { around: rest[k], amp: idle[k] };
4569
4641
  const freq = idle.freq ?? 1;
4570
4642
  const from = inTween?.duration ?? 0;
4571
- if (Object.keys(props).length > 0 && idleEnd - from >= 1 / freq) {
4643
+ if (Object.keys(props).length > 0 && idleEnd - from >= 1 / freq - 1e-9) {
4572
4644
  keyframes.push(...wiggle({ at: from, duration: idleEnd - from, freq, seed: (idle.seed ?? 0) * 1e3 + i, props }));
4573
4645
  }
4574
4646
  }
@@ -4619,7 +4691,7 @@ function resolveTween(spec, which) {
4619
4691
  let ease = t.ease ?? base?.ease ?? "power2.out";
4620
4692
  if (which === "out" && t.ease === void 0) ease = ease.replace(".out", ".in");
4621
4693
  const at = which === "out" && typeof t.at === "number" && Number.isFinite(t.at) ? t.at : void 0;
4622
- const wave = which === "out" && t.stagger !== void 0 ? t.stagger === 0 ? { each: 0 } : t.stagger : void 0;
4694
+ const wave = which === "out" && t.stagger !== void 0 ? typeof t.stagger === "number" ? { each: t.stagger } : t.stagger : void 0;
4623
4695
  return { from, to: { ...to }, duration: t.duration ?? base?.duration ?? 0.5, ease, ...at !== void 0 ? { at } : {}, ...wave ? { stagger: wave } : {} };
4624
4696
  }
4625
4697
 
@@ -4928,5 +5000,5 @@ function withFade(spec, opts) {
4928
5000
  }
4929
5001
 
4930
5002
  export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, parseEase2 as parseEase, particles, react, resolveLoader, samplePath, splitText, stagger, wiggle, withFade };
4931
- //# sourceMappingURL=chunk-OVTTCX2E.js.map
4932
- //# sourceMappingURL=chunk-OVTTCX2E.js.map
5003
+ //# sourceMappingURL=chunk-6LA7W362.js.map
5004
+ //# sourceMappingURL=chunk-6LA7W362.js.map