pixi-effects 0.3.0 → 0.5.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 (61) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +10 -6
  3. package/ai/SKILL.md +9 -4
  4. package/ai/reference/cheatsheet.md +19 -7
  5. package/ai/reference/pitfalls.md +32 -7
  6. package/ai/reference/recipes.md +117 -0
  7. package/ai/template.html +6 -3
  8. package/ai/tools/check.mjs +385 -0
  9. package/dist/{Base-BDw6dKRB.d.cts → Base-CPSFBkPm.d.ts} +12 -1
  10. package/dist/{Base-Ba4Ta4ap.d.ts → Base-DnRAxroQ.d.cts} +12 -1
  11. package/dist/Composition-63EXHNJJ.cjs +13 -0
  12. package/dist/{Composition-6FDH5OPM.cjs.map → Composition-63EXHNJJ.cjs.map} +1 -1
  13. package/dist/Composition-TUCRSMJU.js +4 -0
  14. package/dist/{Composition-FEYAUMFI.js.map → Composition-TUCRSMJU.js.map} +1 -1
  15. package/dist/Controller.cjs +37 -13
  16. package/dist/Controller.cjs.map +1 -1
  17. package/dist/Controller.d.cts +4 -2
  18. package/dist/Controller.d.ts +4 -2
  19. package/dist/Controller.js +37 -13
  20. package/dist/Controller.js.map +1 -1
  21. package/dist/{Movie-W8ISiEBB.d.cts → Movie-CuU_aaCm.d.cts} +56 -2
  22. package/dist/{Movie-DMpaT67V.d.ts → Movie-VyDiUYOj.d.ts} +56 -2
  23. package/dist/{chunk-ZL262ZRM.cjs → chunk-23M5QTWW.cjs} +70 -20
  24. package/dist/chunk-23M5QTWW.cjs.map +1 -0
  25. package/dist/{chunk-SIRVULFF.cjs → chunk-2RR43OIN.cjs} +256 -60
  26. package/dist/chunk-2RR43OIN.cjs.map +1 -0
  27. package/dist/{chunk-DIJG2RSF.js → chunk-5Q5XZG7Z.js} +59 -9
  28. package/dist/chunk-5Q5XZG7Z.js.map +1 -0
  29. package/dist/chunk-DKGG7WD4.js +61 -0
  30. package/dist/chunk-DKGG7WD4.js.map +1 -0
  31. package/dist/{chunk-PN5A6QA7.js → chunk-UBPZLGLU.js} +255 -61
  32. package/dist/chunk-UBPZLGLU.js.map +1 -0
  33. package/dist/chunk-UQBGW5DO.cjs +64 -0
  34. package/dist/chunk-UQBGW5DO.cjs.map +1 -0
  35. package/dist/index.cjs +336 -89
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +54 -5
  38. package/dist/index.d.ts +54 -5
  39. package/dist/index.js +320 -75
  40. package/dist/index.js.map +1 -1
  41. package/dist/sfx-MZJZXYY3.cjs +446 -0
  42. package/dist/sfx-MZJZXYY3.cjs.map +1 -0
  43. package/dist/sfx-S37DDC66.js +434 -0
  44. package/dist/sfx-S37DDC66.js.map +1 -0
  45. package/dist/three.cjs +5 -5
  46. package/dist/three.d.cts +2 -2
  47. package/dist/three.d.ts +2 -2
  48. package/dist/three.js +1 -1
  49. package/dist/{types-3c8Vymgw.d.ts → types-7VtTEKUW.d.cts} +102 -6
  50. package/dist/{types-3c8Vymgw.d.cts → types-7VtTEKUW.d.ts} +102 -6
  51. package/docs/api.md +36 -0
  52. package/docs/dsl.md +56 -4
  53. package/llms-full.txt +268 -21
  54. package/llms.txt +1 -1
  55. package/package.json +3 -1
  56. package/dist/Composition-6FDH5OPM.cjs +0 -13
  57. package/dist/Composition-FEYAUMFI.js +0 -4
  58. package/dist/chunk-DIJG2RSF.js.map +0 -1
  59. package/dist/chunk-PN5A6QA7.js.map +0 -1
  60. package/dist/chunk-SIRVULFF.cjs.map +0 -1
  61. package/dist/chunk-ZL262ZRM.cjs.map +0 -1
@@ -0,0 +1,446 @@
1
+ 'use strict';
2
+
3
+ var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
4
+
5
+ // src/audio/dsp.ts
6
+ function lcg(seed) {
7
+ let s = seed >>> 0;
8
+ return () => {
9
+ s = Math.imul(s, 1664525) + 1013904223 >>> 0;
10
+ return s / 2147483648 - 1;
11
+ };
12
+ }
13
+ var expSweep = (r, u) => typeof r === "number" ? r : r[0] * Math.pow(r[1] / r[0], u);
14
+ var linSweep = (r, u) => typeof r === "number" ? r : r[0] + (r[1] - r[0]) * u;
15
+ var smooth = (u) => u * u * (3 - 2 * u);
16
+ function envelope(env, u) {
17
+ switch (env.shape) {
18
+ case "bell":
19
+ return u < env.attack ? smooth(u / env.attack) : Math.pow(1 - (u - env.attack) / (1 - env.attack), 2);
20
+ case "fall":
21
+ return Math.min(1, u / env.attack) * Math.pow(1 - u, env.power);
22
+ case "swell":
23
+ return Math.pow(u, env.power) * (u > 0.97 ? (1 - u) / 0.03 : 1);
24
+ case "hold":
25
+ return 1;
26
+ }
27
+ }
28
+ var Svf = class {
29
+ constructor(type, q, sampleRate, oversample) {
30
+ this.type = type;
31
+ this.sampleRate = sampleRate;
32
+ this.oversample = oversample;
33
+ this.q = 1 / Math.max(0.7, q);
34
+ }
35
+ type;
36
+ sampleRate;
37
+ oversample;
38
+ low = 0;
39
+ band = 0;
40
+ q;
41
+ /** Whether a filter sweeping over `freq` needs the oversampled form at this rate. */
42
+ static needsOversampling(freq, sampleRate) {
43
+ const top = typeof freq === "number" ? freq : Math.max(freq[0], freq[1]);
44
+ return top > sampleRate / 6;
45
+ }
46
+ process(x, fc) {
47
+ if (this.oversample === 1) {
48
+ const f2 = 2 * Math.sin(Math.PI * Math.min(fc, this.sampleRate * 0.45) / this.sampleRate);
49
+ return this.step(x, f2);
50
+ }
51
+ const f = 2 * Math.sin(Math.PI * Math.min(fc, this.sampleRate * 0.3) / (2 * this.sampleRate));
52
+ this.step(x, f);
53
+ return this.step(x, f);
54
+ }
55
+ step(x, f) {
56
+ this.low += f * this.band;
57
+ const high = x - this.low - this.q * this.band;
58
+ this.band += f * high;
59
+ return this.type === "highpass" ? high : this.type === "lowpass" ? this.low : this.band;
60
+ }
61
+ };
62
+ function polyBlep(t, dt) {
63
+ if (t < dt) {
64
+ t /= dt;
65
+ return t + t - t * t - 1;
66
+ }
67
+ if (t > 1 - dt) {
68
+ t = (t - 1) / dt;
69
+ return t * t + t + t + 1;
70
+ }
71
+ return 0;
72
+ }
73
+ function oscillator(wave, phase, dt) {
74
+ switch (wave) {
75
+ case "sine":
76
+ return Math.sin(2 * Math.PI * phase);
77
+ case "triangle":
78
+ return 1 - 4 * Math.abs(phase - 0.5);
79
+ case "saw":
80
+ return 2 * phase - 1 - polyBlep(phase, dt);
81
+ case "square":
82
+ return (phase < 0.5 ? 1 : -1) + polyBlep(phase, dt) - polyBlep((phase + 0.5) % 1, dt);
83
+ }
84
+ }
85
+ var EDGE_FADE = 3e-3;
86
+ function renderVoice(left, right, v, sampleRate, noise) {
87
+ const n = left.length;
88
+ const i0 = Math.max(0, Math.min(n, Math.round((v.from ?? 0) * n)));
89
+ const i1 = Math.max(i0, Math.min(n, Math.round((v.to ?? 1) * n)));
90
+ const span = i1 - i0;
91
+ if (span < 2) return;
92
+ const filter = v.filter ? new Svf(v.filter.type, v.filter.q, sampleRate, Svf.needsOversampling(v.filter.freq, sampleRate) ? 2 : 1) : null;
93
+ const fade = Math.max(1, Math.min(Math.round(EDGE_FADE * sampleRate), Math.floor(span / 2)));
94
+ let phase = 0;
95
+ for (let j = 0; j < span; j++) {
96
+ const u = j / (span - 1);
97
+ let x;
98
+ if (v.wave === "noise") {
99
+ x = noise();
100
+ } else {
101
+ const dt = Math.min(expSweep(v.freq ?? 440, u), 0.45 * sampleRate) / sampleRate;
102
+ x = oscillator(v.wave, phase, dt);
103
+ phase += dt;
104
+ if (phase >= 1) phase -= Math.floor(phase);
105
+ }
106
+ if (filter) x = filter.process(x, expSweep(v.filter.freq, u));
107
+ x *= envelope(v.env, u) * v.gain;
108
+ if (j < fade) x *= j / fade;
109
+ if (span - 1 - j < fade) x *= (span - 1 - j) / fade;
110
+ const a = (linSweep(v.pan ?? 0, u) + 1) * Math.PI / 4;
111
+ left[i0 + j] += x * Math.cos(a);
112
+ right[i0 + j] += x * Math.sin(a);
113
+ }
114
+ }
115
+ function tilt(channel, b, sampleRate) {
116
+ const a = 1 - Math.exp(-2 * Math.PI * 1500 / sampleRate);
117
+ let low = 0;
118
+ for (let i = 0; i < channel.length; i++) {
119
+ const x = channel[i];
120
+ low += a * (x - low);
121
+ channel[i] = low + (1 + b) * (x - low);
122
+ }
123
+ const fade = Math.min(Math.round(EDGE_FADE * sampleRate), channel.length);
124
+ for (let k = 0; k < fade; k++) channel[channel.length - 1 - k] = channel[channel.length - 1 - k] * (k / fade);
125
+ }
126
+
127
+ // src/audio/presets.ts
128
+ var fall = (attack, power) => ({ shape: "fall", attack, power });
129
+ var hold = { shape: "hold" };
130
+ var PRESETS = {
131
+ // UI click / tick: a bright noise snap with a short tonal tick
132
+ click: { length: 0.04, peak: 0.05, level: 0.8, seed: 101, voices: ({ p }) => [
133
+ { wave: "noise", filter: { type: "highpass", freq: 2500 * p, q: 0.7 }, env: fall(0.05, 4), gain: 1 },
134
+ { wave: "sine", freq: 1800 * p, to: 0.6, env: fall(0.05, 4), gain: 0.6 }
135
+ ] },
136
+ // an element popping in: a fast upward sine blip
137
+ pop: { length: 0.12, peak: 0.05, level: 1, seed: 202, voices: ({ p }) => [
138
+ { wave: "sine", freq: [300 * p, 1100 * p], env: fall(0.02, 3), gain: 1 },
139
+ { wave: "noise", filter: { type: "bandpass", freq: 2e3 * p, q: 1 }, to: 0.15, env: fall(0.1, 2), gain: 0.25 }
140
+ ] },
141
+ // APPROVED by the owner ("air"): band-passed noise sweeping 500 → 5200 Hz, bell envelope, moving left → right
142
+ swoosh: { length: 0.5, peak: 0.32, level: 1, seed: 6839, voices: ({ p }) => [
143
+ { wave: "noise", filter: { type: "bandpass", freq: [500 * p, 5200 * p], q: 1.3 }, env: { shape: "bell", attack: 0.32 }, pan: [-0.6, 0.6], gain: 3.2 }
144
+ ] },
145
+ // quick UI swipe (candidate "swipe"): short, bright, hard front
146
+ swipe: { length: 0.22, peak: 0.04, level: 0.8, seed: 7816, voices: ({ p }) => [
147
+ { wave: "noise", filter: { type: "highpass", freq: [2200 * p, 9e3 * p], q: 0.9 }, env: fall(0.04, 3), pan: [-0.3, 0.3], gain: 1.6 }
148
+ ] },
149
+ // impact / thud for a slam: a falling low sine plus a burst of dark noise
150
+ hit: { length: 0.7, peak: 0.01, level: 1, seed: 303, voices: ({ p }) => [
151
+ { wave: "sine", freq: [140 * p, 42 * p], env: fall(5e-3, 4), gain: 1 },
152
+ { wave: "noise", filter: { type: "lowpass", freq: [4e3 * p, 200 * p], q: 0.7 }, to: 0.3, env: fall(0.02, 2), gain: 0.6 }
153
+ ] },
154
+ // tension build cut at its top (candidate "riser", reverse-cymbal style): a hit should follow it
155
+ riser: { length: 1, peak: 0.97, level: 0.9, seed: 8793, voices: ({ p }) => [
156
+ { wave: "noise", filter: { type: "bandpass", freq: [300 * p, 6500 * p], q: 0.8 }, env: { shape: "swell", power: 2.2 }, gain: 3 }
157
+ ] },
158
+ // success / ding: a bell (inharmonic partials, the higher ones die sooner)
159
+ chime: { length: 1.4, peak: 0, level: 0.8, seed: 404, voices: ({ p }) => [
160
+ [1, 1, 1],
161
+ [2.76, 0.45, 0.7],
162
+ [5.4, 0.25, 0.45],
163
+ [8.93, 0.12, 0.3]
164
+ ].map(([ratio, gain, to]) => ({ wave: "sine", freq: 1046.5 * p * ratio, to, env: fall(2e-3, 3), gain })) },
165
+ // countdown / alert beep: a steady soft square tone
166
+ beep: { length: 0.16, peak: 0.5, level: 0.5, seed: 505, voices: ({ p }) => [
167
+ { wave: "square", freq: 880 * p, filter: { type: "lowpass", freq: 3e3 * p, q: 0.7 }, env: hold, gain: 1 }
168
+ ] },
169
+ // collect / reward: two quick square notes (B5 → E6)
170
+ coin: { length: 0.4, peak: 0.2, level: 0.6, seed: 606, voices: ({ p }) => [
171
+ { wave: "square", freq: 987.77 * p, to: 0.18, env: hold, gain: 0.8 },
172
+ { wave: "square", freq: 1318.51 * p, from: 0.18, env: fall(0.01, 2), gain: 1 }
173
+ ] },
174
+ // digital glitch: a seeded run of short square / noise bursts at random pitches
175
+ glitch: { length: 0.35, peak: 0, level: 0.5, seed: 707, voices: ({ p, rand }) => {
176
+ const out = [];
177
+ for (let t = 0; t < 0.95; ) {
178
+ const to = Math.min(1, t + 0.04 + rand() * 0.12);
179
+ out.push({
180
+ wave: rand() < 0.3 ? "noise" : "square",
181
+ freq: (80 + rand() * 1600) * p,
182
+ from: t,
183
+ to,
184
+ env: hold,
185
+ pan: rand() * 1.2 - 0.6,
186
+ gain: 0.5 + rand() * 0.5
187
+ });
188
+ t = to + rand() * 0.05;
189
+ }
190
+ return out;
191
+ } },
192
+ // one typewriter key: a resonant noise click plus a low thump; `seed` changes the key
193
+ typewriter: { length: 0.06, peak: 0.03, level: 0.8, seed: 808, voices: ({ p, rand }) => [
194
+ { wave: "noise", filter: { type: "bandpass", freq: (2200 + rand() * 1600) * p, q: 2.5 }, env: fall(0.03, 4), gain: 1 },
195
+ { wave: "sine", freq: (150 + rand() * 40) * p, env: fall(0.03, 4), gain: 0.5 }
196
+ ] }
197
+ };
198
+ var SFX_PRESETS = Object.keys(PRESETS);
199
+
200
+ // src/audio/sfx.ts
201
+ var SFX_PEAK = 0.25;
202
+ var MIN_LENGTH = 0.02;
203
+ var MAX_LENGTH = 10;
204
+ var MAX_PITCH = 24;
205
+ var MAX_VOICES = 16;
206
+ var CUSTOM_LENGTH = 0.5;
207
+ var OPTION_KEYS = ["preset", "voices", "pitch", "brightness", "seed"];
208
+ var VOICE_KEYS = ["wave", "freq", "filter", "envelope", "pan", "from", "to", "gain"];
209
+ var WAVES = ["sine", "triangle", "square", "saw", "noise"];
210
+ var FILTERS = ["lowpass", "highpass", "bandpass"];
211
+ var ENVELOPES = {
212
+ fall: { shape: "fall", attack: 0.02, power: 3 },
213
+ bell: { shape: "bell", attack: 0.3 },
214
+ swell: { shape: "swell", power: 2 },
215
+ hold: { shape: "hold" }
216
+ };
217
+ var LAYER_KEYS = ["duration", "length", "volume", "at", "loop"];
218
+ var SFX_ALIASES = {
219
+ whoosh: "swoosh",
220
+ swish: "swoosh",
221
+ woosh: "swoosh",
222
+ transition: "swoosh",
223
+ flick: "swipe",
224
+ ding: "chime",
225
+ bell: "chime",
226
+ success: "chime",
227
+ impact: "hit",
228
+ thud: "hit",
229
+ boom: "hit",
230
+ slam: "hit",
231
+ kick: "hit",
232
+ tick: "click",
233
+ tap: "click",
234
+ ui: "click",
235
+ blip: "beep",
236
+ alert: "beep",
237
+ countdown: "beep",
238
+ key: "typewriter",
239
+ keystroke: "typewriter",
240
+ typing: "typewriter",
241
+ type: "typewriter",
242
+ rise: "riser",
243
+ uplifter: "riser",
244
+ build: "riser",
245
+ sparkle: "coin",
246
+ collect: "coin",
247
+ reward: "coin",
248
+ static: "glitch",
249
+ error: "glitch",
250
+ bubble: "pop"
251
+ };
252
+ var WORD_ALIASES = {
253
+ waveform: "wave",
254
+ frequency: "freq",
255
+ hz: "freq",
256
+ env: "envelope",
257
+ volume: "gain",
258
+ level: "gain",
259
+ bright: "brightness",
260
+ tone: "brightness",
261
+ semitones: "pitch",
262
+ sawtooth: "saw",
263
+ white: "noise",
264
+ lp: "lowpass",
265
+ hp: "highpass",
266
+ bp: "bandpass",
267
+ decay: "fall",
268
+ fade: "fall",
269
+ attack: "bell",
270
+ rise: "swell",
271
+ sustain: "hold"
272
+ };
273
+ var defaultWarn = (m) => console.warn(m);
274
+ var guess = (name, valid) => {
275
+ const alias = WORD_ALIASES[name.toLowerCase()];
276
+ return alias && valid.includes(alias) ? alias : chunkUQBGW5DO_cjs.suggestName(name, valid);
277
+ };
278
+ var didYouMean = (g) => g ? ` \u2014 did you mean "${g}"?` : "";
279
+ var clamp = (x, lo, hi) => Math.min(hi, Math.max(lo, x));
280
+ function sfxDefaultLength(preset) {
281
+ return PRESETS[preset].length;
282
+ }
283
+ function range(v, lo, hi, fallback, what, warn) {
284
+ if (v === void 0) return fallback;
285
+ const ok = (x) => typeof x === "number" && Number.isFinite(x);
286
+ if (ok(v)) return clamp(v, lo, hi);
287
+ if (Array.isArray(v) && v.length === 2 && ok(v[0]) && ok(v[1])) return [clamp(v[0], lo, hi), clamp(v[1], lo, hi)];
288
+ warn(`pixi-effects: ${what} must be a number or [from, to] (got ${JSON.stringify(v)}); using ${JSON.stringify(fallback)}`);
289
+ return fallback;
290
+ }
291
+ function resolveVoice(raw, where, warn) {
292
+ if (!raw || typeof raw !== "object") {
293
+ warn(`pixi-effects: ${where} must be an object like { wave: 'noise', filter: { type: 'bandpass', freq: [500, 5000] } }. The voice is skipped.`);
294
+ return null;
295
+ }
296
+ const v = raw;
297
+ chunkUQBGW5DO_cjs.warnUnknownOptions(where, v, VOICE_KEYS);
298
+ if (!WAVES.includes(String(v.wave))) {
299
+ warn(`pixi-effects: ${where}: unknown wave "${v.wave}"${didYouMean(guess(String(v.wave), WAVES))} (waves: ${WAVES.join(", ")}). The voice is skipped.`);
300
+ return null;
301
+ }
302
+ const num = (x, d) => typeof x === "number" && Number.isFinite(x) ? x : d;
303
+ const from = clamp(num(v.from, 0), 0, 1), to = clamp(num(v.to, 1), 0, 1);
304
+ if (!(from < to)) {
305
+ warn(`pixi-effects: ${where}: from (${from}) must be less than to (${to}): the voice would play for no time. The voice is skipped.`);
306
+ return null;
307
+ }
308
+ if (!(num(v.gain, 1) > 0)) {
309
+ warn(`pixi-effects: ${where}: gain is ${JSON.stringify(v.gain)}, which is silent (use a positive number; the default is 1). The voice is skipped.`);
310
+ return null;
311
+ }
312
+ const out = {
313
+ wave: v.wave,
314
+ freq: range(v.freq, 20, 2e4, 440, `${where}.freq`, warn),
315
+ env: ENVELOPES.fall,
316
+ pan: range(v.pan, -1, 1, 0, `${where}.pan`, warn),
317
+ from,
318
+ to,
319
+ gain: Math.max(0, num(v.gain, 1))
320
+ };
321
+ if (v.envelope !== void 0) {
322
+ const e = String(v.envelope);
323
+ if (e in ENVELOPES) out.env = ENVELOPES[e];
324
+ else warn(`pixi-effects: ${where}: unknown envelope "${e}"${didYouMean(guess(e, Object.keys(ENVELOPES)))} (envelopes: ${Object.keys(ENVELOPES).join(", ")}); using "fall"`);
325
+ }
326
+ if (v.filter !== void 0) {
327
+ const f = v.filter;
328
+ const t = f && typeof f === "object" ? String(f.type) : String(f);
329
+ if (!f || typeof f !== "object" || !FILTERS.includes(t)) {
330
+ warn(`pixi-effects: ${where}.filter: unknown type "${t}"${didYouMean(guess(t, FILTERS))} (types: ${FILTERS.join(", ")}). No filter.`);
331
+ } else {
332
+ chunkUQBGW5DO_cjs.warnUnknownOptions(`${where}.filter`, f, ["type", "freq", "q"]);
333
+ out.filter = { type: t, freq: range(f.freq, 20, 2e4, 1e3, `${where}.filter.freq`, warn), q: Math.max(0.7, num(f.q, 0.7)) };
334
+ }
335
+ }
336
+ return out;
337
+ }
338
+ function resolveSfx(sfx, duration, who, warn = defaultWarn) {
339
+ const opts = typeof sfx === "string" ? { preset: sfx } : sfx;
340
+ if (!opts || typeof opts !== "object" || Array.isArray(opts)) {
341
+ warn(`pixi-effects: ${who}: sfx must be a preset name, { preset, pitch, brightness, seed } or { voices: [...] } (got ${JSON.stringify(sfx)}). The layer is silent.`);
342
+ return null;
343
+ }
344
+ const rest = {};
345
+ for (const [k, v] of Object.entries(opts)) {
346
+ if (LAYER_KEYS.includes(k)) warn(`pixi-effects: ${who}: "${k}" goes on the audio layer, not inside sfx (the sound's length is the layer's duration)`);
347
+ else rest[k] = v;
348
+ }
349
+ chunkUQBGW5DO_cjs.warnUnknownOptions(`${who} sfx`, rest, OPTION_KEYS);
350
+ let preset = null;
351
+ let voices;
352
+ if (opts.voices !== void 0) {
353
+ if (opts.preset !== void 0) warn(`pixi-effects: ${who}: sfx has both preset and voices \u2014 the voices play; remove one`);
354
+ const list = Array.isArray(opts.voices) ? opts.voices : [];
355
+ if (list.length > MAX_VOICES) warn(`pixi-effects: ${who}: sfx has ${list.length} voices; only the first ${MAX_VOICES} play`);
356
+ voices = list.slice(0, MAX_VOICES).map((v, i) => resolveVoice(v, `${who} sfx.voices[${i}]`, warn)).filter((v) => v !== null);
357
+ if (voices.length === 0) {
358
+ warn(`pixi-effects: ${who}: sfx.voices has no usable voice (example: { voices: [{ wave: 'noise', filter: { type: 'bandpass', freq: [500, 5000] }, envelope: 'bell' }] }). The layer is silent.`);
359
+ return null;
360
+ }
361
+ } else {
362
+ const name = String(opts.preset);
363
+ if (!SFX_PRESETS.includes(name)) {
364
+ const g = SFX_ALIASES[name.toLowerCase()] ?? chunkUQBGW5DO_cjs.suggestName(name, SFX_PRESETS);
365
+ warn(`pixi-effects: ${who}: unknown sfx preset "${name}"${didYouMean(g)} (presets: ${SFX_PRESETS.join(", ")}). The layer is silent.`);
366
+ return null;
367
+ }
368
+ preset = name;
369
+ }
370
+ const knob = (key, max, unit) => {
371
+ const x = Number(opts[key] ?? 0);
372
+ if (!Number.isFinite(x)) {
373
+ warn(`pixi-effects: ${who}: sfx ${key} must be a number${unit} (got ${JSON.stringify(opts[key])}); using 0`);
374
+ return 0;
375
+ }
376
+ if (Math.abs(x) > max) {
377
+ warn(`pixi-effects: ${who}: sfx ${key} ${x} is outside \xB1${max}${unit}; using ${Math.sign(x) * max}`);
378
+ return Math.sign(x) * max;
379
+ }
380
+ return x;
381
+ };
382
+ const pitch = knob("pitch", MAX_PITCH, " semitones");
383
+ const brightness = knob("brightness", 1, "");
384
+ const rawSeed = Number(opts.seed ?? 0);
385
+ const seed = Number.isFinite(rawSeed) ? Math.trunc(rawSeed) : 0;
386
+ if (opts.seed !== void 0 && !Number.isFinite(rawSeed)) {
387
+ warn(`pixi-effects: ${who}: seed must be a number (got ${JSON.stringify(opts.seed)}); using 0`);
388
+ }
389
+ const natural = preset ? PRESETS[preset].length : CUSTOM_LENGTH;
390
+ let given;
391
+ if (duration !== void 0) {
392
+ if (typeof duration === "number" && Number.isFinite(duration)) given = duration;
393
+ else warn(`pixi-effects: ${who}: duration must be a number of seconds (got ${JSON.stringify(duration)}); using the sound's own length, ${natural}s`);
394
+ }
395
+ let length = given ?? natural;
396
+ if (!(length >= MIN_LENGTH && length <= MAX_LENGTH)) {
397
+ const clamped = Number.isFinite(length) ? clamp(length, MIN_LENGTH, MAX_LENGTH) : natural;
398
+ warn(`pixi-effects: ${who}: an sfx lasts ${MIN_LENGTH}\u2013${MAX_LENGTH}s (got duration ${length}); using ${clamped}`);
399
+ length = clamped;
400
+ }
401
+ return { preset, ...voices ? { voices } : {}, pitch, brightness, seed, length };
402
+ }
403
+ function sfxKey(s) {
404
+ return `${s.preset ?? JSON.stringify(s.voices)}|${s.pitch}|${s.brightness}|${s.seed}|${s.length}`;
405
+ }
406
+ var noiseSeed = (base, seed, voice) => base + seed * 977 + voice * 7919 >>> 0;
407
+ var transpose = (r, p) => typeof r === "number" ? r * p : [r[0] * p, r[1] * p];
408
+ function renderSfx(s, sampleRate) {
409
+ const p = 2 ** (s.pitch / 12);
410
+ const def = s.preset ? PRESETS[s.preset] : null;
411
+ const base = def?.seed ?? 0;
412
+ const n = Math.max(2, Math.round(s.length * sampleRate));
413
+ const left = new Float32Array(n);
414
+ const right = new Float32Array(n);
415
+ const structure = lcg(noiseSeed(base, s.seed, 104729));
416
+ const voices = def ? def.voices({ p, rand: () => (structure() + 1) / 2 }) : s.voices.map((v) => ({ ...v, freq: transpose(v.freq ?? 440, p), ...v.filter ? { filter: { ...v.filter, freq: transpose(v.filter.freq, p) } } : {} }));
417
+ voices.forEach((v, i) => renderVoice(left, right, v, sampleRate, lcg(noiseSeed(base, s.seed, i))));
418
+ if (s.brightness !== 0) {
419
+ tilt(left, s.brightness, sampleRate);
420
+ tilt(right, s.brightness, sampleRate);
421
+ }
422
+ let peak = 0;
423
+ for (let i = 0; i < n; i++) peak = Math.max(peak, Math.abs(left[i]), Math.abs(right[i]));
424
+ if (peak > 0) {
425
+ const scale = SFX_PEAK * (def?.level ?? 1) / peak;
426
+ for (let i = 0; i < n; i++) {
427
+ left[i] = left[i] * scale;
428
+ right[i] = right[i] * scale;
429
+ }
430
+ }
431
+ return [left, right];
432
+ }
433
+
434
+ exports.CUSTOM_LENGTH = CUSTOM_LENGTH;
435
+ exports.MAX_LENGTH = MAX_LENGTH;
436
+ exports.MAX_PITCH = MAX_PITCH;
437
+ exports.MAX_VOICES = MAX_VOICES;
438
+ exports.MIN_LENGTH = MIN_LENGTH;
439
+ exports.SFX_PEAK = SFX_PEAK;
440
+ exports.SFX_PRESETS = SFX_PRESETS;
441
+ exports.renderSfx = renderSfx;
442
+ exports.resolveSfx = resolveSfx;
443
+ exports.sfxDefaultLength = sfxDefaultLength;
444
+ exports.sfxKey = sfxKey;
445
+ //# sourceMappingURL=sfx-MZJZXYY3.cjs.map
446
+ //# sourceMappingURL=sfx-MZJZXYY3.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/audio/dsp.ts","../src/audio/presets.ts","../src/audio/sfx.ts"],"names":["f","suggestName","warnUnknownOptions"],"mappings":"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","file":"sfx-MZJZXYY3.cjs","sourcesContent":["/**\n * Sample-level building blocks for synthesised sound effects, ported from the generator the owner\n * approved by ear (scripts/sfx/swoosh-candidates.mjs): seeded white noise, a Chamberlin state-variable\n * filter whose centre frequency moves every sample, envelope curves, constant-power pan, 3 ms edge\n * fades. Pure functions over Float32Array: no Web Audio, no DOM, no Math.random — the same input\n * always gives the same samples.\n */\n\nexport type Wave = 'sine' | 'triangle' | 'square' | 'saw' | 'noise';\n/** A fixed value, or `[from, to]` swept over the voice (exponentially for Hz, linearly for pan). */\nexport type Range = number | readonly [number, number];\n\n/**\n * Level over a voice, as a function of u = 0…1 (the voice's own progress, so it stretches with the sound):\n * - `bell`: smooth rise until `attack`, then a quadratic fall to 0 (swoosh).\n * - `fall`: linear rise until `attack`, then `(1 − u) ^ power` (clicks, pops, hits, bells).\n * - `swell`: `u ^ power`, cut over the last 3 % (a riser that ends at full level).\n * - `hold`: full level (a beep). Every voice also fades over 3 ms at both ends, so none clicks.\n */\nexport type Envelope =\n | { shape: 'bell'; attack: number }\n | { shape: 'fall'; attack: number; power: number }\n | { shape: 'swell'; power: number }\n | { shape: 'hold' };\n\nexport interface Filter {\n type: 'lowpass' | 'highpass' | 'bandpass';\n /** Cutoff / centre in Hz; a pair sweeps exponentially. */\n freq: Range;\n /** Resonance. Minimum 0.7 (the filter's stable range). */\n q: number;\n}\n\nexport interface Voice {\n wave: Wave;\n /** Hz; a pair sweeps exponentially. Ignored for `'noise'`. */\n freq?: Range;\n filter?: Filter;\n env: Envelope;\n /** −1 (left) … 1 (right); a pair moves linearly. Default 0 (centre). */\n pan?: Range;\n /** Where in the sound this voice plays, as fractions 0–1 of the sound's length. Default 0 → 1. */\n from?: number;\n to?: number;\n /** Level relative to the other voices of the same sound. */\n gain: number;\n}\n\n/** The approved generator's PRNG (a 32-bit LCG): white noise in [-1, 1). Deterministic per seed. */\nexport function lcg(seed: number): () => number {\n let s = seed >>> 0;\n return () => {\n s = (Math.imul(s, 1664525) + 1013904223) >>> 0;\n return s / 2147483648 - 1;\n };\n}\n\nexport const expSweep = (r: Range, u: number): number =>\n typeof r === 'number' ? r : r[0] * Math.pow(r[1] / r[0], u);\nexport const linSweep = (r: Range, u: number): number =>\n typeof r === 'number' ? r : r[0] + (r[1] - r[0]) * u;\nexport const smooth = (u: number): number => u * u * (3 - 2 * u);\n\nexport function envelope(env: Envelope, u: number): number {\n switch (env.shape) {\n case 'bell': return u < env.attack ? smooth(u / env.attack) : Math.pow(1 - (u - env.attack) / (1 - env.attack), 2);\n case 'fall': return Math.min(1, u / env.attack) * Math.pow(1 - u, env.power);\n case 'swell': return Math.pow(u, env.power) * (u > 0.97 ? (1 - u) / 0.03 : 1);\n case 'hold': return 1;\n }\n}\n\n/**\n * Chamberlin state-variable filter, exactly as in the approved generator while the cutoff stays below\n * sampleRate / 6. Above that the plain form blows up, so a voice whose sweep goes higher runs it twice\n * per sample (2× oversampling, cutoff capped at 0.3 × sampleRate), which is stable for q ≥ 0.7.\n */\nexport class Svf {\n private low = 0;\n private band = 0;\n private readonly q: number;\n constructor(private readonly type: Filter['type'], q: number, private readonly sampleRate: number, private readonly oversample: 1 | 2) {\n this.q = 1 / Math.max(0.7, q);\n }\n\n /** Whether a filter sweeping over `freq` needs the oversampled form at this rate. */\n static needsOversampling(freq: Range, sampleRate: number): boolean {\n const top = typeof freq === 'number' ? freq : Math.max(freq[0], freq[1]);\n return top > sampleRate / 6;\n }\n\n process(x: number, fc: number): number {\n if (this.oversample === 1) {\n const f = 2 * Math.sin((Math.PI * Math.min(fc, this.sampleRate * 0.45)) / this.sampleRate);\n return this.step(x, f);\n }\n const f = 2 * Math.sin((Math.PI * Math.min(fc, this.sampleRate * 0.3)) / (2 * this.sampleRate));\n this.step(x, f);\n return this.step(x, f);\n }\n\n private step(x: number, f: number): number {\n this.low += f * this.band;\n const high = x - this.low - this.q * this.band;\n this.band += f * high;\n return this.type === 'highpass' ? high : this.type === 'lowpass' ? this.low : this.band;\n }\n}\n\nfunction polyBlep(t: number, dt: number): number {\n if (t < dt) { t /= dt; return t + t - t * t - 1; }\n if (t > 1 - dt) { t = (t - 1) / dt; return t * t + t + t + 1; }\n return 0;\n}\n\n/** One sample of a periodic `wave` at `phase` (0–1). `dt` = frequency / sampleRate (band-limits saw / square). */\nexport function oscillator(wave: Exclude<Wave, 'noise'>, phase: number, dt: number): number {\n switch (wave) {\n case 'sine': return Math.sin(2 * Math.PI * phase);\n case 'triangle': return 1 - 4 * Math.abs(phase - 0.5);\n case 'saw': return 2 * phase - 1 - polyBlep(phase, dt);\n case 'square': return (phase < 0.5 ? 1 : -1) + polyBlep(phase, dt) - polyBlep((phase + 0.5) % 1, dt);\n }\n}\n\n/** Seconds of linear fade at both ends of every voice (as in the approved generator). */\nexport const EDGE_FADE = 0.003;\n\n/** Add one voice into the stereo pair `left` / `right` (the whole sound). `noise` is this voice's own generator. */\nexport function renderVoice(left: Float32Array, right: Float32Array, v: Voice, sampleRate: number, noise: () => number): void {\n const n = left.length;\n const i0 = Math.max(0, Math.min(n, Math.round((v.from ?? 0) * n)));\n const i1 = Math.max(i0, Math.min(n, Math.round((v.to ?? 1) * n)));\n const span = i1 - i0;\n if (span < 2) return;\n const filter = v.filter\n ? new Svf(v.filter.type, v.filter.q, sampleRate, Svf.needsOversampling(v.filter.freq, sampleRate) ? 2 : 1)\n : null;\n const fade = Math.max(1, Math.min(Math.round(EDGE_FADE * sampleRate), Math.floor(span / 2)));\n let phase = 0;\n for (let j = 0; j < span; j++) {\n const u = j / (span - 1);\n let x: number;\n if (v.wave === 'noise') {\n x = noise();\n } else {\n const dt = Math.min(expSweep(v.freq ?? 440, u), 0.45 * sampleRate) / sampleRate;\n x = oscillator(v.wave, phase, dt);\n phase += dt;\n if (phase >= 1) phase -= Math.floor(phase);\n }\n if (filter) x = filter.process(x, expSweep(v.filter!.freq, u));\n x *= envelope(v.env, u) * v.gain;\n if (j < fade) x *= j / fade;\n if (span - 1 - j < fade) x *= (span - 1 - j) / fade;\n const a = ((linSweep(v.pan ?? 0, u) + 1) * Math.PI) / 4; // constant-power pan\n left[i0 + j] += x * Math.cos(a);\n right[i0 + j] += x * Math.sin(a);\n }\n}\n\n/**\n * Brightness as a tilt around 1.5 kHz: `out = low + (1 + b) × (x − low)`, `b` in [-1, 1]. −1 keeps only the\n * lows (dark, muffled), +1 doubles the highs (bright, crisp). In place; callers skip `b = 0` (so the default is exact).\n */\nexport function tilt(channel: Float32Array, b: number, sampleRate: number): void {\n const a = 1 - Math.exp((-2 * Math.PI * 1500) / sampleRate);\n let low = 0;\n for (let i = 0; i < channel.length; i++) {\n const x = channel[i]!;\n low += a * (x - low);\n channel[i] = low + (1 + b) * (x - low);\n }\n // the filter's memory would leave the last sample off zero: fade the end again\n const fade = Math.min(Math.round(EDGE_FADE * sampleRate), channel.length);\n for (let k = 0; k < fade; k++) channel[channel.length - 1 - k] = channel[channel.length - 1 - k]! * (k / fade);\n}\n","import type { Voice } from './dsp';\nimport type { SfxPreset } from '../types';\n\nexport interface PresetContext {\n /** Pitch ratio, `2 ** (pitch / 12)`. Multiply every frequency and cutoff by it. */\n p: number;\n /** Seeded random numbers in [0, 1) for the preset's own structure (glitch pattern, key jitter). */\n rand: () => number;\n}\n\nexport interface PresetDef {\n /** Length in seconds when the layer gives no `duration`. */\n length: number;\n /** Where the sound is loudest, as a fraction of its length — what to line up with the picture. */\n peak: number;\n /** Peak level relative to SFX_PEAK. Tuned by ear so the presets sound about equally loud. */\n level: number;\n /** Base noise seed (the take that was approved by ear); the layer's `seed` offsets it. */\n seed: number;\n voices(c: PresetContext): Voice[];\n}\n\nconst fall = (attack: number, power: number) => ({ shape: 'fall' as const, attack, power });\nconst hold = { shape: 'hold' as const };\n\nexport const PRESETS: Record<SfxPreset, PresetDef> = {\n // UI click / tick: a bright noise snap with a short tonal tick\n click: { length: 0.04, peak: 0.05, level: 0.8, seed: 101, voices: ({ p }) => [\n { wave: 'noise', filter: { type: 'highpass', freq: 2500 * p, q: 0.7 }, env: fall(0.05, 4), gain: 1 },\n { wave: 'sine', freq: 1800 * p, to: 0.6, env: fall(0.05, 4), gain: 0.6 },\n ] },\n // an element popping in: a fast upward sine blip\n pop: { length: 0.12, peak: 0.05, level: 1, seed: 202, voices: ({ p }) => [\n { wave: 'sine', freq: [300 * p, 1100 * p], env: fall(0.02, 3), gain: 1 },\n { wave: 'noise', filter: { type: 'bandpass', freq: 2000 * p, q: 1 }, to: 0.15, env: fall(0.1, 2), gain: 0.25 },\n ] },\n // APPROVED by the owner (\"air\"): band-passed noise sweeping 500 → 5200 Hz, bell envelope, moving left → right\n swoosh: { length: 0.5, peak: 0.32, level: 1, seed: 6839, voices: ({ p }) => [\n { wave: 'noise', filter: { type: 'bandpass', freq: [500 * p, 5200 * p], q: 1.3 }, env: { shape: 'bell', attack: 0.32 }, pan: [-0.6, 0.6], gain: 3.2 },\n ] },\n // quick UI swipe (candidate \"swipe\"): short, bright, hard front\n swipe: { length: 0.22, peak: 0.04, level: 0.8, seed: 7816, voices: ({ p }) => [\n { wave: 'noise', filter: { type: 'highpass', freq: [2200 * p, 9000 * p], q: 0.9 }, env: fall(0.04, 3), pan: [-0.3, 0.3], gain: 1.6 },\n ] },\n // impact / thud for a slam: a falling low sine plus a burst of dark noise\n hit: { length: 0.7, peak: 0.01, level: 1, seed: 303, voices: ({ p }) => [\n { wave: 'sine', freq: [140 * p, 42 * p], env: fall(0.005, 4), gain: 1 },\n { wave: 'noise', filter: { type: 'lowpass', freq: [4000 * p, 200 * p], q: 0.7 }, to: 0.3, env: fall(0.02, 2), gain: 0.6 },\n ] },\n // tension build cut at its top (candidate \"riser\", reverse-cymbal style): a hit should follow it\n riser: { length: 1, peak: 0.97, level: 0.9, seed: 8793, voices: ({ p }) => [\n { wave: 'noise', filter: { type: 'bandpass', freq: [300 * p, 6500 * p], q: 0.8 }, env: { shape: 'swell', power: 2.2 }, gain: 3 },\n ] },\n // success / ding: a bell (inharmonic partials, the higher ones die sooner)\n chime: { length: 1.4, peak: 0, level: 0.8, seed: 404, voices: ({ p }) => [\n [1, 1, 1], [2.76, 0.45, 0.7], [5.4, 0.25, 0.45], [8.93, 0.12, 0.3],\n ].map(([ratio, gain, to]) => ({ wave: 'sine' as const, freq: 1046.5 * p * ratio!, to, env: fall(0.002, 3), gain: gain! })) },\n // countdown / alert beep: a steady soft square tone\n beep: { length: 0.16, peak: 0.5, level: 0.5, seed: 505, voices: ({ p }) => [\n { wave: 'square', freq: 880 * p, filter: { type: 'lowpass', freq: 3000 * p, q: 0.7 }, env: hold, gain: 1 },\n ] },\n // collect / reward: two quick square notes (B5 → E6)\n coin: { length: 0.4, peak: 0.2, level: 0.6, seed: 606, voices: ({ p }) => [\n { wave: 'square', freq: 987.77 * p, to: 0.18, env: hold, gain: 0.8 },\n { wave: 'square', freq: 1318.51 * p, from: 0.18, env: fall(0.01, 2), gain: 1 },\n ] },\n // digital glitch: a seeded run of short square / noise bursts at random pitches\n glitch: { length: 0.35, peak: 0, level: 0.5, seed: 707, voices: ({ p, rand }) => {\n const out: Voice[] = [];\n for (let t = 0; t < 0.95;) {\n const to = Math.min(1, t + 0.04 + rand() * 0.12);\n out.push({ wave: rand() < 0.3 ? 'noise' : 'square', freq: (80 + rand() * 1600) * p, from: t, to,\n env: hold, pan: rand() * 1.2 - 0.6, gain: 0.5 + rand() * 0.5 });\n t = to + rand() * 0.05;\n }\n return out;\n } },\n // one typewriter key: a resonant noise click plus a low thump; `seed` changes the key\n typewriter: { length: 0.06, peak: 0.03, level: 0.8, seed: 808, voices: ({ p, rand }) => [\n { wave: 'noise', filter: { type: 'bandpass', freq: (2200 + rand() * 1600) * p, q: 2.5 }, env: fall(0.03, 4), gain: 1 },\n { wave: 'sine', freq: (150 + rand() * 40) * p, env: fall(0.03, 4), gain: 0.5 },\n ] },\n};\n\n/** Every preset name, in the order of the docs. (`PRESETS` is a `Record<SfxPreset, …>`, so a missing name is a type error.) */\nexport const SFX_PRESETS = Object.keys(PRESETS) as SfxPreset[];\n","import { lcg, renderVoice, tilt, type Envelope, type Filter, type Range, type Voice } from './dsp';\nimport { PRESETS, SFX_PRESETS } from './presets';\nimport { suggestName, warnUnknownOptions } from '../core/options';\nimport type { SfxPreset, SfxVoice } from '../types';\n\nexport { SFX_PRESETS };\n\n/** Peak of a sound at `volume: 1` (−12 dBFS), so several sounds can overlap before the mix has to limit. */\nexport const SFX_PEAK = 0.25;\nexport const MIN_LENGTH = 0.02;\nexport const MAX_LENGTH = 10;\nexport const MAX_PITCH = 24;\nexport const MAX_VOICES = 16;\n/** Length of a custom (`voices`) sound when the layer gives no `duration`. */\nexport const CUSTOM_LENGTH = 0.5;\n\n/** A validated sound: everything renderSfx needs. Same value ⇒ same samples (at a given sample rate). */\nexport interface ResolvedSfx {\n /** A preset, or null for a custom sound (then `voices` is set). */\n preset: SfxPreset | null;\n voices?: Voice[];\n pitch: number;\n brightness: number;\n seed: number;\n /** Seconds. */\n length: number;\n}\n\nconst OPTION_KEYS = ['preset', 'voices', 'pitch', 'brightness', 'seed'] as const;\nconst VOICE_KEYS = ['wave', 'freq', 'filter', 'envelope', 'pan', 'from', 'to', 'gain'] as const;\nconst WAVES = ['sine', 'triangle', 'square', 'saw', 'noise'] as const;\nconst FILTERS = ['lowpass', 'highpass', 'bandpass'] as const;\nconst ENVELOPES: Record<NonNullable<SfxVoice['envelope']>, Envelope> = {\n fall: { shape: 'fall', attack: 0.02, power: 3 },\n bell: { shape: 'bell', attack: 0.3 },\n swell: { shape: 'swell', power: 2 },\n hold: { shape: 'hold' },\n};\n/** Layer fields an author may put inside `sfx` by mistake. */\nconst LAYER_KEYS = ['duration', 'length', 'volume', 'at', 'loop'];\n\n/** Words authors reach for, mapped to the preset that makes that sound. */\nconst SFX_ALIASES: Record<string, SfxPreset> = {\n whoosh: 'swoosh', swish: 'swoosh', woosh: 'swoosh', transition: 'swoosh', flick: 'swipe',\n ding: 'chime', bell: 'chime', success: 'chime',\n impact: 'hit', thud: 'hit', boom: 'hit', slam: 'hit', kick: 'hit',\n tick: 'click', tap: 'click', ui: 'click',\n blip: 'beep', alert: 'beep', countdown: 'beep',\n key: 'typewriter', keystroke: 'typewriter', typing: 'typewriter', type: 'typewriter',\n rise: 'riser', uplifter: 'riser', build: 'riser',\n sparkle: 'coin', collect: 'coin', reward: 'coin',\n static: 'glitch', error: 'glitch', bubble: 'pop',\n};\n/** Other words authors reach for (option, wave, filter and envelope names). */\nconst WORD_ALIASES: Record<string, string> = {\n waveform: 'wave', frequency: 'freq', hz: 'freq', env: 'envelope', volume: 'gain', level: 'gain',\n bright: 'brightness', tone: 'brightness', semitones: 'pitch', sawtooth: 'saw', white: 'noise',\n lp: 'lowpass', hp: 'highpass', bp: 'bandpass', decay: 'fall', fade: 'fall', attack: 'bell', rise: 'swell', sustain: 'hold',\n};\n\ntype Warn = (message: string) => void;\nconst defaultWarn: Warn = m => console.warn(m);\n\nconst guess = (name: string, valid: readonly string[]): string | null => {\n const alias = WORD_ALIASES[name.toLowerCase()];\n return alias && valid.includes(alias) ? alias : suggestName(name, valid);\n};\nconst didYouMean = (g: string | null) => (g ? ` — did you mean \"${g}\"?` : '');\nconst clamp = (x: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, x));\n\nexport function sfxDefaultLength(preset: SfxPreset): number {\n return PRESETS[preset].length;\n}\n\n/** A number or a `[from, to]` pair, clamped to [lo, hi]; anything else → `fallback`, with a warning. */\nfunction range(v: unknown, lo: number, hi: number, fallback: Range, what: string, warn: Warn): Range {\n if (v === undefined) return fallback;\n const ok = (x: unknown): x is number => typeof x === 'number' && Number.isFinite(x);\n if (ok(v)) return clamp(v, lo, hi);\n if (Array.isArray(v) && v.length === 2 && ok(v[0]) && ok(v[1])) return [clamp(v[0], lo, hi), clamp(v[1], lo, hi)];\n warn(`pixi-effects: ${what} must be a number or [from, to] (got ${JSON.stringify(v)}); using ${JSON.stringify(fallback)}`);\n return fallback;\n}\n\n/** A custom voice as written by an author → the engine's voice, with every mistake named. Null = skip it. */\nfunction resolveVoice(raw: unknown, where: string, warn: Warn): Voice | null {\n if (!raw || typeof raw !== 'object') {\n warn(`pixi-effects: ${where} must be an object like { wave: 'noise', filter: { type: 'bandpass', freq: [500, 5000] } }. The voice is skipped.`);\n return null;\n }\n const v = raw as Record<string, unknown>;\n warnUnknownOptions(where, v, VOICE_KEYS);\n if (!(WAVES as readonly string[]).includes(String(v.wave))) {\n warn(`pixi-effects: ${where}: unknown wave \"${v.wave}\"${didYouMean(guess(String(v.wave), WAVES))} (waves: ${WAVES.join(', ')}). The voice is skipped.`);\n return null;\n }\n const num = (x: unknown, d: number) => (typeof x === 'number' && Number.isFinite(x) ? x : d);\n const from = clamp(num(v.from, 0), 0, 1), to = clamp(num(v.to, 1), 0, 1);\n if (!(from < to)) {\n warn(`pixi-effects: ${where}: from (${from}) must be less than to (${to}): the voice would play for no time. The voice is skipped.`);\n return null;\n }\n if (!(num(v.gain, 1) > 0)) {\n warn(`pixi-effects: ${where}: gain is ${JSON.stringify(v.gain)}, which is silent (use a positive number; the default is 1). The voice is skipped.`);\n return null;\n }\n const out: Voice = {\n wave: v.wave as Voice['wave'],\n freq: range(v.freq, 20, 20000, 440, `${where}.freq`, warn),\n env: ENVELOPES.fall,\n pan: range(v.pan, -1, 1, 0, `${where}.pan`, warn),\n from,\n to,\n gain: Math.max(0, num(v.gain, 1)),\n };\n if (v.envelope !== undefined) {\n const e = String(v.envelope);\n if (e in ENVELOPES) out.env = ENVELOPES[e as keyof typeof ENVELOPES];\n else warn(`pixi-effects: ${where}: unknown envelope \"${e}\"${didYouMean(guess(e, Object.keys(ENVELOPES)))} (envelopes: ${Object.keys(ENVELOPES).join(', ')}); using \"fall\"`);\n }\n if (v.filter !== undefined) {\n const f = v.filter as Record<string, unknown> | null;\n const t = f && typeof f === 'object' ? String(f.type) : String(f);\n if (!f || typeof f !== 'object' || !(FILTERS as readonly string[]).includes(t)) {\n warn(`pixi-effects: ${where}.filter: unknown type \"${t}\"${didYouMean(guess(t, FILTERS))} (types: ${FILTERS.join(', ')}). No filter.`);\n } else {\n warnUnknownOptions(`${where}.filter`, f, ['type', 'freq', 'q']);\n out.filter = { type: t as Filter['type'], freq: range(f.freq, 20, 20000, 1000, `${where}.filter.freq`, warn), q: Math.max(0.7, num(f.q, 0.7)) };\n }\n }\n return out;\n}\n\n/**\n * Check an author's `sfx` value and the layer's `duration`, warn about anything that will not do what\n * they meant, and return the sound to render — or null (the layer stays silent) when nothing usable is left.\n */\nexport function resolveSfx(sfx: unknown, duration: unknown, who: string, warn: Warn = defaultWarn): ResolvedSfx | null {\n const opts = (typeof sfx === 'string' ? { preset: sfx } : sfx) as Record<string, unknown> | null;\n if (!opts || typeof opts !== 'object' || Array.isArray(opts)) {\n warn(`pixi-effects: ${who}: sfx must be a preset name, { preset, pitch, brightness, seed } or { voices: [...] } (got ${JSON.stringify(sfx)}). The layer is silent.`);\n return null;\n }\n const rest: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(opts)) {\n if (LAYER_KEYS.includes(k)) warn(`pixi-effects: ${who}: \"${k}\" goes on the audio layer, not inside sfx (the sound's length is the layer's duration)`);\n else rest[k] = v;\n }\n warnUnknownOptions(`${who} sfx`, rest, OPTION_KEYS);\n\n let preset: SfxPreset | null = null;\n let voices: Voice[] | undefined;\n if (opts.voices !== undefined) {\n if (opts.preset !== undefined) warn(`pixi-effects: ${who}: sfx has both preset and voices — the voices play; remove one`);\n const list: unknown[] = Array.isArray(opts.voices) ? opts.voices : [];\n if (list.length > MAX_VOICES) warn(`pixi-effects: ${who}: sfx has ${list.length} voices; only the first ${MAX_VOICES} play`);\n voices = list.slice(0, MAX_VOICES).map((v, i) => resolveVoice(v, `${who} sfx.voices[${i}]`, warn)).filter((v): v is Voice => v !== null);\n if (voices.length === 0) {\n warn(`pixi-effects: ${who}: sfx.voices has no usable voice (example: { voices: [{ wave: 'noise', filter: { type: 'bandpass', freq: [500, 5000] }, envelope: 'bell' }] }). The layer is silent.`);\n return null;\n }\n } else {\n const name = String(opts.preset);\n if (!(SFX_PRESETS as string[]).includes(name)) {\n const g = SFX_ALIASES[name.toLowerCase()] ?? suggestName(name, SFX_PRESETS);\n warn(`pixi-effects: ${who}: unknown sfx preset \"${name}\"${didYouMean(g)} (presets: ${SFX_PRESETS.join(', ')}). The layer is silent.`);\n return null;\n }\n preset = name as SfxPreset;\n }\n\n const knob = (key: 'pitch' | 'brightness', max: number, unit: string): number => {\n const x = Number(opts[key] ?? 0);\n if (!Number.isFinite(x)) {\n warn(`pixi-effects: ${who}: sfx ${key} must be a number${unit} (got ${JSON.stringify(opts[key])}); using 0`);\n return 0;\n }\n if (Math.abs(x) > max) {\n warn(`pixi-effects: ${who}: sfx ${key} ${x} is outside ±${max}${unit}; using ${Math.sign(x) * max}`);\n return Math.sign(x) * max;\n }\n return x;\n };\n const pitch = knob('pitch', MAX_PITCH, ' semitones');\n const brightness = knob('brightness', 1, '');\n const rawSeed = Number(opts.seed ?? 0);\n const seed = Number.isFinite(rawSeed) ? Math.trunc(rawSeed) : 0;\n if (opts.seed !== undefined && !Number.isFinite(rawSeed)) {\n warn(`pixi-effects: ${who}: seed must be a number (got ${JSON.stringify(opts.seed)}); using 0`);\n }\n\n const natural = preset ? PRESETS[preset].length : CUSTOM_LENGTH;\n let given: number | undefined;\n if (duration !== undefined) {\n if (typeof duration === 'number' && Number.isFinite(duration)) given = duration;\n else warn(`pixi-effects: ${who}: duration must be a number of seconds (got ${JSON.stringify(duration)}); using the sound's own length, ${natural}s`);\n }\n let length = given ?? natural;\n if (!(length >= MIN_LENGTH && length <= MAX_LENGTH)) {\n const clamped = Number.isFinite(length) ? clamp(length, MIN_LENGTH, MAX_LENGTH) : natural;\n warn(`pixi-effects: ${who}: an sfx lasts ${MIN_LENGTH}–${MAX_LENGTH}s (got duration ${length}); using ${clamped}`);\n length = clamped;\n }\n return { preset, ...(voices ? { voices } : {}), pitch, brightness, seed, length };\n}\n\n/** Cache key: equal keys render equal samples. */\nexport function sfxKey(s: ResolvedSfx): string {\n return `${s.preset ?? JSON.stringify(s.voices)}|${s.pitch}|${s.brightness}|${s.seed}|${s.length}`;\n}\n\n/** Seed of voice `i`'s noise: the preset's approved take, offset by the layer's `seed`. */\nconst noiseSeed = (base: number, seed: number, voice: number) => (base + seed * 977 + voice * 7919) >>> 0;\n\nconst transpose = (r: Range, p: number): Range => (typeof r === 'number' ? r * p : [r[0] * p, r[1] * p]);\n\n/**\n * Stereo samples `[left, right]` of the sound at `sampleRate`. The louder channel peaks at exactly\n * SFX_PEAK × the preset's level (1 for a custom sound); both channels start and end at 0.\n */\nexport function renderSfx(s: ResolvedSfx, sampleRate: number): [Float32Array, Float32Array] {\n const p = 2 ** (s.pitch / 12);\n const def = s.preset ? PRESETS[s.preset] : null;\n const base = def?.seed ?? 0;\n const n = Math.max(2, Math.round(s.length * sampleRate));\n const left = new Float32Array(n);\n const right = new Float32Array(n);\n const structure = lcg(noiseSeed(base, s.seed, 104729));\n const voices: Voice[] = def\n ? def.voices({ p, rand: () => (structure() + 1) / 2 })\n : s.voices!.map(v => ({ ...v, freq: transpose(v.freq ?? 440, p), ...(v.filter ? { filter: { ...v.filter, freq: transpose(v.filter.freq, p) } } : {}) }));\n voices.forEach((v, i) => renderVoice(left, right, v, sampleRate, lcg(noiseSeed(base, s.seed, i))));\n if (s.brightness !== 0) {\n tilt(left, s.brightness, sampleRate);\n tilt(right, s.brightness, sampleRate);\n }\n let peak = 0;\n for (let i = 0; i < n; i++) peak = Math.max(peak, Math.abs(left[i]!), Math.abs(right[i]!));\n if (peak > 0) {\n const scale = (SFX_PEAK * (def?.level ?? 1)) / peak;\n for (let i = 0; i < n; i++) {\n left[i] = left[i]! * scale;\n right[i] = right[i]! * scale;\n }\n }\n return [left, right];\n}\n"]}