@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.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
createPlaybackPublisher: () => createPlaybackPublisher,
|
|
28
28
|
createPlaybackReceiver: () => createPlaybackReceiver,
|
|
29
29
|
fromWordPressMeta: () => fromWordPressMeta,
|
|
30
|
+
validateBeatAlignment: () => validateBeatAlignment,
|
|
30
31
|
validatePageConfig: () => validatePageConfig
|
|
31
32
|
});
|
|
32
33
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -135,6 +136,38 @@ function install(__dmScope) {
|
|
|
135
136
|
return ticks;
|
|
136
137
|
}
|
|
137
138
|
var bpm = effectiveBpm(rawConfig.bpm);
|
|
139
|
+
var bpmSource = ["explicit", "analysis"].indexOf(rawConfig.bpmSource) >= 0 ? rawConfig.bpmSource : "fallback";
|
|
140
|
+
function beatAlignmentState(audio) {
|
|
141
|
+
var reading = rawConfig.beatAlignment;
|
|
142
|
+
if (!reading)
|
|
143
|
+
return { status: rawConfig.beatAlignmentStatus === "invalid" ? "invalid" : "unconfigured", applied: false };
|
|
144
|
+
var source = playbackAudio(audio);
|
|
145
|
+
var identity2 = source && (source.masterSourceUrl || source.masterId || source.currentSrc || source.src);
|
|
146
|
+
try {
|
|
147
|
+
if (!identity2 || new URL(String(identity2), document2.baseURI).href !== new URL(reading.source_url).href) {
|
|
148
|
+
return { status: "source-mismatch", applied: false };
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return { status: "source-mismatch", applied: false };
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
status: reading.phase_status,
|
|
155
|
+
applied: true,
|
|
156
|
+
firstBeatSeconds: reading.first_beat_seconds,
|
|
157
|
+
offsetPips: reading.offset_pips,
|
|
158
|
+
sourceSha256: reading.source_sha256,
|
|
159
|
+
bpmStatus: reading.bpm_status
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function rhythmicTime(audio) {
|
|
163
|
+
var source = playbackAudio(audio);
|
|
164
|
+
var seconds = source && Number.isFinite(Number(source.currentTime)) ? Number(source.currentTime) : 0;
|
|
165
|
+
var state = beatAlignmentState(source);
|
|
166
|
+
return seconds - (state.applied ? state.firstBeatSeconds : 0);
|
|
167
|
+
}
|
|
168
|
+
function sourceTick(audio, bpmOverride) {
|
|
169
|
+
return rhythmicTime(audio) * effectiveBpm(bpmOverride || bpm) * TICKS_PER_BEAT / 60;
|
|
170
|
+
}
|
|
138
171
|
function durationMilliseconds(ticks, bpmOverride) {
|
|
139
172
|
return 3750 / effectiveBpm(bpmOverride || bpm) * quantizeTicks(ticks);
|
|
140
173
|
}
|
|
@@ -142,7 +175,7 @@ function install(__dmScope) {
|
|
|
142
175
|
var settings = options || {};
|
|
143
176
|
var audio = playbackAudio(settings.audio) || (settings.clock === "audio" ? activeAudio : null);
|
|
144
177
|
if (audio && !audio.paused && !audio.ended && Number.isFinite(Number(audio.currentTime))) {
|
|
145
|
-
return
|
|
178
|
+
return sourceTick(audio, settings.bpm);
|
|
146
179
|
}
|
|
147
180
|
var now = window2.performance && typeof window2.performance.now === "function" ? window2.performance.now() : Date.now();
|
|
148
181
|
return Math.max(0, now - pageClockStarted) * effectiveBpm(settings.bpm || bpm) * TICKS_PER_BEAT / 6e4;
|
|
@@ -441,7 +474,7 @@ function install(__dmScope) {
|
|
|
441
474
|
__dmScope.dataset(document2.documentElement, "danceMovesSharedControls", null);
|
|
442
475
|
}
|
|
443
476
|
__dmScope.dataset(document2.documentElement, "danceMovesBpm", String(bpm));
|
|
444
|
-
__dmScope.dataset(document2.documentElement, "danceMovesBpmSource",
|
|
477
|
+
__dmScope.dataset(document2.documentElement, "danceMovesBpmSource", bpmSource);
|
|
445
478
|
__dmScope.dataset(document2.documentElement, "danceMovesVersion", String(rawConfig.version || ""));
|
|
446
479
|
}
|
|
447
480
|
function registerAnimationScope(root, selectors) {
|
|
@@ -977,7 +1010,7 @@ function install(__dmScope) {
|
|
|
977
1010
|
var root = document2.querySelector(".ks-epk");
|
|
978
1011
|
if (root) {
|
|
979
1012
|
__dmScope.dataset(root, "danceMovesBpm", String(bpm));
|
|
980
|
-
__dmScope.dataset(root, "danceMovesBpmSource",
|
|
1013
|
+
__dmScope.dataset(root, "danceMovesBpmSource", bpmSource);
|
|
981
1014
|
if (rawConfig.lyricTimingUrl)
|
|
982
1015
|
__dmScope.dataset(root, "danceMovesLyricTiming", String(rawConfig.lyricTimingUrl));
|
|
983
1016
|
if (rawConfig.cueTimingUrl)
|
|
@@ -1006,11 +1039,14 @@ function install(__dmScope) {
|
|
|
1006
1039
|
var api = {
|
|
1007
1040
|
version: String(rawConfig.version || ""),
|
|
1008
1041
|
bpm,
|
|
1009
|
-
bpmSource
|
|
1042
|
+
bpmSource,
|
|
1010
1043
|
ticksPerBeat: TICKS_PER_BEAT,
|
|
1011
1044
|
quantizeTicks,
|
|
1012
1045
|
durationMilliseconds,
|
|
1013
1046
|
currentTick,
|
|
1047
|
+
sourceTick,
|
|
1048
|
+
rhythmicTime,
|
|
1049
|
+
beatAlignmentState,
|
|
1014
1050
|
nextIntervalTick,
|
|
1015
1051
|
scheduleAtInterval,
|
|
1016
1052
|
deferStart,
|
|
@@ -1065,7 +1101,7 @@ function install2(__dmScope) {
|
|
|
1065
1101
|
return;
|
|
1066
1102
|
var instances = /* @__PURE__ */ new Map();
|
|
1067
1103
|
var serial2 = 0;
|
|
1068
|
-
function
|
|
1104
|
+
function finite3(value, fallback) {
|
|
1069
1105
|
value = Number(value);
|
|
1070
1106
|
return Number.isFinite(value) ? value : fallback;
|
|
1071
1107
|
}
|
|
@@ -1119,8 +1155,8 @@ function install2(__dmScope) {
|
|
|
1119
1155
|
rect = bounds.getBoundingClientRect();
|
|
1120
1156
|
}
|
|
1121
1157
|
function apply(x, y, reason) {
|
|
1122
|
-
latestX = clamp(
|
|
1123
|
-
latestY = clamp(
|
|
1158
|
+
latestX = clamp(finite3(x, 0), -1, 1);
|
|
1159
|
+
latestY = clamp(finite3(y, 0), -1, 1);
|
|
1124
1160
|
__dmScope.setStyle(state.root.style, "--dance-moves-x", latestX.toFixed(4));
|
|
1125
1161
|
__dmScope.setStyle(state.root.style, "--dance-moves-y", latestY.toFixed(4));
|
|
1126
1162
|
__dmScope.dataset(state.root, "danceMovesPointer", reason || "active");
|
|
@@ -1211,13 +1247,14 @@ function install2(__dmScope) {
|
|
|
1211
1247
|
var audio = playbackElement(options.audio, state.root.querySelector("audio"));
|
|
1212
1248
|
if (!audio)
|
|
1213
1249
|
throw new Error("DanceMovesEffects pulse requires an audio element");
|
|
1214
|
-
var ticks = Math.max(1, Math.round(
|
|
1250
|
+
var ticks = Math.max(1, Math.round(finite3(options.ticks, 16)));
|
|
1215
1251
|
var className = String(options.className || "dance-moves-playing");
|
|
1216
1252
|
var prefix = String(options.propertyPrefix || "--dance-moves-pulse").replace(/-+$/, "");
|
|
1217
1253
|
function sync() {
|
|
1218
|
-
var rate = Math.max(0.01,
|
|
1254
|
+
var rate = Math.max(0.01, finite3(audio.playbackRate, 1));
|
|
1219
1255
|
var duration = motion.durationMilliseconds(ticks) / 1e3 / rate;
|
|
1220
|
-
var
|
|
1256
|
+
var rhythmicSeconds = motion.rhythmicTime ? motion.rhythmicTime(audio) : finite3(audio.currentTime, 0);
|
|
1257
|
+
var phase = -((rhythmicSeconds / rate % duration + duration) % duration);
|
|
1221
1258
|
__dmScope.setStyle(state.root.style, prefix + "-duration", duration.toFixed(6) + "s");
|
|
1222
1259
|
__dmScope.setStyle(state.root.style, prefix + "-delay", phase.toFixed(6) + "s");
|
|
1223
1260
|
__dmScope.dataset(state.root, "danceMovesPulse", state.reduced.matches || state.forced.matches ? "inactive" : audio.paused ? "paused" : "playing");
|
|
@@ -1256,7 +1293,7 @@ function install2(__dmScope) {
|
|
|
1256
1293
|
var state = commonState(options, "cue-class");
|
|
1257
1294
|
var cue = String(options.cue || "*");
|
|
1258
1295
|
var className = String(options.className || "dance-moves-cue-active");
|
|
1259
|
-
var durationTicks = Math.max(1, Math.round(
|
|
1296
|
+
var durationTicks = Math.max(1, Math.round(finite3(options.durationTicks, 16)));
|
|
1260
1297
|
var timer = 0, count = 0;
|
|
1261
1298
|
function clear(reason) {
|
|
1262
1299
|
if (timer)
|
|
@@ -1312,16 +1349,16 @@ function install2(__dmScope) {
|
|
|
1312
1349
|
if (!audio)
|
|
1313
1350
|
throw new Error("DanceMovesEffects cueTimeline requires an audio element");
|
|
1314
1351
|
var cues = (Array.isArray(options.cues) ? options.cues : []).map(function(cue, index) {
|
|
1315
|
-
var time =
|
|
1352
|
+
var time = finite3(cue && cue.time, NaN);
|
|
1316
1353
|
if (!Number.isFinite(time) || time < 0)
|
|
1317
1354
|
throw new TypeError("DanceMovesEffects cueTimeline cue time must be a non-negative number");
|
|
1318
1355
|
return Object.freeze({ id: String(cue.id || "cue-" + index), time, end: Number.isFinite(Number(cue.end)) ? Number(cue.end) : null, data: cue.data });
|
|
1319
1356
|
}).sort(function(a, b) {
|
|
1320
1357
|
return a.time - b.time;
|
|
1321
1358
|
});
|
|
1322
|
-
var tolerance = Math.max(0,
|
|
1323
|
-
var maxCrossingGap = Math.max(0.05,
|
|
1324
|
-
var fired = /* @__PURE__ */ new Set(), frame = 0, previous =
|
|
1359
|
+
var tolerance = Math.max(0, finite3(options.seekLandingTolerance, 0.05));
|
|
1360
|
+
var maxCrossingGap = Math.max(0.05, finite3(options.maxCrossingGap, 1.25));
|
|
1361
|
+
var fired = /* @__PURE__ */ new Set(), frame = 0, previous = finite3(audio.currentTime, 0), status = "idle", generation = 0, previousWall = 0;
|
|
1325
1362
|
function blocked() {
|
|
1326
1363
|
return document2.hidden || state.reduced.matches || state.forced.matches;
|
|
1327
1364
|
}
|
|
@@ -1331,7 +1368,7 @@ function install2(__dmScope) {
|
|
|
1331
1368
|
});
|
|
1332
1369
|
}
|
|
1333
1370
|
function publish(reason, extra) {
|
|
1334
|
-
var time =
|
|
1371
|
+
var time = finite3(audio.currentTime, 0);
|
|
1335
1372
|
__dmScope.dataset(state.root, "danceMovesCueTimeline", reason);
|
|
1336
1373
|
if (typeof options.render === "function")
|
|
1337
1374
|
options.render({ root: state.root, audio, time, previousTime: previous, reason, playing: !audio.paused && !audio.ended && !blocked(), active: activeAt(time), generation, detail: extra || null });
|
|
@@ -1341,10 +1378,10 @@ function install2(__dmScope) {
|
|
|
1341
1378
|
return;
|
|
1342
1379
|
fired.add(cue.id);
|
|
1343
1380
|
if (typeof options.onCue === "function")
|
|
1344
|
-
options.onCue({ root: state.root, audio, cue, time:
|
|
1381
|
+
options.onCue({ root: state.root, audio, cue, time: finite3(audio.currentTime, cue.time), reason, generation });
|
|
1345
1382
|
}
|
|
1346
1383
|
function rebuild(time, reason, allowLanding) {
|
|
1347
|
-
time = Math.max(0,
|
|
1384
|
+
time = Math.max(0, finite3(time, 0));
|
|
1348
1385
|
generation += 1;
|
|
1349
1386
|
fired.clear();
|
|
1350
1387
|
cues.forEach(function(cue) {
|
|
@@ -1370,7 +1407,7 @@ function install2(__dmScope) {
|
|
|
1370
1407
|
frame = 0;
|
|
1371
1408
|
if (audio.paused || audio.ended || blocked())
|
|
1372
1409
|
return stop(audio.ended ? "ended" : blocked() ? "inactive" : "paused");
|
|
1373
|
-
var now = Math.max(0,
|
|
1410
|
+
var now = Math.max(0, finite3(audio.currentTime, previous));
|
|
1374
1411
|
var delta = now - previous;
|
|
1375
1412
|
if (delta < -1e-3 || delta > maxCrossingGap)
|
|
1376
1413
|
rebuild(now, "discontinuity", false);
|
|
@@ -1379,8 +1416,8 @@ function install2(__dmScope) {
|
|
|
1379
1416
|
if (previous < cue.time && now >= cue.time)
|
|
1380
1417
|
fire(cue, "crossing");
|
|
1381
1418
|
});
|
|
1382
|
-
var wallDelta = previousWall ? Math.min(0.25, Math.max(0, (
|
|
1383
|
-
previousWall =
|
|
1419
|
+
var wallDelta = previousWall ? Math.min(0.25, Math.max(0, (finite3(wallNow, previousWall) - previousWall) / 1e3)) : 0;
|
|
1420
|
+
previousWall = finite3(wallNow, previousWall);
|
|
1384
1421
|
previous = now;
|
|
1385
1422
|
status = "playing";
|
|
1386
1423
|
publish("frame", { deltaSeconds: wallDelta });
|
|
@@ -1389,7 +1426,7 @@ function install2(__dmScope) {
|
|
|
1389
1426
|
function start(reason) {
|
|
1390
1427
|
if (frame || audio.paused || audio.ended || blocked())
|
|
1391
1428
|
return publish(blocked() ? "inactive" : audio.ended ? "ended" : "paused");
|
|
1392
|
-
previous = Math.max(0,
|
|
1429
|
+
previous = Math.max(0, finite3(audio.currentTime, 0));
|
|
1393
1430
|
previousWall = 0;
|
|
1394
1431
|
status = reason || "playing";
|
|
1395
1432
|
publish(status);
|
|
@@ -1453,7 +1490,7 @@ function install2(__dmScope) {
|
|
|
1453
1490
|
start,
|
|
1454
1491
|
stop,
|
|
1455
1492
|
snapshot: function() {
|
|
1456
|
-
return { id: state.id, type: "cue-timeline", status, time:
|
|
1493
|
+
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) {
|
|
1457
1494
|
return cue.id;
|
|
1458
1495
|
}) };
|
|
1459
1496
|
},
|
|
@@ -1476,28 +1513,28 @@ function install2(__dmScope) {
|
|
|
1476
1513
|
if (!target)
|
|
1477
1514
|
throw new Error("DanceMovesEffects spritePlayback requires a target element");
|
|
1478
1515
|
var frameMap = Array.isArray(options.frameMap) && options.frameMap.length ? options.frameMap.map(function(value) {
|
|
1479
|
-
value = Math.floor(
|
|
1516
|
+
value = Math.floor(finite3(value, -1));
|
|
1480
1517
|
if (value < 0)
|
|
1481
1518
|
throw new TypeError("DanceMovesEffects spritePlayback frameMap values must be non-negative integers");
|
|
1482
1519
|
return value;
|
|
1483
1520
|
}) : null;
|
|
1484
|
-
var frameCount = frameMap ? frameMap.length : Math.max(1, Math.round(
|
|
1485
|
-
var columns = Math.max(1, Math.round(
|
|
1486
|
-
var framesPerRow = clamp(Math.round(
|
|
1487
|
-
var columnOffset = clamp(Math.round(
|
|
1488
|
-
var rowOffset = Math.max(0, Math.round(
|
|
1489
|
-
var cycleTicks = Math.max(1, Math.round(
|
|
1490
|
-
var phaseOffsetSeconds =
|
|
1491
|
-
var staticFrame = clamp(Math.round(
|
|
1521
|
+
var frameCount = frameMap ? frameMap.length : Math.max(1, Math.round(finite3(options.frameCount, 1)));
|
|
1522
|
+
var columns = Math.max(1, Math.round(finite3(options.columns, frameCount)));
|
|
1523
|
+
var framesPerRow = clamp(Math.round(finite3(options.framesPerRow, columns)), 1, columns);
|
|
1524
|
+
var columnOffset = clamp(Math.round(finite3(options.columnOffset, 0)), 0, columns - framesPerRow);
|
|
1525
|
+
var rowOffset = Math.max(0, Math.round(finite3(options.rowOffset, 0)));
|
|
1526
|
+
var cycleTicks = Math.max(1, Math.round(finite3(options.cycleTicks, 32)));
|
|
1527
|
+
var phaseOffsetSeconds = finite3(options.phaseOffsetSeconds, 0);
|
|
1528
|
+
var staticFrame = clamp(Math.round(finite3(options.staticFrame, 0)), 0, frameCount - 1);
|
|
1492
1529
|
var prefix = String(options.propertyPrefix || "--dance-moves-sprite").replace(/-+$/, "");
|
|
1493
1530
|
if (prefix.slice(0, 2) !== "--")
|
|
1494
1531
|
prefix = "--" + prefix.replace(/^-+/, "");
|
|
1495
1532
|
var tiers = Array.isArray(options.qualityTiers) && options.qualityTiers.length ? options.qualityTiers.map(String) : ["full", "constrained", "minimal"];
|
|
1496
1533
|
var suppliedFps = Array.isArray(options.qualityFramesPerSecond) ? options.qualityFramesPerSecond : [60, 30, 15];
|
|
1497
1534
|
var qualityFps = tiers.map(function(_, index) {
|
|
1498
|
-
return Math.max(1,
|
|
1535
|
+
return Math.max(1, finite3(suppliedFps[index], suppliedFps[suppliedFps.length - 1] || 15));
|
|
1499
1536
|
});
|
|
1500
|
-
var qualityIndex = clamp(Math.round(
|
|
1537
|
+
var qualityIndex = clamp(Math.round(finite3(options.initialQuality, 0)), 0, tiers.length - 1);
|
|
1501
1538
|
var frame = 0, status = "idle", lastWall = -Infinity, renders = 0, latest = null;
|
|
1502
1539
|
function blocked() {
|
|
1503
1540
|
return document2.hidden || state.reduced.matches || state.forced.matches;
|
|
@@ -1507,9 +1544,10 @@ function install2(__dmScope) {
|
|
|
1507
1544
|
return { sourceFrame, column: sourceFrame % columns, row: Math.floor(sourceFrame / columns) };
|
|
1508
1545
|
}
|
|
1509
1546
|
function calculate(reason, staticFallback) {
|
|
1510
|
-
var cycleSeconds = Math.max(1e-3,
|
|
1511
|
-
var time = Math.max(0,
|
|
1512
|
-
var
|
|
1547
|
+
var cycleSeconds = Math.max(1e-3, finite3(motion.durationMilliseconds(cycleTicks), 1) / 1e3);
|
|
1548
|
+
var time = Math.max(0, finite3(audio.currentTime, 0));
|
|
1549
|
+
var rhythmicSeconds = motion.rhythmicTime ? motion.rhythmicTime(audio) : time;
|
|
1550
|
+
var phase = ((rhythmicSeconds - phaseOffsetSeconds) % cycleSeconds + cycleSeconds) % cycleSeconds / cycleSeconds;
|
|
1513
1551
|
var localFrame = staticFallback ? staticFrame : Math.min(frameCount - 1, Math.floor(phase * frameCount + 1e-9));
|
|
1514
1552
|
var mapped = mappedFrame(localFrame);
|
|
1515
1553
|
return {
|
|
@@ -1563,8 +1601,8 @@ function install2(__dmScope) {
|
|
|
1563
1601
|
if (audio.paused || audio.ended || blocked())
|
|
1564
1602
|
return stop(audio.ended ? "ended" : blocked() ? "inactive" : "paused", audio.ended || blocked());
|
|
1565
1603
|
var minimumInterval = 1e3 / qualityFps[qualityIndex];
|
|
1566
|
-
if (!Number.isFinite(lastWall) ||
|
|
1567
|
-
lastWall =
|
|
1604
|
+
if (!Number.isFinite(lastWall) || finite3(wallNow, lastWall) - lastWall >= minimumInterval - 0.01) {
|
|
1605
|
+
lastWall = finite3(wallNow, lastWall);
|
|
1568
1606
|
status = "playing";
|
|
1569
1607
|
publish("frame", false);
|
|
1570
1608
|
}
|
|
@@ -1595,7 +1633,7 @@ function install2(__dmScope) {
|
|
|
1595
1633
|
}
|
|
1596
1634
|
function setQuality(value, reason) {
|
|
1597
1635
|
var named = typeof value === "string" ? tiers.indexOf(value) : -1;
|
|
1598
|
-
qualityIndex = named >= 0 ? named : clamp(Math.round(
|
|
1636
|
+
qualityIndex = named >= 0 ? named : clamp(Math.round(finite3(value, qualityIndex)), 0, tiers.length - 1);
|
|
1599
1637
|
lastWall = -Infinity;
|
|
1600
1638
|
publish(reason || "quality-change", audio.ended || blocked());
|
|
1601
1639
|
}
|
|
@@ -1649,7 +1687,7 @@ function install2(__dmScope) {
|
|
|
1649
1687
|
},
|
|
1650
1688
|
setQuality,
|
|
1651
1689
|
snapshot: function() {
|
|
1652
|
-
return { id: state.id, type: "sprite-playback", status, time:
|
|
1690
|
+
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) };
|
|
1653
1691
|
},
|
|
1654
1692
|
teardown: function() {
|
|
1655
1693
|
stop("teardown", true);
|
|
@@ -1716,8 +1754,8 @@ function install2(__dmScope) {
|
|
|
1716
1754
|
popover.appendChild(viewport);
|
|
1717
1755
|
__dmScope.dataset(popover, "danceMovesLyricStage", "ready");
|
|
1718
1756
|
__dmScope.setStyle(popover.style, "--dance-moves-lyric-progress", "0");
|
|
1719
|
-
var travelTicks = Math.max(1, Math.round(
|
|
1720
|
-
var defaultCueTicks = Math.max(1, Math.round(
|
|
1757
|
+
var travelTicks = Math.max(1, Math.round(finite3(options.travelTicks, 32)));
|
|
1758
|
+
var defaultCueTicks = Math.max(1, Math.round(finite3(options.cueDurationTicks, 32)));
|
|
1721
1759
|
var latest = null, phase = "idle", progress = 0, frame = 0, wakeTimer = 0, cueTimer = 0, cueCleanup = null, cueCount = 0;
|
|
1722
1760
|
var lyricEffects = [];
|
|
1723
1761
|
var slots = Object.freeze({ previous, current, next, viewport, track });
|
|
@@ -1766,7 +1804,7 @@ function install2(__dmScope) {
|
|
|
1766
1804
|
publish(reason || value);
|
|
1767
1805
|
}
|
|
1768
1806
|
function setProgress(value) {
|
|
1769
|
-
progress = clamp(
|
|
1807
|
+
progress = clamp(finite3(value, 0), 0, 1);
|
|
1770
1808
|
__dmScope.setStyle(popover.style, "--dance-moves-lyric-progress", progress.toFixed(5));
|
|
1771
1809
|
}
|
|
1772
1810
|
function copyLyrics(detail) {
|
|
@@ -1783,7 +1821,7 @@ function install2(__dmScope) {
|
|
|
1783
1821
|
var cleanup = typeof result === "function" ? result : typeof result.cleanup === "function" ? result.cleanup : null;
|
|
1784
1822
|
if (!cleanup)
|
|
1785
1823
|
return;
|
|
1786
|
-
var durationTicks = result && typeof result === "object" ? Math.max(1, Math.round(
|
|
1824
|
+
var durationTicks = result && typeof result === "object" ? Math.max(1, Math.round(finite3(result.durationTicks, 64))) : 64;
|
|
1787
1825
|
var record = { cleanup, timer: 0 };
|
|
1788
1826
|
record.timer = window2.setTimeout(function() {
|
|
1789
1827
|
lyricEffects = lyricEffects.filter(function(item) {
|
|
@@ -1809,7 +1847,7 @@ function install2(__dmScope) {
|
|
|
1809
1847
|
setProgress(0);
|
|
1810
1848
|
return setPhase("waiting", "no-next-line");
|
|
1811
1849
|
}
|
|
1812
|
-
var now = Math.max(0,
|
|
1850
|
+
var now = Math.max(0, finite3(audio.currentTime, 0));
|
|
1813
1851
|
if (now < windowRange.start)
|
|
1814
1852
|
return reconcile("frame-before-window");
|
|
1815
1853
|
if (now >= windowRange.end) {
|
|
@@ -1839,7 +1877,7 @@ function install2(__dmScope) {
|
|
|
1839
1877
|
setProgress(0);
|
|
1840
1878
|
return setPhase("waiting", reason || "no-next-line");
|
|
1841
1879
|
}
|
|
1842
|
-
var now = Math.max(0,
|
|
1880
|
+
var now = Math.max(0, finite3(audio.currentTime, 0));
|
|
1843
1881
|
if (now >= windowRange.end) {
|
|
1844
1882
|
setProgress(1);
|
|
1845
1883
|
return setPhase("arrived", reason || "arrived");
|
|
@@ -1854,7 +1892,7 @@ function install2(__dmScope) {
|
|
|
1854
1892
|
wakeTimer = window2.setTimeout(function() {
|
|
1855
1893
|
wakeTimer = 0;
|
|
1856
1894
|
tick();
|
|
1857
|
-
}, Math.max(0, (windowRange.start - now) * 1e3 / Math.max(0.01,
|
|
1895
|
+
}, Math.max(0, (windowRange.start - now) * 1e3 / Math.max(0.01, finite3(audio.playbackRate, 1))));
|
|
1858
1896
|
}
|
|
1859
1897
|
function onLyric(detail) {
|
|
1860
1898
|
var previousDetail = latest;
|
|
@@ -1909,7 +1947,7 @@ function install2(__dmScope) {
|
|
|
1909
1947
|
else if (result && typeof result === "object") {
|
|
1910
1948
|
if (typeof result.cleanup === "function")
|
|
1911
1949
|
cueCleanup = result.cleanup;
|
|
1912
|
-
durationTicks = Math.max(1, Math.round(
|
|
1950
|
+
durationTicks = Math.max(1, Math.round(finite3(result.durationTicks, defaultCueTicks)));
|
|
1913
1951
|
}
|
|
1914
1952
|
cueTimer = window2.setTimeout(function() {
|
|
1915
1953
|
clearCue("complete");
|
|
@@ -1981,16 +2019,16 @@ function install2(__dmScope) {
|
|
|
1981
2019
|
var hazardSources = Array.isArray(options.hazardSources) ? options.hazardSources.filter(Boolean).map(String) : [];
|
|
1982
2020
|
if (!shipSources.length || !hazardSources.length)
|
|
1983
2021
|
throw new Error("DanceMovesEffects cooperativeArena requires ship and hazard sources");
|
|
1984
|
-
var shipCount = clamp(Math.round(
|
|
1985
|
-
var hazardCount = clamp(Math.round(
|
|
1986
|
-
var speed = clamp(
|
|
2022
|
+
var shipCount = clamp(Math.round(finite3(options.shipCount, 3)), 2, 12);
|
|
2023
|
+
var hazardCount = clamp(Math.round(finite3(options.hazardCount, 8)), 3, 48);
|
|
2024
|
+
var speed = clamp(finite3(options.speed, 1), 0.35, 2.4);
|
|
1987
2025
|
var inlineTransform = options.inlineTransform === true;
|
|
1988
|
-
var bpm = Math.max(1,
|
|
1989
|
-
var shotIntervalTicks = clamp(Math.round(
|
|
2026
|
+
var bpm = Math.max(1, finite3(options.bpm, motion.bpm || 120));
|
|
2027
|
+
var shotIntervalTicks = clamp(Math.round(finite3(options.shotIntervalTicks, 32)), 4, 256);
|
|
1990
2028
|
var shotIntervalSeconds = motion.durationMilliseconds ? motion.durationMilliseconds(shotIntervalTicks, bpm) / 1e3 : shotIntervalTicks * 60 / (bpm * 16);
|
|
1991
|
-
var burstDurationSeconds = clamp(
|
|
1992
|
-
var shipHitboxScale = clamp(
|
|
1993
|
-
var hazardHitboxScale = clamp(
|
|
2029
|
+
var burstDurationSeconds = clamp(finite3(options.burstDurationBeats, 1.5), 0.1, 8) * 60 / bpm;
|
|
2030
|
+
var shipHitboxScale = clamp(finite3(options.shipHitboxScale, 1), 0.4, 1);
|
|
2031
|
+
var hazardHitboxScale = clamp(finite3(options.hazardHitboxScale, 1), 0.4, 1);
|
|
1994
2032
|
var removers = state.removers, ships = [], hazards = [], shots = [], bursts = [], burstTimers = [];
|
|
1995
2033
|
var activeShips = shipCount, activeHazards = hazardCount;
|
|
1996
2034
|
var width = 1, height = 1, frame = 0, last = 0, elapsed = 0, nextShot = 0, hits = 0;
|
|
@@ -2322,7 +2360,7 @@ function install2(__dmScope) {
|
|
|
2322
2360
|
reconcile(reason || "manual");
|
|
2323
2361
|
},
|
|
2324
2362
|
setDensity: function(nextShips, nextHazards) {
|
|
2325
|
-
var shipsNext = clamp(Math.round(
|
|
2363
|
+
var shipsNext = clamp(Math.round(finite3(nextShips, activeShips)), 2, shipCount), hazardsNext = clamp(Math.round(finite3(nextHazards, activeHazards)), 3, hazardCount);
|
|
2326
2364
|
if (shipsNext === activeShips && hazardsNext === activeHazards)
|
|
2327
2365
|
return;
|
|
2328
2366
|
activeShips = shipsNext;
|
|
@@ -2365,12 +2403,12 @@ function install2(__dmScope) {
|
|
|
2365
2403
|
options = options || {};
|
|
2366
2404
|
var state = commonState(options, "quality");
|
|
2367
2405
|
var tiers = Array.isArray(options.tiers) && options.tiers.length ? options.tiers.map(String) : ["full", "constrained", "minimal"];
|
|
2368
|
-
var index = clamp(Math.round(
|
|
2369
|
-
var sampleMs = Math.max(800,
|
|
2370
|
-
var poorWindows = Math.max(2, Math.round(
|
|
2371
|
-
var healthyWindows = Math.max(3, Math.round(
|
|
2372
|
-
var downgradeRatio = clamp(
|
|
2373
|
-
var recoveryRatio = clamp(
|
|
2406
|
+
var index = clamp(Math.round(finite3(options.initial, 0)), 0, tiers.length - 1);
|
|
2407
|
+
var sampleMs = Math.max(800, finite3(options.sampleMilliseconds, 1600));
|
|
2408
|
+
var poorWindows = Math.max(2, Math.round(finite3(options.poorWindows, 2)));
|
|
2409
|
+
var healthyWindows = Math.max(3, Math.round(finite3(options.healthyWindows, 5)));
|
|
2410
|
+
var downgradeRatio = clamp(finite3(options.downgradeRatio, 0.72), 0.2, 0.95);
|
|
2411
|
+
var recoveryRatio = clamp(finite3(options.recoveryRatio, 0.9), downgradeRatio, 1);
|
|
2374
2412
|
var frame = 0, frames = 0, start = 0, reference = 0, poor = 0, healthy = 0, lastShift = 0, running = true;
|
|
2375
2413
|
var preferenceLimited = false, tierBeforePreference = index;
|
|
2376
2414
|
function publish(reason, sample) {
|
|
@@ -2414,12 +2452,12 @@ function install2(__dmScope) {
|
|
|
2414
2452
|
poor = 0;
|
|
2415
2453
|
healthy = 0;
|
|
2416
2454
|
}
|
|
2417
|
-
if (poor >= poorWindows && index < tiers.length - 1 && now - lastShift >=
|
|
2455
|
+
if (poor >= poorWindows && index < tiers.length - 1 && now - lastShift >= finite3(options.downgradeCooldownMilliseconds, 5e3)) {
|
|
2418
2456
|
index += 1;
|
|
2419
2457
|
poor = 0;
|
|
2420
2458
|
lastShift = now;
|
|
2421
2459
|
publish("sustained-low-fps", { fps });
|
|
2422
|
-
} else if (healthy >= healthyWindows && index > 0 && now - lastShift >=
|
|
2460
|
+
} else if (healthy >= healthyWindows && index > 0 && now - lastShift >= finite3(options.recoveryCooldownMilliseconds, 12e3)) {
|
|
2423
2461
|
index -= 1;
|
|
2424
2462
|
healthy = 0;
|
|
2425
2463
|
lastShift = now;
|
|
@@ -2458,7 +2496,7 @@ function install2(__dmScope) {
|
|
|
2458
2496
|
id: state.id,
|
|
2459
2497
|
type: "quality",
|
|
2460
2498
|
setTier: function(value, reason) {
|
|
2461
|
-
index = clamp(Math.round(
|
|
2499
|
+
index = clamp(Math.round(finite3(value, index)), 0, tiers.length - 1);
|
|
2462
2500
|
publish(reason || "manual");
|
|
2463
2501
|
},
|
|
2464
2502
|
snapshot: function() {
|
|
@@ -4928,8 +4966,9 @@ function install9(__dmScope) {
|
|
|
4928
4966
|
anchor = 0;
|
|
4929
4967
|
lastClock = mode;
|
|
4930
4968
|
const tempo = bpm();
|
|
4931
|
-
const
|
|
4932
|
-
|
|
4969
|
+
const sourceBeats = useAudio ? Math.max(0, Number.isFinite(audio.currentTime) ? audio.currentTime : 0) * tempo / 60 : motion.currentTick({ clock: "page" }) / 16;
|
|
4970
|
+
const beats = useAudio && motion.sourceTick ? motion.sourceTick(audio) / 16 : sourceBeats;
|
|
4971
|
+
return { beats, sourceBeats, mode, tempo };
|
|
4933
4972
|
}
|
|
4934
4973
|
function blocked() {
|
|
4935
4974
|
if (!enabled)
|
|
@@ -4967,7 +5006,8 @@ function install9(__dmScope) {
|
|
|
4967
5006
|
audio: s.mode === "audio" ? audio : null,
|
|
4968
5007
|
rudiment: row.name,
|
|
4969
5008
|
clock: s.mode,
|
|
4970
|
-
sourceBeats: s.
|
|
5009
|
+
sourceBeats: s.sourceBeats,
|
|
5010
|
+
rhythmicBeats: s.beats,
|
|
4971
5011
|
bpm: s.tempo,
|
|
4972
5012
|
rate,
|
|
4973
5013
|
pip: neutral ? null : pip,
|
|
@@ -5513,6 +5553,35 @@ function createScope(root, config, { legacyGlobals = false, nonce, orientation =
|
|
|
5513
5553
|
return api;
|
|
5514
5554
|
}
|
|
5515
5555
|
|
|
5556
|
+
// .build/modules/beat-alignment.mjs
|
|
5557
|
+
var hash = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
5558
|
+
function rational(value, label) {
|
|
5559
|
+
if (typeof value !== "string" || !/^-?\d{1,80}(?:\/[1-9]\d{0,79})?$/.test(value)) throw new TypeError(`Invalid exact ${label}`);
|
|
5560
|
+
const [n, d = "1"] = value.split("/");
|
|
5561
|
+
return [BigInt(n), BigInt(d)];
|
|
5562
|
+
}
|
|
5563
|
+
function numeric(value, label, min, max) {
|
|
5564
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) throw new RangeError(`Invalid ${label}`);
|
|
5565
|
+
return value;
|
|
5566
|
+
}
|
|
5567
|
+
function validateBeatAlignment(input) {
|
|
5568
|
+
if (input === void 0 || input === null || input === "") return null;
|
|
5569
|
+
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");
|
|
5570
|
+
const bpm = numeric(input.bpm, "alignment BPM", 20, 400);
|
|
5571
|
+
const first = numeric(input.first_beat_seconds, "first beat", 0, 86400);
|
|
5572
|
+
const offset = numeric(input.offset_pips, "pip offset", -36864e3, 0);
|
|
5573
|
+
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");
|
|
5574
|
+
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");
|
|
5575
|
+
if (!["canonical", "estimated"].includes(input.bpm_status) || !["estimated", "confirmed", "synthetic"].includes(input.phase_status)) throw new TypeError("Alignment authority is required");
|
|
5576
|
+
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}`);
|
|
5577
|
+
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");
|
|
5578
|
+
const sourceUrl = input.source_url;
|
|
5579
|
+
if (typeof sourceUrl !== "string" || sourceUrl.length > 4096 || /[\u0000-\u001f]/.test(sourceUrl)) throw new TypeError("An exact verified master URL is required");
|
|
5580
|
+
const parsed = new URL(sourceUrl);
|
|
5581
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password || parsed.hash) throw new TypeError("Invalid verified master URL");
|
|
5582
|
+
return Object.freeze({ ...input, bpm, first_beat_seconds: first, offset_pips: offset, source_audio: Object.freeze({ ...input.source_audio }) });
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5516
5585
|
// .build/modules/config.mjs
|
|
5517
5586
|
var PAGE_META_KEYS = Object.freeze({
|
|
5518
5587
|
bpm: "_dance_moves_bpm",
|
|
@@ -5520,7 +5589,9 @@ var PAGE_META_KEYS = Object.freeze({
|
|
|
5520
5589
|
cue: "_dance_moves_cue_timing_id",
|
|
5521
5590
|
popups: "_dance_moves_lyric_popups_enabled",
|
|
5522
5591
|
duration: "_dance_moves_master_duration_ms",
|
|
5523
|
-
effect: "_dance_moves_effect"
|
|
5592
|
+
effect: "_dance_moves_effect",
|
|
5593
|
+
alignment: "_dance_moves_beat_alignment",
|
|
5594
|
+
alignmentAudio: "_dance_moves_beat_alignment_audio_id"
|
|
5524
5595
|
});
|
|
5525
5596
|
var object = (value) => value && typeof value === "object" && !Array.isArray(value);
|
|
5526
5597
|
function finite(value, name, min, max) {
|
|
@@ -5539,7 +5610,11 @@ function url(value, name) {
|
|
|
5539
5610
|
function validatePageConfig(input = {}) {
|
|
5540
5611
|
if (!object(input)) throw new TypeError("Page configuration must be an object");
|
|
5541
5612
|
const explicit = input.bpm !== void 0 && input.bpm !== null && input.bpm !== "";
|
|
5542
|
-
const
|
|
5613
|
+
const beatAlignment = validateBeatAlignment(input.beatAlignment);
|
|
5614
|
+
const canonicalExplicit = explicit && !["fallback", "analysis"].includes(input.bpmSource);
|
|
5615
|
+
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");
|
|
5616
|
+
const bpm = beatAlignment ? beatAlignment.bpm : explicit ? Math.round(finite(input.bpm, "bpm", 20, 400) * 1e3) / 1e3 : 120;
|
|
5617
|
+
if (explicit) finite(input.bpm, "bpm", 20, 400);
|
|
5543
5618
|
const effect = input.effect ?? "";
|
|
5544
5619
|
if (!["", "paper-planes"].includes(effect)) throw new RangeError("effect must be empty or paper-planes");
|
|
5545
5620
|
if (input.lyricPopupsEnabled !== void 0 && typeof input.lyricPopupsEnabled !== "boolean") throw new TypeError("lyricPopupsEnabled must be boolean");
|
|
@@ -5551,8 +5626,9 @@ function validatePageConfig(input = {}) {
|
|
|
5551
5626
|
return Object.freeze({
|
|
5552
5627
|
pageId,
|
|
5553
5628
|
bpm,
|
|
5554
|
-
bpmSource:
|
|
5629
|
+
bpmSource: canonicalExplicit ? "explicit" : beatAlignment ? "analysis" : "fallback",
|
|
5555
5630
|
fallbackBpm: 120,
|
|
5631
|
+
beatAlignment,
|
|
5556
5632
|
ticksPerBeat: 16,
|
|
5557
5633
|
tickDefinition: "sixteenth-of-beat",
|
|
5558
5634
|
longDurationQuantumTicks: 16,
|
|
@@ -5568,9 +5644,9 @@ function validatePageConfig(input = {}) {
|
|
|
5568
5644
|
}
|
|
5569
5645
|
async function fromWordPressMeta(meta, resolveAttachment) {
|
|
5570
5646
|
if (!object(meta)) throw new TypeError("meta must be an object");
|
|
5571
|
-
const
|
|
5647
|
+
const numeric2 = (value) => value !== "" && value !== null && value !== void 0 && Number.isFinite(Number(value));
|
|
5572
5648
|
const b = meta[PAGE_META_KEYS.bpm];
|
|
5573
|
-
const bpm =
|
|
5649
|
+
const bpm = numeric2(b) && Number(b) >= 20 && Number(b) <= 400 ? Number(b) : void 0;
|
|
5574
5650
|
const resolve = async (key) => {
|
|
5575
5651
|
const value = Number(meta[key] || 0);
|
|
5576
5652
|
if (!Number.isSafeInteger(value)) throw new RangeError("Attachment ID must be a safe integer");
|
|
@@ -5580,8 +5656,15 @@ async function fromWordPressMeta(meta, resolveAttachment) {
|
|
|
5580
5656
|
return await resolveAttachment(id) || "";
|
|
5581
5657
|
};
|
|
5582
5658
|
const duration = Number(meta[PAGE_META_KEYS.duration]);
|
|
5659
|
+
let beatAlignment = meta[PAGE_META_KEYS.alignment] || null;
|
|
5660
|
+
if (typeof beatAlignment === "string") {
|
|
5661
|
+
if (beatAlignment.length > 16384) throw new RangeError("Beat alignment exceeds 16 KiB");
|
|
5662
|
+
beatAlignment = JSON.parse(beatAlignment);
|
|
5663
|
+
}
|
|
5664
|
+
if (beatAlignment) beatAlignment = { ...beatAlignment, source_url: await resolve(PAGE_META_KEYS.alignmentAudio) };
|
|
5583
5665
|
return validatePageConfig({
|
|
5584
5666
|
bpm,
|
|
5667
|
+
beatAlignment,
|
|
5585
5668
|
lyricTimingUrl: await resolve(PAGE_META_KEYS.lyric),
|
|
5586
5669
|
cueTimingUrl: await resolve(PAGE_META_KEYS.cue),
|
|
5587
5670
|
lyricPopupsEnabled: meta[PAGE_META_KEYS.popups] === true || String(meta[PAGE_META_KEYS.popups]) === "1",
|
|
@@ -6122,7 +6205,8 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
|
|
|
6122
6205
|
error: { get: () => read().error || null },
|
|
6123
6206
|
element: { value: isNative ? master : null },
|
|
6124
6207
|
activePlayer: { get: () => delegate ? delegate.activePlayer : active.element },
|
|
6125
|
-
masterId: { get: () => delegate ? delegate.masterId : master.currentSrc || master.src || master.id }
|
|
6208
|
+
masterId: { get: () => delegate ? delegate.masterId : master.currentSrc || master.src || master.id },
|
|
6209
|
+
masterSourceUrl: { get: () => delegate ? delegate.masterSourceUrl : reference?.sourceUrl || master.currentSrc || master.src || "" }
|
|
6126
6210
|
});
|
|
6127
6211
|
transport.matches = (selector) => target.matches(selector);
|
|
6128
6212
|
transport.getAttribute = (name) => target.getAttribute(name);
|
|
@@ -6436,6 +6520,184 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
|
|
|
6436
6520
|
});
|
|
6437
6521
|
}
|
|
6438
6522
|
|
|
6523
|
+
// .build/modules/recordings.mjs
|
|
6524
|
+
var WASM = "AGFzbQEAAAABDwJgAAF/YAZ/f39/f38BfAMDAgABBQUBAQKAQAYIAX8BQYCIBAsHKgMGbWVtb3J5AgAKdGFibGVfYmFzZQAAEHNhbXBsZV9yZWNvcmRpbmcAAQpvAggAQYCIhIAAC2QCAXwBfkQAAAAAAAD4fyEGAkAgAEUNACABQQFIDQAgAkEBSA0AIAVBAEgNACAFIAJPDQAgBKwgA6x9IgdCAFMNACAHIAGtWg0AIAAgB6cgAmxBA3RqIAVBA3RqKwMAIQYLIAYL";
|
|
6525
|
+
var MAX_VALUES = 8388608;
|
|
6526
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
6527
|
+
var modulePromise;
|
|
6528
|
+
var finite2 = (n, label) => {
|
|
6529
|
+
if (typeof n !== "number" || !Number.isFinite(n)) throw new TypeError(label + " must be finite");
|
|
6530
|
+
return n;
|
|
6531
|
+
};
|
|
6532
|
+
function validateRecording(input) {
|
|
6533
|
+
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");
|
|
6534
|
+
if (typeof input.name !== "string" || !input.name.length || input.name.length > 128) throw new TypeError("Recording name is required");
|
|
6535
|
+
const dimensions = input.dimensions;
|
|
6536
|
+
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");
|
|
6537
|
+
const c = input.clock;
|
|
6538
|
+
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");
|
|
6539
|
+
finite2(c.beat_zero_seconds, "Beat zero");
|
|
6540
|
+
if (!Number.isInteger(c.start_pip) || c.start_pip < -2147483648) throw new RangeError("Invalid recording start pip");
|
|
6541
|
+
const rows = input.samples;
|
|
6542
|
+
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");
|
|
6543
|
+
const values = new Float64Array(rows.length * dimensions.length);
|
|
6544
|
+
rows.forEach((row, i) => {
|
|
6545
|
+
if (!Array.isArray(row) || row.length !== dimensions.length) throw new TypeError("Recording sample dimensions differ");
|
|
6546
|
+
row.forEach((v, j) => {
|
|
6547
|
+
finite2(v, "Sample");
|
|
6548
|
+
if (Math.abs(v) > 1) throw new RangeError("Recording samples must be in [-1,1]");
|
|
6549
|
+
values[i * dimensions.length + j] = v;
|
|
6550
|
+
});
|
|
6551
|
+
});
|
|
6552
|
+
return Object.freeze({ name: input.name, dimensions: Object.freeze([...dimensions]), count: rows.length, clock: Object.freeze({ ...c }), values });
|
|
6553
|
+
}
|
|
6554
|
+
async function samplerFromData(data) {
|
|
6555
|
+
if (!modulePromise) modulePromise = WebAssembly.compile(Uint8Array.from(atob(WASM), (c) => c.charCodeAt(0))).catch((error) => {
|
|
6556
|
+
modulePromise = null;
|
|
6557
|
+
throw error;
|
|
6558
|
+
});
|
|
6559
|
+
const api = (await WebAssembly.instantiate(await modulePromise, {})).exports;
|
|
6560
|
+
const base = api.table_base(), pages = Math.ceil((base + data.values.byteLength - api.memory.buffer.byteLength) / 65536);
|
|
6561
|
+
if (pages > 0) api.memory.grow(pages);
|
|
6562
|
+
new Float64Array(api.memory.buffer, base, data.values.length).set(data.values);
|
|
6563
|
+
return Object.freeze({ ...data, sample(pip) {
|
|
6564
|
+
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");
|
|
6565
|
+
return data.dimensions.map((_, axis) => api.sample_recording(base, data.count, data.dimensions.length, data.clock.start_pip, pip, axis));
|
|
6566
|
+
} });
|
|
6567
|
+
}
|
|
6568
|
+
function createRecordingService(scope, rudiments, config) {
|
|
6569
|
+
const handles = /* @__PURE__ */ new Map();
|
|
6570
|
+
async function load(url2) {
|
|
6571
|
+
if (scope.disposed) throw new Error("DanceMoves mount is destroyed");
|
|
6572
|
+
const response = await scope.window.fetch(url2);
|
|
6573
|
+
if (!response.ok) throw new Error("Recording request failed: " + response.status);
|
|
6574
|
+
if (Number(response.headers?.get("content-length")) > MAX_BYTES) throw new RangeError("Recording exceeds 64 MiB");
|
|
6575
|
+
let text;
|
|
6576
|
+
if (response.body?.getReader) {
|
|
6577
|
+
const reader = response.body.getReader(), chunks = [];
|
|
6578
|
+
let size = 0;
|
|
6579
|
+
for (; ; ) {
|
|
6580
|
+
const { done, value } = await reader.read();
|
|
6581
|
+
if (done) break;
|
|
6582
|
+
size += value.byteLength;
|
|
6583
|
+
if (size > MAX_BYTES) {
|
|
6584
|
+
await reader.cancel();
|
|
6585
|
+
throw new RangeError("Recording exceeds 64 MiB");
|
|
6586
|
+
}
|
|
6587
|
+
chunks.push(value);
|
|
6588
|
+
}
|
|
6589
|
+
const bytes = new Uint8Array(size);
|
|
6590
|
+
let offset = 0;
|
|
6591
|
+
for (const chunk of chunks) {
|
|
6592
|
+
bytes.set(chunk, offset);
|
|
6593
|
+
offset += chunk.length;
|
|
6594
|
+
}
|
|
6595
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
6596
|
+
} else {
|
|
6597
|
+
text = await response.text();
|
|
6598
|
+
if (new TextEncoder().encode(text).length > MAX_BYTES) throw new RangeError("Recording exceeds 64 MiB");
|
|
6599
|
+
}
|
|
6600
|
+
const input = JSON.parse(text);
|
|
6601
|
+
validateRecording(input);
|
|
6602
|
+
return input;
|
|
6603
|
+
}
|
|
6604
|
+
function animate(options = {}) {
|
|
6605
|
+
if (scope.disposed) throw new Error("DanceMoves mount is destroyed");
|
|
6606
|
+
if (typeof options.id !== "string" || !options.id.trim() || handles.has(options.id)) throw new TypeError("Recording controller requires a unique id");
|
|
6607
|
+
if (typeof options.render !== "function") throw new TypeError("Recording render callback is required");
|
|
6608
|
+
if (!options.audio) throw new TypeError("Recording playback requires an audio/player source");
|
|
6609
|
+
if (["rate", "phasePips", "clock", "bpm"].some((k) => Object.hasOwn(options, k))) throw new TypeError("Recording clock, rate and origin come from its capture");
|
|
6610
|
+
const data = validateRecording(options.recording);
|
|
6611
|
+
if (Math.abs(data.clock.bpm - config.bpm) > 1e-9) throw new RangeError("Recording BPM does not match the authoritative page BPM");
|
|
6612
|
+
let inner = null, sampler = null, destroyed = false, enabled = options.enabled !== false, paused = false, status = "loading", error = null, last = null;
|
|
6613
|
+
const output = (frame, values, pip, reason) => {
|
|
6614
|
+
if (destroyed) return;
|
|
6615
|
+
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 };
|
|
6616
|
+
options.render(last);
|
|
6617
|
+
};
|
|
6618
|
+
const handle = {
|
|
6619
|
+
id: options.id,
|
|
6620
|
+
ready: null,
|
|
6621
|
+
setEnabled(value) {
|
|
6622
|
+
if (typeof value !== "boolean") throw new TypeError("enabled must be boolean");
|
|
6623
|
+
enabled = value;
|
|
6624
|
+
inner?.setEnabled(value);
|
|
6625
|
+
},
|
|
6626
|
+
pause() {
|
|
6627
|
+
paused = true;
|
|
6628
|
+
inner?.pause();
|
|
6629
|
+
},
|
|
6630
|
+
resume() {
|
|
6631
|
+
paused = false;
|
|
6632
|
+
inner?.resume();
|
|
6633
|
+
},
|
|
6634
|
+
refresh() {
|
|
6635
|
+
inner?.refresh();
|
|
6636
|
+
},
|
|
6637
|
+
destroy() {
|
|
6638
|
+
if (destroyed) return;
|
|
6639
|
+
inner?.destroy();
|
|
6640
|
+
inner = null;
|
|
6641
|
+
sampler = null;
|
|
6642
|
+
destroyed = true;
|
|
6643
|
+
status = "destroyed";
|
|
6644
|
+
handles.delete(options.id);
|
|
6645
|
+
},
|
|
6646
|
+
snapshot() {
|
|
6647
|
+
const state = inner?.snapshot() ?? null;
|
|
6648
|
+
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 };
|
|
6649
|
+
}
|
|
6650
|
+
};
|
|
6651
|
+
handle.teardown = handle.destroy;
|
|
6652
|
+
handles.set(options.id, handle);
|
|
6653
|
+
handle.ready = samplerFromData(data).then(async (native) => {
|
|
6654
|
+
if (destroyed || scope.disposed) return handle;
|
|
6655
|
+
sampler = native;
|
|
6656
|
+
inner = rudiments.animate({
|
|
6657
|
+
id: options.id,
|
|
6658
|
+
root: options.root,
|
|
6659
|
+
target: options.target,
|
|
6660
|
+
audio: options.audio,
|
|
6661
|
+
clock: "audio",
|
|
6662
|
+
rudiment: "circle",
|
|
6663
|
+
rate: 1,
|
|
6664
|
+
phasePips: 0,
|
|
6665
|
+
amplitude: 0,
|
|
6666
|
+
css: false,
|
|
6667
|
+
renderEveryFrame: true,
|
|
6668
|
+
offscreen: options.offscreen !== false,
|
|
6669
|
+
enabled,
|
|
6670
|
+
render(frame) {
|
|
6671
|
+
if (destroyed) return;
|
|
6672
|
+
const neutral = ["disabled", "reduced-motion", "forced-colors"].includes(frame.reason), precise = (frame.sourceBeats - data.clock.beat_zero_seconds * data.clock.bpm / 60) * 64;
|
|
6673
|
+
const pip = Math.floor(precise + 1e-8), outside = pip < data.clock.start_pip || pip >= data.clock.start_pip + data.count;
|
|
6674
|
+
status = neutral ? frame.reason : outside ? "outside-recording" : frame.reason;
|
|
6675
|
+
const values = neutral || outside ? data.dimensions.map(() => 0) : sampler.sample(pip);
|
|
6676
|
+
output(frame, values, pip, status);
|
|
6677
|
+
}
|
|
6678
|
+
});
|
|
6679
|
+
if (paused) inner.pause();
|
|
6680
|
+
await inner.ready;
|
|
6681
|
+
return handle;
|
|
6682
|
+
}).catch((failure) => {
|
|
6683
|
+
error = String(failure.message || failure);
|
|
6684
|
+
status = "error";
|
|
6685
|
+
inner?.destroy();
|
|
6686
|
+
inner = null;
|
|
6687
|
+
sampler = null;
|
|
6688
|
+
if (handles.get(options.id) === handle) handles.delete(options.id);
|
|
6689
|
+
throw failure;
|
|
6690
|
+
});
|
|
6691
|
+
return handle;
|
|
6692
|
+
}
|
|
6693
|
+
scope.onDispose(() => {
|
|
6694
|
+
for (const handle of [...handles.values()]) handle.destroy();
|
|
6695
|
+
});
|
|
6696
|
+
return Object.freeze({ load, animate, get: (id) => handles.get(id) || null, destroyAll: () => {
|
|
6697
|
+
for (const h of [...handles.values()]) h.destroy();
|
|
6698
|
+
}, snapshot: () => ({ native: "DanceRudiments 0.3.0 recording C++/WASM", instances: [...handles.values()].map((h) => h.snapshot()) }) });
|
|
6699
|
+
}
|
|
6700
|
+
|
|
6439
6701
|
// .build/modules/child-playback.mjs
|
|
6440
6702
|
var PROTOCOL = "dancemoves.child-playback.v1";
|
|
6441
6703
|
var EVENTS = /* @__PURE__ */ new Set(["snapshot", "loadedmetadata", "durationchange", "play", "playing", "pause", "ended", "seeking", "seeked", "ratechange", "timeupdate", "waiting", "error"]);
|
|
@@ -6860,7 +7122,7 @@ function installChildPlayback(scope, api, host) {
|
|
|
6860
7122
|
}
|
|
6861
7123
|
|
|
6862
7124
|
// .build/modules/version.mjs
|
|
6863
|
-
var VERSION = "3.
|
|
7125
|
+
var VERSION = "3.3.0";
|
|
6864
7126
|
|
|
6865
7127
|
// .build/modules/index.mjs
|
|
6866
7128
|
var mounted = /* @__PURE__ */ new WeakMap();
|
|
@@ -6913,6 +7175,7 @@ function createDanceMoves(options = {}) {
|
|
|
6913
7175
|
config,
|
|
6914
7176
|
effects: w.DanceMovesEffects,
|
|
6915
7177
|
rudiments: w.DanceMovesRudiments,
|
|
7178
|
+
recordings: createRecordingService(scope, w.DanceMovesRudiments, config),
|
|
6916
7179
|
orientationCore: w.KSEpkOrientationCore,
|
|
6917
7180
|
getOrientation: () => w.__ksEpkOrientationRuntime || null,
|
|
6918
7181
|
getClay: () => w.DanceMovesClayStars || null,
|