pixi-effects 0.46.0 → 0.47.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.
@@ -1,6 +1,6 @@
1
1
  import { CompositionSequence, maskSourceOf } from './chunk-FPV3NPYC.js';
2
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-DM2OLQOJ.js';
3
- import { analyzeAudio, fft, resampleThrough, measureLoudness, playSpan } from './chunk-K24FF4BT.js';
3
+ import { analyzeAudio, fft, resampleThrough, measureLoudness, playSpan } from './chunk-4SEL6W7H.js';
4
4
  import { usesAdvancedBlend, enableAdvancedBlend } from './chunk-VNP7QCJE.js';
5
5
  import { describeLayer } from './chunk-32MMNDDN.js';
6
6
  import { warnUnknownOptions, suggestName } from './chunk-BXOCEZ4U.js';
@@ -141,18 +141,86 @@ async function mixdown(audios, totalDuration, sampleRate = 44100, pace) {
141
141
  }
142
142
  var KNEE = 0.9;
143
143
  var LOUDNESS_CEILING_DB = -1;
144
+ var MAX_LIMIT_DB = 10;
145
+ function limitInto(source, out, gain, ceil, sampleRate) {
146
+ const n = source[0]?.length ?? 0;
147
+ const req = new Float32Array(n);
148
+ for (let i = 0; i < n; i++) {
149
+ let p = 0;
150
+ for (const c of source) {
151
+ const a = Math.abs(c[i]) * gain;
152
+ if (a > p) p = a;
153
+ }
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);
168
+ for (let i = 0; i < n; i++) {
169
+ run += mn[i];
170
+ if (i > L) run -= mn[i - L - 1];
171
+ const avg = run / Math.min(i + 1, L + 1);
172
+ const g = Math.min(avg, prev + (1 - prev) * release);
173
+ gains[i] = g;
174
+ prev = g;
175
+ if (g < lowest) {
176
+ lowest = g;
177
+ lowestAt = i;
178
+ }
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;
185
+ }
186
+ }
187
+ return { reductionDb: lowest < 1 ? -20 * Math.log10(lowest) : 0, at: lowestAt / sampleRate };
188
+ }
144
189
  function normalizeMix(buffer, target) {
145
190
  const chans = [];
146
191
  for (let ch = 0; ch < buffer.numberOfChannels; ch++) chans.push(buffer.getChannelData(ch));
147
- const m = measureLoudness(chans, buffer.sampleRate, { truePeak: false });
192
+ const sr = buffer.sampleRate;
193
+ const m = measureLoudness(chans, sr, { truePeak: false });
148
194
  if (m.integratedLufs === null) return { target, gainDb: 0, reachedLufs: null, limited: false };
195
+ const original = chans.map((c) => Float32Array.from(c));
149
196
  let gainDb = target - m.integratedLufs;
150
- const room = LOUDNESS_CEILING_DB - m.samplePeakDb;
151
- const limited = gainDb > room;
152
- if (limited) gainDb = room;
153
- const gain = 10 ** (gainDb / 20);
154
- for (const d of chans) for (let i = 0; i < d.length; i++) d[i] = d[i] * gain;
155
- return { target, gainDb: Math.round(gainDb * 10) / 10, reachedLufs: Math.round((m.integratedLufs + gainDb) * 10) / 10, limited };
197
+ 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;
201
+ 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;
208
+ }
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
+ }
215
+ const limited = measured.integratedLufs < target - 0.15;
216
+ const reduction = Math.round(applied.reductionDb * 10) / 10;
217
+ return {
218
+ target,
219
+ gainDb: Math.round(gainDb * 10) / 10,
220
+ reachedLufs: measured.integratedLufs,
221
+ limited,
222
+ ...reduction >= 0.1 ? { limiterDb: reduction, limiterAt: Math.round(applied.at * 100) / 100 } : {}
223
+ };
156
224
  }
157
225
  function limitMix(buffer, audios) {
158
226
  let peak = 0;
@@ -4446,20 +4514,25 @@ function animateText(text, style, options) {
4446
4514
  const pieces = splitText(text, style, { by, x, y, align, ...vertical ? { vertical } : {} });
4447
4515
  if (pieces.length === 0) return [];
4448
4516
  const lineHeight = measureText("M", style).height;
4449
- const inTween = options.in === false ? null : resolveTween(options.in ?? "rise", "in");
4450
- const outTween = options.out === void 0 || options.out === false ? null : resolveTween(options.out, "out");
4517
+ const given = (v) => v === false || v === "none" ? void 0 : v;
4518
+ const inSpec = options.in === void 0 ? "rise" : given(options.in), outSpec = given(options.out);
4519
+ const inTween = inSpec === void 0 ? null : resolveTween(inSpec, "in");
4520
+ const outTween = outSpec === void 0 ? null : resolveTween(outSpec, "out");
4451
4521
  const delays = stagger(pieces.length, options.stagger ?? { each: 0.04 });
4452
4522
  const wave = Math.max(...delays);
4453
- const need = (inTween?.duration ?? 0) + (outTween?.duration ?? 0) + wave;
4523
+ const outDelays = outTween?.stagger ? stagger(pieces.length, outTween.stagger) : delays;
4524
+ const outWave = Math.max(...outDelays);
4525
+ const lead = Math.max(...delays.map((d, i) => d - outDelays[i]));
4526
+ const need = (inTween?.duration ?? 0) + (outTween ? outTween.duration + outWave + lead : wave);
4454
4527
  if (duration < need - 1e-9) {
4455
4528
  throw new Error(`animateText(): duration ${duration} is too short for this wave: the entrance${outTween ? ", the exit" : ""} and the stagger need at least ${+need.toFixed(3)} s`);
4456
4529
  }
4457
4530
  const end = at + duration;
4458
4531
  if (outTween?.at !== void 0) {
4459
4532
  if (outTween.at < at - 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} is before the layer starts (at ${at}): the exit starts with the piece; use out.at \u2265 ${at}`);
4460
- const latest = end - outTween.duration - wave;
4461
- if (outTween.at > latest + 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} leaves after the layer ends (the exit and the wave take ${+(outTween.duration + wave).toFixed(3)} s and the layer ends at ${+end.toFixed(3)} s): use out.at \u2264 ${+latest.toFixed(3)}, or a longer duration`);
4462
- if (inTween && outTween.at - at < inTween.duration - 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} is before the entrance has finished (it takes ${inTween.duration} s from at ${at}): use out.at \u2265 ${+(at + inTween.duration).toFixed(3)}`);
4533
+ const latest = end - outTween.duration - outWave;
4534
+ if (outTween.at > latest + 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} leaves after the layer ends (the exit and the wave take ${+(outTween.duration + outWave).toFixed(3)} s and the layer ends at ${+end.toFixed(3)} s): use out.at \u2264 ${+latest.toFixed(3)}, or a longer duration`);
4535
+ if (inTween && outTween.at - at < inTween.duration + lead - 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} is before the entrance has finished (it takes ${inTween.duration} s from at ${at}): use out.at \u2265 ${+(at + inTween.duration + lead).toFixed(3)}`);
4463
4536
  }
4464
4537
  return pieces.map((p, i) => {
4465
4538
  const cx = p.cx ?? p.x + p.width / 2, cy = p.cy ?? p.y + lineHeight / 2;
@@ -4479,8 +4552,8 @@ function animateText(text, style, options) {
4479
4552
  }
4480
4553
  let idleEnd = len;
4481
4554
  if (outTween) {
4482
- const begin = outTween.at !== void 0 ? void 0 : outTween.duration + (wave - delays[i]);
4483
- const local = outTween.at !== void 0 ? Math.max(0, outTween.at - at) : -begin;
4555
+ 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;
4484
4557
  idleEnd = outTween.at !== void 0 ? local : len - begin;
4485
4558
  keyframes.push({
4486
4559
  at: local,
@@ -4516,6 +4589,10 @@ function resolveTween(spec, which) {
4516
4589
  if (typeof spec === "object" && spec) {
4517
4590
  for (const key of Object.keys(spec)) {
4518
4591
  if (TWEEN_KEYS.includes(key)) continue;
4592
+ if (key === "stagger") {
4593
+ if (which === "in") console.warn("pixi-effects: animateText(): in.stagger is ignored: the entrance wave is the options' `stagger` (only `out` can have a wave of its own: out: { stagger: 0 } leaves all at once)");
4594
+ continue;
4595
+ }
4519
4596
  if (which === "out" && key === "at") {
4520
4597
  if (typeof spec.at !== "number" || !Number.isFinite(spec.at)) console.warn(`pixi-effects: animateText(): out.at must be a number of seconds (the composition second the exit starts at), got ${JSON.stringify(spec.at)}; ignored`);
4521
4598
  continue;
@@ -4527,7 +4604,7 @@ function resolveTween(spec, which) {
4527
4604
  }
4528
4605
  if (t.preset !== void 0 && !(t.preset in PRESETS)) {
4529
4606
  const guess = suggestName(String(t.preset), PRESET_NAMES);
4530
- throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")})`);
4607
+ throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")}; for no ${which === "out" ? "exit: omit `out` or write out: false (the pieces stay until the layer ends)" : "entrance: in: false"})`);
4531
4608
  }
4532
4609
  const base = t.preset ? PRESETS[t.preset] : void 0;
4533
4610
  const from = { ...base?.from ?? {}, ...t.from ?? {} };
@@ -4536,11 +4613,14 @@ function resolveTween(spec, which) {
4536
4613
  for (const k of Object.keys(from)) {
4537
4614
  if (!known.has(k) && to[k] === void 0) throw new Error(`animateText(): ${which}.from has "${k}", which has no resting value; give ${which}.to: { ${k}: \u2026 }`);
4538
4615
  }
4539
- if (Object.keys(from).length === 0) throw new Error(`animateText(): ${which} needs a preset or a from`);
4616
+ if (Object.keys(from).length === 0) {
4617
+ throw new Error(`animateText(): ${which} needs a preset or a from${which === "out" && Object.keys(to).length ? ` \u2014 ${which} takes \`from\`: the state the piece leaves to (out: { from: { y: 18, alpha: 0 } }); \`to\` is only the resting value of a property that has none` : ""}`);
4618
+ }
4540
4619
  let ease = t.ease ?? base?.ease ?? "power2.out";
4541
4620
  if (which === "out" && t.ease === void 0) ease = ease.replace(".out", ".in");
4542
4621
  const at = which === "out" && typeof t.at === "number" && Number.isFinite(t.at) ? t.at : void 0;
4543
- return { from, to: { ...to }, duration: t.duration ?? base?.duration ?? 0.5, ease, ...at !== void 0 ? { at } : {} };
4622
+ const wave = which === "out" && t.stagger !== void 0 ? t.stagger === 0 ? { each: 0 } : t.stagger : void 0;
4623
+ return { from, to: { ...to }, duration: t.duration ?? base?.duration ?? 0.5, ease, ...at !== void 0 ? { at } : {}, ...wave ? { stagger: wave } : {} };
4544
4624
  }
4545
4625
 
4546
4626
  // src/presets/react.ts
@@ -4848,5 +4928,5 @@ function withFade(spec, opts) {
4848
4928
  }
4849
4929
 
4850
4930
  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 };
4851
- //# sourceMappingURL=chunk-2E74SP63.js.map
4852
- //# sourceMappingURL=chunk-2E74SP63.js.map
4931
+ //# sourceMappingURL=chunk-W46VWAZC.js.map
4932
+ //# sourceMappingURL=chunk-W46VWAZC.js.map