reframe-video 0.6.26 → 0.6.28

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/dist/cli.js CHANGED
@@ -6,6 +6,42 @@ import { tmpdir as tmpdir6 } from "node:os";
6
6
  import { basename, dirname as dirname7, join as join9, resolve as resolve6 } from "node:path";
7
7
 
8
8
  // ../core/src/ir.ts
9
+ var SFX_NAMES = [
10
+ "whoosh",
11
+ "swish",
12
+ "swoosh",
13
+ "rise",
14
+ "riser",
15
+ "warp",
16
+ "tick",
17
+ "click",
18
+ "blip",
19
+ "pop",
20
+ "select",
21
+ "thud",
22
+ "boom",
23
+ "knock",
24
+ "sub",
25
+ "chime",
26
+ "ding",
27
+ "coin",
28
+ "sparkle",
29
+ "shimmer",
30
+ "success",
31
+ "zap",
32
+ "error",
33
+ "glitch",
34
+ "static",
35
+ "scan",
36
+ "powerup",
37
+ "powerdown",
38
+ "snare",
39
+ "hat",
40
+ "bubble",
41
+ "notify",
42
+ "camera"
43
+ ];
44
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
9
45
  var DEFAULT_CROSSFADE = 0.5;
10
46
  var DEFAULT_TO_DURATION = 0.5;
11
47
  var DEFAULT_TWEEN_DURATION = 0.5;
@@ -656,7 +692,6 @@ function validateScene(ir) {
656
692
  }
657
693
  }
658
694
  }
659
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
660
695
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
661
696
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
662
697
  problems.push(
@@ -692,6 +727,10 @@ function validateScene(ir) {
692
727
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
693
728
  problems.push('audio.bgm: use either "file" or "synth", not both');
694
729
  }
730
+ const bgmSynth = ir.audio?.bgm?.synth;
731
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
732
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
733
+ }
695
734
  if (problems.length > 0) throw new SceneValidationError(problems);
696
735
  }
697
736
  var TRANSITIONS = ["cut", "crossfade"];
@@ -991,12 +1030,46 @@ var SET = 1 / 120;
991
1030
 
992
1031
  // ../core/src/audio.ts
993
1032
  var SFX_DURATION = {
1033
+ // transition
994
1034
  whoosh: 0.35,
995
- pop: 0.12,
996
- tick: 0.03,
1035
+ swish: 0.32,
1036
+ swoosh: 0.35,
997
1037
  rise: 0.5,
1038
+ riser: 0.85,
1039
+ warp: 0.5,
1040
+ // ui
1041
+ tick: 0.03,
1042
+ click: 0.05,
1043
+ blip: 0.1,
1044
+ pop: 0.12,
1045
+ select: 0.18,
1046
+ // impact
1047
+ thud: 0.25,
1048
+ boom: 0.6,
1049
+ knock: 0.14,
1050
+ sub: 0.7,
1051
+ // positive
1052
+ chime: 0.7,
1053
+ ding: 0.5,
1054
+ coin: 0.3,
1055
+ sparkle: 0.6,
998
1056
  shimmer: 0.9,
999
- thud: 0.25
1057
+ success: 0.6,
1058
+ // alert
1059
+ zap: 0.22,
1060
+ error: 0.4,
1061
+ // tech
1062
+ glitch: 0.3,
1063
+ static: 0.18,
1064
+ scan: 0.45,
1065
+ powerup: 0.4,
1066
+ powerdown: 0.5,
1067
+ // rhythm / foley
1068
+ snare: 0.18,
1069
+ hat: 0.05,
1070
+ bubble: 0.16,
1071
+ notify: 0.45,
1072
+ camera: 0.18
1000
1073
  };
1001
1074
  var FILE_CUE_DURATION = 0.4;
1002
1075
  function collectClipAudio(ir, duration, warnings) {
@@ -1056,7 +1129,11 @@ function resolveAudioPlan(compiled) {
1056
1129
  fadeIn: cue.fadeIn ?? 0,
1057
1130
  fadeOut: cue.fadeOut ?? 0,
1058
1131
  pan: cue.pan ?? 0,
1059
- source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
1132
+ source: cue.sfx ? (
1133
+ // auto-vary: default the seed to the cue's order so repeated sfx differ
1134
+ // (pitch/texture); an explicit params.seed always wins.
1135
+ { kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
1136
+ ) : { kind: "file", path: cue.file }
1060
1137
  });
1061
1138
  }
1062
1139
  cues.sort((a, b) => a.t - b.t);
@@ -1136,7 +1213,11 @@ function resolveCompositionAudioPlan(comp) {
1136
1213
  fadeIn: cue.fadeIn ?? 0,
1137
1214
  fadeOut: cue.fadeOut ?? 0,
1138
1215
  pan: cue.pan ?? 0,
1139
- source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
1216
+ source: cue.sfx ? (
1217
+ // auto-vary: default the seed to the cue's order so repeated sfx differ
1218
+ // (pitch/texture); an explicit params.seed always wins.
1219
+ { kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
1220
+ ) : { kind: "file", path: cue.file }
1140
1221
  });
1141
1222
  }
1142
1223
  if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
@@ -1240,19 +1321,23 @@ function hash01(n, seed) {
1240
1321
  var noise = (n, seed) => hash01(n, seed) * 2 - 1;
1241
1322
  var TAU = Math.PI * 2;
1242
1323
  var expDecay = (t, dur, k = 5) => Math.exp(-k * t / dur);
1324
+ var square = (ph) => (Math.sin(ph) + 0.33 * Math.sin(3 * ph) + 0.2 * Math.sin(5 * ph)) / 1.4;
1325
+ var PITCH_STEPS = [0, 2, 4, 7, 9, 12, 5, -3, 16, -5];
1326
+ function seedPitch(seed) {
1327
+ const i = (Math.round(seed) % PITCH_STEPS.length + PITCH_STEPS.length) % PITCH_STEPS.length;
1328
+ return Math.pow(2, PITCH_STEPS[i] / 12);
1329
+ }
1243
1330
  function buffer(duration) {
1244
1331
  const n = Math.round(duration * SAMPLE_RATE);
1245
1332
  return { out: new Float32Array(n), n };
1246
1333
  }
1247
- function whoosh(seed) {
1334
+ function whoosh(seed, pitch) {
1248
1335
  const dur = 0.35;
1249
1336
  const { out, n } = buffer(dur);
1250
- let lp = 0;
1251
- let lp2 = 0;
1337
+ let lp = 0, lp2 = 0;
1252
1338
  for (let i = 0; i < n; i++) {
1253
- const t = i / SAMPLE_RATE;
1254
- const u = t / dur;
1255
- const center = 1200 * Math.pow(300 / 1200, u);
1339
+ const t = i / SAMPLE_RATE, u = t / dur;
1340
+ const center = 1200 * pitch * Math.pow(0.25, u);
1256
1341
  const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
1257
1342
  lp += alpha * (noise(i, seed) - lp);
1258
1343
  lp2 += alpha * 0.5 * (lp - lp2);
@@ -1261,110 +1346,568 @@ function whoosh(seed) {
1261
1346
  }
1262
1347
  return out;
1263
1348
  }
1264
- function pop(seed) {
1349
+ function swish(seed, pitch) {
1350
+ const dur = 0.32;
1351
+ const { out, n } = buffer(dur);
1352
+ let lp = 0, lp2 = 0;
1353
+ for (let i = 0; i < n; i++) {
1354
+ const t = i / SAMPLE_RATE, u = t / dur;
1355
+ const center = 2600 * pitch * Math.pow(0.2, u);
1356
+ const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
1357
+ lp += alpha * (noise(i, seed) - lp);
1358
+ lp2 += alpha * 0.5 * (lp - lp2);
1359
+ const env = u < 0.15 ? u / 0.15 : expDecay(t - 0.15 * dur, dur * 0.85, 5);
1360
+ out[i] = (lp - lp2) * env * 2.4;
1361
+ }
1362
+ return out;
1363
+ }
1364
+ function rise(_seed, pitch) {
1365
+ const dur = 0.5;
1366
+ const { out, n } = buffer(dur);
1367
+ let phase = 0;
1368
+ for (let i = 0; i < n; i++) {
1369
+ const t = i / SAMPLE_RATE, u = t / dur;
1370
+ const freq = 220 * pitch * Math.pow(4, u);
1371
+ phase += TAU * freq / SAMPLE_RATE;
1372
+ const env = Math.sin(Math.PI * Math.min(1, u * 1.05)) ** 1.5;
1373
+ out[i] = (Math.sin(phase) + 0.3 * Math.sin(2 * phase)) * env * 0.45;
1374
+ }
1375
+ return out;
1376
+ }
1377
+ function riser(seed, pitch) {
1378
+ const dur = 0.85;
1379
+ const { out, n } = buffer(dur);
1380
+ let lp = 0, phase = 0;
1381
+ for (let i = 0; i < n; i++) {
1382
+ const t = i / SAMPLE_RATE, u = t / dur;
1383
+ const center = 200 * pitch * Math.pow(12, u);
1384
+ const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
1385
+ lp += alpha * (noise(i, seed) - lp);
1386
+ const freq = 120 * pitch * Math.pow(6, u);
1387
+ phase += TAU * freq / SAMPLE_RATE;
1388
+ const env = Math.pow(u, 1.6);
1389
+ out[i] = (lp * 1.6 + Math.sin(phase) * 0.5) * env * 0.9;
1390
+ }
1391
+ return out;
1392
+ }
1393
+ function warp(_seed, pitch) {
1394
+ const dur = 0.5;
1395
+ const { out, n } = buffer(dur);
1396
+ let phase = 0;
1397
+ for (let i = 0; i < n; i++) {
1398
+ const t = i / SAMPLE_RATE, u = t / dur;
1399
+ const bend = Math.sin(Math.PI * u);
1400
+ const vib = 1 + 0.4 * Math.sin(TAU * 18 * t);
1401
+ const freq = 300 * pitch * (1 + 2.5 * bend) * vib;
1402
+ phase += TAU * freq / SAMPLE_RATE;
1403
+ const env = Math.sin(Math.PI * u) ** 0.8;
1404
+ out[i] = square(phase) * env * 0.5;
1405
+ }
1406
+ return out;
1407
+ }
1408
+ function tick(seed, pitch) {
1409
+ const dur = 0.03;
1410
+ const { out, n } = buffer(dur);
1411
+ for (let i = 0; i < n; i++) {
1412
+ const t = i / SAMPLE_RATE;
1413
+ const sine = t < 4e-3 ? Math.sin(TAU * 4e3 * pitch * t) : 0;
1414
+ out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur, 8);
1415
+ }
1416
+ return out;
1417
+ }
1418
+ function click(seed, pitch) {
1419
+ const dur = 0.05;
1420
+ const { out, n } = buffer(dur);
1421
+ let lp = 0;
1422
+ for (let i = 0; i < n; i++) {
1423
+ const t = i / SAMPLE_RATE;
1424
+ lp += 0.5 * (noise(i, seed) - lp);
1425
+ const sine = Math.sin(TAU * 1500 * pitch * t);
1426
+ out[i] = (sine * 0.5 + lp * 0.6) * expDecay(t, dur, 11);
1427
+ }
1428
+ return out;
1429
+ }
1430
+ function blip(_seed, pitch) {
1431
+ const dur = 0.1;
1432
+ const { out, n } = buffer(dur);
1433
+ let phase = 0;
1434
+ for (let i = 0; i < n; i++) {
1435
+ const t = i / SAMPLE_RATE, u = t / dur;
1436
+ phase += TAU * 880 * pitch / SAMPLE_RATE;
1437
+ const env = Math.min(1, u * 12) * Math.min(1, (1 - u) * 6);
1438
+ out[i] = square(phase) * env * 0.5;
1439
+ }
1440
+ return out;
1441
+ }
1442
+ function pop(seed, pitch) {
1265
1443
  const dur = 0.12;
1266
1444
  const { out, n } = buffer(dur);
1267
1445
  let phase = 0;
1268
1446
  for (let i = 0; i < n; i++) {
1269
1447
  const t = i / SAMPLE_RATE;
1270
- const freq = 600 * Math.pow(150 / 600, t / 0.08);
1448
+ const freq = 600 * pitch * Math.pow(0.25, t / 0.08);
1271
1449
  phase += TAU * freq / SAMPLE_RATE;
1272
1450
  const transient = t < 2e-3 ? noise(i, seed) * 0.5 : 0;
1273
1451
  out[i] = (Math.sin(phase) + transient) * expDecay(t, dur, 6) * 0.8;
1274
1452
  }
1275
1453
  return out;
1276
1454
  }
1277
- function tick(seed) {
1278
- const dur = 0.03;
1455
+ function select(_seed, pitch) {
1456
+ const dur = 0.18;
1457
+ const { out, n } = buffer(dur);
1458
+ let phase = 0;
1459
+ for (let i = 0; i < n; i++) {
1460
+ const t = i / SAMPLE_RATE, u = t / dur;
1461
+ const freq = (t < 0.08 ? 620 : 930) * pitch;
1462
+ phase += TAU * freq / SAMPLE_RATE;
1463
+ const env = Math.min(1, u * 16) * Math.min(1, (1 - u) * 5);
1464
+ out[i] = (Math.sin(phase) + 0.25 * Math.sin(2 * phase)) * env * 0.5;
1465
+ }
1466
+ return out;
1467
+ }
1468
+ function thud(seed, pitch) {
1469
+ const dur = 0.25;
1279
1470
  const { out, n } = buffer(dur);
1471
+ let phase = 0, lp = 0;
1280
1472
  for (let i = 0; i < n; i++) {
1281
1473
  const t = i / SAMPLE_RATE;
1282
- const sine = t < 4e-3 ? Math.sin(TAU * 4e3 * t) : 0;
1283
- out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur, 8);
1474
+ const freq = 90 * pitch * Math.pow(0.5, t / 0.15);
1475
+ phase += TAU * freq / SAMPLE_RATE;
1476
+ lp += 0.02 * (noise(i, seed) - lp);
1477
+ const attack = t < 0.01 ? lp * 3 : 0;
1478
+ out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur, 5);
1284
1479
  }
1285
1480
  return out;
1286
1481
  }
1287
- function rise(seed) {
1482
+ function boom(seed, pitch) {
1483
+ const dur = 0.6;
1484
+ const { out, n } = buffer(dur);
1485
+ let phase = 0, lp = 0;
1486
+ for (let i = 0; i < n; i++) {
1487
+ const t = i / SAMPLE_RATE;
1488
+ const freq = 70 * pitch * Math.pow(0.5, t / 0.3);
1489
+ phase += TAU * freq / SAMPLE_RATE;
1490
+ lp += 0.06 * (noise(i, seed) - lp);
1491
+ const body = t < 0.06 ? lp * 2.5 * (1 - t / 0.06) : 0;
1492
+ out[i] = (Math.sin(phase) * 1 + body) * expDecay(t, dur, 3.2);
1493
+ }
1494
+ return out;
1495
+ }
1496
+ function knock(seed, pitch) {
1497
+ const dur = 0.14;
1498
+ const { out, n } = buffer(dur);
1499
+ let phase = 0, lp = 0;
1500
+ for (let i = 0; i < n; i++) {
1501
+ const t = i / SAMPLE_RATE;
1502
+ const freq = 220 * pitch * Math.pow(0.7, t / 0.05);
1503
+ phase += TAU * freq / SAMPLE_RATE;
1504
+ lp += 0.3 * (noise(i, seed) - lp);
1505
+ const tap = t < 5e-3 ? lp : 0;
1506
+ out[i] = (Math.sin(phase) * 0.8 + tap * 0.6) * expDecay(t, dur, 9);
1507
+ }
1508
+ return out;
1509
+ }
1510
+ function chime(seed, pitch) {
1511
+ const dur = 0.7;
1512
+ const { out, n } = buffer(dur);
1513
+ const f0 = 800 * pitch;
1514
+ const partials = [
1515
+ { f: f0, a: 1, k: 4 },
1516
+ { f: f0 * 2.76, a: 0.5, k: 5.5 },
1517
+ { f: f0 * 5.4, a: 0.28, k: 7 },
1518
+ { f: f0 * 8.9, a: 0.13, k: 9 }
1519
+ ];
1520
+ for (let i = 0; i < n; i++) {
1521
+ const t = i / SAMPLE_RATE;
1522
+ let s = 0;
1523
+ for (const p of partials) s += Math.sin(TAU * p.f * t) * p.a * expDecay(t, dur, p.k);
1524
+ const strike = t < 3e-3 ? noise(i, seed) * 0.3 : 0;
1525
+ out[i] = (s / 1.9 + strike) * 0.6;
1526
+ }
1527
+ return out;
1528
+ }
1529
+ function ding(_seed, pitch) {
1288
1530
  const dur = 0.5;
1289
1531
  const { out, n } = buffer(dur);
1290
- let phase = 0;
1532
+ const f0 = 1200 * pitch;
1291
1533
  for (let i = 0; i < n; i++) {
1292
1534
  const t = i / SAMPLE_RATE;
1293
- const u = t / dur;
1294
- const freq = 220 * Math.pow(880 / 220, u);
1535
+ const s = Math.sin(TAU * f0 * t) + 0.4 * Math.sin(TAU * f0 * 2 * t) + 0.2 * Math.sin(TAU * f0 * 3.01 * t);
1536
+ out[i] = s / 1.6 * expDecay(t, dur, 4.5) * 0.6;
1537
+ }
1538
+ return out;
1539
+ }
1540
+ function coin(_seed, pitch) {
1541
+ const dur = 0.3;
1542
+ const { out, n } = buffer(dur);
1543
+ let phase = 0;
1544
+ for (let i = 0; i < n; i++) {
1545
+ const t = i / SAMPLE_RATE, u = t / dur;
1546
+ const freq = (t < 0.06 ? 988 : 1319) * pitch;
1295
1547
  phase += TAU * freq / SAMPLE_RATE;
1296
- const env = Math.sin(Math.PI * Math.min(1, u * 1.05)) ** 1.5;
1297
- out[i] = (Math.sin(phase) + 0.3 * Math.sin(2 * phase)) * env * 0.45;
1548
+ const env = Math.min(1, u * 30) * expDecay(Math.max(0, t - 0.06), dur, 3.5);
1549
+ out[i] = square(phase) * env * 0.55;
1298
1550
  }
1299
1551
  return out;
1300
1552
  }
1301
- function shimmer(seed) {
1553
+ function sparkle(seed, pitch) {
1554
+ const dur = 0.6;
1555
+ const { out, n } = buffer(dur);
1556
+ const steps = [1, 1.5, 2, 3, 4, 5, 6];
1557
+ const base = 1200 * pitch;
1558
+ for (let i = 0; i < n; i++) {
1559
+ const t = i / SAMPLE_RATE, u = t / dur;
1560
+ let s = 0;
1561
+ for (let g = 0; g < steps.length; g++) {
1562
+ const on = u * steps.length - g;
1563
+ if (on > 0 && on < 3) {
1564
+ const ge = Math.exp(-on * 1.8);
1565
+ s += Math.sin(TAU * base * steps[g] * t + hash01(g, seed) * TAU) * ge;
1566
+ }
1567
+ }
1568
+ out[i] = s / 2.4 * Math.sin(Math.PI * u) ** 0.5 * 0.5;
1569
+ }
1570
+ return out;
1571
+ }
1572
+ function success(_seed, pitch) {
1573
+ const dur = 0.6;
1574
+ const { out, n } = buffer(dur);
1575
+ const notes = [523.25, 659.25, 783.99].map((f) => f * pitch);
1576
+ let phase = 0, cur = 0;
1577
+ for (let i = 0; i < n; i++) {
1578
+ const t = i / SAMPLE_RATE, u = t / dur;
1579
+ const idx = Math.min(notes.length - 1, Math.floor(u * 3.2));
1580
+ if (idx !== cur) cur = idx;
1581
+ phase += TAU * notes[cur] / SAMPLE_RATE;
1582
+ const local = u * 3.2 - idx;
1583
+ const env = Math.min(1, local * 12) * Math.min(1, (1 - Math.min(1, local)) * 4 + 0.2);
1584
+ out[i] = (Math.sin(phase) + 0.3 * Math.sin(2 * phase)) * env * 0.42;
1585
+ }
1586
+ return out;
1587
+ }
1588
+ function shimmer(seed, pitch) {
1302
1589
  const dur = 0.9;
1303
1590
  const { out, n } = buffer(dur);
1304
1591
  const partials = Array.from({ length: 5 }, (_, p) => ({
1305
- freq: 2e3 + hash01(p, seed + 7) * 2e3,
1592
+ freq: (2e3 + hash01(p, seed + 7) * 2e3) * pitch,
1306
1593
  am: 0.5 + hash01(p, seed + 8) * 1.5,
1307
1594
  phase: hash01(p, seed + 9) * TAU
1308
1595
  }));
1309
1596
  for (let i = 0; i < n; i++) {
1310
- const t = i / SAMPLE_RATE;
1311
- const u = t / dur;
1597
+ const t = i / SAMPLE_RATE, u = t / dur;
1312
1598
  const env = Math.sin(Math.PI * u) ** 1.2;
1313
1599
  let s = 0;
1314
- for (const part of partials) {
1315
- s += Math.sin(TAU * part.freq * t + part.phase) * (0.6 + 0.4 * Math.sin(TAU * part.am * t));
1316
- }
1600
+ for (const part of partials) s += Math.sin(TAU * part.freq * t + part.phase) * (0.6 + 0.4 * Math.sin(TAU * part.am * t));
1317
1601
  out[i] = s / 5 * env * 0.5;
1318
1602
  }
1319
1603
  return out;
1320
1604
  }
1321
- function thud(seed) {
1322
- const dur = 0.25;
1605
+ function zap(seed, pitch) {
1606
+ const dur = 0.22;
1607
+ const { out, n } = buffer(dur);
1608
+ let phase = 0;
1609
+ for (let i = 0; i < n; i++) {
1610
+ const t = i / SAMPLE_RATE, u = t / dur;
1611
+ const freq = 1600 * pitch * Math.pow(0.12, u);
1612
+ phase += TAU * freq / SAMPLE_RATE;
1613
+ const grit = noise(i, seed) * 0.25;
1614
+ out[i] = (square(phase) + grit) * expDecay(t, dur, 4.5) * 0.5;
1615
+ }
1616
+ return out;
1617
+ }
1618
+ function error(_seed, pitch) {
1619
+ const dur = 0.4;
1323
1620
  const { out, n } = buffer(dur);
1324
1621
  let phase = 0;
1622
+ for (let i = 0; i < n; i++) {
1623
+ const t = i / SAMPLE_RATE, u = t / dur;
1624
+ const freq = (t < 0.16 ? 311 : 233) * pitch;
1625
+ phase += TAU * freq / SAMPLE_RATE;
1626
+ const seg = t < 0.16 ? u / 0.4 : (u - 0.4) / 0.6;
1627
+ const env = Math.min(1, seg * 18) * Math.min(1, (1 - seg) * 6);
1628
+ out[i] = square(phase) * env * 0.5;
1629
+ }
1630
+ return out;
1631
+ }
1632
+ function swoosh(seed, pitch) {
1633
+ const dur = 0.35;
1634
+ const { out, n } = buffer(dur);
1635
+ let lp = 0, lp2 = 0;
1636
+ for (let i = 0; i < n; i++) {
1637
+ const t = i / SAMPLE_RATE, u = t / dur;
1638
+ const center = 300 * pitch * Math.pow(7.3, u);
1639
+ const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
1640
+ lp += alpha * (noise(i, seed) - lp);
1641
+ lp2 += alpha * 0.5 * (lp - lp2);
1642
+ const env = Math.sin(Math.PI * u) ** 0.9;
1643
+ out[i] = (lp - lp2) * env * 2.4;
1644
+ }
1645
+ return out;
1646
+ }
1647
+ function glitch(seed, pitch) {
1648
+ const dur = 0.3;
1649
+ const { out, n } = buffer(dur);
1650
+ const cell = Math.round(SAMPLE_RATE * 0.012);
1651
+ let phase = 0;
1652
+ for (let i = 0; i < n; i++) {
1653
+ const t = i / SAMPLE_RATE, u = t / dur;
1654
+ const c = Math.floor(i / cell);
1655
+ const on = hash01(c, seed) > 0.4 ? 1 : 0;
1656
+ const freq = 400 * pitch * (1 + Math.floor(hash01(c, seed + 1) * 6));
1657
+ phase += TAU * freq / SAMPLE_RATE;
1658
+ const crush = Math.round(square(phase) * 4) / 4;
1659
+ out[i] = (crush * 0.7 + noise(i, seed + c) * 0.3) * on * (1 - u * 0.3) * 0.55;
1660
+ }
1661
+ return out;
1662
+ }
1663
+ function staticHit(seed, pitch) {
1664
+ const dur = 0.18;
1665
+ const { out, n } = buffer(dur);
1325
1666
  let lp = 0;
1667
+ const alpha = Math.min(0.8, TAU * 3e3 * pitch / SAMPLE_RATE);
1668
+ for (let i = 0; i < n; i++) {
1669
+ const t = i / SAMPLE_RATE, u = t / dur;
1670
+ lp += alpha * (noise(i, seed) - lp);
1671
+ const env = Math.min(1, u * 30) * expDecay(t, dur, 5);
1672
+ out[i] = (noise(i, seed) - lp) * env * 1.4;
1673
+ }
1674
+ return out;
1675
+ }
1676
+ function scan(seed, pitch) {
1677
+ const dur = 0.45;
1678
+ const { out, n } = buffer(dur);
1679
+ let phase = 0;
1680
+ for (let i = 0; i < n; i++) {
1681
+ const t = i / SAMPLE_RATE, u = t / dur;
1682
+ const step = Math.floor(u * 6) / 6;
1683
+ const freq = 500 * pitch * Math.pow(3, step);
1684
+ phase += TAU * freq / SAMPLE_RATE;
1685
+ const cell = u * 6 % 1;
1686
+ const env = Math.min(1, cell * 12) * Math.min(1, (1 - cell) * 4) * 0.7;
1687
+ out[i] = square(phase) * env * 0.5;
1688
+ }
1689
+ return out;
1690
+ }
1691
+ function powerup(seed, pitch) {
1692
+ const dur = 0.4;
1693
+ const { out, n } = buffer(dur);
1694
+ const notes = [392, 523, 659, 784, 1046].map((f) => f * pitch);
1695
+ let phase = 0;
1696
+ for (let i = 0; i < n; i++) {
1697
+ const t = i / SAMPLE_RATE, u = t / dur;
1698
+ const idx = Math.min(notes.length - 1, Math.floor(u * notes.length));
1699
+ phase += TAU * notes[idx] / SAMPLE_RATE;
1700
+ const local = u * notes.length - idx;
1701
+ const env = Math.min(1, local * 14) * Math.min(1, (1 - Math.min(1, local)) * 4 + 0.3);
1702
+ out[i] = (Math.sin(phase) + 0.25 * Math.sin(2 * phase)) * env * 0.45;
1703
+ }
1704
+ return out;
1705
+ }
1706
+ function powerdown(seed, pitch) {
1707
+ const dur = 0.5;
1708
+ const { out, n } = buffer(dur);
1709
+ let phase = 0;
1710
+ for (let i = 0; i < n; i++) {
1711
+ const t = i / SAMPLE_RATE, u = t / dur;
1712
+ const freq = 700 * pitch * Math.pow(0.18, u);
1713
+ phase += TAU * freq / SAMPLE_RATE;
1714
+ const env = (1 - u) ** 0.8;
1715
+ out[i] = (square(phase) * 0.7 + Math.sin(phase) * 0.3) * env * 0.5;
1716
+ }
1717
+ return out;
1718
+ }
1719
+ function sub(seed, pitch) {
1720
+ const dur = 0.7;
1721
+ const { out, n } = buffer(dur);
1722
+ let phase = 0;
1326
1723
  for (let i = 0; i < n; i++) {
1327
1724
  const t = i / SAMPLE_RATE;
1328
- const freq = 90 * Math.pow(45 / 90, t / 0.15);
1725
+ const freq = 80 * pitch * Math.pow(0.45, t / 0.4);
1329
1726
  phase += TAU * freq / SAMPLE_RATE;
1330
- lp += 0.02 * (noise(i, seed) - lp);
1331
- const attack = t < 0.01 ? lp * 3 : 0;
1332
- out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur, 5);
1727
+ const click2 = t < 4e-3 ? noise(i, seed) * 0.4 : 0;
1728
+ out[i] = (Math.sin(phase) + click2) * expDecay(t, dur, 3) * 0.95;
1729
+ }
1730
+ return out;
1731
+ }
1732
+ function snare(seed, pitch) {
1733
+ const dur = 0.18;
1734
+ const { out, n } = buffer(dur);
1735
+ let phase = 0;
1736
+ for (let i = 0; i < n; i++) {
1737
+ const t = i / SAMPLE_RATE;
1738
+ phase += TAU * 180 * pitch / SAMPLE_RATE;
1739
+ const body = Math.sin(phase) * 0.5 * expDecay(t, dur, 16);
1740
+ const rattle = noise(i, seed) * expDecay(t, dur, 7);
1741
+ out[i] = (body + rattle * 0.8) * 0.7;
1742
+ }
1743
+ return out;
1744
+ }
1745
+ function hat(seed, pitch) {
1746
+ const dur = 0.05;
1747
+ const { out, n } = buffer(dur);
1748
+ let lp = 0;
1749
+ const alpha = Math.min(0.85, TAU * 2500 * pitch / SAMPLE_RATE);
1750
+ for (let i = 0; i < n; i++) {
1751
+ const t = i / SAMPLE_RATE;
1752
+ lp += alpha * (noise(i, seed) - lp);
1753
+ out[i] = (noise(i, seed) - lp) * expDecay(t, dur, 14) * 0.9;
1754
+ }
1755
+ return out;
1756
+ }
1757
+ function bubble(seed, pitch) {
1758
+ const dur = 0.16;
1759
+ const { out, n } = buffer(dur);
1760
+ let phase = 0;
1761
+ for (let i = 0; i < n; i++) {
1762
+ const t = i / SAMPLE_RATE, u = t / dur;
1763
+ const freq = 400 * pitch * Math.pow(3, u);
1764
+ phase += TAU * freq / SAMPLE_RATE;
1765
+ const env = Math.sin(Math.PI * u) ** 1.1;
1766
+ out[i] = Math.sin(phase) * env * 0.6;
1767
+ }
1768
+ return out;
1769
+ }
1770
+ function notify(seed, pitch) {
1771
+ const dur = 0.45;
1772
+ const { out, n } = buffer(dur);
1773
+ const f0 = 880 * pitch;
1774
+ for (let i = 0; i < n; i++) {
1775
+ const t = i / SAMPLE_RATE;
1776
+ const a = Math.sin(TAU * f0 * t) * expDecay(t, dur, 5);
1777
+ const b = t > 0.09 ? Math.sin(TAU * f0 * 1.5 * t) * expDecay(t - 0.09, dur, 4.5) : 0;
1778
+ out[i] = (a * 0.55 + b * 0.6) * 0.55;
1779
+ }
1780
+ return out;
1781
+ }
1782
+ function camera(seed, pitch) {
1783
+ const dur = 0.18;
1784
+ const { out, n } = buffer(dur);
1785
+ let lp = 0;
1786
+ for (let i = 0; i < n; i++) {
1787
+ const t = i / SAMPLE_RATE;
1788
+ lp += 0.45 * (noise(i, seed) - lp);
1789
+ const k1 = t < 0.02 ? expDecay(t, 0.02, 6) : 0;
1790
+ const k2 = t > 0.08 && t < 0.12 ? expDecay(t - 0.08, 0.04, 6) : 0;
1791
+ const sine = Math.sin(TAU * 2200 * pitch * t);
1792
+ out[i] = (lp * 0.7 + sine * 0.3) * (k1 + k2) * 1.3;
1333
1793
  }
1334
1794
  return out;
1335
1795
  }
1336
1796
  var RECIPES = {
1337
1797
  whoosh,
1338
- pop,
1339
- tick,
1798
+ swish,
1799
+ swoosh,
1340
1800
  rise,
1801
+ riser,
1802
+ warp,
1803
+ tick,
1804
+ click,
1805
+ blip,
1806
+ pop,
1807
+ select,
1808
+ thud,
1809
+ boom,
1810
+ knock,
1811
+ sub,
1812
+ chime,
1813
+ ding,
1814
+ coin,
1815
+ sparkle,
1341
1816
  shimmer,
1342
- thud
1817
+ success,
1818
+ zap,
1819
+ error,
1820
+ glitch,
1821
+ static: staticHit,
1822
+ scan,
1823
+ powerup,
1824
+ powerdown,
1825
+ snare,
1826
+ hat,
1827
+ bubble,
1828
+ notify,
1829
+ camera
1343
1830
  };
1344
1831
  function synthSfx(name, params = {}) {
1345
- const samples = RECIPES[name](params.seed ?? 0);
1832
+ const seed = params.seed ?? 0;
1833
+ const pitch = (params.pitch ?? 1) * seedPitch(seed);
1834
+ const samples = RECIPES[name](seed, pitch);
1346
1835
  if (params.gainDb) {
1347
1836
  const g = Math.pow(10, params.gainDb / 20);
1348
1837
  for (let i = 0; i < samples.length; i++) samples[i] *= g;
1349
1838
  }
1839
+ let peak = 0;
1840
+ for (let i = 0; i < samples.length; i++) peak = Math.max(peak, Math.abs(samples[i]));
1841
+ if (peak > 0.95) {
1842
+ const g = 0.95 / peak;
1843
+ for (let i = 0; i < samples.length; i++) samples[i] *= g;
1844
+ }
1350
1845
  return samples;
1351
1846
  }
1352
- function synthAmbientPad(duration, seed = 0) {
1847
+ function pad(freqs, duration, seed, opts) {
1353
1848
  const { out, n } = buffer(duration);
1354
- const voices = [110, 165, 220].flatMap((f, v) => [
1355
- { freq: f * (1 + (hash01(v, seed + 3) - 0.5) * 4e-3), am: 0.05 + hash01(v, seed + 4) * 0.08, phase: hash01(v, seed + 5) * TAU },
1356
- { freq: f * (1 - (hash01(v, seed + 6) - 0.5) * 4e-3), am: 0.05 + hash01(v, seed + 7) * 0.08, phase: hash01(v, seed + 8) * TAU }
1849
+ const voices = freqs.flatMap((f, v) => [
1850
+ { freq: f * (1 + (hash01(v, seed + 3) - 0.5) * 4e-3), am: opts.amBase + hash01(v, seed + 4) * 0.08, phase: hash01(v, seed + 5) * TAU },
1851
+ { freq: f * (1 - (hash01(v, seed + 6) - 0.5) * 4e-3), am: opts.amBase + hash01(v, seed + 7) * 0.08, phase: hash01(v, seed + 8) * TAU }
1357
1852
  ]);
1358
1853
  for (let i = 0; i < n; i++) {
1359
1854
  const t = i / SAMPLE_RATE;
1360
1855
  let s = 0;
1361
1856
  for (const voice of voices) {
1362
1857
  s += Math.sin(TAU * voice.freq * t + voice.phase) * (0.75 + 0.25 * Math.sin(TAU * voice.am * t));
1858
+ if (opts.bright > 0) s += opts.bright * Math.sin(TAU * voice.freq * 2 * t + voice.phase);
1859
+ }
1860
+ out[i] = s / voices.length * opts.gain;
1861
+ }
1862
+ return out;
1863
+ }
1864
+ function synthAmbientPad(duration, seed = 0) {
1865
+ return pad([110, 165, 220], duration, seed, { amBase: 0.05, bright: 0, gain: 0.7 });
1866
+ }
1867
+ function synthLofi(duration, seed = 0) {
1868
+ return pad([130.81, 164.81, 196, 246.94], duration, seed, { amBase: 0.04, bright: 0.04, gain: 0.62 });
1869
+ }
1870
+ function synthPulse(duration, _seed = 0) {
1871
+ const { out, n } = buffer(duration);
1872
+ const beat2 = 2.2;
1873
+ for (let i = 0; i < n; i++) {
1874
+ const t = i / SAMPLE_RATE;
1875
+ const ph = t * beat2 % 1;
1876
+ const gate = Math.exp(-ph * 5) * 0.9 + 0.1;
1877
+ const s = Math.sin(TAU * 82 * t) + 0.6 * Math.sin(TAU * 123 * t) + 0.3 * Math.sin(TAU * 246 * t);
1878
+ out[i] = s / 1.9 * gate * 0.6;
1879
+ }
1880
+ return out;
1881
+ }
1882
+ function synthTension(duration, seed = 0) {
1883
+ const { out, n } = buffer(duration);
1884
+ const base = [98, 104, 110];
1885
+ for (let i = 0; i < n; i++) {
1886
+ const t = i / SAMPLE_RATE;
1887
+ const drift = 1 + 0.03 * (t / Math.max(1e-3, duration));
1888
+ let s = 0;
1889
+ for (let v = 0; v < base.length; v++) {
1890
+ const f = base[v] * drift * (1 + (hash01(v, seed) - 0.5) * 6e-3);
1891
+ s += Math.sin(TAU * f * t + hash01(v, seed + 1) * TAU);
1363
1892
  }
1364
- out[i] = s / voices.length * 0.7;
1893
+ const swell = 0.6 + 0.4 * Math.sin(TAU * 0.08 * t);
1894
+ out[i] = s / base.length * swell * 0.6;
1365
1895
  }
1366
1896
  return out;
1367
1897
  }
1898
+ function synthUplift(duration, seed = 0) {
1899
+ return pad([196, 246.94, 293.66, 392], duration, seed, { amBase: 0.07, bright: 0.1, gain: 0.6 });
1900
+ }
1901
+ var BGM_RECIPES = {
1902
+ "ambient-pad": synthAmbientPad,
1903
+ lofi: synthLofi,
1904
+ pulse: synthPulse,
1905
+ tension: synthTension,
1906
+ uplift: synthUplift
1907
+ };
1908
+ function synthBgm(name, duration, seed = 0) {
1909
+ return BGM_RECIPES[name](duration, seed);
1910
+ }
1368
1911
 
1369
1912
  // ../render-cli/src/audio/sfx.ts
1370
1913
  var ROOT = true ? resolve(dirname(fileURLToPath(import.meta.url)), "..") : resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..");
@@ -1414,7 +1957,7 @@ async function resolveBgmFile(source, duration, sceneDir) {
1414
1957
  }
1415
1958
  throw new Error(`bgm file "${p}" not found`);
1416
1959
  }
1417
- return writeCached(`ambient-pad-${duration.toFixed(2)}`, () => synthAmbientPad(duration));
1960
+ return writeCached(`${source.name}-${duration.toFixed(2)}`, () => synthBgm(source.name, duration));
1418
1961
  }
1419
1962
 
1420
1963
  // ../render-cli/src/audio/clip.ts