pixi-effects 0.16.3 → 0.17.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 +29 -0
- package/README.md +6 -6
- package/ai/CHAT.md +4 -0
- package/ai/SKILL.md +2 -1
- package/ai/chat-template.html +3 -3
- package/ai/reference/cheatsheet.md +4 -2
- package/ai/reference/pitfalls.md +3 -0
- package/ai/reference/recipes.md +67 -0
- package/ai/template.html +5 -5
- package/ai/tools/check.mjs +162 -18
- package/ai/tools/render.mjs +21 -7
- package/dist/Composition-HYAOYPAX.js +5 -0
- package/dist/{Composition-VJWY2NDL.js.map → Composition-HYAOYPAX.js.map} +1 -1
- package/dist/Composition-LMVEASHF.cjs +14 -0
- package/dist/{Composition-3WWNVRR5.cjs.map → Composition-LMVEASHF.cjs.map} +1 -1
- package/dist/Controller.d.cts +1 -1
- package/dist/Controller.d.ts +1 -1
- package/dist/{Movie-oA0SUAyb.d.cts → Movie-CoxF2Z7t.d.cts} +82 -2
- package/dist/{Movie-DilBC348.d.ts → Movie-DHJRrUfI.d.ts} +82 -2
- package/dist/Presenter.d.cts +1 -1
- package/dist/Presenter.d.ts +1 -1
- package/dist/{chunk-2ROVCPSL.cjs → chunk-ETAIVOHP.cjs} +32 -27
- package/dist/chunk-ETAIVOHP.cjs.map +1 -0
- package/dist/{chunk-FJA5VUOF.cjs → chunk-LBJRXU7N.cjs} +15 -3
- package/dist/chunk-LBJRXU7N.cjs.map +1 -0
- package/dist/{chunk-ZNKQH356.js → chunk-ORTYBA42.js} +15 -4
- package/dist/chunk-ORTYBA42.js.map +1 -0
- package/dist/{chunk-XTIHKODH.js → chunk-Q3IVBYT7.js} +10 -5
- package/dist/chunk-Q3IVBYT7.js.map +1 -0
- package/dist/index.cjs +351 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +329 -19
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/dsl.md +2 -1
- package/llms-full.txt +78 -4
- package/llms.txt +1 -1
- package/package.json +6 -3
- package/dist/Composition-3WWNVRR5.cjs +0 -14
- package/dist/Composition-VJWY2NDL.js +0 -5
- package/dist/chunk-2ROVCPSL.cjs.map +0 -1
- package/dist/chunk-FJA5VUOF.cjs.map +0 -1
- package/dist/chunk-XTIHKODH.js.map +0 -1
- package/dist/chunk-ZNKQH356.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkETAIVOHP_cjs = require('./chunk-ETAIVOHP.cjs');
|
|
4
|
+
var chunkLBJRXU7N_cjs = require('./chunk-LBJRXU7N.cjs');
|
|
5
5
|
var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
|
|
6
6
|
var PIXI = require('pixi.js');
|
|
7
7
|
var gsap = require('gsap');
|
|
@@ -203,6 +203,118 @@ function spectralCentroid(samples, sampleRate, from = 0, to = samples.length) {
|
|
|
203
203
|
return den > 1e-9 ? Math.round(num3 / den / 10) * 10 : 0;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// src/audio/loudness.ts
|
|
207
|
+
var LOUDNESS_NOTES = { quietBelow: -24, loudAbove: -9, headroomDb: -1 };
|
|
208
|
+
function kWeighting(fs) {
|
|
209
|
+
const f0 = 1681.974450955533, G = 3.999843853973347, Q = 0.7071752369554196;
|
|
210
|
+
const K = Math.tan(Math.PI * f0 / fs), Vh = 10 ** (G / 20), Vb = Vh ** 0.4996667741545416;
|
|
211
|
+
const a0 = 1 + K / Q + K * K;
|
|
212
|
+
const shelf = {
|
|
213
|
+
b: [(Vh + Vb * K / Q + K * K) / a0, 2 * (K * K - Vh) / a0, (Vh - Vb * K / Q + K * K) / a0],
|
|
214
|
+
a: [1, 2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0]
|
|
215
|
+
};
|
|
216
|
+
const f1 = 38.13547087602444, Q1 = 0.5003270373238773, K1 = Math.tan(Math.PI * f1 / fs), a1 = 1 + K1 / Q1 + K1 * K1;
|
|
217
|
+
const high = { b: [1, -2, 1], a: [1, 2 * (K1 * K1 - 1) / a1, (1 - K1 / Q1 + K1 * K1) / a1] };
|
|
218
|
+
return [shelf, high];
|
|
219
|
+
}
|
|
220
|
+
function filter(x, stages) {
|
|
221
|
+
let cur = x;
|
|
222
|
+
for (const { b, a } of stages) {
|
|
223
|
+
const out = new Float32Array(cur.length);
|
|
224
|
+
let x1 = 0, x2 = 0, y1 = 0, y2 = 0;
|
|
225
|
+
for (let i = 0; i < cur.length; i++) {
|
|
226
|
+
const xi = cur[i], y = b[0] * xi + b[1] * x1 + b[2] * x2 - a[1] * y1 - a[2] * y2;
|
|
227
|
+
x2 = x1;
|
|
228
|
+
x1 = xi;
|
|
229
|
+
y2 = y1;
|
|
230
|
+
y1 = y;
|
|
231
|
+
out[i] = y;
|
|
232
|
+
}
|
|
233
|
+
cur = out;
|
|
234
|
+
}
|
|
235
|
+
return cur;
|
|
236
|
+
}
|
|
237
|
+
var toDb = (x) => Math.round(200 * Math.log10(Math.max(x, 1e-6))) / 10;
|
|
238
|
+
var TP_HALF = 12;
|
|
239
|
+
var TP_BLOCK = 32;
|
|
240
|
+
var TP_KERNELS = [0.25, 0.5, 0.75].map((p) => Float64Array.from({ length: 2 * TP_HALF }, (_, j) => {
|
|
241
|
+
const k = j - TP_HALF + 1, x = k - p, s = x === 0 ? 1 : Math.sin(Math.PI * x) / (Math.PI * x);
|
|
242
|
+
return s * (0.5 + 0.5 * Math.cos(Math.PI * x / (TP_HALF + 1)));
|
|
243
|
+
}));
|
|
244
|
+
var TP_GAIN = Math.max(...TP_KERNELS.map((k) => k.reduce((a, v) => a + Math.abs(v), 0)));
|
|
245
|
+
function truePeak(chans) {
|
|
246
|
+
let peak = 0;
|
|
247
|
+
for (const c of chans) {
|
|
248
|
+
const n = c.length;
|
|
249
|
+
const blocks = Math.ceil(n / TP_BLOCK), blockMax = new Float32Array(blocks + 2);
|
|
250
|
+
for (let i = 0; i < n; i++) {
|
|
251
|
+
const a = Math.abs(c[i]), b = (i / TP_BLOCK | 0) + 1;
|
|
252
|
+
if (a > blockMax[b]) blockMax[b] = a;
|
|
253
|
+
if (a > peak) peak = a;
|
|
254
|
+
}
|
|
255
|
+
const padded = new Float32Array(n + 2 * TP_HALF);
|
|
256
|
+
padded.set(c, TP_HALF);
|
|
257
|
+
for (let i = 0; i < n; i++) {
|
|
258
|
+
const b = (i / TP_BLOCK | 0) + 1;
|
|
259
|
+
const local = Math.max(blockMax[b - 1], blockMax[b], blockMax[b + 1]);
|
|
260
|
+
if (local * TP_GAIN <= peak) continue;
|
|
261
|
+
for (let pi = 0; pi < 3; pi++) {
|
|
262
|
+
const k = TP_KERNELS[pi];
|
|
263
|
+
let v = 0;
|
|
264
|
+
for (let j = 0; j < 2 * TP_HALF; j++) v += padded[i + 1 + j] * k[j];
|
|
265
|
+
const a = Math.abs(v);
|
|
266
|
+
if (a > peak) peak = a;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return peak;
|
|
271
|
+
}
|
|
272
|
+
function measureLoudness(channels, sampleRate, opts = {}) {
|
|
273
|
+
let samplePeak = 0, clipped = 0;
|
|
274
|
+
for (const c of channels) for (let i = 0; i < c.length; i++) {
|
|
275
|
+
const a = Math.abs(c[i]);
|
|
276
|
+
if (a > samplePeak) samplePeak = a;
|
|
277
|
+
if (a >= 1) clipped++;
|
|
278
|
+
}
|
|
279
|
+
const stages = kWeighting(sampleRate);
|
|
280
|
+
const weighted = channels.map((c) => filter(c, stages));
|
|
281
|
+
const block = Math.round(0.4 * sampleRate), hop = Math.round(0.1 * sampleRate);
|
|
282
|
+
const energies = [];
|
|
283
|
+
for (let s = 0; s + block <= (weighted[0]?.length ?? 0); s += hop) {
|
|
284
|
+
let sum = 0;
|
|
285
|
+
for (const w of weighted) {
|
|
286
|
+
let z = 0;
|
|
287
|
+
for (let i = s; i < s + block; i++) z += w[i] * w[i];
|
|
288
|
+
sum += z / block;
|
|
289
|
+
}
|
|
290
|
+
energies.push(sum);
|
|
291
|
+
}
|
|
292
|
+
const lufs = (e) => -0.691 + 10 * Math.log10(e);
|
|
293
|
+
const absolute = energies.filter((e) => e > 0 && lufs(e) > -70);
|
|
294
|
+
let integrated = null;
|
|
295
|
+
if (absolute.length) {
|
|
296
|
+
const relative = lufs(absolute.reduce((a, b) => a + b, 0) / absolute.length) - 10;
|
|
297
|
+
const gated = absolute.filter((e) => lufs(e) > relative);
|
|
298
|
+
if (gated.length) integrated = Math.round(10 * lufs(gated.reduce((a, b) => a + b, 0) / gated.length)) / 10;
|
|
299
|
+
}
|
|
300
|
+
return { integratedLufs: integrated, truePeakDb: opts.truePeak === false ? null : toDb(truePeak(channels)), samplePeakDb: toDb(samplePeak), clippedSamples: clipped };
|
|
301
|
+
}
|
|
302
|
+
function findSilences(channels, sampleRate, opts = {}) {
|
|
303
|
+
const min = Math.round((opts.minLength ?? 1) * sampleRate), limit = 10 ** ((opts.thresholdDb ?? -60) / 20);
|
|
304
|
+
const n = channels[0]?.length ?? 0, out = [];
|
|
305
|
+
let start = -1;
|
|
306
|
+
const quiet = (i) => channels.every((c) => Math.abs(c[i]) < limit);
|
|
307
|
+
for (let i = 0; i <= n; i++) {
|
|
308
|
+
const q = i < n && quiet(i);
|
|
309
|
+
if (q && start < 0) start = i;
|
|
310
|
+
if (!q && start >= 0) {
|
|
311
|
+
if (i - start >= min) out.push({ from: Math.round(start / sampleRate * 1e3) / 1e3, to: Math.round(i / sampleRate * 1e3) / 1e3 });
|
|
312
|
+
start = -1;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
206
318
|
// src/core/inspectAudio.ts
|
|
207
319
|
var INAUDIBLE_DB = -50;
|
|
208
320
|
var db = (x) => Math.round(10 * Math.max(-120, 20 * Math.log10(x))) / 10;
|
|
@@ -228,7 +340,19 @@ function measure(chans, sampleRate) {
|
|
|
228
340
|
}
|
|
229
341
|
function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
|
|
230
342
|
if (!mix) {
|
|
231
|
-
return {
|
|
343
|
+
return {
|
|
344
|
+
duration: movieDuration,
|
|
345
|
+
sampleRate: 0,
|
|
346
|
+
peakDb: -120,
|
|
347
|
+
peakAt: 0,
|
|
348
|
+
sources: [],
|
|
349
|
+
windows: [],
|
|
350
|
+
loudness: { integratedLufs: null, truePeakDb: -120, clippedSamples: 0, silences: [] },
|
|
351
|
+
scenes: [],
|
|
352
|
+
cues: [],
|
|
353
|
+
notes: [],
|
|
354
|
+
issues: ['no audio: the movie has no audio layers (sound effects need no files: { type: "audio", sfx: "pop", at: 1 })']
|
|
355
|
+
};
|
|
232
356
|
}
|
|
233
357
|
const sr = mix.sampleRate;
|
|
234
358
|
const chans = Array.from({ length: mix.numberOfChannels }, (_, c) => mix.getChannelData(c));
|
|
@@ -286,6 +410,24 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
|
|
|
286
410
|
}
|
|
287
411
|
return { layer, source, start: s.start, end: s.end, peakDb, sound };
|
|
288
412
|
});
|
|
413
|
+
const whole = measureLoudness(chans, sr);
|
|
414
|
+
const silences = findSilences(chans, sr, { minLength: 1 });
|
|
415
|
+
if (whole.clippedSamples > 0) issues.push(`${whole.clippedSamples} sample(s) of the mix are at full scale (clipped) \u2014 lower the volume of the layers that play loudest`);
|
|
416
|
+
if (whole.truePeakDb > 0) issues.push(`the mix's true peak is ${whole.truePeakDb} dBTP: it will distort when the video is encoded \u2014 lower the volume`);
|
|
417
|
+
const notes = [];
|
|
418
|
+
if (whole.integratedLufs !== null) {
|
|
419
|
+
if (whole.integratedLufs < LOUDNESS_NOTES.quietBelow) notes.push(`the mix is quiet for web video: ${whole.integratedLufs} LUFS (typical \u221214 to \u221216); raise the volume if that is not on purpose`);
|
|
420
|
+
else if (whole.integratedLufs > LOUDNESS_NOTES.loudAbove) notes.push(`the mix is loud: ${whole.integratedLufs} LUFS (typical web video \u221214 to \u221216)`);
|
|
421
|
+
}
|
|
422
|
+
if (whole.truePeakDb > LOUDNESS_NOTES.headroomDb && whole.truePeakDb <= 0) notes.push(`the true peak is ${whole.truePeakDb} dBTP: little headroom left for encoding (keep it below ${LOUDNESS_NOTES.headroomDb} dBTP)`);
|
|
423
|
+
for (const q of silences.slice(0, 3)) notes.push(`the mix is silent from ${q.from} s to ${q.to} s (${Math.round((q.to - q.from) * 10) / 10} s)`);
|
|
424
|
+
if (silences.length > 3) notes.push(`and ${silences.length - 3} more silent stretch(es) (loudness.silences lists them all)`);
|
|
425
|
+
const sceneList = (opts.scenes && opts.scenes.length ? opts.scenes : [{ name: "movie", start: 0, end: movieDuration }]).map((sc) => {
|
|
426
|
+
const a = Math.max(0, Math.round(sc.start * sr)), b = Math.min(mix.length, Math.round(sc.end * sr));
|
|
427
|
+
const part = chans.map((c) => c.subarray(a, Math.max(a, b)));
|
|
428
|
+
const m = measureLoudness(part, sr, { truePeak: false });
|
|
429
|
+
return { name: sc.name, from: sc.start, to: sc.end, lufs: m.integratedLufs, peakDb: m.samplePeakDb, silent: m.samplePeakDb < -60 };
|
|
430
|
+
});
|
|
289
431
|
return {
|
|
290
432
|
duration: movieDuration,
|
|
291
433
|
sampleRate: sr,
|
|
@@ -293,8 +435,69 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
|
|
|
293
435
|
peakAt: stats?.peakAt ?? 0,
|
|
294
436
|
sources: list,
|
|
295
437
|
windows: windows2,
|
|
296
|
-
issues
|
|
438
|
+
issues,
|
|
439
|
+
loudness: { integratedLufs: whole.integratedLufs, truePeakDb: whole.truePeakDb ?? -120, clippedSamples: whole.clippedSamples, silences },
|
|
440
|
+
scenes: sceneList,
|
|
441
|
+
cues: list.map((s) => ({ t: s.start, name: `${s.layer} (${s.source})` })),
|
|
442
|
+
notes
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/text/fonts.ts
|
|
447
|
+
var GENERIC = /* @__PURE__ */ new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "emoji", "math", "fangsong", "-apple-system", "blinkmacsystemfont"]);
|
|
448
|
+
function fontAvailable(family2) {
|
|
449
|
+
const f = family2.trim().replace(/^["']|["']$/g, "");
|
|
450
|
+
if (!f || GENERIC.has(f.toLowerCase())) return true;
|
|
451
|
+
const g = document.createElement("canvas").getContext("2d");
|
|
452
|
+
if (!g) return true;
|
|
453
|
+
const sample = "mmmmmmmmmmlliWWWW 0123 \u3042\u3044\u3046";
|
|
454
|
+
const width = (stack) => {
|
|
455
|
+
g.font = `72px ${stack}`;
|
|
456
|
+
return g.measureText(sample).width;
|
|
457
|
+
};
|
|
458
|
+
const q = JSON.stringify(f);
|
|
459
|
+
return width(`${q}, monospace`) !== width("monospace") || width(`${q}, serif`) !== width("serif");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/core/inspectFonts.ts
|
|
463
|
+
function inspectFonts(root) {
|
|
464
|
+
const failed = /* @__PURE__ */ new Set();
|
|
465
|
+
if (typeof document !== "undefined" && document.fonts) document.fonts.forEach((face) => {
|
|
466
|
+
if (face.status === "error") failed.add(face.family.replace(/^["']|["']$/g, ""));
|
|
467
|
+
});
|
|
468
|
+
const missing = [];
|
|
469
|
+
const walk2 = (seq, index) => {
|
|
470
|
+
const spec = seq.spec;
|
|
471
|
+
if (spec.type === "text") {
|
|
472
|
+
const raw = spec.style?.fontFamily;
|
|
473
|
+
const family2 = Array.isArray(raw) ? raw.join(", ") : typeof raw === "string" ? raw : "";
|
|
474
|
+
const list = family2.split(",").map((f) => f.trim()).filter(Boolean);
|
|
475
|
+
if (list.length && !list.some(fontAvailable) && !list.some((f) => failed.has(f.replace(/^["']|["']$/g, "")))) missing.push({ layer: spec.name ?? `text#${index}`, family: family2 });
|
|
476
|
+
}
|
|
477
|
+
if (seq.maskSequence) walk2(seq.maskSequence, index);
|
|
478
|
+
(seq._children ?? []).forEach((child, i) => walk2(child, i));
|
|
297
479
|
};
|
|
480
|
+
walk2(root, 0);
|
|
481
|
+
return { missing, failed: [...failed] };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/core/scenes.ts
|
|
485
|
+
function namedScenes(rows) {
|
|
486
|
+
return rows.filter((r) => r.depth === 0 && r.type === "composition" && !/[#×]/.test(r.name) && r.end - r.start >= 1).map((r) => ({ name: r.name, start: r.start, end: r.end }));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/core/onion.ts
|
|
490
|
+
function onionTimes(from, to, count) {
|
|
491
|
+
if (count <= 1) return [from];
|
|
492
|
+
return Array.from({ length: count }, (_, i) => Math.round((from + (to - from) * i / (count - 1)) * 1e6) / 1e6);
|
|
493
|
+
}
|
|
494
|
+
function onionAlphas(count, ramp = 0.25) {
|
|
495
|
+
const weights = Array.from({ length: count }, (_, i) => count === 1 ? 1 : ramp + (1 - ramp) * (i / (count - 1)));
|
|
496
|
+
let sum = 0;
|
|
497
|
+
return weights.map((w) => {
|
|
498
|
+
sum += w;
|
|
499
|
+
return w / sum;
|
|
500
|
+
});
|
|
298
501
|
}
|
|
299
502
|
|
|
300
503
|
// src/core/motionBlur.ts
|
|
@@ -313,6 +516,57 @@ function blurTimes(frame, frameRate, mb, duration) {
|
|
|
313
516
|
return Array.from({ length: mb.samples }, (_, k) => Math.min(Math.max(t + ((k + 0.5) / mb.samples - 0.5) * width, 0), duration));
|
|
314
517
|
}
|
|
315
518
|
|
|
519
|
+
// src/core/renderRange.ts
|
|
520
|
+
function resolveRange(range2, ctx) {
|
|
521
|
+
const { frameRate: fps, totalFrames, duration } = ctx;
|
|
522
|
+
if (range2 === void 0) return { fromFrame: 0, toFrame: totalFrames, fromSec: 0, toSec: duration, partial: false };
|
|
523
|
+
let from, to;
|
|
524
|
+
if (typeof range2 === "string") {
|
|
525
|
+
const rows = (ctx.rows ?? []).filter((r) => r.depth === 0);
|
|
526
|
+
const row = rows.find((r) => r.name === range2);
|
|
527
|
+
if (!row) throw new Error(`pixi-effects: render({ range: "${range2}" }): no layer named "${range2}" at the top level (names: ${rows.map((r) => r.name).join(", ") || "none"})`);
|
|
528
|
+
from = row.start;
|
|
529
|
+
to = row.end;
|
|
530
|
+
} else {
|
|
531
|
+
if (!Array.isArray(range2) || range2.length !== 2 || !range2.every((n) => typeof n === "number" && Number.isFinite(n))) {
|
|
532
|
+
throw new Error("pixi-effects: render({ range }) must be [from, to] in seconds (numbers) or the name of a top-level layer");
|
|
533
|
+
}
|
|
534
|
+
[from, to] = range2;
|
|
535
|
+
}
|
|
536
|
+
if (from < 0 || to <= from) throw new Error(`pixi-effects: render({ range: [${from}, ${to}] }): a range must start before it ends (from >= 0, from < to)`);
|
|
537
|
+
if (to > duration + 0.5 / fps) throw new Error(`pixi-effects: render({ range: [${from}, ${to}] }) goes past the end: the movie is ${duration} s long`);
|
|
538
|
+
const fromFrame = Math.round(from * fps);
|
|
539
|
+
const toFrame = Math.min(Math.round(to * fps), totalFrames);
|
|
540
|
+
if (toFrame <= fromFrame) throw new Error(`pixi-effects: render({ range: [${from}, ${to}] }) is shorter than one frame (${(1 / fps).toFixed(4)} s at ${fps} fps)`);
|
|
541
|
+
const partial = fromFrame > 0 || toFrame < totalFrames;
|
|
542
|
+
return { fromFrame, toFrame, fromSec: fromFrame / fps, toSec: toFrame / fps, partial };
|
|
543
|
+
}
|
|
544
|
+
function sliceChannels(channels, sampleRate, fromSec, toSec, opts = {}) {
|
|
545
|
+
const fade = Math.max(0, Math.round((opts.fade ?? 0.01) * sampleRate));
|
|
546
|
+
const from = Math.round(fromSec * sampleRate), len = Math.max(1, Math.round(toSec * sampleRate) - from);
|
|
547
|
+
return channels.map((src) => {
|
|
548
|
+
const out = new Float32Array(len);
|
|
549
|
+
for (let i = 0; i < len; i++) {
|
|
550
|
+
const j = from + i;
|
|
551
|
+
if (j >= 0 && j < src.length) out[i] = src[j];
|
|
552
|
+
}
|
|
553
|
+
const f = Math.min(fade, len >> 1);
|
|
554
|
+
if (opts.fadeIn !== false) for (let i = 0; i < f; i++) out[i] = out[i] * (i / f);
|
|
555
|
+
if (opts.fadeOut !== false) for (let i = 0; i < f; i++) out[len - 1 - i] = out[len - 1 - i] * (i / f);
|
|
556
|
+
return out;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function resolveRenderOptions(o) {
|
|
560
|
+
const draft = !!o.draft;
|
|
561
|
+
const scale = o.scale ?? (draft ? 0.5 : 1);
|
|
562
|
+
if (!(typeof scale === "number" && scale > 0 && scale <= 1)) throw new Error(`pixi-effects: render({ scale }): scale must be above 0 and at most 1 (got ${scale})`);
|
|
563
|
+
return {
|
|
564
|
+
scale,
|
|
565
|
+
bitrate: o.video?.bitrate ?? (draft ? "low" : void 0),
|
|
566
|
+
motionBlur: o.motionBlur !== void 0 ? o.motionBlur : draft ? false : void 0
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
316
570
|
// src/core/Renderer.ts
|
|
317
571
|
var VIDEO_CODEC_BY_FORMAT = {
|
|
318
572
|
mp4: "avc",
|
|
@@ -367,13 +621,22 @@ function makeOutputFormat(name) {
|
|
|
367
621
|
return new mediabunny.MkvOutputFormat();
|
|
368
622
|
}
|
|
369
623
|
}
|
|
624
|
+
function sliceAudio(buffer, fromSec, toSec, fadeIn, fadeOut) {
|
|
625
|
+
const channels = Array.from({ length: buffer.numberOfChannels }, (_, c) => buffer.getChannelData(c));
|
|
626
|
+
const cut = sliceChannels(channels, buffer.sampleRate, fromSec, toSec, { fadeIn, fadeOut });
|
|
627
|
+
const out = new AudioBuffer({ length: cut[0].length, numberOfChannels: cut.length, sampleRate: buffer.sampleRate });
|
|
628
|
+
cut.forEach((data, c) => out.copyToChannel(data, c));
|
|
629
|
+
return out;
|
|
630
|
+
}
|
|
370
631
|
async function exportFrames(movie, options = {}) {
|
|
371
632
|
const fmt = options.format ?? "mp4";
|
|
633
|
+
const eff = resolveRenderOptions(options);
|
|
634
|
+
const range2 = resolveRange(options.range, { frameRate: movie.frameRate, totalFrames: movie.totalFrames, duration: movie.duration, rows: typeof options.range === "string" ? movie.timelineData().rows : void 0 });
|
|
372
635
|
const opts = {
|
|
373
636
|
format: fmt,
|
|
374
637
|
video: {
|
|
375
638
|
codec: options.video?.codec ?? VIDEO_CODEC_BY_FORMAT[fmt],
|
|
376
|
-
bitrate: qualityMap[
|
|
639
|
+
bitrate: qualityMap[eff.bitrate ?? "high"] ?? mediabunny.QUALITY_HIGH
|
|
377
640
|
},
|
|
378
641
|
audio: {
|
|
379
642
|
codec: options.audio?.codec ?? AUDIO_CODEC_BY_FORMAT[fmt],
|
|
@@ -381,27 +644,30 @@ async function exportFrames(movie, options = {}) {
|
|
|
381
644
|
}
|
|
382
645
|
};
|
|
383
646
|
const container = makeOutputFormat(opts.format);
|
|
384
|
-
|
|
647
|
+
const audio = movie.audioBuffer && range2.partial ? sliceAudio(movie.audioBuffer, range2.fromSec, range2.toSec, range2.fromFrame > 0, range2.toFrame < movie.totalFrames) : movie.audioBuffer;
|
|
648
|
+
if (audio) opts.audio.codec = await chooseAudioCodec(fmt, options.audio?.codec, container, audio, opts.audio.bitrate);
|
|
385
649
|
const output = new mediabunny.Output({
|
|
386
650
|
format: container,
|
|
387
651
|
target: new mediabunny.BufferTarget()
|
|
388
652
|
});
|
|
389
|
-
const mb =
|
|
653
|
+
const mb = eff.motionBlur !== void 0 ? resolveMotionBlur(eff.motionBlur, "movie.render()") : movie.motionBlur ?? null;
|
|
390
654
|
const stage = movie.app.canvas;
|
|
391
655
|
const blurCanvas = mb ? Object.assign(document.createElement("canvas"), { width: stage.width, height: stage.height }) : null;
|
|
392
|
-
const
|
|
656
|
+
const small = eff.scale < 1 ? Object.assign(document.createElement("canvas"), { width: Math.max(2, Math.round(stage.width * eff.scale / 2) * 2), height: Math.max(2, Math.round(stage.height * eff.scale / 2) * 2) }) : null;
|
|
657
|
+
const smallCtx = small?.getContext("2d") ?? null;
|
|
658
|
+
const canvasSource = new mediabunny.CanvasSource(small ?? blurCanvas ?? stage, {
|
|
393
659
|
codec: opts.video.codec,
|
|
394
660
|
bitrate: opts.video.bitrate
|
|
395
661
|
});
|
|
396
662
|
output.addVideoTrack(canvasSource, { frameRate: movie.frameRate });
|
|
397
|
-
if (
|
|
663
|
+
if (audio) {
|
|
398
664
|
const audioSource = new mediabunny.AudioBufferSource({
|
|
399
665
|
codec: opts.audio.codec,
|
|
400
666
|
bitrate: opts.audio.bitrate
|
|
401
|
-
}, { startTimestamp: opts.audio.codec === "aac" ? -2112 /
|
|
667
|
+
}, { startTimestamp: opts.audio.codec === "aac" ? -2112 / audio.sampleRate : 0 });
|
|
402
668
|
output.addAudioTrack(audioSource);
|
|
403
669
|
await output.start();
|
|
404
|
-
await audioSource.add(
|
|
670
|
+
await audioSource.add(audio);
|
|
405
671
|
await audioSource.close();
|
|
406
672
|
} else {
|
|
407
673
|
await output.start();
|
|
@@ -409,13 +675,16 @@ async function exportFrames(movie, options = {}) {
|
|
|
409
675
|
movie.app.ticker.stop();
|
|
410
676
|
const keyframeIntervalFrames = Math.max(1, Math.round(2 * movie.frameRate));
|
|
411
677
|
try {
|
|
412
|
-
|
|
678
|
+
const count = Math.max(1, range2.toFrame - range2.fromFrame);
|
|
679
|
+
for (let frame = range2.fromFrame; frame <= range2.toFrame; frame++) {
|
|
413
680
|
if (mb && blurCanvas) await movie._exposeFrame(frame, mb, blurCanvas, false);
|
|
414
681
|
else await movie.gotoFrame(frame, true);
|
|
415
|
-
|
|
682
|
+
if (small && smallCtx) smallCtx.drawImage(blurCanvas ?? stage, 0, 0, small.width, small.height);
|
|
683
|
+
const rel = frame - range2.fromFrame;
|
|
684
|
+
const isKey = rel === 0 || rel % keyframeIntervalFrames === 0;
|
|
416
685
|
const addOpts = isKey ? { keyFrame: true } : void 0;
|
|
417
|
-
await canvasSource.add(
|
|
418
|
-
const progress = Math.floor(
|
|
686
|
+
await canvasSource.add(rel / movie.frameRate, 1 / movie.frameRate, addOpts);
|
|
687
|
+
const progress = Math.floor(rel / count * 100);
|
|
419
688
|
movie.emit("progress", { progress, frame, totalFrames: movie.totalFrames });
|
|
420
689
|
}
|
|
421
690
|
await canvasSource.close();
|
|
@@ -867,16 +1136,16 @@ function expandTransitions(spec) {
|
|
|
867
1136
|
if (toUses > 1) {
|
|
868
1137
|
throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
|
|
869
1138
|
}
|
|
870
|
-
const tStart =
|
|
1139
|
+
const tStart = chunkLBJRXU7N_cjs.resolveAt(t.at, parentDuration);
|
|
871
1140
|
const tEnd = tStart + t.duration;
|
|
872
1141
|
const fromSeq = fromEntry.seq;
|
|
873
|
-
const fromStart =
|
|
1142
|
+
const fromStart = chunkLBJRXU7N_cjs.resolveAt(fromSeq.at, parentDuration);
|
|
874
1143
|
const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
|
|
875
1144
|
if (tStart < fromStart - EPS || tEnd > fromEnd + EPS) {
|
|
876
1145
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
|
|
877
1146
|
}
|
|
878
1147
|
const toSeq = toEntry.seq;
|
|
879
|
-
const toStart =
|
|
1148
|
+
const toStart = chunkLBJRXU7N_cjs.resolveAt(toSeq.at, parentDuration);
|
|
880
1149
|
const toEnd = toStart + (toSeq.duration ?? parentDuration);
|
|
881
1150
|
if (tStart < toStart - EPS || tEnd > toEnd + EPS) {
|
|
882
1151
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
|
|
@@ -1216,9 +1485,9 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1216
1485
|
onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
|
|
1217
1486
|
if (area === 0) onCanvas = "none";
|
|
1218
1487
|
}
|
|
1219
|
-
const keys =
|
|
1488
|
+
const keys = chunkLBJRXU7N_cjs.collectPropKeys(seq.spec);
|
|
1220
1489
|
const carried = carriers.some((c) => {
|
|
1221
|
-
const k =
|
|
1490
|
+
const k = chunkLBJRXU7N_cjs.collectPropKeys(c.spec);
|
|
1222
1491
|
return (c.spec.keyframes ?? []).length > 0 && (k.has("x") || k.has("y") || k.has("rotation") || k.has("scale"));
|
|
1223
1492
|
});
|
|
1224
1493
|
const moving = parentMoving || carried || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
|
|
@@ -1239,7 +1508,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1239
1508
|
const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
|
|
1240
1509
|
if (group) scene.set(info, group);
|
|
1241
1510
|
if (zero || seq.showsNothingYet) scaledToNothing.add(info);
|
|
1242
|
-
if (seq instanceof
|
|
1511
|
+
if (seq instanceof chunkETAIVOHP_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
|
|
1243
1512
|
});
|
|
1244
1513
|
};
|
|
1245
1514
|
walk2(root, "", false, true);
|
|
@@ -1300,10 +1569,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
|
|
|
1300
1569
|
const start = parentStart + (seq.at ?? 0);
|
|
1301
1570
|
const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
|
|
1302
1571
|
const span = end - start;
|
|
1303
|
-
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start +
|
|
1572
|
+
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkLBJRXU7N_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
|
|
1304
1573
|
const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
|
|
1305
1574
|
const inner = [];
|
|
1306
|
-
const isComp = seq instanceof
|
|
1575
|
+
const isComp = seq instanceof chunkETAIVOHP_cjs.CompositionSequence;
|
|
1307
1576
|
if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
|
|
1308
1577
|
entries.push({ row, inner, groupable: !isComp });
|
|
1309
1578
|
});
|
|
@@ -1851,7 +2120,7 @@ var Movie = class {
|
|
|
1851
2120
|
height: options.composition.height ?? this.height,
|
|
1852
2121
|
duration: options.composition.duration ?? this.duration
|
|
1853
2122
|
} : void 0;
|
|
1854
|
-
await
|
|
2123
|
+
await chunkLBJRXU7N_cjs.ensureFilterLibrary(seededComposition);
|
|
1855
2124
|
const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
|
|
1856
2125
|
const rootSeqSpec = {
|
|
1857
2126
|
type: "composition",
|
|
@@ -1861,7 +2130,7 @@ var Movie = class {
|
|
|
1861
2130
|
...userComposition
|
|
1862
2131
|
};
|
|
1863
2132
|
if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
|
|
1864
|
-
const composition = new
|
|
2133
|
+
const composition = new chunkETAIVOHP_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
|
|
1865
2134
|
await composition.build();
|
|
1866
2135
|
this._rootSequence = composition;
|
|
1867
2136
|
if (composition.target) root.addChild(composition.target);
|
|
@@ -2059,6 +2328,37 @@ var Movie = class {
|
|
|
2059
2328
|
})));
|
|
2060
2329
|
return new Blob([buildPdf(pages, { title: opts.title })], { type: "application/pdf" });
|
|
2061
2330
|
}
|
|
2331
|
+
async onionSkin(opts = {}) {
|
|
2332
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.onionSkin()", opts, ["from", "to", "count", "scale", "as", "motionBlur"]);
|
|
2333
|
+
this._requireReady("onionSkin");
|
|
2334
|
+
const from = opts.from ?? 0, to = opts.to ?? this.duration, count = opts.count ?? 8, scale = opts.scale ?? 1;
|
|
2335
|
+
if (!(Number.isInteger(count) && count >= 1 && count <= 64)) throw new Error(`pixi-effects: movie.onionSkin(): count must be a whole number from 1 to 64, got ${count}`);
|
|
2336
|
+
if (!(from >= 0 && to <= this.duration + 1e-9 && (count === 1 || from < to))) throw new Error(`pixi-effects: movie.onionSkin(): from must be before to, inside the movie (0 to ${this.duration} s), got ${from} to ${to}`);
|
|
2337
|
+
if (!(scale > 0 && scale <= 4)) throw new Error(`pixi-effects: movie.onionSkin(): scale must be above 0 and at most 4, got ${scale}`);
|
|
2338
|
+
const mb = this._blurFor(opts.motionBlur, "movie.onionSkin()");
|
|
2339
|
+
const blurred = mb ? this._blurCanvas() : null;
|
|
2340
|
+
const out = document.createElement("canvas");
|
|
2341
|
+
out.width = Math.max(1, Math.round(this.width * scale));
|
|
2342
|
+
out.height = Math.max(1, Math.round(this.height * scale));
|
|
2343
|
+
const g = out.getContext("2d");
|
|
2344
|
+
const alphas = onionAlphas(count);
|
|
2345
|
+
const frames = onionTimes(from, to, count).map((t) => clampFrame(t * this.frameRate, this.totalFrames));
|
|
2346
|
+
const back = this.currentFrame, wasAtPoster = this._atPoster;
|
|
2347
|
+
try {
|
|
2348
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2349
|
+
const f = frames[i];
|
|
2350
|
+
if (mb && blurred) await this._quietly(() => this._exposeFrame(f, mb, blurred, true));
|
|
2351
|
+
else await this._quietly(() => this.gotoFrame(f, true));
|
|
2352
|
+
g.globalAlpha = alphas[i];
|
|
2353
|
+
g.drawImage(blurred ?? this.app.canvas, 0, 0, out.width, out.height);
|
|
2354
|
+
}
|
|
2355
|
+
} finally {
|
|
2356
|
+
g.globalAlpha = 1;
|
|
2357
|
+
if (wasAtPoster) await this._showPoster();
|
|
2358
|
+
else await this._quietly(() => this.gotoFrame(back, true));
|
|
2359
|
+
}
|
|
2360
|
+
return encodeCanvas(out, "image/png", opts.as ?? "blob");
|
|
2361
|
+
}
|
|
2062
2362
|
async contactSheet(opts = {}) {
|
|
2063
2363
|
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as", "motionBlur"]);
|
|
2064
2364
|
this._requireReady("contactSheet");
|
|
@@ -2125,10 +2425,19 @@ var Movie = class {
|
|
|
2125
2425
|
* The soundtrack, measured: when every audio layer plays, how loud the mix is over time, and `issues`
|
|
2126
2426
|
* (limiting, inaudible layers, sounds cut off by the end). Use it to CHECK sound you cannot hear.
|
|
2127
2427
|
*/
|
|
2428
|
+
/**
|
|
2429
|
+
* The fonts of the text layers: `missing` lists layers none of whose `fontFamily` entries is available in this browser (they are
|
|
2430
|
+
* drawn in a fallback font), `failed` lists web fonts (`@font-face`) whose file could not be loaded.
|
|
2431
|
+
*/
|
|
2432
|
+
inspectFonts() {
|
|
2433
|
+
this._requireReady("inspectFonts");
|
|
2434
|
+
return inspectFonts(this._rootSequence);
|
|
2435
|
+
}
|
|
2128
2436
|
inspectAudio(opts = {}) {
|
|
2129
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.inspectAudio()", opts, ["window"]);
|
|
2437
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.inspectAudio()", opts, ["window", "scenes"]);
|
|
2130
2438
|
this._requireReady("inspectAudio");
|
|
2131
|
-
|
|
2439
|
+
const scenes = opts.scenes ?? namedScenes(this.timelineData().rows);
|
|
2440
|
+
return analyzeAudio(this.audioBuffer, this._audioSources, this._mixStats, this.duration, { ...opts, scenes });
|
|
2132
2441
|
}
|
|
2133
2442
|
/**
|
|
2134
2443
|
* Draw the stage now. PixiJS's CullerPlugin only culls inside `app.render()` (the ticker), and we render
|
|
@@ -2396,7 +2705,7 @@ var Movie = class {
|
|
|
2396
2705
|
return this.muted;
|
|
2397
2706
|
}
|
|
2398
2707
|
async render(options) {
|
|
2399
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio", "motionBlur"]);
|
|
2708
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio", "motionBlur", "range", "scale", "draft"]);
|
|
2400
2709
|
try {
|
|
2401
2710
|
return await this._quietly(() => exportFrames(this, options));
|
|
2402
2711
|
} catch (err) {
|
|
@@ -2607,6 +2916,7 @@ function clamp(v, lo, hi) {
|
|
|
2607
2916
|
return Math.max(lo, Math.min(hi, v));
|
|
2608
2917
|
}
|
|
2609
2918
|
function parseEase(name) {
|
|
2919
|
+
chunkLBJRXU7N_cjs.checkEase(name, "a preset");
|
|
2610
2920
|
return gsap.gsap.parseEase(name);
|
|
2611
2921
|
}
|
|
2612
2922
|
|
|
@@ -2629,17 +2939,17 @@ function orbit(opts) {
|
|
|
2629
2939
|
throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
|
|
2630
2940
|
}
|
|
2631
2941
|
const [cx, cy] = opts.center ?? [width / 2, height / 2];
|
|
2632
|
-
const fixedR = opts.radius ??
|
|
2942
|
+
const fixedR = opts.radius ?? chunkLBJRXU7N_cjs.homeDistance(height, fov ?? chunkLBJRXU7N_cjs.DEFAULT_FOV);
|
|
2633
2943
|
const start = opts.start ?? -degrees / 2;
|
|
2634
2944
|
const steps = Math.max(1, Math.round(duration * stepsPerSecond));
|
|
2635
2945
|
const easeFn = parseEase(ease);
|
|
2636
2946
|
const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
|
|
2637
2947
|
const pos = (i) => {
|
|
2638
2948
|
const p = i / steps;
|
|
2639
|
-
const a = (start + degrees * easeFn(p)) *
|
|
2949
|
+
const a = (start + degrees * easeFn(p)) * chunkLBJRXU7N_cjs.DEG;
|
|
2640
2950
|
if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
|
|
2641
2951
|
const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
|
|
2642
|
-
const R =
|
|
2952
|
+
const R = chunkLBJRXU7N_cjs.homeDistance(height, f);
|
|
2643
2953
|
return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
|
|
2644
2954
|
};
|
|
2645
2955
|
const keyframes = [];
|
|
@@ -2694,7 +3004,7 @@ function particles(opts) {
|
|
|
2694
3004
|
const round3 = (v) => Math.round(v * 1e6) / 1e6;
|
|
2695
3005
|
const out = [];
|
|
2696
3006
|
for (let i = 0; i < count; i++) {
|
|
2697
|
-
const r =
|
|
3007
|
+
const r = chunkLBJRXU7N_cjs.random(seed * 1000003 + i * 7919 + 17);
|
|
2698
3008
|
const pick = ([a2, b]) => a2 + r() * (b - a2);
|
|
2699
3009
|
const born = at + r() * emit;
|
|
2700
3010
|
const lifetime = pick(life);
|
|
@@ -2707,7 +3017,7 @@ function particles(opts) {
|
|
|
2707
3017
|
const turn = spin ? pick(spin) : 0;
|
|
2708
3018
|
const startAngle = spin ? r() * 360 : 0;
|
|
2709
3019
|
const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
|
|
2710
|
-
const n0 = sway ?
|
|
3020
|
+
const n0 = sway ? chunkLBJRXU7N_cjs.noise(0, seed * 1000003 + i) : 0;
|
|
2711
3021
|
const at_ = (t) => {
|
|
2712
3022
|
const axis = (p0, v0, acc) => {
|
|
2713
3023
|
if (drag > 0) {
|
|
@@ -2716,7 +3026,7 @@ function particles(opts) {
|
|
|
2716
3026
|
}
|
|
2717
3027
|
return p0 + v0 * t + 0.5 * acc * t * t;
|
|
2718
3028
|
};
|
|
2719
|
-
const sw = sway ? swayAmp * (
|
|
3029
|
+
const sw = sway ? swayAmp * (chunkLBJRXU7N_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
|
|
2720
3030
|
return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
|
|
2721
3031
|
};
|
|
2722
3032
|
const kfs = [];
|
|
@@ -2778,7 +3088,7 @@ function followPath(opts) {
|
|
|
2778
3088
|
if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
|
|
2779
3089
|
const segments = [];
|
|
2780
3090
|
let total = 0;
|
|
2781
|
-
for (const line of
|
|
3091
|
+
for (const line of chunkLBJRXU7N_cjs.flattenSvgPath(d)) {
|
|
2782
3092
|
const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
|
|
2783
3093
|
for (let i = 2; i < p.length; i += 2) {
|
|
2784
3094
|
const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
|
|
@@ -2931,7 +3241,7 @@ function distances(n, from, grid, seed) {
|
|
|
2931
3241
|
if (n === 0) return [];
|
|
2932
3242
|
if (from === "random") {
|
|
2933
3243
|
const order = Array.from({ length: n }, (_, i) => i);
|
|
2934
|
-
const r =
|
|
3244
|
+
const r = chunkLBJRXU7N_cjs.random(seed);
|
|
2935
3245
|
for (let i = n - 1; i > 0; i--) {
|
|
2936
3246
|
const j = Math.floor(r() * (i + 1));
|
|
2937
3247
|
[order[i], order[j]] = [order[j], order[i]];
|
|
@@ -2993,7 +3303,7 @@ function wiggle(opts) {
|
|
|
2993
3303
|
to[n] = p.around;
|
|
2994
3304
|
return;
|
|
2995
3305
|
}
|
|
2996
|
-
const off = (
|
|
3306
|
+
const off = (chunkLBJRXU7N_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
|
|
2997
3307
|
to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
|
|
2998
3308
|
});
|
|
2999
3309
|
out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
|
|
@@ -3458,19 +3768,19 @@ function withFade(spec, opts) {
|
|
|
3458
3768
|
|
|
3459
3769
|
Object.defineProperty(exports, "noise", {
|
|
3460
3770
|
enumerable: true,
|
|
3461
|
-
get: function () { return
|
|
3771
|
+
get: function () { return chunkLBJRXU7N_cjs.noise; }
|
|
3462
3772
|
});
|
|
3463
3773
|
Object.defineProperty(exports, "rand", {
|
|
3464
3774
|
enumerable: true,
|
|
3465
|
-
get: function () { return
|
|
3775
|
+
get: function () { return chunkLBJRXU7N_cjs.rand; }
|
|
3466
3776
|
});
|
|
3467
3777
|
Object.defineProperty(exports, "random", {
|
|
3468
3778
|
enumerable: true,
|
|
3469
|
-
get: function () { return
|
|
3779
|
+
get: function () { return chunkLBJRXU7N_cjs.random; }
|
|
3470
3780
|
});
|
|
3471
3781
|
Object.defineProperty(exports, "registerSequenceType", {
|
|
3472
3782
|
enumerable: true,
|
|
3473
|
-
get: function () { return
|
|
3783
|
+
get: function () { return chunkLBJRXU7N_cjs.registerSequenceType; }
|
|
3474
3784
|
});
|
|
3475
3785
|
exports.Movie = Movie;
|
|
3476
3786
|
exports.animateText = animateText;
|