@kieransimkin/dancemoves 3.1.23 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/docs/audio-recording-playback.md +56 -0
- package/docs/beat-alignment-media.json +99 -0
- package/docs/beat-alignment.md +79 -0
- package/docs/screenshots/beat-alignment-first-beat.png +0 -0
- package/docs/videos/beat-alignment-browser.mp4 +0 -0
- package/lib/admin.min.js +1 -1
- package/lib/build-manifest.json +21 -21
- package/lib/dancemoves.min.js +3 -3
- package/lib/index.cjs +340 -77
- package/lib/index.d.ts +18 -4
- package/lib/index.mjs +340 -77
- package/lib/react.cjs +7 -2
- package/lib/react.mjs +7 -2
- package/lib/server.cjs +54 -11
- package/lib/server.mjs +54 -11
- package/lib/wordpress.js +3 -3
- package/package.json +1 -1
- package/readme.txt +2 -2
package/lib/index.mjs
CHANGED
|
@@ -102,6 +102,38 @@ function install(__dmScope) {
|
|
|
102
102
|
return ticks;
|
|
103
103
|
}
|
|
104
104
|
var bpm = effectiveBpm(rawConfig.bpm);
|
|
105
|
+
var bpmSource = ["explicit", "analysis"].indexOf(rawConfig.bpmSource) >= 0 ? rawConfig.bpmSource : "fallback";
|
|
106
|
+
function beatAlignmentState(audio) {
|
|
107
|
+
var reading = rawConfig.beatAlignment;
|
|
108
|
+
if (!reading)
|
|
109
|
+
return { status: rawConfig.beatAlignmentStatus === "invalid" ? "invalid" : "unconfigured", applied: false };
|
|
110
|
+
var source = playbackAudio(audio);
|
|
111
|
+
var identity2 = source && (source.masterSourceUrl || source.masterId || source.currentSrc || source.src);
|
|
112
|
+
try {
|
|
113
|
+
if (!identity2 || new URL(String(identity2), document2.baseURI).href !== new URL(reading.source_url).href) {
|
|
114
|
+
return { status: "source-mismatch", applied: false };
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return { status: "source-mismatch", applied: false };
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
status: reading.phase_status,
|
|
121
|
+
applied: true,
|
|
122
|
+
firstBeatSeconds: reading.first_beat_seconds,
|
|
123
|
+
offsetPips: reading.offset_pips,
|
|
124
|
+
sourceSha256: reading.source_sha256,
|
|
125
|
+
bpmStatus: reading.bpm_status
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function rhythmicTime(audio) {
|
|
129
|
+
var source = playbackAudio(audio);
|
|
130
|
+
var seconds = source && Number.isFinite(Number(source.currentTime)) ? Number(source.currentTime) : 0;
|
|
131
|
+
var state = beatAlignmentState(source);
|
|
132
|
+
return seconds - (state.applied ? state.firstBeatSeconds : 0);
|
|
133
|
+
}
|
|
134
|
+
function sourceTick(audio, bpmOverride) {
|
|
135
|
+
return rhythmicTime(audio) * effectiveBpm(bpmOverride || bpm) * TICKS_PER_BEAT / 60;
|
|
136
|
+
}
|
|
105
137
|
function durationMilliseconds(ticks, bpmOverride) {
|
|
106
138
|
return 3750 / effectiveBpm(bpmOverride || bpm) * quantizeTicks(ticks);
|
|
107
139
|
}
|
|
@@ -109,7 +141,7 @@ function install(__dmScope) {
|
|
|
109
141
|
var settings = options || {};
|
|
110
142
|
var audio = playbackAudio(settings.audio) || (settings.clock === "audio" ? activeAudio : null);
|
|
111
143
|
if (audio && !audio.paused && !audio.ended && Number.isFinite(Number(audio.currentTime))) {
|
|
112
|
-
return
|
|
144
|
+
return sourceTick(audio, settings.bpm);
|
|
113
145
|
}
|
|
114
146
|
var now = window2.performance && typeof window2.performance.now === "function" ? window2.performance.now() : Date.now();
|
|
115
147
|
return Math.max(0, now - pageClockStarted) * effectiveBpm(settings.bpm || bpm) * TICKS_PER_BEAT / 6e4;
|
|
@@ -408,7 +440,7 @@ function install(__dmScope) {
|
|
|
408
440
|
__dmScope.dataset(document2.documentElement, "danceMovesSharedControls", null);
|
|
409
441
|
}
|
|
410
442
|
__dmScope.dataset(document2.documentElement, "danceMovesBpm", String(bpm));
|
|
411
|
-
__dmScope.dataset(document2.documentElement, "danceMovesBpmSource",
|
|
443
|
+
__dmScope.dataset(document2.documentElement, "danceMovesBpmSource", bpmSource);
|
|
412
444
|
__dmScope.dataset(document2.documentElement, "danceMovesVersion", String(rawConfig.version || ""));
|
|
413
445
|
}
|
|
414
446
|
function registerAnimationScope(root, selectors) {
|
|
@@ -944,7 +976,7 @@ function install(__dmScope) {
|
|
|
944
976
|
var root = document2.querySelector(".ks-epk");
|
|
945
977
|
if (root) {
|
|
946
978
|
__dmScope.dataset(root, "danceMovesBpm", String(bpm));
|
|
947
|
-
__dmScope.dataset(root, "danceMovesBpmSource",
|
|
979
|
+
__dmScope.dataset(root, "danceMovesBpmSource", bpmSource);
|
|
948
980
|
if (rawConfig.lyricTimingUrl)
|
|
949
981
|
__dmScope.dataset(root, "danceMovesLyricTiming", String(rawConfig.lyricTimingUrl));
|
|
950
982
|
if (rawConfig.cueTimingUrl)
|
|
@@ -973,11 +1005,14 @@ function install(__dmScope) {
|
|
|
973
1005
|
var api = {
|
|
974
1006
|
version: String(rawConfig.version || ""),
|
|
975
1007
|
bpm,
|
|
976
|
-
bpmSource
|
|
1008
|
+
bpmSource,
|
|
977
1009
|
ticksPerBeat: TICKS_PER_BEAT,
|
|
978
1010
|
quantizeTicks,
|
|
979
1011
|
durationMilliseconds,
|
|
980
1012
|
currentTick,
|
|
1013
|
+
sourceTick,
|
|
1014
|
+
rhythmicTime,
|
|
1015
|
+
beatAlignmentState,
|
|
981
1016
|
nextIntervalTick,
|
|
982
1017
|
scheduleAtInterval,
|
|
983
1018
|
deferStart,
|
|
@@ -1032,7 +1067,7 @@ function install2(__dmScope) {
|
|
|
1032
1067
|
return;
|
|
1033
1068
|
var instances = /* @__PURE__ */ new Map();
|
|
1034
1069
|
var serial2 = 0;
|
|
1035
|
-
function
|
|
1070
|
+
function finite3(value, fallback) {
|
|
1036
1071
|
value = Number(value);
|
|
1037
1072
|
return Number.isFinite(value) ? value : fallback;
|
|
1038
1073
|
}
|
|
@@ -1086,8 +1121,8 @@ function install2(__dmScope) {
|
|
|
1086
1121
|
rect = bounds.getBoundingClientRect();
|
|
1087
1122
|
}
|
|
1088
1123
|
function apply(x, y, reason) {
|
|
1089
|
-
latestX = clamp(
|
|
1090
|
-
latestY = clamp(
|
|
1124
|
+
latestX = clamp(finite3(x, 0), -1, 1);
|
|
1125
|
+
latestY = clamp(finite3(y, 0), -1, 1);
|
|
1091
1126
|
__dmScope.setStyle(state.root.style, "--dance-moves-x", latestX.toFixed(4));
|
|
1092
1127
|
__dmScope.setStyle(state.root.style, "--dance-moves-y", latestY.toFixed(4));
|
|
1093
1128
|
__dmScope.dataset(state.root, "danceMovesPointer", reason || "active");
|
|
@@ -1178,13 +1213,14 @@ function install2(__dmScope) {
|
|
|
1178
1213
|
var audio = playbackElement(options.audio, state.root.querySelector("audio"));
|
|
1179
1214
|
if (!audio)
|
|
1180
1215
|
throw new Error("DanceMovesEffects pulse requires an audio element");
|
|
1181
|
-
var ticks = Math.max(1, Math.round(
|
|
1216
|
+
var ticks = Math.max(1, Math.round(finite3(options.ticks, 16)));
|
|
1182
1217
|
var className = String(options.className || "dance-moves-playing");
|
|
1183
1218
|
var prefix = String(options.propertyPrefix || "--dance-moves-pulse").replace(/-+$/, "");
|
|
1184
1219
|
function sync() {
|
|
1185
|
-
var rate = Math.max(0.01,
|
|
1220
|
+
var rate = Math.max(0.01, finite3(audio.playbackRate, 1));
|
|
1186
1221
|
var duration = motion.durationMilliseconds(ticks) / 1e3 / rate;
|
|
1187
|
-
var
|
|
1222
|
+
var rhythmicSeconds = motion.rhythmicTime ? motion.rhythmicTime(audio) : finite3(audio.currentTime, 0);
|
|
1223
|
+
var phase = -((rhythmicSeconds / rate % duration + duration) % duration);
|
|
1188
1224
|
__dmScope.setStyle(state.root.style, prefix + "-duration", duration.toFixed(6) + "s");
|
|
1189
1225
|
__dmScope.setStyle(state.root.style, prefix + "-delay", phase.toFixed(6) + "s");
|
|
1190
1226
|
__dmScope.dataset(state.root, "danceMovesPulse", state.reduced.matches || state.forced.matches ? "inactive" : audio.paused ? "paused" : "playing");
|
|
@@ -1223,7 +1259,7 @@ function install2(__dmScope) {
|
|
|
1223
1259
|
var state = commonState(options, "cue-class");
|
|
1224
1260
|
var cue = String(options.cue || "*");
|
|
1225
1261
|
var className = String(options.className || "dance-moves-cue-active");
|
|
1226
|
-
var durationTicks = Math.max(1, Math.round(
|
|
1262
|
+
var durationTicks = Math.max(1, Math.round(finite3(options.durationTicks, 16)));
|
|
1227
1263
|
var timer = 0, count = 0;
|
|
1228
1264
|
function clear(reason) {
|
|
1229
1265
|
if (timer)
|
|
@@ -1279,16 +1315,16 @@ function install2(__dmScope) {
|
|
|
1279
1315
|
if (!audio)
|
|
1280
1316
|
throw new Error("DanceMovesEffects cueTimeline requires an audio element");
|
|
1281
1317
|
var cues = (Array.isArray(options.cues) ? options.cues : []).map(function(cue, index) {
|
|
1282
|
-
var time =
|
|
1318
|
+
var time = finite3(cue && cue.time, NaN);
|
|
1283
1319
|
if (!Number.isFinite(time) || time < 0)
|
|
1284
1320
|
throw new TypeError("DanceMovesEffects cueTimeline cue time must be a non-negative number");
|
|
1285
1321
|
return Object.freeze({ id: String(cue.id || "cue-" + index), time, end: Number.isFinite(Number(cue.end)) ? Number(cue.end) : null, data: cue.data });
|
|
1286
1322
|
}).sort(function(a, b) {
|
|
1287
1323
|
return a.time - b.time;
|
|
1288
1324
|
});
|
|
1289
|
-
var tolerance = Math.max(0,
|
|
1290
|
-
var maxCrossingGap = Math.max(0.05,
|
|
1291
|
-
var fired = /* @__PURE__ */ new Set(), frame = 0, previous =
|
|
1325
|
+
var tolerance = Math.max(0, finite3(options.seekLandingTolerance, 0.05));
|
|
1326
|
+
var maxCrossingGap = Math.max(0.05, finite3(options.maxCrossingGap, 1.25));
|
|
1327
|
+
var fired = /* @__PURE__ */ new Set(), frame = 0, previous = finite3(audio.currentTime, 0), status = "idle", generation = 0, previousWall = 0;
|
|
1292
1328
|
function blocked() {
|
|
1293
1329
|
return document2.hidden || state.reduced.matches || state.forced.matches;
|
|
1294
1330
|
}
|
|
@@ -1298,7 +1334,7 @@ function install2(__dmScope) {
|
|
|
1298
1334
|
});
|
|
1299
1335
|
}
|
|
1300
1336
|
function publish(reason, extra) {
|
|
1301
|
-
var time =
|
|
1337
|
+
var time = finite3(audio.currentTime, 0);
|
|
1302
1338
|
__dmScope.dataset(state.root, "danceMovesCueTimeline", reason);
|
|
1303
1339
|
if (typeof options.render === "function")
|
|
1304
1340
|
options.render({ root: state.root, audio, time, previousTime: previous, reason, playing: !audio.paused && !audio.ended && !blocked(), active: activeAt(time), generation, detail: extra || null });
|
|
@@ -1308,10 +1344,10 @@ function install2(__dmScope) {
|
|
|
1308
1344
|
return;
|
|
1309
1345
|
fired.add(cue.id);
|
|
1310
1346
|
if (typeof options.onCue === "function")
|
|
1311
|
-
options.onCue({ root: state.root, audio, cue, time:
|
|
1347
|
+
options.onCue({ root: state.root, audio, cue, time: finite3(audio.currentTime, cue.time), reason, generation });
|
|
1312
1348
|
}
|
|
1313
1349
|
function rebuild(time, reason, allowLanding) {
|
|
1314
|
-
time = Math.max(0,
|
|
1350
|
+
time = Math.max(0, finite3(time, 0));
|
|
1315
1351
|
generation += 1;
|
|
1316
1352
|
fired.clear();
|
|
1317
1353
|
cues.forEach(function(cue) {
|
|
@@ -1337,7 +1373,7 @@ function install2(__dmScope) {
|
|
|
1337
1373
|
frame = 0;
|
|
1338
1374
|
if (audio.paused || audio.ended || blocked())
|
|
1339
1375
|
return stop(audio.ended ? "ended" : blocked() ? "inactive" : "paused");
|
|
1340
|
-
var now = Math.max(0,
|
|
1376
|
+
var now = Math.max(0, finite3(audio.currentTime, previous));
|
|
1341
1377
|
var delta = now - previous;
|
|
1342
1378
|
if (delta < -1e-3 || delta > maxCrossingGap)
|
|
1343
1379
|
rebuild(now, "discontinuity", false);
|
|
@@ -1346,8 +1382,8 @@ function install2(__dmScope) {
|
|
|
1346
1382
|
if (previous < cue.time && now >= cue.time)
|
|
1347
1383
|
fire(cue, "crossing");
|
|
1348
1384
|
});
|
|
1349
|
-
var wallDelta = previousWall ? Math.min(0.25, Math.max(0, (
|
|
1350
|
-
previousWall =
|
|
1385
|
+
var wallDelta = previousWall ? Math.min(0.25, Math.max(0, (finite3(wallNow, previousWall) - previousWall) / 1e3)) : 0;
|
|
1386
|
+
previousWall = finite3(wallNow, previousWall);
|
|
1351
1387
|
previous = now;
|
|
1352
1388
|
status = "playing";
|
|
1353
1389
|
publish("frame", { deltaSeconds: wallDelta });
|
|
@@ -1356,7 +1392,7 @@ function install2(__dmScope) {
|
|
|
1356
1392
|
function start(reason) {
|
|
1357
1393
|
if (frame || audio.paused || audio.ended || blocked())
|
|
1358
1394
|
return publish(blocked() ? "inactive" : audio.ended ? "ended" : "paused");
|
|
1359
|
-
previous = Math.max(0,
|
|
1395
|
+
previous = Math.max(0, finite3(audio.currentTime, 0));
|
|
1360
1396
|
previousWall = 0;
|
|
1361
1397
|
status = reason || "playing";
|
|
1362
1398
|
publish(status);
|
|
@@ -1420,7 +1456,7 @@ function install2(__dmScope) {
|
|
|
1420
1456
|
start,
|
|
1421
1457
|
stop,
|
|
1422
1458
|
snapshot: function() {
|
|
1423
|
-
return { id: state.id, type: "cue-timeline", status, time:
|
|
1459
|
+
return { id: state.id, type: "cue-timeline", status, time: finite3(audio.currentTime, 0), previousTime: previous, generation, fired: Array.from(fired), active: activeAt(finite3(audio.currentTime, 0)).map(function(cue) {
|
|
1424
1460
|
return cue.id;
|
|
1425
1461
|
}) };
|
|
1426
1462
|
},
|
|
@@ -1443,28 +1479,28 @@ function install2(__dmScope) {
|
|
|
1443
1479
|
if (!target)
|
|
1444
1480
|
throw new Error("DanceMovesEffects spritePlayback requires a target element");
|
|
1445
1481
|
var frameMap = Array.isArray(options.frameMap) && options.frameMap.length ? options.frameMap.map(function(value) {
|
|
1446
|
-
value = Math.floor(
|
|
1482
|
+
value = Math.floor(finite3(value, -1));
|
|
1447
1483
|
if (value < 0)
|
|
1448
1484
|
throw new TypeError("DanceMovesEffects spritePlayback frameMap values must be non-negative integers");
|
|
1449
1485
|
return value;
|
|
1450
1486
|
}) : null;
|
|
1451
|
-
var frameCount = frameMap ? frameMap.length : Math.max(1, Math.round(
|
|
1452
|
-
var columns = Math.max(1, Math.round(
|
|
1453
|
-
var framesPerRow = clamp(Math.round(
|
|
1454
|
-
var columnOffset = clamp(Math.round(
|
|
1455
|
-
var rowOffset = Math.max(0, Math.round(
|
|
1456
|
-
var cycleTicks = Math.max(1, Math.round(
|
|
1457
|
-
var phaseOffsetSeconds =
|
|
1458
|
-
var staticFrame = clamp(Math.round(
|
|
1487
|
+
var frameCount = frameMap ? frameMap.length : Math.max(1, Math.round(finite3(options.frameCount, 1)));
|
|
1488
|
+
var columns = Math.max(1, Math.round(finite3(options.columns, frameCount)));
|
|
1489
|
+
var framesPerRow = clamp(Math.round(finite3(options.framesPerRow, columns)), 1, columns);
|
|
1490
|
+
var columnOffset = clamp(Math.round(finite3(options.columnOffset, 0)), 0, columns - framesPerRow);
|
|
1491
|
+
var rowOffset = Math.max(0, Math.round(finite3(options.rowOffset, 0)));
|
|
1492
|
+
var cycleTicks = Math.max(1, Math.round(finite3(options.cycleTicks, 32)));
|
|
1493
|
+
var phaseOffsetSeconds = finite3(options.phaseOffsetSeconds, 0);
|
|
1494
|
+
var staticFrame = clamp(Math.round(finite3(options.staticFrame, 0)), 0, frameCount - 1);
|
|
1459
1495
|
var prefix = String(options.propertyPrefix || "--dance-moves-sprite").replace(/-+$/, "");
|
|
1460
1496
|
if (prefix.slice(0, 2) !== "--")
|
|
1461
1497
|
prefix = "--" + prefix.replace(/^-+/, "");
|
|
1462
1498
|
var tiers = Array.isArray(options.qualityTiers) && options.qualityTiers.length ? options.qualityTiers.map(String) : ["full", "constrained", "minimal"];
|
|
1463
1499
|
var suppliedFps = Array.isArray(options.qualityFramesPerSecond) ? options.qualityFramesPerSecond : [60, 30, 15];
|
|
1464
1500
|
var qualityFps = tiers.map(function(_, index) {
|
|
1465
|
-
return Math.max(1,
|
|
1501
|
+
return Math.max(1, finite3(suppliedFps[index], suppliedFps[suppliedFps.length - 1] || 15));
|
|
1466
1502
|
});
|
|
1467
|
-
var qualityIndex = clamp(Math.round(
|
|
1503
|
+
var qualityIndex = clamp(Math.round(finite3(options.initialQuality, 0)), 0, tiers.length - 1);
|
|
1468
1504
|
var frame = 0, status = "idle", lastWall = -Infinity, renders = 0, latest = null;
|
|
1469
1505
|
function blocked() {
|
|
1470
1506
|
return document2.hidden || state.reduced.matches || state.forced.matches;
|
|
@@ -1474,9 +1510,10 @@ function install2(__dmScope) {
|
|
|
1474
1510
|
return { sourceFrame, column: sourceFrame % columns, row: Math.floor(sourceFrame / columns) };
|
|
1475
1511
|
}
|
|
1476
1512
|
function calculate(reason, staticFallback) {
|
|
1477
|
-
var cycleSeconds = Math.max(1e-3,
|
|
1478
|
-
var time = Math.max(0,
|
|
1479
|
-
var
|
|
1513
|
+
var cycleSeconds = Math.max(1e-3, finite3(motion.durationMilliseconds(cycleTicks), 1) / 1e3);
|
|
1514
|
+
var time = Math.max(0, finite3(audio.currentTime, 0));
|
|
1515
|
+
var rhythmicSeconds = motion.rhythmicTime ? motion.rhythmicTime(audio) : time;
|
|
1516
|
+
var phase = ((rhythmicSeconds - phaseOffsetSeconds) % cycleSeconds + cycleSeconds) % cycleSeconds / cycleSeconds;
|
|
1480
1517
|
var localFrame = staticFallback ? staticFrame : Math.min(frameCount - 1, Math.floor(phase * frameCount + 1e-9));
|
|
1481
1518
|
var mapped = mappedFrame(localFrame);
|
|
1482
1519
|
return {
|
|
@@ -1530,8 +1567,8 @@ function install2(__dmScope) {
|
|
|
1530
1567
|
if (audio.paused || audio.ended || blocked())
|
|
1531
1568
|
return stop(audio.ended ? "ended" : blocked() ? "inactive" : "paused", audio.ended || blocked());
|
|
1532
1569
|
var minimumInterval = 1e3 / qualityFps[qualityIndex];
|
|
1533
|
-
if (!Number.isFinite(lastWall) ||
|
|
1534
|
-
lastWall =
|
|
1570
|
+
if (!Number.isFinite(lastWall) || finite3(wallNow, lastWall) - lastWall >= minimumInterval - 0.01) {
|
|
1571
|
+
lastWall = finite3(wallNow, lastWall);
|
|
1535
1572
|
status = "playing";
|
|
1536
1573
|
publish("frame", false);
|
|
1537
1574
|
}
|
|
@@ -1562,7 +1599,7 @@ function install2(__dmScope) {
|
|
|
1562
1599
|
}
|
|
1563
1600
|
function setQuality(value, reason) {
|
|
1564
1601
|
var named = typeof value === "string" ? tiers.indexOf(value) : -1;
|
|
1565
|
-
qualityIndex = named >= 0 ? named : clamp(Math.round(
|
|
1602
|
+
qualityIndex = named >= 0 ? named : clamp(Math.round(finite3(value, qualityIndex)), 0, tiers.length - 1);
|
|
1566
1603
|
lastWall = -Infinity;
|
|
1567
1604
|
publish(reason || "quality-change", audio.ended || blocked());
|
|
1568
1605
|
}
|
|
@@ -1616,7 +1653,7 @@ function install2(__dmScope) {
|
|
|
1616
1653
|
},
|
|
1617
1654
|
setQuality,
|
|
1618
1655
|
snapshot: function() {
|
|
1619
|
-
return { id: state.id, type: "sprite-playback", status, time:
|
|
1656
|
+
return { id: state.id, type: "sprite-playback", status, time: finite3(audio.currentTime, 0), frame: latest ? latest.frame : null, localFrame: latest ? latest.localFrame : null, column: latest ? latest.column : null, row: latest ? latest.row : null, phase: latest ? latest.phase : null, cycleTicks, cycleSeconds: latest ? latest.cycleSeconds : null, qualityTier: tiers[qualityIndex], qualityIndex, renders, running: Boolean(frame) };
|
|
1620
1657
|
},
|
|
1621
1658
|
teardown: function() {
|
|
1622
1659
|
stop("teardown", true);
|
|
@@ -1683,8 +1720,8 @@ function install2(__dmScope) {
|
|
|
1683
1720
|
popover.appendChild(viewport);
|
|
1684
1721
|
__dmScope.dataset(popover, "danceMovesLyricStage", "ready");
|
|
1685
1722
|
__dmScope.setStyle(popover.style, "--dance-moves-lyric-progress", "0");
|
|
1686
|
-
var travelTicks = Math.max(1, Math.round(
|
|
1687
|
-
var defaultCueTicks = Math.max(1, Math.round(
|
|
1723
|
+
var travelTicks = Math.max(1, Math.round(finite3(options.travelTicks, 32)));
|
|
1724
|
+
var defaultCueTicks = Math.max(1, Math.round(finite3(options.cueDurationTicks, 32)));
|
|
1688
1725
|
var latest = null, phase = "idle", progress = 0, frame = 0, wakeTimer = 0, cueTimer = 0, cueCleanup = null, cueCount = 0;
|
|
1689
1726
|
var lyricEffects = [];
|
|
1690
1727
|
var slots = Object.freeze({ previous, current, next, viewport, track });
|
|
@@ -1733,7 +1770,7 @@ function install2(__dmScope) {
|
|
|
1733
1770
|
publish(reason || value);
|
|
1734
1771
|
}
|
|
1735
1772
|
function setProgress(value) {
|
|
1736
|
-
progress = clamp(
|
|
1773
|
+
progress = clamp(finite3(value, 0), 0, 1);
|
|
1737
1774
|
__dmScope.setStyle(popover.style, "--dance-moves-lyric-progress", progress.toFixed(5));
|
|
1738
1775
|
}
|
|
1739
1776
|
function copyLyrics(detail) {
|
|
@@ -1750,7 +1787,7 @@ function install2(__dmScope) {
|
|
|
1750
1787
|
var cleanup = typeof result === "function" ? result : typeof result.cleanup === "function" ? result.cleanup : null;
|
|
1751
1788
|
if (!cleanup)
|
|
1752
1789
|
return;
|
|
1753
|
-
var durationTicks = result && typeof result === "object" ? Math.max(1, Math.round(
|
|
1790
|
+
var durationTicks = result && typeof result === "object" ? Math.max(1, Math.round(finite3(result.durationTicks, 64))) : 64;
|
|
1754
1791
|
var record = { cleanup, timer: 0 };
|
|
1755
1792
|
record.timer = window2.setTimeout(function() {
|
|
1756
1793
|
lyricEffects = lyricEffects.filter(function(item) {
|
|
@@ -1776,7 +1813,7 @@ function install2(__dmScope) {
|
|
|
1776
1813
|
setProgress(0);
|
|
1777
1814
|
return setPhase("waiting", "no-next-line");
|
|
1778
1815
|
}
|
|
1779
|
-
var now = Math.max(0,
|
|
1816
|
+
var now = Math.max(0, finite3(audio.currentTime, 0));
|
|
1780
1817
|
if (now < windowRange.start)
|
|
1781
1818
|
return reconcile("frame-before-window");
|
|
1782
1819
|
if (now >= windowRange.end) {
|
|
@@ -1806,7 +1843,7 @@ function install2(__dmScope) {
|
|
|
1806
1843
|
setProgress(0);
|
|
1807
1844
|
return setPhase("waiting", reason || "no-next-line");
|
|
1808
1845
|
}
|
|
1809
|
-
var now = Math.max(0,
|
|
1846
|
+
var now = Math.max(0, finite3(audio.currentTime, 0));
|
|
1810
1847
|
if (now >= windowRange.end) {
|
|
1811
1848
|
setProgress(1);
|
|
1812
1849
|
return setPhase("arrived", reason || "arrived");
|
|
@@ -1821,7 +1858,7 @@ function install2(__dmScope) {
|
|
|
1821
1858
|
wakeTimer = window2.setTimeout(function() {
|
|
1822
1859
|
wakeTimer = 0;
|
|
1823
1860
|
tick();
|
|
1824
|
-
}, Math.max(0, (windowRange.start - now) * 1e3 / Math.max(0.01,
|
|
1861
|
+
}, Math.max(0, (windowRange.start - now) * 1e3 / Math.max(0.01, finite3(audio.playbackRate, 1))));
|
|
1825
1862
|
}
|
|
1826
1863
|
function onLyric(detail) {
|
|
1827
1864
|
var previousDetail = latest;
|
|
@@ -1876,7 +1913,7 @@ function install2(__dmScope) {
|
|
|
1876
1913
|
else if (result && typeof result === "object") {
|
|
1877
1914
|
if (typeof result.cleanup === "function")
|
|
1878
1915
|
cueCleanup = result.cleanup;
|
|
1879
|
-
durationTicks = Math.max(1, Math.round(
|
|
1916
|
+
durationTicks = Math.max(1, Math.round(finite3(result.durationTicks, defaultCueTicks)));
|
|
1880
1917
|
}
|
|
1881
1918
|
cueTimer = window2.setTimeout(function() {
|
|
1882
1919
|
clearCue("complete");
|
|
@@ -1948,16 +1985,16 @@ function install2(__dmScope) {
|
|
|
1948
1985
|
var hazardSources = Array.isArray(options.hazardSources) ? options.hazardSources.filter(Boolean).map(String) : [];
|
|
1949
1986
|
if (!shipSources.length || !hazardSources.length)
|
|
1950
1987
|
throw new Error("DanceMovesEffects cooperativeArena requires ship and hazard sources");
|
|
1951
|
-
var shipCount = clamp(Math.round(
|
|
1952
|
-
var hazardCount = clamp(Math.round(
|
|
1953
|
-
var speed = clamp(
|
|
1988
|
+
var shipCount = clamp(Math.round(finite3(options.shipCount, 3)), 2, 12);
|
|
1989
|
+
var hazardCount = clamp(Math.round(finite3(options.hazardCount, 8)), 3, 48);
|
|
1990
|
+
var speed = clamp(finite3(options.speed, 1), 0.35, 2.4);
|
|
1954
1991
|
var inlineTransform = options.inlineTransform === true;
|
|
1955
|
-
var bpm = Math.max(1,
|
|
1956
|
-
var shotIntervalTicks = clamp(Math.round(
|
|
1992
|
+
var bpm = Math.max(1, finite3(options.bpm, motion.bpm || 120));
|
|
1993
|
+
var shotIntervalTicks = clamp(Math.round(finite3(options.shotIntervalTicks, 32)), 4, 256);
|
|
1957
1994
|
var shotIntervalSeconds = motion.durationMilliseconds ? motion.durationMilliseconds(shotIntervalTicks, bpm) / 1e3 : shotIntervalTicks * 60 / (bpm * 16);
|
|
1958
|
-
var burstDurationSeconds = clamp(
|
|
1959
|
-
var shipHitboxScale = clamp(
|
|
1960
|
-
var hazardHitboxScale = clamp(
|
|
1995
|
+
var burstDurationSeconds = clamp(finite3(options.burstDurationBeats, 1.5), 0.1, 8) * 60 / bpm;
|
|
1996
|
+
var shipHitboxScale = clamp(finite3(options.shipHitboxScale, 1), 0.4, 1);
|
|
1997
|
+
var hazardHitboxScale = clamp(finite3(options.hazardHitboxScale, 1), 0.4, 1);
|
|
1961
1998
|
var removers = state.removers, ships = [], hazards = [], shots = [], bursts = [], burstTimers = [];
|
|
1962
1999
|
var activeShips = shipCount, activeHazards = hazardCount;
|
|
1963
2000
|
var width = 1, height = 1, frame = 0, last = 0, elapsed = 0, nextShot = 0, hits = 0;
|
|
@@ -2289,7 +2326,7 @@ function install2(__dmScope) {
|
|
|
2289
2326
|
reconcile(reason || "manual");
|
|
2290
2327
|
},
|
|
2291
2328
|
setDensity: function(nextShips, nextHazards) {
|
|
2292
|
-
var shipsNext = clamp(Math.round(
|
|
2329
|
+
var shipsNext = clamp(Math.round(finite3(nextShips, activeShips)), 2, shipCount), hazardsNext = clamp(Math.round(finite3(nextHazards, activeHazards)), 3, hazardCount);
|
|
2293
2330
|
if (shipsNext === activeShips && hazardsNext === activeHazards)
|
|
2294
2331
|
return;
|
|
2295
2332
|
activeShips = shipsNext;
|
|
@@ -2332,12 +2369,12 @@ function install2(__dmScope) {
|
|
|
2332
2369
|
options = options || {};
|
|
2333
2370
|
var state = commonState(options, "quality");
|
|
2334
2371
|
var tiers = Array.isArray(options.tiers) && options.tiers.length ? options.tiers.map(String) : ["full", "constrained", "minimal"];
|
|
2335
|
-
var index = clamp(Math.round(
|
|
2336
|
-
var sampleMs = Math.max(800,
|
|
2337
|
-
var poorWindows = Math.max(2, Math.round(
|
|
2338
|
-
var healthyWindows = Math.max(3, Math.round(
|
|
2339
|
-
var downgradeRatio = clamp(
|
|
2340
|
-
var recoveryRatio = clamp(
|
|
2372
|
+
var index = clamp(Math.round(finite3(options.initial, 0)), 0, tiers.length - 1);
|
|
2373
|
+
var sampleMs = Math.max(800, finite3(options.sampleMilliseconds, 1600));
|
|
2374
|
+
var poorWindows = Math.max(2, Math.round(finite3(options.poorWindows, 2)));
|
|
2375
|
+
var healthyWindows = Math.max(3, Math.round(finite3(options.healthyWindows, 5)));
|
|
2376
|
+
var downgradeRatio = clamp(finite3(options.downgradeRatio, 0.72), 0.2, 0.95);
|
|
2377
|
+
var recoveryRatio = clamp(finite3(options.recoveryRatio, 0.9), downgradeRatio, 1);
|
|
2341
2378
|
var frame = 0, frames = 0, start = 0, reference = 0, poor = 0, healthy = 0, lastShift = 0, running = true;
|
|
2342
2379
|
var preferenceLimited = false, tierBeforePreference = index;
|
|
2343
2380
|
function publish(reason, sample) {
|
|
@@ -2381,12 +2418,12 @@ function install2(__dmScope) {
|
|
|
2381
2418
|
poor = 0;
|
|
2382
2419
|
healthy = 0;
|
|
2383
2420
|
}
|
|
2384
|
-
if (poor >= poorWindows && index < tiers.length - 1 && now - lastShift >=
|
|
2421
|
+
if (poor >= poorWindows && index < tiers.length - 1 && now - lastShift >= finite3(options.downgradeCooldownMilliseconds, 5e3)) {
|
|
2385
2422
|
index += 1;
|
|
2386
2423
|
poor = 0;
|
|
2387
2424
|
lastShift = now;
|
|
2388
2425
|
publish("sustained-low-fps", { fps });
|
|
2389
|
-
} else if (healthy >= healthyWindows && index > 0 && now - lastShift >=
|
|
2426
|
+
} else if (healthy >= healthyWindows && index > 0 && now - lastShift >= finite3(options.recoveryCooldownMilliseconds, 12e3)) {
|
|
2390
2427
|
index -= 1;
|
|
2391
2428
|
healthy = 0;
|
|
2392
2429
|
lastShift = now;
|
|
@@ -2425,7 +2462,7 @@ function install2(__dmScope) {
|
|
|
2425
2462
|
id: state.id,
|
|
2426
2463
|
type: "quality",
|
|
2427
2464
|
setTier: function(value, reason) {
|
|
2428
|
-
index = clamp(Math.round(
|
|
2465
|
+
index = clamp(Math.round(finite3(value, index)), 0, tiers.length - 1);
|
|
2429
2466
|
publish(reason || "manual");
|
|
2430
2467
|
},
|
|
2431
2468
|
snapshot: function() {
|
|
@@ -4895,8 +4932,9 @@ function install9(__dmScope) {
|
|
|
4895
4932
|
anchor = 0;
|
|
4896
4933
|
lastClock = mode;
|
|
4897
4934
|
const tempo = bpm();
|
|
4898
|
-
const
|
|
4899
|
-
|
|
4935
|
+
const sourceBeats = useAudio ? Math.max(0, Number.isFinite(audio.currentTime) ? audio.currentTime : 0) * tempo / 60 : motion.currentTick({ clock: "page" }) / 16;
|
|
4936
|
+
const beats = useAudio && motion.sourceTick ? motion.sourceTick(audio) / 16 : sourceBeats;
|
|
4937
|
+
return { beats, sourceBeats, mode, tempo };
|
|
4900
4938
|
}
|
|
4901
4939
|
function blocked() {
|
|
4902
4940
|
if (!enabled)
|
|
@@ -4934,7 +4972,8 @@ function install9(__dmScope) {
|
|
|
4934
4972
|
audio: s.mode === "audio" ? audio : null,
|
|
4935
4973
|
rudiment: row.name,
|
|
4936
4974
|
clock: s.mode,
|
|
4937
|
-
sourceBeats: s.
|
|
4975
|
+
sourceBeats: s.sourceBeats,
|
|
4976
|
+
rhythmicBeats: s.beats,
|
|
4938
4977
|
bpm: s.tempo,
|
|
4939
4978
|
rate,
|
|
4940
4979
|
pip: neutral ? null : pip,
|
|
@@ -5480,6 +5519,35 @@ function createScope(root, config, { legacyGlobals = false, nonce, orientation =
|
|
|
5480
5519
|
return api;
|
|
5481
5520
|
}
|
|
5482
5521
|
|
|
5522
|
+
// .build/modules/beat-alignment.mjs
|
|
5523
|
+
var hash = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
5524
|
+
function rational(value, label) {
|
|
5525
|
+
if (typeof value !== "string" || !/^-?\d{1,80}(?:\/[1-9]\d{0,79})?$/.test(value)) throw new TypeError(`Invalid exact ${label}`);
|
|
5526
|
+
const [n, d = "1"] = value.split("/");
|
|
5527
|
+
return [BigInt(n), BigInt(d)];
|
|
5528
|
+
}
|
|
5529
|
+
function numeric(value, label, min, max) {
|
|
5530
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) throw new RangeError(`Invalid ${label}`);
|
|
5531
|
+
return value;
|
|
5532
|
+
}
|
|
5533
|
+
function validateBeatAlignment(input) {
|
|
5534
|
+
if (input === void 0 || input === null || input === "") return null;
|
|
5535
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || input.schema !== "stemlab.beat-alignment.v1" || input.pips_per_beat !== 64 || input.canonical_first_beat_pip !== 0 || !hash(input.source_sha256) || !hash(input.evidence_sha256) || !hash(input.report_sha256)) throw new TypeError("Invalid source-bound StemLab beat alignment");
|
|
5536
|
+
const bpm = numeric(input.bpm, "alignment BPM", 20, 400);
|
|
5537
|
+
const first = numeric(input.first_beat_seconds, "first beat", 0, 86400);
|
|
5538
|
+
const offset = numeric(input.offset_pips, "pip offset", -36864e3, 0);
|
|
5539
|
+
const [bn, bd] = rational(input.exact_bpm, "BPM"), [fn, fd] = rational(input.first_beat_exact_seconds, "first beat"), [pn, pd] = rational(input.exact_offset_pips, "pip offset");
|
|
5540
|
+
if (fn * bn * 64n * pd + pn * fd * bd * 60n !== 0n || bn <= 0n || fn < 0n || Math.abs(Number(bn) / Number(bd) - bpm) > 1e-9 || Math.abs(Number(fn) / Number(fd) - first) > 1e-9 || Math.abs(Number(pn) / Number(pd) - offset) > 1e-7) throw new RangeError("Exact beat alignment and numeric clock disagree");
|
|
5541
|
+
if (!["canonical", "estimated"].includes(input.bpm_status) || !["estimated", "confirmed", "synthetic"].includes(input.phase_status)) throw new TypeError("Alignment authority is required");
|
|
5542
|
+
for (const key of ["analysis_id", "bpm_basis"]) if (typeof input[key] !== "string" || !/^[a-zA-Z0-9_.-]{1,128}$/.test(input[key])) throw new TypeError(`Invalid ${key}`);
|
|
5543
|
+
if (input.source_audio_verified !== true || !input.source_audio || !Number.isSafeInteger(input.source_audio.num_frames) || input.source_audio.num_frames < 1 || !Number.isSafeInteger(input.source_audio.sample_rate) || input.source_audio.sample_rate < 1 || first >= input.source_audio.num_frames / input.source_audio.sample_rate) throw new TypeError("Verified source audio frame bounds are required");
|
|
5544
|
+
const sourceUrl = input.source_url;
|
|
5545
|
+
if (typeof sourceUrl !== "string" || sourceUrl.length > 4096 || /[\u0000-\u001f]/.test(sourceUrl)) throw new TypeError("An exact verified master URL is required");
|
|
5546
|
+
const parsed = new URL(sourceUrl);
|
|
5547
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password || parsed.hash) throw new TypeError("Invalid verified master URL");
|
|
5548
|
+
return Object.freeze({ ...input, bpm, first_beat_seconds: first, offset_pips: offset, source_audio: Object.freeze({ ...input.source_audio }) });
|
|
5549
|
+
}
|
|
5550
|
+
|
|
5483
5551
|
// .build/modules/config.mjs
|
|
5484
5552
|
var PAGE_META_KEYS = Object.freeze({
|
|
5485
5553
|
bpm: "_dance_moves_bpm",
|
|
@@ -5487,7 +5555,9 @@ var PAGE_META_KEYS = Object.freeze({
|
|
|
5487
5555
|
cue: "_dance_moves_cue_timing_id",
|
|
5488
5556
|
popups: "_dance_moves_lyric_popups_enabled",
|
|
5489
5557
|
duration: "_dance_moves_master_duration_ms",
|
|
5490
|
-
effect: "_dance_moves_effect"
|
|
5558
|
+
effect: "_dance_moves_effect",
|
|
5559
|
+
alignment: "_dance_moves_beat_alignment",
|
|
5560
|
+
alignmentAudio: "_dance_moves_beat_alignment_audio_id"
|
|
5491
5561
|
});
|
|
5492
5562
|
var object = (value) => value && typeof value === "object" && !Array.isArray(value);
|
|
5493
5563
|
function finite(value, name, min, max) {
|
|
@@ -5506,7 +5576,11 @@ function url(value, name) {
|
|
|
5506
5576
|
function validatePageConfig(input = {}) {
|
|
5507
5577
|
if (!object(input)) throw new TypeError("Page configuration must be an object");
|
|
5508
5578
|
const explicit = input.bpm !== void 0 && input.bpm !== null && input.bpm !== "";
|
|
5509
|
-
const
|
|
5579
|
+
const beatAlignment = validateBeatAlignment(input.beatAlignment);
|
|
5580
|
+
const canonicalExplicit = explicit && !["fallback", "analysis"].includes(input.bpmSource);
|
|
5581
|
+
if (canonicalExplicit && beatAlignment && Math.abs(input.bpm - beatAlignment.bpm) > 500001e-9) throw new RangeError("Stored BPM and selected beat alignment disagree; select a matching BPM basis");
|
|
5582
|
+
const bpm = beatAlignment ? beatAlignment.bpm : explicit ? Math.round(finite(input.bpm, "bpm", 20, 400) * 1e3) / 1e3 : 120;
|
|
5583
|
+
if (explicit) finite(input.bpm, "bpm", 20, 400);
|
|
5510
5584
|
const effect = input.effect ?? "";
|
|
5511
5585
|
if (!["", "paper-planes"].includes(effect)) throw new RangeError("effect must be empty or paper-planes");
|
|
5512
5586
|
if (input.lyricPopupsEnabled !== void 0 && typeof input.lyricPopupsEnabled !== "boolean") throw new TypeError("lyricPopupsEnabled must be boolean");
|
|
@@ -5518,8 +5592,9 @@ function validatePageConfig(input = {}) {
|
|
|
5518
5592
|
return Object.freeze({
|
|
5519
5593
|
pageId,
|
|
5520
5594
|
bpm,
|
|
5521
|
-
bpmSource:
|
|
5595
|
+
bpmSource: canonicalExplicit ? "explicit" : beatAlignment ? "analysis" : "fallback",
|
|
5522
5596
|
fallbackBpm: 120,
|
|
5597
|
+
beatAlignment,
|
|
5523
5598
|
ticksPerBeat: 16,
|
|
5524
5599
|
tickDefinition: "sixteenth-of-beat",
|
|
5525
5600
|
longDurationQuantumTicks: 16,
|
|
@@ -5535,9 +5610,9 @@ function validatePageConfig(input = {}) {
|
|
|
5535
5610
|
}
|
|
5536
5611
|
async function fromWordPressMeta(meta, resolveAttachment) {
|
|
5537
5612
|
if (!object(meta)) throw new TypeError("meta must be an object");
|
|
5538
|
-
const
|
|
5613
|
+
const numeric2 = (value) => value !== "" && value !== null && value !== void 0 && Number.isFinite(Number(value));
|
|
5539
5614
|
const b = meta[PAGE_META_KEYS.bpm];
|
|
5540
|
-
const bpm =
|
|
5615
|
+
const bpm = numeric2(b) && Number(b) >= 20 && Number(b) <= 400 ? Number(b) : void 0;
|
|
5541
5616
|
const resolve = async (key) => {
|
|
5542
5617
|
const value = Number(meta[key] || 0);
|
|
5543
5618
|
if (!Number.isSafeInteger(value)) throw new RangeError("Attachment ID must be a safe integer");
|
|
@@ -5547,8 +5622,15 @@ async function fromWordPressMeta(meta, resolveAttachment) {
|
|
|
5547
5622
|
return await resolveAttachment(id) || "";
|
|
5548
5623
|
};
|
|
5549
5624
|
const duration = Number(meta[PAGE_META_KEYS.duration]);
|
|
5625
|
+
let beatAlignment = meta[PAGE_META_KEYS.alignment] || null;
|
|
5626
|
+
if (typeof beatAlignment === "string") {
|
|
5627
|
+
if (beatAlignment.length > 16384) throw new RangeError("Beat alignment exceeds 16 KiB");
|
|
5628
|
+
beatAlignment = JSON.parse(beatAlignment);
|
|
5629
|
+
}
|
|
5630
|
+
if (beatAlignment) beatAlignment = { ...beatAlignment, source_url: await resolve(PAGE_META_KEYS.alignmentAudio) };
|
|
5550
5631
|
return validatePageConfig({
|
|
5551
5632
|
bpm,
|
|
5633
|
+
beatAlignment,
|
|
5552
5634
|
lyricTimingUrl: await resolve(PAGE_META_KEYS.lyric),
|
|
5553
5635
|
cueTimingUrl: await resolve(PAGE_META_KEYS.cue),
|
|
5554
5636
|
lyricPopupsEnabled: meta[PAGE_META_KEYS.popups] === true || String(meta[PAGE_META_KEYS.popups]) === "1",
|
|
@@ -6089,7 +6171,8 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
|
|
|
6089
6171
|
error: { get: () => read().error || null },
|
|
6090
6172
|
element: { value: isNative ? master : null },
|
|
6091
6173
|
activePlayer: { get: () => delegate ? delegate.activePlayer : active.element },
|
|
6092
|
-
masterId: { get: () => delegate ? delegate.masterId : master.currentSrc || master.src || master.id }
|
|
6174
|
+
masterId: { get: () => delegate ? delegate.masterId : master.currentSrc || master.src || master.id },
|
|
6175
|
+
masterSourceUrl: { get: () => delegate ? delegate.masterSourceUrl : reference?.sourceUrl || master.currentSrc || master.src || "" }
|
|
6093
6176
|
});
|
|
6094
6177
|
transport.matches = (selector) => target.matches(selector);
|
|
6095
6178
|
transport.getAttribute = (name) => target.getAttribute(name);
|
|
@@ -6403,6 +6486,184 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
|
|
|
6403
6486
|
});
|
|
6404
6487
|
}
|
|
6405
6488
|
|
|
6489
|
+
// .build/modules/recordings.mjs
|
|
6490
|
+
var WASM = "AGFzbQEAAAABDwJgAAF/YAZ/f39/f38BfAMDAgABBQUBAQKAQAYIAX8BQYCIBAsHKgMGbWVtb3J5AgAKdGFibGVfYmFzZQAAEHNhbXBsZV9yZWNvcmRpbmcAAQpvAggAQYCIhIAAC2QCAXwBfkQAAAAAAAD4fyEGAkAgAEUNACABQQFIDQAgAkEBSA0AIAVBAEgNACAFIAJPDQAgBKwgA6x9IgdCAFMNACAHIAGtWg0AIAAgB6cgAmxBA3RqIAVBA3RqKwMAIQYLIAYL";
|
|
6491
|
+
var MAX_VALUES = 8388608;
|
|
6492
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
6493
|
+
var modulePromise;
|
|
6494
|
+
var finite2 = (n, label) => {
|
|
6495
|
+
if (typeof n !== "number" || !Number.isFinite(n)) throw new TypeError(label + " must be finite");
|
|
6496
|
+
return n;
|
|
6497
|
+
};
|
|
6498
|
+
function validateRecording(input) {
|
|
6499
|
+
if (!input || input.format !== "dancerudiments.recording" || input.schema_version !== 1 || input.pips_per_beat !== 64 || input.sample_dtype !== "float64") throw new TypeError("Expected dancerudiments.recording v1, float64, 64 pips per beat");
|
|
6500
|
+
if (typeof input.name !== "string" || !input.name.length || input.name.length > 128) throw new TypeError("Recording name is required");
|
|
6501
|
+
const dimensions = input.dimensions;
|
|
6502
|
+
if (!Array.isArray(dimensions) || !dimensions.length || dimensions.length > 64 || new Set(dimensions).size !== dimensions.length || dimensions.some((d) => typeof d !== "string" || !/^[a-z][a-z0-9_]{0,127}$/.test(d))) throw new TypeError("Recording requires 1..64 unique named dimensions");
|
|
6503
|
+
const c = input.clock;
|
|
6504
|
+
if (!c || finite2(c.bpm, "Recording BPM") <= 0 || !["confirmed", "estimated"].includes(c.bpm_status) || !["unverified", "estimated", "supplied"].includes(c.phase_status) || typeof c.bpm_source !== "string" || !c.bpm_source.trim()) throw new TypeError("Recording clock requires BPM and provenance");
|
|
6505
|
+
finite2(c.beat_zero_seconds, "Beat zero");
|
|
6506
|
+
if (!Number.isInteger(c.start_pip) || c.start_pip < -2147483648) throw new RangeError("Invalid recording start pip");
|
|
6507
|
+
const rows = input.samples;
|
|
6508
|
+
if (!Array.isArray(rows) || !rows.length || rows.length * dimensions.length > MAX_VALUES || c.start_pip + rows.length > 2147483647) throw new RangeError("Recording exceeds bounded sample limits");
|
|
6509
|
+
const values = new Float64Array(rows.length * dimensions.length);
|
|
6510
|
+
rows.forEach((row, i) => {
|
|
6511
|
+
if (!Array.isArray(row) || row.length !== dimensions.length) throw new TypeError("Recording sample dimensions differ");
|
|
6512
|
+
row.forEach((v, j) => {
|
|
6513
|
+
finite2(v, "Sample");
|
|
6514
|
+
if (Math.abs(v) > 1) throw new RangeError("Recording samples must be in [-1,1]");
|
|
6515
|
+
values[i * dimensions.length + j] = v;
|
|
6516
|
+
});
|
|
6517
|
+
});
|
|
6518
|
+
return Object.freeze({ name: input.name, dimensions: Object.freeze([...dimensions]), count: rows.length, clock: Object.freeze({ ...c }), values });
|
|
6519
|
+
}
|
|
6520
|
+
async function samplerFromData(data) {
|
|
6521
|
+
if (!modulePromise) modulePromise = WebAssembly.compile(Uint8Array.from(atob(WASM), (c) => c.charCodeAt(0))).catch((error) => {
|
|
6522
|
+
modulePromise = null;
|
|
6523
|
+
throw error;
|
|
6524
|
+
});
|
|
6525
|
+
const api = (await WebAssembly.instantiate(await modulePromise, {})).exports;
|
|
6526
|
+
const base = api.table_base(), pages = Math.ceil((base + data.values.byteLength - api.memory.buffer.byteLength) / 65536);
|
|
6527
|
+
if (pages > 0) api.memory.grow(pages);
|
|
6528
|
+
new Float64Array(api.memory.buffer, base, data.values.length).set(data.values);
|
|
6529
|
+
return Object.freeze({ ...data, sample(pip) {
|
|
6530
|
+
if (!Number.isInteger(pip) || pip < data.clock.start_pip || pip >= data.clock.start_pip + data.count) throw new RangeError("Position is outside the non-looping recording");
|
|
6531
|
+
return data.dimensions.map((_, axis) => api.sample_recording(base, data.count, data.dimensions.length, data.clock.start_pip, pip, axis));
|
|
6532
|
+
} });
|
|
6533
|
+
}
|
|
6534
|
+
function createRecordingService(scope, rudiments, config) {
|
|
6535
|
+
const handles = /* @__PURE__ */ new Map();
|
|
6536
|
+
async function load(url2) {
|
|
6537
|
+
if (scope.disposed) throw new Error("DanceMoves mount is destroyed");
|
|
6538
|
+
const response = await scope.window.fetch(url2);
|
|
6539
|
+
if (!response.ok) throw new Error("Recording request failed: " + response.status);
|
|
6540
|
+
if (Number(response.headers?.get("content-length")) > MAX_BYTES) throw new RangeError("Recording exceeds 64 MiB");
|
|
6541
|
+
let text;
|
|
6542
|
+
if (response.body?.getReader) {
|
|
6543
|
+
const reader = response.body.getReader(), chunks = [];
|
|
6544
|
+
let size = 0;
|
|
6545
|
+
for (; ; ) {
|
|
6546
|
+
const { done, value } = await reader.read();
|
|
6547
|
+
if (done) break;
|
|
6548
|
+
size += value.byteLength;
|
|
6549
|
+
if (size > MAX_BYTES) {
|
|
6550
|
+
await reader.cancel();
|
|
6551
|
+
throw new RangeError("Recording exceeds 64 MiB");
|
|
6552
|
+
}
|
|
6553
|
+
chunks.push(value);
|
|
6554
|
+
}
|
|
6555
|
+
const bytes = new Uint8Array(size);
|
|
6556
|
+
let offset = 0;
|
|
6557
|
+
for (const chunk of chunks) {
|
|
6558
|
+
bytes.set(chunk, offset);
|
|
6559
|
+
offset += chunk.length;
|
|
6560
|
+
}
|
|
6561
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
6562
|
+
} else {
|
|
6563
|
+
text = await response.text();
|
|
6564
|
+
if (new TextEncoder().encode(text).length > MAX_BYTES) throw new RangeError("Recording exceeds 64 MiB");
|
|
6565
|
+
}
|
|
6566
|
+
const input = JSON.parse(text);
|
|
6567
|
+
validateRecording(input);
|
|
6568
|
+
return input;
|
|
6569
|
+
}
|
|
6570
|
+
function animate(options = {}) {
|
|
6571
|
+
if (scope.disposed) throw new Error("DanceMoves mount is destroyed");
|
|
6572
|
+
if (typeof options.id !== "string" || !options.id.trim() || handles.has(options.id)) throw new TypeError("Recording controller requires a unique id");
|
|
6573
|
+
if (typeof options.render !== "function") throw new TypeError("Recording render callback is required");
|
|
6574
|
+
if (!options.audio) throw new TypeError("Recording playback requires an audio/player source");
|
|
6575
|
+
if (["rate", "phasePips", "clock", "bpm"].some((k) => Object.hasOwn(options, k))) throw new TypeError("Recording clock, rate and origin come from its capture");
|
|
6576
|
+
const data = validateRecording(options.recording);
|
|
6577
|
+
if (Math.abs(data.clock.bpm - config.bpm) > 1e-9) throw new RangeError("Recording BPM does not match the authoritative page BPM");
|
|
6578
|
+
let inner = null, sampler = null, destroyed = false, enabled = options.enabled !== false, paused = false, status = "loading", error = null, last = null;
|
|
6579
|
+
const output = (frame, values, pip, reason) => {
|
|
6580
|
+
if (destroyed) return;
|
|
6581
|
+
last = { ...frame, rudiment: null, recording: data.name, dimensions: data.dimensions, values, position: { x: values[0] || 0, y: values[1] || 0, z: values[2] || 0 }, pip, positionPips: pip, loopProgress: null, reason };
|
|
6582
|
+
options.render(last);
|
|
6583
|
+
};
|
|
6584
|
+
const handle = {
|
|
6585
|
+
id: options.id,
|
|
6586
|
+
ready: null,
|
|
6587
|
+
setEnabled(value) {
|
|
6588
|
+
if (typeof value !== "boolean") throw new TypeError("enabled must be boolean");
|
|
6589
|
+
enabled = value;
|
|
6590
|
+
inner?.setEnabled(value);
|
|
6591
|
+
},
|
|
6592
|
+
pause() {
|
|
6593
|
+
paused = true;
|
|
6594
|
+
inner?.pause();
|
|
6595
|
+
},
|
|
6596
|
+
resume() {
|
|
6597
|
+
paused = false;
|
|
6598
|
+
inner?.resume();
|
|
6599
|
+
},
|
|
6600
|
+
refresh() {
|
|
6601
|
+
inner?.refresh();
|
|
6602
|
+
},
|
|
6603
|
+
destroy() {
|
|
6604
|
+
if (destroyed) return;
|
|
6605
|
+
inner?.destroy();
|
|
6606
|
+
inner = null;
|
|
6607
|
+
sampler = null;
|
|
6608
|
+
destroyed = true;
|
|
6609
|
+
status = "destroyed";
|
|
6610
|
+
handles.delete(options.id);
|
|
6611
|
+
},
|
|
6612
|
+
snapshot() {
|
|
6613
|
+
const state = inner?.snapshot() ?? null;
|
|
6614
|
+
return { id: options.id, type: "recording", recording: data.name, status: !destroyed && ["hidden", "offscreen", "paused", "seeking"].includes(state?.status) ? state.status : status, error, destroyed, enabled, paused, pip: last?.pip ?? null, dimensions: data.dimensions, count: data.count, clock: data.clock, inner: state };
|
|
6615
|
+
}
|
|
6616
|
+
};
|
|
6617
|
+
handle.teardown = handle.destroy;
|
|
6618
|
+
handles.set(options.id, handle);
|
|
6619
|
+
handle.ready = samplerFromData(data).then(async (native) => {
|
|
6620
|
+
if (destroyed || scope.disposed) return handle;
|
|
6621
|
+
sampler = native;
|
|
6622
|
+
inner = rudiments.animate({
|
|
6623
|
+
id: options.id,
|
|
6624
|
+
root: options.root,
|
|
6625
|
+
target: options.target,
|
|
6626
|
+
audio: options.audio,
|
|
6627
|
+
clock: "audio",
|
|
6628
|
+
rudiment: "circle",
|
|
6629
|
+
rate: 1,
|
|
6630
|
+
phasePips: 0,
|
|
6631
|
+
amplitude: 0,
|
|
6632
|
+
css: false,
|
|
6633
|
+
renderEveryFrame: true,
|
|
6634
|
+
offscreen: options.offscreen !== false,
|
|
6635
|
+
enabled,
|
|
6636
|
+
render(frame) {
|
|
6637
|
+
if (destroyed) return;
|
|
6638
|
+
const neutral = ["disabled", "reduced-motion", "forced-colors"].includes(frame.reason), precise = (frame.sourceBeats - data.clock.beat_zero_seconds * data.clock.bpm / 60) * 64;
|
|
6639
|
+
const pip = Math.floor(precise + 1e-8), outside = pip < data.clock.start_pip || pip >= data.clock.start_pip + data.count;
|
|
6640
|
+
status = neutral ? frame.reason : outside ? "outside-recording" : frame.reason;
|
|
6641
|
+
const values = neutral || outside ? data.dimensions.map(() => 0) : sampler.sample(pip);
|
|
6642
|
+
output(frame, values, pip, status);
|
|
6643
|
+
}
|
|
6644
|
+
});
|
|
6645
|
+
if (paused) inner.pause();
|
|
6646
|
+
await inner.ready;
|
|
6647
|
+
return handle;
|
|
6648
|
+
}).catch((failure) => {
|
|
6649
|
+
error = String(failure.message || failure);
|
|
6650
|
+
status = "error";
|
|
6651
|
+
inner?.destroy();
|
|
6652
|
+
inner = null;
|
|
6653
|
+
sampler = null;
|
|
6654
|
+
if (handles.get(options.id) === handle) handles.delete(options.id);
|
|
6655
|
+
throw failure;
|
|
6656
|
+
});
|
|
6657
|
+
return handle;
|
|
6658
|
+
}
|
|
6659
|
+
scope.onDispose(() => {
|
|
6660
|
+
for (const handle of [...handles.values()]) handle.destroy();
|
|
6661
|
+
});
|
|
6662
|
+
return Object.freeze({ load, animate, get: (id) => handles.get(id) || null, destroyAll: () => {
|
|
6663
|
+
for (const h of [...handles.values()]) h.destroy();
|
|
6664
|
+
}, snapshot: () => ({ native: "DanceRudiments 0.3.0 recording C++/WASM", instances: [...handles.values()].map((h) => h.snapshot()) }) });
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6406
6667
|
// .build/modules/child-playback.mjs
|
|
6407
6668
|
var PROTOCOL = "dancemoves.child-playback.v1";
|
|
6408
6669
|
var EVENTS = /* @__PURE__ */ new Set(["snapshot", "loadedmetadata", "durationchange", "play", "playing", "pause", "ended", "seeking", "seeked", "ratechange", "timeupdate", "waiting", "error"]);
|
|
@@ -6827,7 +7088,7 @@ function installChildPlayback(scope, api, host) {
|
|
|
6827
7088
|
}
|
|
6828
7089
|
|
|
6829
7090
|
// .build/modules/version.mjs
|
|
6830
|
-
var VERSION = "3.
|
|
7091
|
+
var VERSION = "3.3.0";
|
|
6831
7092
|
|
|
6832
7093
|
// .build/modules/index.mjs
|
|
6833
7094
|
var mounted = /* @__PURE__ */ new WeakMap();
|
|
@@ -6880,6 +7141,7 @@ function createDanceMoves(options = {}) {
|
|
|
6880
7141
|
config,
|
|
6881
7142
|
effects: w.DanceMovesEffects,
|
|
6882
7143
|
rudiments: w.DanceMovesRudiments,
|
|
7144
|
+
recordings: createRecordingService(scope, w.DanceMovesRudiments, config),
|
|
6883
7145
|
orientationCore: w.KSEpkOrientationCore,
|
|
6884
7146
|
getOrientation: () => w.__ksEpkOrientationRuntime || null,
|
|
6885
7147
|
getClay: () => w.DanceMovesClayStars || null,
|
|
@@ -6921,5 +7183,6 @@ export {
|
|
|
6921
7183
|
createPlaybackPublisher,
|
|
6922
7184
|
createPlaybackReceiver,
|
|
6923
7185
|
fromWordPressMeta,
|
|
7186
|
+
validateBeatAlignment,
|
|
6924
7187
|
validatePageConfig
|
|
6925
7188
|
};
|