reframe-video 0.6.26 → 0.6.27
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/bin.js +388 -50
- package/dist/cli.js +390 -49
- package/dist/compile-api.js +30 -1
- package/dist/compile.js +30 -1
- package/dist/diff.js +28 -1
- package/dist/frame.js +28 -1
- package/dist/index.js +63 -6
- package/dist/labels.js +28 -1
- package/dist/types/audio.d.ts +2 -2
- package/dist/types/ir.d.ts +21 -4
- package/guides/edsl-guide.md +22 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,30 @@ 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
|
+
"rise",
|
|
13
|
+
"riser",
|
|
14
|
+
"warp",
|
|
15
|
+
"tick",
|
|
16
|
+
"click",
|
|
17
|
+
"blip",
|
|
18
|
+
"pop",
|
|
19
|
+
"select",
|
|
20
|
+
"thud",
|
|
21
|
+
"boom",
|
|
22
|
+
"knock",
|
|
23
|
+
"chime",
|
|
24
|
+
"ding",
|
|
25
|
+
"coin",
|
|
26
|
+
"sparkle",
|
|
27
|
+
"shimmer",
|
|
28
|
+
"success",
|
|
29
|
+
"zap",
|
|
30
|
+
"error"
|
|
31
|
+
];
|
|
32
|
+
var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
|
|
9
33
|
var DEFAULT_CROSSFADE = 0.5;
|
|
10
34
|
var DEFAULT_TO_DURATION = 0.5;
|
|
11
35
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
@@ -656,7 +680,6 @@ function validateScene(ir) {
|
|
|
656
680
|
}
|
|
657
681
|
}
|
|
658
682
|
}
|
|
659
|
-
const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
|
|
660
683
|
for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
|
|
661
684
|
if (typeof cue.at === "string" && !labels.has(cue.at)) {
|
|
662
685
|
problems.push(
|
|
@@ -692,6 +715,10 @@ function validateScene(ir) {
|
|
|
692
715
|
if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
|
|
693
716
|
problems.push('audio.bgm: use either "file" or "synth", not both');
|
|
694
717
|
}
|
|
718
|
+
const bgmSynth = ir.audio?.bgm?.synth;
|
|
719
|
+
if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
|
|
720
|
+
problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
|
|
721
|
+
}
|
|
695
722
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
696
723
|
}
|
|
697
724
|
var TRANSITIONS = ["cut", "crossfade"];
|
|
@@ -991,12 +1018,32 @@ var SET = 1 / 120;
|
|
|
991
1018
|
|
|
992
1019
|
// ../core/src/audio.ts
|
|
993
1020
|
var SFX_DURATION = {
|
|
1021
|
+
// transition
|
|
994
1022
|
whoosh: 0.35,
|
|
995
|
-
|
|
996
|
-
tick: 0.03,
|
|
1023
|
+
swish: 0.32,
|
|
997
1024
|
rise: 0.5,
|
|
1025
|
+
riser: 0.85,
|
|
1026
|
+
warp: 0.5,
|
|
1027
|
+
// ui
|
|
1028
|
+
tick: 0.03,
|
|
1029
|
+
click: 0.05,
|
|
1030
|
+
blip: 0.1,
|
|
1031
|
+
pop: 0.12,
|
|
1032
|
+
select: 0.18,
|
|
1033
|
+
// impact
|
|
1034
|
+
thud: 0.25,
|
|
1035
|
+
boom: 0.6,
|
|
1036
|
+
knock: 0.14,
|
|
1037
|
+
// positive
|
|
1038
|
+
chime: 0.7,
|
|
1039
|
+
ding: 0.5,
|
|
1040
|
+
coin: 0.3,
|
|
1041
|
+
sparkle: 0.6,
|
|
998
1042
|
shimmer: 0.9,
|
|
999
|
-
|
|
1043
|
+
success: 0.6,
|
|
1044
|
+
// alert
|
|
1045
|
+
zap: 0.22,
|
|
1046
|
+
error: 0.4
|
|
1000
1047
|
};
|
|
1001
1048
|
var FILE_CUE_DURATION = 0.4;
|
|
1002
1049
|
function collectClipAudio(ir, duration, warnings) {
|
|
@@ -1056,7 +1103,11 @@ function resolveAudioPlan(compiled) {
|
|
|
1056
1103
|
fadeIn: cue.fadeIn ?? 0,
|
|
1057
1104
|
fadeOut: cue.fadeOut ?? 0,
|
|
1058
1105
|
pan: cue.pan ?? 0,
|
|
1059
|
-
source: cue.sfx ?
|
|
1106
|
+
source: cue.sfx ? (
|
|
1107
|
+
// auto-vary: default the seed to the cue's order so repeated sfx differ
|
|
1108
|
+
// (pitch/texture); an explicit params.seed always wins.
|
|
1109
|
+
{ kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
|
|
1110
|
+
) : { kind: "file", path: cue.file }
|
|
1060
1111
|
});
|
|
1061
1112
|
}
|
|
1062
1113
|
cues.sort((a, b) => a.t - b.t);
|
|
@@ -1136,7 +1187,11 @@ function resolveCompositionAudioPlan(comp) {
|
|
|
1136
1187
|
fadeIn: cue.fadeIn ?? 0,
|
|
1137
1188
|
fadeOut: cue.fadeOut ?? 0,
|
|
1138
1189
|
pan: cue.pan ?? 0,
|
|
1139
|
-
source: cue.sfx ?
|
|
1190
|
+
source: cue.sfx ? (
|
|
1191
|
+
// auto-vary: default the seed to the cue's order so repeated sfx differ
|
|
1192
|
+
// (pitch/texture); an explicit params.seed always wins.
|
|
1193
|
+
{ kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
|
|
1194
|
+
) : { kind: "file", path: cue.file }
|
|
1140
1195
|
});
|
|
1141
1196
|
}
|
|
1142
1197
|
if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
|
|
@@ -1240,19 +1295,23 @@ function hash01(n, seed) {
|
|
|
1240
1295
|
var noise = (n, seed) => hash01(n, seed) * 2 - 1;
|
|
1241
1296
|
var TAU = Math.PI * 2;
|
|
1242
1297
|
var expDecay = (t, dur, k = 5) => Math.exp(-k * t / dur);
|
|
1298
|
+
var square = (ph) => (Math.sin(ph) + 0.33 * Math.sin(3 * ph) + 0.2 * Math.sin(5 * ph)) / 1.4;
|
|
1299
|
+
var PITCH_STEPS = [0, 2, 4, 7, 9, 12, 5, -3, 16, -5];
|
|
1300
|
+
function seedPitch(seed) {
|
|
1301
|
+
const i = (Math.round(seed) % PITCH_STEPS.length + PITCH_STEPS.length) % PITCH_STEPS.length;
|
|
1302
|
+
return Math.pow(2, PITCH_STEPS[i] / 12);
|
|
1303
|
+
}
|
|
1243
1304
|
function buffer(duration) {
|
|
1244
1305
|
const n = Math.round(duration * SAMPLE_RATE);
|
|
1245
1306
|
return { out: new Float32Array(n), n };
|
|
1246
1307
|
}
|
|
1247
|
-
function whoosh(seed) {
|
|
1308
|
+
function whoosh(seed, pitch) {
|
|
1248
1309
|
const dur = 0.35;
|
|
1249
1310
|
const { out, n } = buffer(dur);
|
|
1250
|
-
let lp = 0;
|
|
1251
|
-
let lp2 = 0;
|
|
1311
|
+
let lp = 0, lp2 = 0;
|
|
1252
1312
|
for (let i = 0; i < n; i++) {
|
|
1253
|
-
const t = i / SAMPLE_RATE;
|
|
1254
|
-
const
|
|
1255
|
-
const center = 1200 * Math.pow(300 / 1200, u);
|
|
1313
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1314
|
+
const center = 1200 * pitch * Math.pow(0.25, u);
|
|
1256
1315
|
const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
|
|
1257
1316
|
lp += alpha * (noise(i, seed) - lp);
|
|
1258
1317
|
lp2 += alpha * 0.5 * (lp - lp2);
|
|
@@ -1261,110 +1320,392 @@ function whoosh(seed) {
|
|
|
1261
1320
|
}
|
|
1262
1321
|
return out;
|
|
1263
1322
|
}
|
|
1264
|
-
function
|
|
1323
|
+
function swish(seed, pitch) {
|
|
1324
|
+
const dur = 0.32;
|
|
1325
|
+
const { out, n } = buffer(dur);
|
|
1326
|
+
let lp = 0, lp2 = 0;
|
|
1327
|
+
for (let i = 0; i < n; i++) {
|
|
1328
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1329
|
+
const center = 2600 * pitch * Math.pow(0.2, u);
|
|
1330
|
+
const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
|
|
1331
|
+
lp += alpha * (noise(i, seed) - lp);
|
|
1332
|
+
lp2 += alpha * 0.5 * (lp - lp2);
|
|
1333
|
+
const env = u < 0.15 ? u / 0.15 : expDecay(t - 0.15 * dur, dur * 0.85, 5);
|
|
1334
|
+
out[i] = (lp - lp2) * env * 2.4;
|
|
1335
|
+
}
|
|
1336
|
+
return out;
|
|
1337
|
+
}
|
|
1338
|
+
function rise(_seed, pitch) {
|
|
1339
|
+
const dur = 0.5;
|
|
1340
|
+
const { out, n } = buffer(dur);
|
|
1341
|
+
let phase = 0;
|
|
1342
|
+
for (let i = 0; i < n; i++) {
|
|
1343
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1344
|
+
const freq = 220 * pitch * Math.pow(4, u);
|
|
1345
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1346
|
+
const env = Math.sin(Math.PI * Math.min(1, u * 1.05)) ** 1.5;
|
|
1347
|
+
out[i] = (Math.sin(phase) + 0.3 * Math.sin(2 * phase)) * env * 0.45;
|
|
1348
|
+
}
|
|
1349
|
+
return out;
|
|
1350
|
+
}
|
|
1351
|
+
function riser(seed, pitch) {
|
|
1352
|
+
const dur = 0.85;
|
|
1353
|
+
const { out, n } = buffer(dur);
|
|
1354
|
+
let lp = 0, phase = 0;
|
|
1355
|
+
for (let i = 0; i < n; i++) {
|
|
1356
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1357
|
+
const center = 200 * pitch * Math.pow(12, u);
|
|
1358
|
+
const alpha = Math.min(1, TAU * center / SAMPLE_RATE);
|
|
1359
|
+
lp += alpha * (noise(i, seed) - lp);
|
|
1360
|
+
const freq = 120 * pitch * Math.pow(6, u);
|
|
1361
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1362
|
+
const env = Math.pow(u, 1.6);
|
|
1363
|
+
out[i] = (lp * 1.6 + Math.sin(phase) * 0.5) * env * 0.9;
|
|
1364
|
+
}
|
|
1365
|
+
return out;
|
|
1366
|
+
}
|
|
1367
|
+
function warp(_seed, pitch) {
|
|
1368
|
+
const dur = 0.5;
|
|
1369
|
+
const { out, n } = buffer(dur);
|
|
1370
|
+
let phase = 0;
|
|
1371
|
+
for (let i = 0; i < n; i++) {
|
|
1372
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1373
|
+
const bend = Math.sin(Math.PI * u);
|
|
1374
|
+
const vib = 1 + 0.4 * Math.sin(TAU * 18 * t);
|
|
1375
|
+
const freq = 300 * pitch * (1 + 2.5 * bend) * vib;
|
|
1376
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1377
|
+
const env = Math.sin(Math.PI * u) ** 0.8;
|
|
1378
|
+
out[i] = square(phase) * env * 0.5;
|
|
1379
|
+
}
|
|
1380
|
+
return out;
|
|
1381
|
+
}
|
|
1382
|
+
function tick(seed, pitch) {
|
|
1383
|
+
const dur = 0.03;
|
|
1384
|
+
const { out, n } = buffer(dur);
|
|
1385
|
+
for (let i = 0; i < n; i++) {
|
|
1386
|
+
const t = i / SAMPLE_RATE;
|
|
1387
|
+
const sine = t < 4e-3 ? Math.sin(TAU * 4e3 * pitch * t) : 0;
|
|
1388
|
+
out[i] = (sine * 0.6 + noise(i, seed) * 0.35) * expDecay(t, dur, 8);
|
|
1389
|
+
}
|
|
1390
|
+
return out;
|
|
1391
|
+
}
|
|
1392
|
+
function click(seed, pitch) {
|
|
1393
|
+
const dur = 0.05;
|
|
1394
|
+
const { out, n } = buffer(dur);
|
|
1395
|
+
let lp = 0;
|
|
1396
|
+
for (let i = 0; i < n; i++) {
|
|
1397
|
+
const t = i / SAMPLE_RATE;
|
|
1398
|
+
lp += 0.5 * (noise(i, seed) - lp);
|
|
1399
|
+
const sine = Math.sin(TAU * 1500 * pitch * t);
|
|
1400
|
+
out[i] = (sine * 0.5 + lp * 0.6) * expDecay(t, dur, 11);
|
|
1401
|
+
}
|
|
1402
|
+
return out;
|
|
1403
|
+
}
|
|
1404
|
+
function blip(_seed, pitch) {
|
|
1405
|
+
const dur = 0.1;
|
|
1406
|
+
const { out, n } = buffer(dur);
|
|
1407
|
+
let phase = 0;
|
|
1408
|
+
for (let i = 0; i < n; i++) {
|
|
1409
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1410
|
+
phase += TAU * 880 * pitch / SAMPLE_RATE;
|
|
1411
|
+
const env = Math.min(1, u * 12) * Math.min(1, (1 - u) * 6);
|
|
1412
|
+
out[i] = square(phase) * env * 0.5;
|
|
1413
|
+
}
|
|
1414
|
+
return out;
|
|
1415
|
+
}
|
|
1416
|
+
function pop(seed, pitch) {
|
|
1265
1417
|
const dur = 0.12;
|
|
1266
1418
|
const { out, n } = buffer(dur);
|
|
1267
1419
|
let phase = 0;
|
|
1268
1420
|
for (let i = 0; i < n; i++) {
|
|
1269
1421
|
const t = i / SAMPLE_RATE;
|
|
1270
|
-
const freq = 600 * Math.pow(
|
|
1422
|
+
const freq = 600 * pitch * Math.pow(0.25, t / 0.08);
|
|
1271
1423
|
phase += TAU * freq / SAMPLE_RATE;
|
|
1272
1424
|
const transient = t < 2e-3 ? noise(i, seed) * 0.5 : 0;
|
|
1273
1425
|
out[i] = (Math.sin(phase) + transient) * expDecay(t, dur, 6) * 0.8;
|
|
1274
1426
|
}
|
|
1275
1427
|
return out;
|
|
1276
1428
|
}
|
|
1277
|
-
function
|
|
1278
|
-
const dur = 0.
|
|
1429
|
+
function select(_seed, pitch) {
|
|
1430
|
+
const dur = 0.18;
|
|
1431
|
+
const { out, n } = buffer(dur);
|
|
1432
|
+
let phase = 0;
|
|
1433
|
+
for (let i = 0; i < n; i++) {
|
|
1434
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1435
|
+
const freq = (t < 0.08 ? 620 : 930) * pitch;
|
|
1436
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1437
|
+
const env = Math.min(1, u * 16) * Math.min(1, (1 - u) * 5);
|
|
1438
|
+
out[i] = (Math.sin(phase) + 0.25 * Math.sin(2 * phase)) * env * 0.5;
|
|
1439
|
+
}
|
|
1440
|
+
return out;
|
|
1441
|
+
}
|
|
1442
|
+
function thud(seed, pitch) {
|
|
1443
|
+
const dur = 0.25;
|
|
1279
1444
|
const { out, n } = buffer(dur);
|
|
1445
|
+
let phase = 0, lp = 0;
|
|
1280
1446
|
for (let i = 0; i < n; i++) {
|
|
1281
1447
|
const t = i / SAMPLE_RATE;
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1448
|
+
const freq = 90 * pitch * Math.pow(0.5, t / 0.15);
|
|
1449
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1450
|
+
lp += 0.02 * (noise(i, seed) - lp);
|
|
1451
|
+
const attack = t < 0.01 ? lp * 3 : 0;
|
|
1452
|
+
out[i] = (Math.sin(phase) * 0.9 + attack) * expDecay(t, dur, 5);
|
|
1284
1453
|
}
|
|
1285
1454
|
return out;
|
|
1286
1455
|
}
|
|
1287
|
-
function
|
|
1456
|
+
function boom(seed, pitch) {
|
|
1457
|
+
const dur = 0.6;
|
|
1458
|
+
const { out, n } = buffer(dur);
|
|
1459
|
+
let phase = 0, lp = 0;
|
|
1460
|
+
for (let i = 0; i < n; i++) {
|
|
1461
|
+
const t = i / SAMPLE_RATE;
|
|
1462
|
+
const freq = 70 * pitch * Math.pow(0.5, t / 0.3);
|
|
1463
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1464
|
+
lp += 0.06 * (noise(i, seed) - lp);
|
|
1465
|
+
const body = t < 0.06 ? lp * 2.5 * (1 - t / 0.06) : 0;
|
|
1466
|
+
out[i] = (Math.sin(phase) * 1 + body) * expDecay(t, dur, 3.2);
|
|
1467
|
+
}
|
|
1468
|
+
return out;
|
|
1469
|
+
}
|
|
1470
|
+
function knock(seed, pitch) {
|
|
1471
|
+
const dur = 0.14;
|
|
1472
|
+
const { out, n } = buffer(dur);
|
|
1473
|
+
let phase = 0, lp = 0;
|
|
1474
|
+
for (let i = 0; i < n; i++) {
|
|
1475
|
+
const t = i / SAMPLE_RATE;
|
|
1476
|
+
const freq = 220 * pitch * Math.pow(0.7, t / 0.05);
|
|
1477
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1478
|
+
lp += 0.3 * (noise(i, seed) - lp);
|
|
1479
|
+
const tap = t < 5e-3 ? lp : 0;
|
|
1480
|
+
out[i] = (Math.sin(phase) * 0.8 + tap * 0.6) * expDecay(t, dur, 9);
|
|
1481
|
+
}
|
|
1482
|
+
return out;
|
|
1483
|
+
}
|
|
1484
|
+
function chime(seed, pitch) {
|
|
1485
|
+
const dur = 0.7;
|
|
1486
|
+
const { out, n } = buffer(dur);
|
|
1487
|
+
const f0 = 800 * pitch;
|
|
1488
|
+
const partials = [
|
|
1489
|
+
{ f: f0, a: 1, k: 4 },
|
|
1490
|
+
{ f: f0 * 2.76, a: 0.5, k: 5.5 },
|
|
1491
|
+
{ f: f0 * 5.4, a: 0.28, k: 7 },
|
|
1492
|
+
{ f: f0 * 8.9, a: 0.13, k: 9 }
|
|
1493
|
+
];
|
|
1494
|
+
for (let i = 0; i < n; i++) {
|
|
1495
|
+
const t = i / SAMPLE_RATE;
|
|
1496
|
+
let s = 0;
|
|
1497
|
+
for (const p of partials) s += Math.sin(TAU * p.f * t) * p.a * expDecay(t, dur, p.k);
|
|
1498
|
+
const strike = t < 3e-3 ? noise(i, seed) * 0.3 : 0;
|
|
1499
|
+
out[i] = (s / 1.9 + strike) * 0.6;
|
|
1500
|
+
}
|
|
1501
|
+
return out;
|
|
1502
|
+
}
|
|
1503
|
+
function ding(_seed, pitch) {
|
|
1288
1504
|
const dur = 0.5;
|
|
1289
1505
|
const { out, n } = buffer(dur);
|
|
1290
|
-
|
|
1506
|
+
const f0 = 1200 * pitch;
|
|
1291
1507
|
for (let i = 0; i < n; i++) {
|
|
1292
1508
|
const t = i / SAMPLE_RATE;
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1509
|
+
const s = Math.sin(TAU * f0 * t) + 0.4 * Math.sin(TAU * f0 * 2 * t) + 0.2 * Math.sin(TAU * f0 * 3.01 * t);
|
|
1510
|
+
out[i] = s / 1.6 * expDecay(t, dur, 4.5) * 0.6;
|
|
1511
|
+
}
|
|
1512
|
+
return out;
|
|
1513
|
+
}
|
|
1514
|
+
function coin(_seed, pitch) {
|
|
1515
|
+
const dur = 0.3;
|
|
1516
|
+
const { out, n } = buffer(dur);
|
|
1517
|
+
let phase = 0;
|
|
1518
|
+
for (let i = 0; i < n; i++) {
|
|
1519
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1520
|
+
const freq = (t < 0.06 ? 988 : 1319) * pitch;
|
|
1295
1521
|
phase += TAU * freq / SAMPLE_RATE;
|
|
1296
|
-
const env = Math.
|
|
1297
|
-
out[i] = (
|
|
1522
|
+
const env = Math.min(1, u * 30) * expDecay(Math.max(0, t - 0.06), dur, 3.5);
|
|
1523
|
+
out[i] = square(phase) * env * 0.55;
|
|
1524
|
+
}
|
|
1525
|
+
return out;
|
|
1526
|
+
}
|
|
1527
|
+
function sparkle(seed, pitch) {
|
|
1528
|
+
const dur = 0.6;
|
|
1529
|
+
const { out, n } = buffer(dur);
|
|
1530
|
+
const steps = [1, 1.5, 2, 3, 4, 5, 6];
|
|
1531
|
+
const base = 1200 * pitch;
|
|
1532
|
+
for (let i = 0; i < n; i++) {
|
|
1533
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1534
|
+
let s = 0;
|
|
1535
|
+
for (let g = 0; g < steps.length; g++) {
|
|
1536
|
+
const on = u * steps.length - g;
|
|
1537
|
+
if (on > 0 && on < 3) {
|
|
1538
|
+
const ge = Math.exp(-on * 1.8);
|
|
1539
|
+
s += Math.sin(TAU * base * steps[g] * t + hash01(g, seed) * TAU) * ge;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
out[i] = s / 2.4 * Math.sin(Math.PI * u) ** 0.5 * 0.5;
|
|
1543
|
+
}
|
|
1544
|
+
return out;
|
|
1545
|
+
}
|
|
1546
|
+
function success(_seed, pitch) {
|
|
1547
|
+
const dur = 0.6;
|
|
1548
|
+
const { out, n } = buffer(dur);
|
|
1549
|
+
const notes = [523.25, 659.25, 783.99].map((f) => f * pitch);
|
|
1550
|
+
let phase = 0, cur = 0;
|
|
1551
|
+
for (let i = 0; i < n; i++) {
|
|
1552
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1553
|
+
const idx = Math.min(notes.length - 1, Math.floor(u * 3.2));
|
|
1554
|
+
if (idx !== cur) cur = idx;
|
|
1555
|
+
phase += TAU * notes[cur] / SAMPLE_RATE;
|
|
1556
|
+
const local = u * 3.2 - idx;
|
|
1557
|
+
const env = Math.min(1, local * 12) * Math.min(1, (1 - Math.min(1, local)) * 4 + 0.2);
|
|
1558
|
+
out[i] = (Math.sin(phase) + 0.3 * Math.sin(2 * phase)) * env * 0.42;
|
|
1298
1559
|
}
|
|
1299
1560
|
return out;
|
|
1300
1561
|
}
|
|
1301
|
-
function shimmer(seed) {
|
|
1562
|
+
function shimmer(seed, pitch) {
|
|
1302
1563
|
const dur = 0.9;
|
|
1303
1564
|
const { out, n } = buffer(dur);
|
|
1304
1565
|
const partials = Array.from({ length: 5 }, (_, p) => ({
|
|
1305
|
-
freq: 2e3 + hash01(p, seed + 7) * 2e3,
|
|
1566
|
+
freq: (2e3 + hash01(p, seed + 7) * 2e3) * pitch,
|
|
1306
1567
|
am: 0.5 + hash01(p, seed + 8) * 1.5,
|
|
1307
1568
|
phase: hash01(p, seed + 9) * TAU
|
|
1308
1569
|
}));
|
|
1309
1570
|
for (let i = 0; i < n; i++) {
|
|
1310
|
-
const t = i / SAMPLE_RATE;
|
|
1311
|
-
const u = t / dur;
|
|
1571
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1312
1572
|
const env = Math.sin(Math.PI * u) ** 1.2;
|
|
1313
1573
|
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
|
-
}
|
|
1574
|
+
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
1575
|
out[i] = s / 5 * env * 0.5;
|
|
1318
1576
|
}
|
|
1319
1577
|
return out;
|
|
1320
1578
|
}
|
|
1321
|
-
function
|
|
1322
|
-
const dur = 0.
|
|
1579
|
+
function zap(seed, pitch) {
|
|
1580
|
+
const dur = 0.22;
|
|
1323
1581
|
const { out, n } = buffer(dur);
|
|
1324
1582
|
let phase = 0;
|
|
1325
|
-
let lp = 0;
|
|
1326
1583
|
for (let i = 0; i < n; i++) {
|
|
1327
|
-
const t = i / SAMPLE_RATE;
|
|
1328
|
-
const freq =
|
|
1584
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1585
|
+
const freq = 1600 * pitch * Math.pow(0.12, u);
|
|
1329
1586
|
phase += TAU * freq / SAMPLE_RATE;
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1587
|
+
const grit = noise(i, seed) * 0.25;
|
|
1588
|
+
out[i] = (square(phase) + grit) * expDecay(t, dur, 4.5) * 0.5;
|
|
1589
|
+
}
|
|
1590
|
+
return out;
|
|
1591
|
+
}
|
|
1592
|
+
function error(_seed, pitch) {
|
|
1593
|
+
const dur = 0.4;
|
|
1594
|
+
const { out, n } = buffer(dur);
|
|
1595
|
+
let phase = 0;
|
|
1596
|
+
for (let i = 0; i < n; i++) {
|
|
1597
|
+
const t = i / SAMPLE_RATE, u = t / dur;
|
|
1598
|
+
const freq = (t < 0.16 ? 311 : 233) * pitch;
|
|
1599
|
+
phase += TAU * freq / SAMPLE_RATE;
|
|
1600
|
+
const seg = t < 0.16 ? u / 0.4 : (u - 0.4) / 0.6;
|
|
1601
|
+
const env = Math.min(1, seg * 18) * Math.min(1, (1 - seg) * 6);
|
|
1602
|
+
out[i] = square(phase) * env * 0.5;
|
|
1333
1603
|
}
|
|
1334
1604
|
return out;
|
|
1335
1605
|
}
|
|
1336
1606
|
var RECIPES = {
|
|
1337
1607
|
whoosh,
|
|
1338
|
-
|
|
1339
|
-
tick,
|
|
1608
|
+
swish,
|
|
1340
1609
|
rise,
|
|
1610
|
+
riser,
|
|
1611
|
+
warp,
|
|
1612
|
+
tick,
|
|
1613
|
+
click,
|
|
1614
|
+
blip,
|
|
1615
|
+
pop,
|
|
1616
|
+
select,
|
|
1617
|
+
thud,
|
|
1618
|
+
boom,
|
|
1619
|
+
knock,
|
|
1620
|
+
chime,
|
|
1621
|
+
ding,
|
|
1622
|
+
coin,
|
|
1623
|
+
sparkle,
|
|
1341
1624
|
shimmer,
|
|
1342
|
-
|
|
1625
|
+
success,
|
|
1626
|
+
zap,
|
|
1627
|
+
error
|
|
1343
1628
|
};
|
|
1344
1629
|
function synthSfx(name, params = {}) {
|
|
1345
|
-
const
|
|
1630
|
+
const seed = params.seed ?? 0;
|
|
1631
|
+
const pitch = (params.pitch ?? 1) * seedPitch(seed);
|
|
1632
|
+
const samples = RECIPES[name](seed, pitch);
|
|
1346
1633
|
if (params.gainDb) {
|
|
1347
1634
|
const g = Math.pow(10, params.gainDb / 20);
|
|
1348
1635
|
for (let i = 0; i < samples.length; i++) samples[i] *= g;
|
|
1349
1636
|
}
|
|
1637
|
+
let peak = 0;
|
|
1638
|
+
for (let i = 0; i < samples.length; i++) peak = Math.max(peak, Math.abs(samples[i]));
|
|
1639
|
+
if (peak > 0.95) {
|
|
1640
|
+
const g = 0.95 / peak;
|
|
1641
|
+
for (let i = 0; i < samples.length; i++) samples[i] *= g;
|
|
1642
|
+
}
|
|
1350
1643
|
return samples;
|
|
1351
1644
|
}
|
|
1352
|
-
function
|
|
1645
|
+
function pad(freqs, duration, seed, opts) {
|
|
1353
1646
|
const { out, n } = buffer(duration);
|
|
1354
|
-
const voices =
|
|
1355
|
-
{ freq: f * (1 + (hash01(v, seed + 3) - 0.5) * 4e-3), am:
|
|
1356
|
-
{ freq: f * (1 - (hash01(v, seed + 6) - 0.5) * 4e-3), am:
|
|
1647
|
+
const voices = freqs.flatMap((f, v) => [
|
|
1648
|
+
{ 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 },
|
|
1649
|
+
{ 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
1650
|
]);
|
|
1358
1651
|
for (let i = 0; i < n; i++) {
|
|
1359
1652
|
const t = i / SAMPLE_RATE;
|
|
1360
1653
|
let s = 0;
|
|
1361
1654
|
for (const voice of voices) {
|
|
1362
1655
|
s += Math.sin(TAU * voice.freq * t + voice.phase) * (0.75 + 0.25 * Math.sin(TAU * voice.am * t));
|
|
1656
|
+
if (opts.bright > 0) s += opts.bright * Math.sin(TAU * voice.freq * 2 * t + voice.phase);
|
|
1363
1657
|
}
|
|
1364
|
-
out[i] = s / voices.length *
|
|
1658
|
+
out[i] = s / voices.length * opts.gain;
|
|
1365
1659
|
}
|
|
1366
1660
|
return out;
|
|
1367
1661
|
}
|
|
1662
|
+
function synthAmbientPad(duration, seed = 0) {
|
|
1663
|
+
return pad([110, 165, 220], duration, seed, { amBase: 0.05, bright: 0, gain: 0.7 });
|
|
1664
|
+
}
|
|
1665
|
+
function synthLofi(duration, seed = 0) {
|
|
1666
|
+
return pad([130.81, 164.81, 196, 246.94], duration, seed, { amBase: 0.04, bright: 0.04, gain: 0.62 });
|
|
1667
|
+
}
|
|
1668
|
+
function synthPulse(duration, _seed = 0) {
|
|
1669
|
+
const { out, n } = buffer(duration);
|
|
1670
|
+
const beat2 = 2.2;
|
|
1671
|
+
for (let i = 0; i < n; i++) {
|
|
1672
|
+
const t = i / SAMPLE_RATE;
|
|
1673
|
+
const ph = t * beat2 % 1;
|
|
1674
|
+
const gate = Math.exp(-ph * 5) * 0.9 + 0.1;
|
|
1675
|
+
const s = Math.sin(TAU * 82 * t) + 0.6 * Math.sin(TAU * 123 * t) + 0.3 * Math.sin(TAU * 246 * t);
|
|
1676
|
+
out[i] = s / 1.9 * gate * 0.6;
|
|
1677
|
+
}
|
|
1678
|
+
return out;
|
|
1679
|
+
}
|
|
1680
|
+
function synthTension(duration, seed = 0) {
|
|
1681
|
+
const { out, n } = buffer(duration);
|
|
1682
|
+
const base = [98, 104, 110];
|
|
1683
|
+
for (let i = 0; i < n; i++) {
|
|
1684
|
+
const t = i / SAMPLE_RATE;
|
|
1685
|
+
const drift = 1 + 0.03 * (t / Math.max(1e-3, duration));
|
|
1686
|
+
let s = 0;
|
|
1687
|
+
for (let v = 0; v < base.length; v++) {
|
|
1688
|
+
const f = base[v] * drift * (1 + (hash01(v, seed) - 0.5) * 6e-3);
|
|
1689
|
+
s += Math.sin(TAU * f * t + hash01(v, seed + 1) * TAU);
|
|
1690
|
+
}
|
|
1691
|
+
const swell = 0.6 + 0.4 * Math.sin(TAU * 0.08 * t);
|
|
1692
|
+
out[i] = s / base.length * swell * 0.6;
|
|
1693
|
+
}
|
|
1694
|
+
return out;
|
|
1695
|
+
}
|
|
1696
|
+
function synthUplift(duration, seed = 0) {
|
|
1697
|
+
return pad([196, 246.94, 293.66, 392], duration, seed, { amBase: 0.07, bright: 0.1, gain: 0.6 });
|
|
1698
|
+
}
|
|
1699
|
+
var BGM_RECIPES = {
|
|
1700
|
+
"ambient-pad": synthAmbientPad,
|
|
1701
|
+
lofi: synthLofi,
|
|
1702
|
+
pulse: synthPulse,
|
|
1703
|
+
tension: synthTension,
|
|
1704
|
+
uplift: synthUplift
|
|
1705
|
+
};
|
|
1706
|
+
function synthBgm(name, duration, seed = 0) {
|
|
1707
|
+
return BGM_RECIPES[name](duration, seed);
|
|
1708
|
+
}
|
|
1368
1709
|
|
|
1369
1710
|
// ../render-cli/src/audio/sfx.ts
|
|
1370
1711
|
var ROOT = true ? resolve(dirname(fileURLToPath(import.meta.url)), "..") : resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..");
|
|
@@ -1414,7 +1755,7 @@ async function resolveBgmFile(source, duration, sceneDir) {
|
|
|
1414
1755
|
}
|
|
1415
1756
|
throw new Error(`bgm file "${p}" not found`);
|
|
1416
1757
|
}
|
|
1417
|
-
return writeCached(
|
|
1758
|
+
return writeCached(`${source.name}-${duration.toFixed(2)}`, () => synthBgm(source.name, duration));
|
|
1418
1759
|
}
|
|
1419
1760
|
|
|
1420
1761
|
// ../render-cli/src/audio/clip.ts
|
package/dist/compile-api.js
CHANGED
|
@@ -4,6 +4,32 @@ import { readFile } from "node:fs/promises";
|
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
+
// ../core/src/ir.ts
|
|
8
|
+
var SFX_NAMES = [
|
|
9
|
+
"whoosh",
|
|
10
|
+
"swish",
|
|
11
|
+
"rise",
|
|
12
|
+
"riser",
|
|
13
|
+
"warp",
|
|
14
|
+
"tick",
|
|
15
|
+
"click",
|
|
16
|
+
"blip",
|
|
17
|
+
"pop",
|
|
18
|
+
"select",
|
|
19
|
+
"thud",
|
|
20
|
+
"boom",
|
|
21
|
+
"knock",
|
|
22
|
+
"chime",
|
|
23
|
+
"ding",
|
|
24
|
+
"coin",
|
|
25
|
+
"sparkle",
|
|
26
|
+
"shimmer",
|
|
27
|
+
"success",
|
|
28
|
+
"zap",
|
|
29
|
+
"error"
|
|
30
|
+
];
|
|
31
|
+
var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
|
|
32
|
+
|
|
7
33
|
// ../core/src/interpolate.ts
|
|
8
34
|
var BACK_C1 = 1.70158;
|
|
9
35
|
var BACK_C2 = BACK_C1 * 1.525;
|
|
@@ -329,7 +355,6 @@ function validateScene(ir) {
|
|
|
329
355
|
}
|
|
330
356
|
}
|
|
331
357
|
}
|
|
332
|
-
const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
|
|
333
358
|
for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
|
|
334
359
|
if (typeof cue.at === "string" && !labels.has(cue.at)) {
|
|
335
360
|
problems.push(
|
|
@@ -365,6 +390,10 @@ function validateScene(ir) {
|
|
|
365
390
|
if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
|
|
366
391
|
problems.push('audio.bgm: use either "file" or "synth", not both');
|
|
367
392
|
}
|
|
393
|
+
const bgmSynth = ir.audio?.bgm?.synth;
|
|
394
|
+
if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
|
|
395
|
+
problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
|
|
396
|
+
}
|
|
368
397
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
369
398
|
}
|
|
370
399
|
var TRANSITIONS = ["cut", "crossfade"];
|
package/dist/compile.js
CHANGED
|
@@ -9,6 +9,32 @@ import { readFile } from "node:fs/promises";
|
|
|
9
9
|
import { dirname, resolve } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
|
|
12
|
+
// ../core/src/ir.ts
|
|
13
|
+
var SFX_NAMES = [
|
|
14
|
+
"whoosh",
|
|
15
|
+
"swish",
|
|
16
|
+
"rise",
|
|
17
|
+
"riser",
|
|
18
|
+
"warp",
|
|
19
|
+
"tick",
|
|
20
|
+
"click",
|
|
21
|
+
"blip",
|
|
22
|
+
"pop",
|
|
23
|
+
"select",
|
|
24
|
+
"thud",
|
|
25
|
+
"boom",
|
|
26
|
+
"knock",
|
|
27
|
+
"chime",
|
|
28
|
+
"ding",
|
|
29
|
+
"coin",
|
|
30
|
+
"sparkle",
|
|
31
|
+
"shimmer",
|
|
32
|
+
"success",
|
|
33
|
+
"zap",
|
|
34
|
+
"error"
|
|
35
|
+
];
|
|
36
|
+
var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
|
|
37
|
+
|
|
12
38
|
// ../core/src/interpolate.ts
|
|
13
39
|
var BACK_C1 = 1.70158;
|
|
14
40
|
var BACK_C2 = BACK_C1 * 1.525;
|
|
@@ -334,7 +360,6 @@ function validateScene(ir) {
|
|
|
334
360
|
}
|
|
335
361
|
}
|
|
336
362
|
}
|
|
337
|
-
const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
|
|
338
363
|
for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
|
|
339
364
|
if (typeof cue.at === "string" && !labels.has(cue.at)) {
|
|
340
365
|
problems.push(
|
|
@@ -370,6 +395,10 @@ function validateScene(ir) {
|
|
|
370
395
|
if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
|
|
371
396
|
problems.push('audio.bgm: use either "file" or "synth", not both');
|
|
372
397
|
}
|
|
398
|
+
const bgmSynth = ir.audio?.bgm?.synth;
|
|
399
|
+
if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
|
|
400
|
+
problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
|
|
401
|
+
}
|
|
373
402
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
374
403
|
}
|
|
375
404
|
|