@neo-ui-kit/react 1.3.9 → 1.3.11
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 +13 -1
- package/dist/chunks/{chunk-VK6GHRLV.js → chunk-RM7YNNMZ.js} +1 -0
- package/dist/components/media/MelodyRoll/melody-glow.d.ts +5 -0
- package/dist/components/media/MelodyRoll/styles.css +60 -9
- package/dist/editor.js +1 -1
- package/dist/index.js +1086 -79
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
PianoRoll,
|
|
4
4
|
isBlackKey,
|
|
5
5
|
noteName
|
|
6
|
-
} from "./chunks/chunk-
|
|
6
|
+
} from "./chunks/chunk-RM7YNNMZ.js";
|
|
7
7
|
import {
|
|
8
8
|
Router,
|
|
9
9
|
matchRoute,
|
|
@@ -2642,12 +2642,12 @@ function DataTable({
|
|
|
2642
2642
|
icon: "download",
|
|
2643
2643
|
label: tr2("\u042D\u043A\u0441\u043F\u043E\u0440\u0442 CSV"),
|
|
2644
2644
|
onClick: () => {
|
|
2645
|
-
const
|
|
2645
|
+
const url2 = URL.createObjectURL(new Blob(["\uFEFF", dataTableCsv(visibleCols, sorted.map(({ row }) => row))], { type: "text/csv;charset=utf-8" }));
|
|
2646
2646
|
const link = document.createElement("a");
|
|
2647
|
-
link.href =
|
|
2647
|
+
link.href = url2;
|
|
2648
2648
|
link.download = "data-table.csv";
|
|
2649
2649
|
link.click();
|
|
2650
|
-
window.setTimeout(() => URL.revokeObjectURL(
|
|
2650
|
+
window.setTimeout(() => URL.revokeObjectURL(url2), 0);
|
|
2651
2651
|
}
|
|
2652
2652
|
}
|
|
2653
2653
|
)
|
|
@@ -4938,7 +4938,7 @@ function createQuantumFieldAudio() {
|
|
|
4938
4938
|
analyser.fftSize = 1024;
|
|
4939
4939
|
let source = null;
|
|
4940
4940
|
let media = null;
|
|
4941
|
-
let
|
|
4941
|
+
let url2 = null;
|
|
4942
4942
|
let ownedStream = null;
|
|
4943
4943
|
let disposed = false;
|
|
4944
4944
|
const disconnect = () => {
|
|
@@ -4946,10 +4946,10 @@ function createQuantumFieldAudio() {
|
|
|
4946
4946
|
source?.disconnect();
|
|
4947
4947
|
analyser.disconnect();
|
|
4948
4948
|
ownedStream?.getTracks().forEach((track) => track.stop());
|
|
4949
|
-
if (
|
|
4949
|
+
if (url2) URL.revokeObjectURL(url2);
|
|
4950
4950
|
media = null;
|
|
4951
4951
|
source = null;
|
|
4952
|
-
|
|
4952
|
+
url2 = null;
|
|
4953
4953
|
ownedStream = null;
|
|
4954
4954
|
};
|
|
4955
4955
|
const assertOpen = () => {
|
|
@@ -4970,8 +4970,8 @@ function createQuantumFieldAudio() {
|
|
|
4970
4970
|
loadFile(file) {
|
|
4971
4971
|
assertOpen();
|
|
4972
4972
|
disconnect();
|
|
4973
|
-
|
|
4974
|
-
media = new Audio(
|
|
4973
|
+
url2 = URL.createObjectURL(file);
|
|
4974
|
+
media = new Audio(url2);
|
|
4975
4975
|
source = context.createMediaElementSource(media);
|
|
4976
4976
|
source.connect(analyser);
|
|
4977
4977
|
analyser.connect(context.destination);
|
|
@@ -5322,7 +5322,7 @@ var rackLines = rackRows.map((row) => `M33 ${50 + row * 3.35} 90 ${38.8 + row *
|
|
|
5322
5322
|
var rackHatches = rackRows.flatMap((row) => Array.from({ length: 9 }, (_, col) => `M${34 + col * 6.15} ${49.8 + row * 3.35 - col * 1.205}l2.6-.51`)).join(" ");
|
|
5323
5323
|
function ServerArt({ upload = false, label, ...p }) {
|
|
5324
5324
|
const id = useSvgId();
|
|
5325
|
-
const
|
|
5325
|
+
const url2 = (name) => `url(#${id}-${name})`;
|
|
5326
5326
|
const box = "0 0 180 170";
|
|
5327
5327
|
const lift = upload ? "translate(0 15)" : void 0;
|
|
5328
5328
|
const dy = upload ? 15 : 0;
|
|
@@ -5379,14 +5379,14 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5379
5379
|
),
|
|
5380
5380
|
/* @__PURE__ */ jsx45("path", { d: "M80 37V18m-7 7 7-7 7 7", stroke: "var(--ad-secondary)", strokeWidth: "1.5" })
|
|
5381
5381
|
] }),
|
|
5382
|
-
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, className: "ad-art-aura-layer", origin: [89, 124 + dy], children: /* @__PURE__ */ jsx45("ellipse", { cx: "89", cy: 124 + dy, rx: "76", ry: "22", fill:
|
|
5382
|
+
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, className: "ad-art-aura-layer", origin: [89, 124 + dy], children: /* @__PURE__ */ jsx45("ellipse", { cx: "89", cy: 124 + dy, rx: "76", ry: "22", fill: url2("aura") }) }),
|
|
5383
5383
|
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs36("g", { transform: lift, children: [
|
|
5384
5384
|
/* @__PURE__ */ jsx45(
|
|
5385
5385
|
"path",
|
|
5386
5386
|
{
|
|
5387
5387
|
d: "M26 43 98 29 150 45 75 61Z",
|
|
5388
|
-
fill:
|
|
5389
|
-
stroke:
|
|
5388
|
+
fill: url2("top"),
|
|
5389
|
+
stroke: url2("edge"),
|
|
5390
5390
|
strokeWidth: ".75"
|
|
5391
5391
|
}
|
|
5392
5392
|
),
|
|
@@ -5394,7 +5394,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5394
5394
|
"path",
|
|
5395
5395
|
{
|
|
5396
5396
|
d: "M98 29 150 45 150 119 98 107Z",
|
|
5397
|
-
fill:
|
|
5397
|
+
fill: url2("side"),
|
|
5398
5398
|
stroke: "var(--ad-primary-700)",
|
|
5399
5399
|
strokeWidth: ".7"
|
|
5400
5400
|
}
|
|
@@ -5403,8 +5403,8 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5403
5403
|
"path",
|
|
5404
5404
|
{
|
|
5405
5405
|
d: "M26 43 98 29 98 107 26 121Z",
|
|
5406
|
-
fill:
|
|
5407
|
-
stroke:
|
|
5406
|
+
fill: url2("front"),
|
|
5407
|
+
stroke: url2("edge"),
|
|
5408
5408
|
strokeWidth: "1"
|
|
5409
5409
|
}
|
|
5410
5410
|
),
|
|
@@ -5427,7 +5427,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5427
5427
|
stroke: "var(--ad-secondary-200)",
|
|
5428
5428
|
strokeWidth: "3",
|
|
5429
5429
|
opacity: ".35",
|
|
5430
|
-
filter:
|
|
5430
|
+
filter: url2("bloom")
|
|
5431
5431
|
}
|
|
5432
5432
|
),
|
|
5433
5433
|
/* @__PURE__ */ jsx45("path", { d: "M27 44 98 30 147 45", stroke: "var(--ad-secondary-200)", strokeWidth: ".75" }),
|
|
@@ -5454,7 +5454,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5454
5454
|
"path",
|
|
5455
5455
|
{
|
|
5456
5456
|
d: "M26 125 98 112 150 126v14l-73 10-51-10Z",
|
|
5457
|
-
fill:
|
|
5457
|
+
fill: url2("side"),
|
|
5458
5458
|
stroke: "var(--ad-primary-700)",
|
|
5459
5459
|
strokeWidth: ".6"
|
|
5460
5460
|
}
|
|
@@ -5463,7 +5463,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5463
5463
|
"path",
|
|
5464
5464
|
{
|
|
5465
5465
|
d: "M26 125 98 112v15l-72 13Z",
|
|
5466
|
-
fill:
|
|
5466
|
+
fill: url2("front"),
|
|
5467
5467
|
stroke: "var(--ad-primary-600)",
|
|
5468
5468
|
strokeWidth: ".65"
|
|
5469
5469
|
}
|
|
@@ -6040,7 +6040,7 @@ function LevelMeter({
|
|
|
6040
6040
|
samples.push(envelope);
|
|
6041
6041
|
shape.setAttribute("d", wavePath2(samples));
|
|
6042
6042
|
}
|
|
6043
|
-
if (!analyser && envelope === 0 && samples.every((sample) => sample === 0)) {
|
|
6043
|
+
if (!analyser && input === 0 && envelope === 0 && samples.every((sample) => sample === 0)) {
|
|
6044
6044
|
shape.setAttribute("transform", "translate(0 0)");
|
|
6045
6045
|
stop?.();
|
|
6046
6046
|
stop = void 0;
|
|
@@ -6147,7 +6147,982 @@ var KaraokeLyrics = ({
|
|
|
6147
6147
|
);
|
|
6148
6148
|
|
|
6149
6149
|
// src/components/media/MelodyRoll/MelodyRoll.tsx
|
|
6150
|
-
import { useMemo as useMemo5, useRef as useRef28 } from "react";
|
|
6150
|
+
import { useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef28, useState as useState13 } from "react";
|
|
6151
|
+
|
|
6152
|
+
// src/components/media/MelodyRoll/melody-glow.js
|
|
6153
|
+
function renderGlow() {
|
|
6154
|
+
const scenes = /* @__PURE__ */ new Map();
|
|
6155
|
+
const fonts = /* @__PURE__ */ new Map();
|
|
6156
|
+
let frame = null;
|
|
6157
|
+
function ease(x, out = false) {
|
|
6158
|
+
if (x <= 0) return 0;
|
|
6159
|
+
if (x >= 1) return 1;
|
|
6160
|
+
let lo = 0, hi = 1;
|
|
6161
|
+
for (let i = 0; i < 18; i++) {
|
|
6162
|
+
const t2 = (lo + hi) / 2;
|
|
6163
|
+
const at = out ? 1.74 * (1 - t2) * t2 * t2 + t2 ** 3 : 0.75 * (1 - t2) * t2 + t2 ** 3;
|
|
6164
|
+
if (at < x) lo = t2;
|
|
6165
|
+
else hi = t2;
|
|
6166
|
+
}
|
|
6167
|
+
const t = (lo + hi) / 2;
|
|
6168
|
+
return (out ? 0 : 0.3 * (1 - t) ** 2 * t) + 3 * (1 - t) * t * t + t ** 3;
|
|
6169
|
+
}
|
|
6170
|
+
function value(scene, now) {
|
|
6171
|
+
return animated(scene.beat, now);
|
|
6172
|
+
}
|
|
6173
|
+
function mix(ctx, from, to, progress2, space = "oklab") {
|
|
6174
|
+
ctx.fillStyle = `color-mix(in ${space}, ${from}, ${to} ${progress2 * 100}%)`;
|
|
6175
|
+
return ctx.fillStyle;
|
|
6176
|
+
}
|
|
6177
|
+
function progress(channel, now) {
|
|
6178
|
+
const fraction = channel.duration > 0 ? Math.min(1, Math.max(0, (now - channel.start) / channel.duration)) : 1;
|
|
6179
|
+
return channel.linear ? fraction : ease(fraction);
|
|
6180
|
+
}
|
|
6181
|
+
function animated(channel, now, ctx) {
|
|
6182
|
+
const amount = progress(channel, now);
|
|
6183
|
+
if (amount === 1) return channel.to;
|
|
6184
|
+
if (amount === 0) return channel.from;
|
|
6185
|
+
if (Array.isArray(channel.to)) return Array.from({ length: Math.max(channel.from.length, channel.to.length) }, (_, i) => {
|
|
6186
|
+
const model = channel.to[i] ?? channel.from[i];
|
|
6187
|
+
const empty = { kind: model.kind, value: 1, x: 0, y: 0, blur: 0, spread: 0, color: "transparent", inset: model.inset };
|
|
6188
|
+
const a = channel.from[i] ?? empty, b = channel.to[i] ?? empty;
|
|
6189
|
+
if (model.kind === "brightness") return { kind: model.kind, value: a.value + (b.value - a.value) * amount };
|
|
6190
|
+
return { kind: model.kind, inset: b.inset, ...Object.fromEntries(["x", "y", "blur", "spread"].map((key) => [key, a[key] + (b[key] - a[key]) * amount])), color: mix(ctx, a.color, b.color, amount) };
|
|
6191
|
+
});
|
|
6192
|
+
return typeof channel.to === "number" ? channel.from + (channel.to - channel.from) * amount : mix(ctx, channel.from, channel.to, amount);
|
|
6193
|
+
}
|
|
6194
|
+
function transition(previous, to, now, duration, ctx, linear = false) {
|
|
6195
|
+
if (Array.isArray(to) && previous?.to.some((shadow, i) => to[i] && (shadow.inset !== to[i].inset || shadow.kind !== to[i].kind))) previous = void 0;
|
|
6196
|
+
if (!previous || duration <= 0) return { from: to, to, start: now, duration: 0, reverse: to, factor: 1, linear };
|
|
6197
|
+
if (previous.to === to) return previous;
|
|
6198
|
+
const from = animated(previous, now, ctx);
|
|
6199
|
+
const reversing = previous.reverse === to;
|
|
6200
|
+
const factor = reversing ? Math.min(1, Math.abs(progress(previous, now) * previous.factor + 1 - previous.factor)) : 1;
|
|
6201
|
+
return { from, to, start: now, duration: duration * factor, reverse: reversing ? previous.to : from, factor, linear };
|
|
6202
|
+
}
|
|
6203
|
+
function box(ctx, style, x, y, width, height, shadows = style.shadows) {
|
|
6204
|
+
const radius2 = style.radius.map((r) => Math.min(r, width / 2, height / 2));
|
|
6205
|
+
const [top, right, bottom, left] = style.borders.map((edge) => edge.width);
|
|
6206
|
+
const innerX = x + left, innerY = y + top, innerW = width - left - right, innerH = height - top - bottom;
|
|
6207
|
+
const innerR = radius2.map((r, i) => ({ x: Math.max(0, r - ([1, 2].includes(i) ? right : left)), y: Math.max(0, r - (i < 2 ? top : bottom)) }));
|
|
6208
|
+
const shape = () => {
|
|
6209
|
+
ctx.beginPath();
|
|
6210
|
+
ctx.roundRect(x, y, width, height, radius2);
|
|
6211
|
+
};
|
|
6212
|
+
const dpr = ctx.getTransform().a;
|
|
6213
|
+
for (const shadow of [...shadows].reverse()) {
|
|
6214
|
+
if (shadow.inset) continue;
|
|
6215
|
+
ctx.save();
|
|
6216
|
+
ctx.beginPath();
|
|
6217
|
+
ctx.rect(x - 200, y - 200, width + 400, height + 400);
|
|
6218
|
+
ctx.roundRect(x, y, width, height, radius2);
|
|
6219
|
+
ctx.clip("evenodd");
|
|
6220
|
+
const offset = width + 1e3;
|
|
6221
|
+
ctx.shadowOffsetX = (offset + shadow.x) * dpr;
|
|
6222
|
+
ctx.shadowOffsetY = shadow.y * dpr;
|
|
6223
|
+
ctx.shadowBlur = shadow.blur * dpr;
|
|
6224
|
+
ctx.shadowColor = shadow.color;
|
|
6225
|
+
ctx.fillStyle = "#000";
|
|
6226
|
+
ctx.beginPath();
|
|
6227
|
+
ctx.roundRect(x - offset - shadow.spread, y - shadow.spread, Math.max(0, width + 2 * shadow.spread), Math.max(0, height + 2 * shadow.spread), radius2.map((r) => Math.max(0, r + shadow.spread)));
|
|
6228
|
+
ctx.fill();
|
|
6229
|
+
ctx.restore();
|
|
6230
|
+
}
|
|
6231
|
+
ctx.save();
|
|
6232
|
+
shape();
|
|
6233
|
+
ctx.clip();
|
|
6234
|
+
ctx.fillStyle = style.backgroundColor;
|
|
6235
|
+
ctx.fillRect(x, y, width, height);
|
|
6236
|
+
if (style.gradient.length) {
|
|
6237
|
+
const gradient = ctx.createLinearGradient(innerX, y, innerX + innerW, y);
|
|
6238
|
+
for (const stop of style.gradient) gradient.addColorStop(stop.at, stop.color);
|
|
6239
|
+
ctx.fillStyle = gradient;
|
|
6240
|
+
ctx.fillRect(x, y, width, height);
|
|
6241
|
+
}
|
|
6242
|
+
for (const shadow of [...shadows].reverse()) {
|
|
6243
|
+
if (!shadow.inset) continue;
|
|
6244
|
+
ctx.save();
|
|
6245
|
+
ctx.beginPath();
|
|
6246
|
+
ctx.roundRect(innerX, innerY, innerW, innerH, innerR);
|
|
6247
|
+
ctx.clip();
|
|
6248
|
+
const offset = width + 1e3;
|
|
6249
|
+
ctx.shadowOffsetX = (offset + shadow.x) * dpr;
|
|
6250
|
+
ctx.shadowOffsetY = shadow.y * dpr;
|
|
6251
|
+
ctx.shadowBlur = shadow.blur * dpr;
|
|
6252
|
+
ctx.shadowColor = shadow.color;
|
|
6253
|
+
ctx.fillStyle = "#000";
|
|
6254
|
+
ctx.beginPath();
|
|
6255
|
+
ctx.rect(innerX - offset - 200, innerY - 200, innerW + 400, innerH + 400);
|
|
6256
|
+
ctx.roundRect(innerX - offset + shadow.spread, innerY + shadow.spread, Math.max(0, innerW - 2 * shadow.spread), Math.max(0, innerH - 2 * shadow.spread), innerR.map((r) => ({ x: Math.max(0, r.x - shadow.spread), y: Math.max(0, r.y - shadow.spread) })));
|
|
6257
|
+
ctx.fill("evenodd");
|
|
6258
|
+
ctx.restore();
|
|
6259
|
+
}
|
|
6260
|
+
const border = style.borders.find((edge) => edge.width > 0);
|
|
6261
|
+
if (border && style.borders.every((edge) => !edge.width || edge.color === border.color)) {
|
|
6262
|
+
ctx.fillStyle = border.color;
|
|
6263
|
+
ctx.beginPath();
|
|
6264
|
+
ctx.roundRect(x, y, width, height, radius2);
|
|
6265
|
+
ctx.roundRect(innerX, innerY, innerW, innerH, innerR);
|
|
6266
|
+
ctx.fill("evenodd");
|
|
6267
|
+
} else for (let i = 0; i < 4; i++) {
|
|
6268
|
+
const edge = style.borders[i];
|
|
6269
|
+
ctx.fillStyle = edge.color;
|
|
6270
|
+
const rect = [[x, y, width, edge.width], [x + width - edge.width, y, edge.width, height], [x, y + height - edge.width, width, edge.width], [x, y, edge.width, height]][i];
|
|
6271
|
+
ctx.fillRect(...rect);
|
|
6272
|
+
}
|
|
6273
|
+
ctx.restore();
|
|
6274
|
+
}
|
|
6275
|
+
function text(ctx, style, value2, x, y, width, height, color3 = style.color, right = false) {
|
|
6276
|
+
ctx.save();
|
|
6277
|
+
if (!right) {
|
|
6278
|
+
ctx.beginPath();
|
|
6279
|
+
ctx.rect(x, y, width, height);
|
|
6280
|
+
ctx.clip();
|
|
6281
|
+
}
|
|
6282
|
+
ctx.font = style.font;
|
|
6283
|
+
ctx.textBaseline = "alphabetic";
|
|
6284
|
+
ctx.textAlign = "left";
|
|
6285
|
+
ctx.letterSpacing = style.letterSpacing;
|
|
6286
|
+
ctx.fillStyle = color3;
|
|
6287
|
+
const metrics = ctx.measureText(value2);
|
|
6288
|
+
const baseline = y + (style.baseline ?? (height - metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2 + metrics.fontBoundingBoxAscent);
|
|
6289
|
+
const start = x + (right ? style.offset : style.padding);
|
|
6290
|
+
for (const shadow of [...style.textShadows].reverse()) {
|
|
6291
|
+
ctx.save();
|
|
6292
|
+
ctx.shadowColor = shadow.color;
|
|
6293
|
+
ctx.shadowBlur = shadow.blur * ctx.getTransform().a;
|
|
6294
|
+
ctx.shadowOffsetX = (width + 1e3 + shadow.x) * ctx.getTransform().a;
|
|
6295
|
+
ctx.shadowOffsetY = shadow.y * ctx.getTransform().a;
|
|
6296
|
+
ctx.fillText(value2, start - width - 1e3, baseline);
|
|
6297
|
+
ctx.restore();
|
|
6298
|
+
}
|
|
6299
|
+
ctx.fillText(value2, start, baseline);
|
|
6300
|
+
ctx.restore();
|
|
6301
|
+
}
|
|
6302
|
+
function retargetForeground(scene, now) {
|
|
6303
|
+
if (!scene.foreground) return;
|
|
6304
|
+
const { state, foreground: style, noteCtx: ctx } = scene;
|
|
6305
|
+
scene.noteStates ??= /* @__PURE__ */ new Map();
|
|
6306
|
+
const kept = /* @__PURE__ */ new Map();
|
|
6307
|
+
const from = state.position - state.span * state.lead;
|
|
6308
|
+
let until = 0;
|
|
6309
|
+
for (const [i, note] of (state.notes ?? []).entries()) {
|
|
6310
|
+
const previous = scene.noteStates.get(note.id);
|
|
6311
|
+
const visible = note.end >= from && note.start <= from + state.span;
|
|
6312
|
+
const hit = visible && state.hits?.[i] === "h";
|
|
6313
|
+
const current = visible && state.position >= note.start && state.position <= note.end;
|
|
6314
|
+
const target = hit ? style.note.hit : current ? style.note.current : style.note.base;
|
|
6315
|
+
const item = {
|
|
6316
|
+
note,
|
|
6317
|
+
visible,
|
|
6318
|
+
hit,
|
|
6319
|
+
buffer: previous?.buffer,
|
|
6320
|
+
opacity: transition(previous?.opacity, visible && !hit && state.position > note.end ? style.note.pastOpacity : 1, now, style.note.opacityDuration),
|
|
6321
|
+
filter: transition(previous?.filter, target.filter, now, style.note.filterDuration, ctx),
|
|
6322
|
+
hitStart: hit && !previous?.hit ? previous ? now : scene.noteTimes?.[note.id] ?? now : previous?.hitStart ?? -Infinity
|
|
6323
|
+
};
|
|
6324
|
+
kept.set(note.id, item);
|
|
6325
|
+
until = Math.max(until, item.opacity.start + item.opacity.duration, item.filter.start + item.filter.duration, hit ? item.hitStart + style.note.hitDuration : 0);
|
|
6326
|
+
}
|
|
6327
|
+
scene.noteStates = kept;
|
|
6328
|
+
scene.keyStates ??= /* @__PURE__ */ new Map();
|
|
6329
|
+
const keyStates = /* @__PURE__ */ new Map();
|
|
6330
|
+
for (const key of style.keys) {
|
|
6331
|
+
const previous = scene.keyStates.get(key.midi);
|
|
6332
|
+
const active = state.pitch !== void 0 && key.midi === Math.round(state.pitch);
|
|
6333
|
+
const hit = active && state.hit;
|
|
6334
|
+
const pressed = scene.pressed?.includes(key.midi);
|
|
6335
|
+
const variant = hit ? "hit" : active ? "active" : pressed ? "pressed" : "base";
|
|
6336
|
+
const target = key[variant];
|
|
6337
|
+
const item = {
|
|
6338
|
+
key,
|
|
6339
|
+
target,
|
|
6340
|
+
hit,
|
|
6341
|
+
variant,
|
|
6342
|
+
buffer: previous?.buffer,
|
|
6343
|
+
color: transition(previous?.color, target.color, now, key.duration, ctx),
|
|
6344
|
+
shadows: transition(previous?.shadows, target.shadows, now, key.duration, ctx),
|
|
6345
|
+
shift: transition(previous?.shift, pressed ? key.pressShift : 0, now, key.pressDuration),
|
|
6346
|
+
hitStart: hit && !previous?.hit ? previous ? now : scene.keyTimes?.[key.midi] ?? now : previous?.hitStart ?? now
|
|
6347
|
+
};
|
|
6348
|
+
keyStates.set(key.midi, item);
|
|
6349
|
+
until = Math.max(until, ...["color", "shadows", "shift"].map((name) => item[name].start + item[name].duration), hit && key.hitDuration > 0 ? item.hitStart + key.hitDuration * key.hitIterations : 0);
|
|
6350
|
+
}
|
|
6351
|
+
scene.keyStates = keyStates;
|
|
6352
|
+
scene.noteTimes = scene.keyTimes = void 0;
|
|
6353
|
+
scene.foregroundUntil = until;
|
|
6354
|
+
}
|
|
6355
|
+
function drawForeground(scene, now) {
|
|
6356
|
+
if (!scene.foreground) return;
|
|
6357
|
+
const { noteCtx: ctx, keyCtx, foreground: style, state, dpr, laneWidth, laneHeight } = scene;
|
|
6358
|
+
ctx.clearRect(0, 0, laneWidth, laneHeight);
|
|
6359
|
+
scene.sprites ??= /* @__PURE__ */ new Map();
|
|
6360
|
+
scene.filteredSprites ??= /* @__PURE__ */ new Map();
|
|
6361
|
+
scene.filteredPixels ??= 0;
|
|
6362
|
+
const from = state.position - state.span * state.lead;
|
|
6363
|
+
ctx.fillStyle = style.grid.color;
|
|
6364
|
+
const spacing2 = laneWidth / state.span;
|
|
6365
|
+
const shift = -((from % 1 + 1) % 1) * spacing2;
|
|
6366
|
+
for (let x = shift; x < laneWidth; x += spacing2) ctx.fillRect(x, 0, style.grid.width, laneHeight);
|
|
6367
|
+
for (const item of scene.noteStates.values()) {
|
|
6368
|
+
if (!item.visible) continue;
|
|
6369
|
+
const { note } = item;
|
|
6370
|
+
const origin = Math.floor(from / state.span) * state.span;
|
|
6371
|
+
const left = Math.floor((note.start - origin) / state.span * laneWidth * 64) / 64;
|
|
6372
|
+
const width = Math.floor(Math.max(6e-3, (note.end - note.start) / state.span) * laneWidth * 64) / 64;
|
|
6373
|
+
const height = Math.round(style.note.height * 64) / 64;
|
|
6374
|
+
const top = Math.floor((state.high - note.pitch) / state.rows * laneHeight * 64) / 64 + height * 0.158;
|
|
6375
|
+
const offsetX = left - Math.floor(left * dpr) / dpr, offsetY = top - Math.floor(top * dpr) / dpr;
|
|
6376
|
+
const lyric = state.showLyrics ? note.lyric ?? "" : "";
|
|
6377
|
+
const spriteKey = JSON.stringify([width, height, lyric, item.hit, offsetX, offsetY]);
|
|
6378
|
+
let sprite = scene.sprites.get(spriteKey);
|
|
6379
|
+
if (!sprite) {
|
|
6380
|
+
sprite = new OffscreenCanvas(Math.ceil((width + offsetX) * dpr), Math.ceil((height + offsetY) * dpr));
|
|
6381
|
+
const paint = sprite.getContext("2d");
|
|
6382
|
+
paint.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
6383
|
+
const base = item.hit ? style.note.hit : style.note.base;
|
|
6384
|
+
paint.save();
|
|
6385
|
+
paint.beginPath();
|
|
6386
|
+
paint.roundRect(offsetX, offsetY, width, height, base.radius.map((radius2) => Math.min(radius2, width / 2, height / 2)));
|
|
6387
|
+
paint.clip();
|
|
6388
|
+
box(paint, base, offsetX, offsetY, width, height);
|
|
6389
|
+
text(paint, style.note.text, lyric, offsetX, offsetY, width, height);
|
|
6390
|
+
paint.restore();
|
|
6391
|
+
scene.sprites.set(spriteKey, sprite);
|
|
6392
|
+
if (scene.sprites.size > 512) scene.sprites.delete(scene.sprites.keys().next().value);
|
|
6393
|
+
}
|
|
6394
|
+
let filter = animated(item.filter, now, ctx);
|
|
6395
|
+
const phase = style.note.hitDuration > 0 ? (now - item.hitStart) / style.note.hitDuration : 1;
|
|
6396
|
+
let scale = 1;
|
|
6397
|
+
if (item.hit && phase < 1) {
|
|
6398
|
+
const amount = phase < 0.28 ? ease(phase / 0.28, true) : 1 - ease((phase - 0.28) / 0.72, true);
|
|
6399
|
+
scale += 0.6 * amount;
|
|
6400
|
+
const a = style.note.hit.filter, b = style.note.peakFilter;
|
|
6401
|
+
filter = a.some((entry, i) => b[i] && entry.kind !== b[i].kind) ? amount < 0.5 ? a : b : animated({ from: a, to: b, start: 0, duration: 1, linear: true }, amount, ctx);
|
|
6402
|
+
}
|
|
6403
|
+
const filterValue = filter.map((part2) => part2.kind === "brightness" ? `brightness(${part2.value})` : `drop-shadow(${part2.x * dpr}px ${part2.y * dpr}px ${part2.blur * dpr}px ${part2.color})`).join(" ") || "none";
|
|
6404
|
+
let painted = sprite, padding = 0;
|
|
6405
|
+
if (filter.length) {
|
|
6406
|
+
padding = Math.ceil(style.note.filterPad * dpr);
|
|
6407
|
+
const filteredKey = spriteKey + filterValue;
|
|
6408
|
+
const settled = progress(item.filter, now) === 1 && (!item.hit || phase >= 1);
|
|
6409
|
+
painted = settled ? scene.filteredSprites.get(filteredKey) : null;
|
|
6410
|
+
if (!painted) {
|
|
6411
|
+
const w = sprite.width + padding * 2, h = sprite.height + padding * 2;
|
|
6412
|
+
if (!item.buffer || item.buffer.width !== w || item.buffer.height !== h) item.buffer = new OffscreenCanvas(w, h);
|
|
6413
|
+
painted = settled ? new OffscreenCanvas(w, h) : item.buffer;
|
|
6414
|
+
const paint = painted.getContext("2d");
|
|
6415
|
+
paint.clearRect(0, 0, w, h);
|
|
6416
|
+
paint.filter = filterValue;
|
|
6417
|
+
paint.drawImage(sprite, padding, padding);
|
|
6418
|
+
if (settled && w * h <= 8 * 1024 * 1024) {
|
|
6419
|
+
scene.filteredSprites.set(filteredKey, painted);
|
|
6420
|
+
scene.filteredPixels += w * h;
|
|
6421
|
+
while (scene.filteredPixels > 8 * 1024 * 1024) {
|
|
6422
|
+
const oldest = scene.filteredSprites.keys().next().value;
|
|
6423
|
+
const old = scene.filteredSprites.get(oldest);
|
|
6424
|
+
scene.filteredPixels -= old.width * old.height;
|
|
6425
|
+
scene.filteredSprites.delete(oldest);
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
6430
|
+
ctx.save();
|
|
6431
|
+
ctx.globalAlpha = animated(item.opacity, now);
|
|
6432
|
+
const x = left - offsetX + Number(((origin - from) / state.span * 100).toFixed(3)) / 100 * laneWidth;
|
|
6433
|
+
const y = top - offsetY;
|
|
6434
|
+
ctx.drawImage(painted, x - padding / dpr, top + height / 2 + (y - padding / dpr - top - height / 2) * scale, painted.width / dpr, painted.height / dpr * scale);
|
|
6435
|
+
ctx.restore();
|
|
6436
|
+
}
|
|
6437
|
+
keyCtx.clearRect(0, 0, style.keyWidth, style.keyHeight);
|
|
6438
|
+
scene.keySprites ??= /* @__PURE__ */ new Map();
|
|
6439
|
+
for (const item of scene.keyStates.values()) {
|
|
6440
|
+
const { key, target } = item;
|
|
6441
|
+
keyCtx.save();
|
|
6442
|
+
const phase = key.hitDuration > 0 ? (now - item.hitStart) / key.hitDuration % 1 : 0;
|
|
6443
|
+
const fraction = phase < 0.5 ? phase * 2 : (1 - phase) * 2;
|
|
6444
|
+
let lo = 0, hi = 1;
|
|
6445
|
+
for (let i = 0; i < 18; i++) {
|
|
6446
|
+
const t2 = (lo + hi) / 2;
|
|
6447
|
+
if (1.26 * (1 - t2) ** 2 * t2 + 1.74 * (1 - t2) * t2 * t2 + t2 ** 3 < fraction) lo = t2;
|
|
6448
|
+
else hi = t2;
|
|
6449
|
+
}
|
|
6450
|
+
const t = (lo + hi) / 2;
|
|
6451
|
+
const baseBrightness = scene.hovered === key.midi ? 1.08 : 1;
|
|
6452
|
+
const brightness = item.hit && key.hitDuration > 0 && now < item.hitStart + key.hitDuration * key.hitIterations ? baseBrightness + (1.25 - baseBrightness) * (3 * (1 - t) * t * t + t ** 3) : baseBrightness;
|
|
6453
|
+
keyCtx.filter = `brightness(${brightness})`;
|
|
6454
|
+
const settled = progress(item.shadows, now) === 1 && progress(item.color, now) === 1;
|
|
6455
|
+
const cacheKey = `${key.midi}:${item.variant}`;
|
|
6456
|
+
let sprite = settled ? scene.keySprites.get(cacheKey) : null;
|
|
6457
|
+
const paintTop = Math.round(key.top * dpr) / dpr, paintWidth = Math.round(key.width * dpr) / dpr;
|
|
6458
|
+
const paintHeight = Math.round((key.top + key.height) * dpr) / dpr - paintTop;
|
|
6459
|
+
const pad = key.pad, width = paintWidth + pad * 2, height = paintHeight + pad * 2;
|
|
6460
|
+
if (!sprite) {
|
|
6461
|
+
sprite = settled ? new OffscreenCanvas(Math.ceil(width * dpr), Math.ceil(height * dpr)) : item.buffer ??= new OffscreenCanvas(Math.ceil(width * dpr), Math.ceil(height * dpr));
|
|
6462
|
+
const paint = sprite.getContext("2d");
|
|
6463
|
+
paint.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
6464
|
+
paint.clearRect(0, 0, width, height);
|
|
6465
|
+
box(paint, target, pad, pad, paintWidth, paintHeight, animated(item.shadows, now, paint));
|
|
6466
|
+
const [top, right, bottom, left] = target.borders.map((edge) => edge.width);
|
|
6467
|
+
text(paint, key.text, key.label, pad + left, pad + top + key.top - paintTop, key.width - left - right, key.height - top - bottom, animated(item.color, now, paint), true);
|
|
6468
|
+
if (settled) scene.keySprites.set(cacheKey, sprite);
|
|
6469
|
+
}
|
|
6470
|
+
keyCtx.drawImage(sprite, 0, 0, width * dpr, height * dpr, animated(item.shift, now) - pad, paintTop - pad, width, height);
|
|
6471
|
+
keyCtx.restore();
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
function voiceValue(scene, now) {
|
|
6475
|
+
return Object.fromEntries(["y", "size", "color"].map((key) => [key, animated(scene.voice[key], now, scene.voiceCtx)]));
|
|
6476
|
+
}
|
|
6477
|
+
function retargetVoice(scene, now, previous) {
|
|
6478
|
+
const { state, voiceStyle: style, voiceCtx: ctx, laneHeight } = scene;
|
|
6479
|
+
const to = {
|
|
6480
|
+
y: (state.high - (state.pitch ?? 0) + 0.5) / state.rows * laneHeight,
|
|
6481
|
+
size: style.size[0] + (style.size[1] - style.size[0]) * state.level,
|
|
6482
|
+
color: state.hit ? style.hitColor : mix(ctx, style.color[0], style.color[1], state.accuracy, "oklch")
|
|
6483
|
+
};
|
|
6484
|
+
const hitStart = state.hit && !previous?.hit ? now : scene.voice?.hitStart ?? -Infinity;
|
|
6485
|
+
scene.voice = { ...Object.fromEntries(Object.entries(to).map(([key, target]) => [
|
|
6486
|
+
key,
|
|
6487
|
+
transition(previous?.pitch !== void 0 ? scene.voice?.[key] : void 0, target, now, style.duration, ctx, key === "y")
|
|
6488
|
+
])), hitStart };
|
|
6489
|
+
}
|
|
6490
|
+
function drawVoice(scene, now) {
|
|
6491
|
+
const { voiceCtx: ctx, laneWidth, laneHeight, state, voiceStyle: style, dpr } = scene;
|
|
6492
|
+
ctx.clearRect(0, 0, laneWidth, laneHeight);
|
|
6493
|
+
ctx.save();
|
|
6494
|
+
const head = state.lead * laneWidth - scene.headWidth / 2;
|
|
6495
|
+
const offset = laneWidth + 200;
|
|
6496
|
+
ctx.beginPath();
|
|
6497
|
+
ctx.rect(0, 0, laneWidth, laneHeight);
|
|
6498
|
+
ctx.rect(head, 0, scene.headWidth, laneHeight);
|
|
6499
|
+
ctx.clip("evenodd");
|
|
6500
|
+
ctx.globalAlpha = state.beat;
|
|
6501
|
+
ctx.shadowOffsetX = offset * dpr;
|
|
6502
|
+
for (const shadow of [...scene.headShadows].reverse()) {
|
|
6503
|
+
ctx.shadowBlur = shadow.blur * dpr;
|
|
6504
|
+
ctx.shadowColor = shadow.color;
|
|
6505
|
+
ctx.fillRect(head - offset, 0, scene.headWidth, laneHeight);
|
|
6506
|
+
}
|
|
6507
|
+
ctx.restore();
|
|
6508
|
+
if (state.pitch === void 0) return;
|
|
6509
|
+
const voice = voiceValue(scene, now);
|
|
6510
|
+
const phase = style.hitDuration > 0 ? (now - scene.voice.hitStart) / style.hitDuration : 1;
|
|
6511
|
+
const scale = state.hit && phase < 1 ? 1 + 0.35 * (phase < 0.45 ? ease(phase / 0.45, true) : 1 - ease((phase - 0.45) / 0.55, true)) : 1;
|
|
6512
|
+
const x = state.lead * laneWidth, r = voice.size * scale / 2;
|
|
6513
|
+
for (let i = style.shadows[0].length - 1; i >= 0; i--) {
|
|
6514
|
+
const a = style.shadows[0][i], b = style.shadows[1][i];
|
|
6515
|
+
const blur = (a.blur + (b.blur - a.blur) * state.accuracy) * scale;
|
|
6516
|
+
const spread = (a.spread + (b.spread - a.spread) * state.accuracy) * scale;
|
|
6517
|
+
ctx.save();
|
|
6518
|
+
const offset2 = laneWidth + 100;
|
|
6519
|
+
ctx.shadowOffsetX = offset2 * dpr;
|
|
6520
|
+
ctx.shadowBlur = blur * dpr;
|
|
6521
|
+
ctx.shadowColor = mix(ctx, a.color, b.color, state.accuracy, "srgb");
|
|
6522
|
+
ctx.fillStyle = "#000";
|
|
6523
|
+
ctx.beginPath();
|
|
6524
|
+
ctx.arc(x - offset2, voice.y, r + spread, 0, Math.PI * 2);
|
|
6525
|
+
ctx.fill();
|
|
6526
|
+
ctx.restore();
|
|
6527
|
+
}
|
|
6528
|
+
ctx.fillStyle = style.borderColor;
|
|
6529
|
+
ctx.beginPath();
|
|
6530
|
+
ctx.arc(x, voice.y, r, 0, Math.PI * 2);
|
|
6531
|
+
ctx.fill();
|
|
6532
|
+
ctx.fillStyle = voice.color;
|
|
6533
|
+
ctx.beginPath();
|
|
6534
|
+
ctx.arc(x, voice.y, Math.max(0, r - style.border * scale), 0, Math.PI * 2);
|
|
6535
|
+
ctx.fill();
|
|
6536
|
+
}
|
|
6537
|
+
function updateTrail(scene) {
|
|
6538
|
+
const { position, pitch } = scene.state;
|
|
6539
|
+
if (scene.trail.at(-1)?.t !== position)
|
|
6540
|
+
scene.trail = [...scene.trail.filter((point) => point.t > position - 3 && point.t < position), { t: position, pitch }];
|
|
6541
|
+
}
|
|
6542
|
+
function drawTrail(scene) {
|
|
6543
|
+
const { trailCtx: ctx, laneWidth, laneHeight, state, trailStyle } = scene;
|
|
6544
|
+
ctx.clearRect(0, 0, laneWidth, laneHeight);
|
|
6545
|
+
const paths = [];
|
|
6546
|
+
let path = null;
|
|
6547
|
+
for (const point of scene.trail) {
|
|
6548
|
+
if (point.pitch === void 0) {
|
|
6549
|
+
path = null;
|
|
6550
|
+
continue;
|
|
6551
|
+
}
|
|
6552
|
+
const x = Number(((point.t - state.position + state.span * state.lead) / state.span * 100).toFixed(2)) / 100 * laneWidth;
|
|
6553
|
+
const y = Number(((state.high - point.pitch + 0.5) / state.rows * 100).toFixed(2)) / 100 * laneHeight;
|
|
6554
|
+
if (!path) {
|
|
6555
|
+
path = new Path2D();
|
|
6556
|
+
path.moveTo(x, y);
|
|
6557
|
+
paths.push(path);
|
|
6558
|
+
} else path.lineTo(x, y);
|
|
6559
|
+
}
|
|
6560
|
+
ctx.lineCap = ctx.lineJoin = "round";
|
|
6561
|
+
for (const path2 of paths) for (const layer of trailStyle) {
|
|
6562
|
+
ctx.strokeStyle = mix(ctx, layer.color[0], layer.color[1], state.accuracy, "oklch");
|
|
6563
|
+
ctx.lineWidth = layer.width;
|
|
6564
|
+
ctx.globalAlpha = layer.opacity;
|
|
6565
|
+
ctx.stroke(path2);
|
|
6566
|
+
}
|
|
6567
|
+
ctx.globalAlpha = 1;
|
|
6568
|
+
}
|
|
6569
|
+
function draw(scene, now) {
|
|
6570
|
+
const { ctx, width, height, pad, radius: radius2, blur, color: color3, dpr } = scene;
|
|
6571
|
+
ctx.clearRect(0, 0, width + pad * 2, height + pad * 2);
|
|
6572
|
+
const amount = value(scene, now);
|
|
6573
|
+
if (amount > 0) {
|
|
6574
|
+
ctx.save();
|
|
6575
|
+
ctx.beginPath();
|
|
6576
|
+
ctx.rect(0, 0, width + pad * 2, height + pad * 2);
|
|
6577
|
+
ctx.roundRect(pad, pad, width, height, radius2);
|
|
6578
|
+
ctx.clip("evenodd");
|
|
6579
|
+
const offset = width + pad * 4;
|
|
6580
|
+
ctx.shadowOffsetX = offset * dpr;
|
|
6581
|
+
ctx.shadowBlur = blur * amount * dpr;
|
|
6582
|
+
ctx.shadowColor = color3;
|
|
6583
|
+
ctx.fillStyle = "#000";
|
|
6584
|
+
ctx.beginPath();
|
|
6585
|
+
ctx.roundRect(pad - offset, pad, width, height, radius2);
|
|
6586
|
+
ctx.fill();
|
|
6587
|
+
ctx.restore();
|
|
6588
|
+
ctx.save();
|
|
6589
|
+
ctx.globalAlpha = amount;
|
|
6590
|
+
ctx.translate(pad, pad);
|
|
6591
|
+
ctx.beginPath();
|
|
6592
|
+
ctx.roundRect(0, 0, width, height, radius2);
|
|
6593
|
+
ctx.clip();
|
|
6594
|
+
ctx.save();
|
|
6595
|
+
ctx.translate(width * 0.25, height * 0.5);
|
|
6596
|
+
ctx.scale(width * 0.8, height * 1.2);
|
|
6597
|
+
const light = ctx.createRadialGradient(0, 0, 0, 0, 0, 1);
|
|
6598
|
+
light.addColorStop(0, scene.lightColors[0]);
|
|
6599
|
+
light.addColorStop(0.7, `rgb(from ${scene.lightColors[0]} r g b / 0)`);
|
|
6600
|
+
ctx.fillStyle = light;
|
|
6601
|
+
ctx.fillRect(-0.25 / 0.8, -0.5 / 1.2, 1 / 0.8, 1 / 1.2);
|
|
6602
|
+
ctx.restore();
|
|
6603
|
+
ctx.strokeStyle = scene.lightBorder;
|
|
6604
|
+
ctx.lineWidth = scene.lightWidth;
|
|
6605
|
+
const half = scene.lightWidth / 2;
|
|
6606
|
+
ctx.beginPath();
|
|
6607
|
+
ctx.roundRect(half, half, width - scene.lightWidth, height - scene.lightWidth, radius2.map((r) => Math.max(0, r - half)));
|
|
6608
|
+
ctx.stroke();
|
|
6609
|
+
ctx.restore();
|
|
6610
|
+
}
|
|
6611
|
+
drawTrail(scene);
|
|
6612
|
+
drawForeground(scene, now);
|
|
6613
|
+
drawVoice(scene, now);
|
|
6614
|
+
}
|
|
6615
|
+
function tick(now) {
|
|
6616
|
+
now += performance.timeOrigin;
|
|
6617
|
+
frame = null;
|
|
6618
|
+
for (const scene of scenes.values()) if (scene.visible && !scene.loading) draw(scene, now);
|
|
6619
|
+
if ([...scenes.values()].some((scene) => scene.visible && !scene.loading && now < Math.max(scene.foregroundUntil ?? 0, scene.beat.start + scene.beat.duration, ...["y", "size", "color"].map((key) => scene.voice[key].start + scene.voice[key].duration), scene.state.hit ? scene.voice.hitStart + scene.voiceStyle.hitDuration : 0)))
|
|
6620
|
+
frame = requestAnimationFrame(tick);
|
|
6621
|
+
}
|
|
6622
|
+
onmessage = async ({ data }) => {
|
|
6623
|
+
try {
|
|
6624
|
+
let scene = scenes.get(data.id);
|
|
6625
|
+
const now = data.time ?? performance.timeOrigin + performance.now();
|
|
6626
|
+
if (data.type === "remove") scenes.delete(data.id);
|
|
6627
|
+
else if (data.type === "configure") {
|
|
6628
|
+
const canvas = scene?.canvas ?? data.canvas;
|
|
6629
|
+
const previous = scene?.state;
|
|
6630
|
+
if (scene?.voice && scene.laneHeight > 0) {
|
|
6631
|
+
scene.voice.y.from *= data.laneHeight / scene.laneHeight;
|
|
6632
|
+
scene.voice.y.to *= data.laneHeight / scene.laneHeight;
|
|
6633
|
+
scene.voice.y.reverse *= data.laneHeight / scene.laneHeight;
|
|
6634
|
+
}
|
|
6635
|
+
const voiceCanvas = scene?.voiceCanvas ?? data.voiceCanvas;
|
|
6636
|
+
const trailCanvas = scene?.trailCanvas ?? data.trailCanvas;
|
|
6637
|
+
scene = { ...scene, ...data, canvas, voiceCanvas, trailCanvas, beat: transition(scene?.beat, data.state.beat, now, data.duration), trail: scene?.trail ?? data.state.trail ?? [] };
|
|
6638
|
+
canvas.width = Math.ceil((data.width + data.pad * 2) * data.dpr);
|
|
6639
|
+
canvas.height = Math.ceil((data.height + data.pad * 2) * data.dpr);
|
|
6640
|
+
scene.ctx = canvas.getContext("2d");
|
|
6641
|
+
scene.ctx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6642
|
+
voiceCanvas.width = Math.ceil(data.laneWidth * data.dpr);
|
|
6643
|
+
voiceCanvas.height = Math.ceil(data.laneHeight * data.dpr);
|
|
6644
|
+
scene.voiceCtx = voiceCanvas.getContext("2d");
|
|
6645
|
+
scene.voiceCtx.fillStyle = "#010203";
|
|
6646
|
+
scene.voiceCtx.fillStyle = "color-mix(in oklch, white, #2ee886 50%)";
|
|
6647
|
+
if (scene.voiceCtx.fillStyle === "#010203") throw new Error("Canvas color interpolation is unavailable");
|
|
6648
|
+
scene.voiceCtx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6649
|
+
trailCanvas.width = Math.ceil(data.laneWidth * data.dpr);
|
|
6650
|
+
trailCanvas.height = Math.ceil(data.laneHeight * data.dpr);
|
|
6651
|
+
scene.trailCtx = trailCanvas.getContext("2d");
|
|
6652
|
+
scene.trailCtx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6653
|
+
if (data.foreground) {
|
|
6654
|
+
scene.noteCanvas ??= data.noteCanvas;
|
|
6655
|
+
scene.keyCanvas ??= data.keyCanvas;
|
|
6656
|
+
scene.noteCanvas.width = Math.ceil(data.laneWidth * data.dpr);
|
|
6657
|
+
scene.noteCanvas.height = Math.ceil(data.laneHeight * data.dpr);
|
|
6658
|
+
scene.keyCanvas.width = Math.ceil(data.foreground.keyWidth * data.dpr);
|
|
6659
|
+
scene.keyCanvas.height = Math.ceil(data.foreground.keyHeight * data.dpr);
|
|
6660
|
+
scene.noteCtx = scene.noteCanvas.getContext("2d");
|
|
6661
|
+
scene.keyCtx = scene.keyCanvas.getContext("2d");
|
|
6662
|
+
scene.noteCtx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6663
|
+
scene.keyCtx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6664
|
+
scene.sprites?.clear();
|
|
6665
|
+
scene.keySprites?.clear();
|
|
6666
|
+
scene.filteredSprites?.clear();
|
|
6667
|
+
scene.filteredPixels = 0;
|
|
6668
|
+
for (const item of scene.noteStates?.values() ?? []) item.buffer = void 0;
|
|
6669
|
+
for (const item of scene.keyStates?.values() ?? []) item.buffer = void 0;
|
|
6670
|
+
retargetForeground(scene, now);
|
|
6671
|
+
}
|
|
6672
|
+
updateTrail(scene);
|
|
6673
|
+
retargetVoice(scene, now, previous);
|
|
6674
|
+
if (data.hitStartedAt !== void 0) scene.voice.hitStart = data.hitStartedAt;
|
|
6675
|
+
scenes.set(data.id, scene);
|
|
6676
|
+
if (scene.foreground?.fonts.length) {
|
|
6677
|
+
scene.loading = true;
|
|
6678
|
+
try {
|
|
6679
|
+
await Promise.all(scene.foreground.fonts.map((definition) => {
|
|
6680
|
+
const key = JSON.stringify(definition);
|
|
6681
|
+
if (!fonts.has(key)) fonts.set(key, new FontFace(definition.family, definition.src, definition.descriptors).load().then((face) => self.fonts.add(face)));
|
|
6682
|
+
return fonts.get(key);
|
|
6683
|
+
}));
|
|
6684
|
+
} catch {
|
|
6685
|
+
scenes.delete(data.id);
|
|
6686
|
+
postMessage({ id: data.id, failed: true });
|
|
6687
|
+
return;
|
|
6688
|
+
}
|
|
6689
|
+
if (scenes.get(data.id) !== scene) return;
|
|
6690
|
+
scene.loading = false;
|
|
6691
|
+
}
|
|
6692
|
+
draw(scene, performance.timeOrigin + performance.now());
|
|
6693
|
+
postMessage({ id: data.id, revision: data.revision });
|
|
6694
|
+
} else if (scene && data.type === "state") {
|
|
6695
|
+
const previous = scene.state;
|
|
6696
|
+
scene.beat = transition(scene.beat, data.state.beat, now, scene.duration);
|
|
6697
|
+
scene.state = { ...scene.state, ...data.state };
|
|
6698
|
+
updateTrail(scene);
|
|
6699
|
+
retargetVoice(scene, now, previous);
|
|
6700
|
+
retargetForeground(scene, now);
|
|
6701
|
+
} else if (scene && data.type === "keys") {
|
|
6702
|
+
scene.pressed = data.pressed;
|
|
6703
|
+
scene.hovered = data.hovered;
|
|
6704
|
+
retargetForeground(scene, now);
|
|
6705
|
+
} else if (scene && data.type === "visible") scene.visible = data.visible;
|
|
6706
|
+
if (frame === null && [...scenes.values()].some((scene2) => scene2.visible && !scene2.loading)) frame = requestAnimationFrame(tick);
|
|
6707
|
+
} catch {
|
|
6708
|
+
scenes.delete(data.id);
|
|
6709
|
+
postMessage({ id: data.id, failed: true });
|
|
6710
|
+
}
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
var thread = null;
|
|
6714
|
+
var url = null;
|
|
6715
|
+
var serial = 0;
|
|
6716
|
+
var unavailable = false;
|
|
6717
|
+
var clients = /* @__PURE__ */ new Map();
|
|
6718
|
+
function createMelodyGlow(root, initialState, onReady) {
|
|
6719
|
+
if (unavailable || typeof Worker === "undefined" || typeof OffscreenCanvas === "undefined" || typeof ResizeObserver === "undefined" || typeof IntersectionObserver === "undefined" || typeof MutationObserver === "undefined" || typeof HTMLCanvasElement === "undefined" || typeof matchMedia === "undefined" || !HTMLCanvasElement.prototype.transferControlToOffscreen) return null;
|
|
6720
|
+
try {
|
|
6721
|
+
if (!thread) {
|
|
6722
|
+
url = URL.createObjectURL(new Blob([`(${renderGlow.toString()})()`], { type: "text/javascript" }));
|
|
6723
|
+
thread = new Worker(url);
|
|
6724
|
+
thread.onmessage = ({ data }) => data.failed ? clients.get(data.id)?.dispose() : clients.get(data.id)?.ready(data);
|
|
6725
|
+
thread.onerror = () => {
|
|
6726
|
+
unavailable = true;
|
|
6727
|
+
for (const client of [...clients.values()]) client.dispose();
|
|
6728
|
+
};
|
|
6729
|
+
}
|
|
6730
|
+
} catch {
|
|
6731
|
+
unavailable = true;
|
|
6732
|
+
if (url) URL.revokeObjectURL(url);
|
|
6733
|
+
thread = url = null;
|
|
6734
|
+
return null;
|
|
6735
|
+
}
|
|
6736
|
+
const worker = thread, id = ++serial;
|
|
6737
|
+
const canvas = document.createElement("canvas");
|
|
6738
|
+
canvas.className = "ad-melody-glow";
|
|
6739
|
+
canvas.style.visibility = "hidden";
|
|
6740
|
+
root.append(canvas);
|
|
6741
|
+
const lane = root.querySelector(".ad-melody-roll-lane");
|
|
6742
|
+
const voiceCanvas = document.createElement("canvas");
|
|
6743
|
+
voiceCanvas.className = "ad-melody-voice-canvas";
|
|
6744
|
+
voiceCanvas.style.visibility = "hidden";
|
|
6745
|
+
lane.append(voiceCanvas);
|
|
6746
|
+
const trailCanvas = document.createElement("canvas");
|
|
6747
|
+
trailCanvas.className = "ad-melody-trail";
|
|
6748
|
+
trailCanvas.style.visibility = "hidden";
|
|
6749
|
+
lane.insertBefore(trailCanvas, lane.querySelector(".ad-melody-playhead"));
|
|
6750
|
+
const noteCanvas = document.createElement("canvas"), keyCanvas = document.createElement("canvas");
|
|
6751
|
+
noteCanvas.className = "ad-melody-note-canvas";
|
|
6752
|
+
keyCanvas.className = "ad-melody-key-canvas";
|
|
6753
|
+
noteCanvas.style.visibility = keyCanvas.style.visibility = "hidden";
|
|
6754
|
+
lane.insertBefore(noteCanvas, trailCanvas);
|
|
6755
|
+
const keyboard = root.querySelector(".ad-melody-roll-keys");
|
|
6756
|
+
keyboard.append(keyCanvas);
|
|
6757
|
+
for (const layer of [canvas, voiceCanvas, trailCanvas, noteCanvas, keyCanvas]) layer.setAttribute("aria-hidden", "true");
|
|
6758
|
+
let state = initialState, disposed = false, transferred = false, inView = true, revision = 0, signature = "";
|
|
6759
|
+
const visible = () => inView && !document.hidden && !root.closest("[data-ad-paused], [data-ad-offscreen]");
|
|
6760
|
+
const visibility = () => worker.postMessage({ type: "visible", id, visible: visible() });
|
|
6761
|
+
const resize = new ResizeObserver(configure);
|
|
6762
|
+
const intersection = new IntersectionObserver(([entry]) => {
|
|
6763
|
+
inView = entry.isIntersecting;
|
|
6764
|
+
visibility();
|
|
6765
|
+
});
|
|
6766
|
+
const tokens = new MutationObserver((records) => {
|
|
6767
|
+
visibility();
|
|
6768
|
+
if (records.some((record) => !["data-ad-paused", "data-ad-offscreen"].includes(record.attributeName))) configure();
|
|
6769
|
+
});
|
|
6770
|
+
for (let node = root; node; node = node.parentElement)
|
|
6771
|
+
tokens.observe(node, { attributes: true, attributeFilter: ["style", "class", "data-ad-theme", "data-ad-color-scheme", "data-ad-motion", "data-ad-paused", "data-ad-offscreen"] });
|
|
6772
|
+
resize.observe(root);
|
|
6773
|
+
resize.observe(lane);
|
|
6774
|
+
intersection.observe(root);
|
|
6775
|
+
document.addEventListener("visibilitychange", visibility);
|
|
6776
|
+
window.addEventListener("resize", configure);
|
|
6777
|
+
const reduced = matchMedia("(prefers-reduced-motion: reduce)");
|
|
6778
|
+
reduced.addEventListener("change", configure);
|
|
6779
|
+
document.fonts?.addEventListener("loadingdone", configure);
|
|
6780
|
+
const keysChanged = () => worker.postMessage({
|
|
6781
|
+
type: "keys",
|
|
6782
|
+
id,
|
|
6783
|
+
time: performance.timeOrigin + performance.now(),
|
|
6784
|
+
pressed: [...keyboard.querySelectorAll("[data-pressed]")].map((node) => Number(node.dataset.midi)),
|
|
6785
|
+
hovered: keyboard.querySelector("button:hover") ? Number(keyboard.querySelector("button:hover").dataset.midi) : void 0
|
|
6786
|
+
});
|
|
6787
|
+
const keyObserver = new MutationObserver((records) => {
|
|
6788
|
+
if (records.some((record) => record.type === "childList")) configure();
|
|
6789
|
+
keysChanged();
|
|
6790
|
+
});
|
|
6791
|
+
keyObserver.observe(keyboard, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-pressed"] });
|
|
6792
|
+
keyboard.addEventListener("pointerover", keysChanged);
|
|
6793
|
+
keyboard.addEventListener("pointerout", keysChanged);
|
|
6794
|
+
const split = (value) => value.split(/,\s*(?![^()]*\))/);
|
|
6795
|
+
const shadows = (value, text = false) => value === "none" ? [] : split(value).map((part2) => {
|
|
6796
|
+
const inset = part2.includes("inset");
|
|
6797
|
+
const match = part2.replace(/\s*inset\s*/, "").match(text ? /^(.*) (-?[\d.]+)px (-?[\d.]+)px ([\d.]+)px$/ : /^(.*) (-?[\d.]+)px (-?[\d.]+)px ([\d.]+)px (-?[\d.]+)px$/);
|
|
6798
|
+
if (!match) throw new Error("Unsupported foreground shadow");
|
|
6799
|
+
return { color: match[1], x: Number(match[2]), y: Number(match[3]), blur: Number(match[4]), spread: Number(match[5] ?? 0), inset };
|
|
6800
|
+
});
|
|
6801
|
+
const filters = (value) => {
|
|
6802
|
+
const result = [];
|
|
6803
|
+
while (value && value !== "none") {
|
|
6804
|
+
const kind = value.slice(0, value.indexOf("("));
|
|
6805
|
+
let depth = 1, end = kind.length + 1;
|
|
6806
|
+
for (; depth && end < value.length; end++) {
|
|
6807
|
+
if (value[end] === "(") depth++;
|
|
6808
|
+
if (value[end] === ")") depth--;
|
|
6809
|
+
}
|
|
6810
|
+
const argument = value.slice(kind.length + 1, end - 1);
|
|
6811
|
+
if (kind === "brightness") result.push({ kind, value: Number(argument) });
|
|
6812
|
+
else if (kind === "drop-shadow") result.push({ kind, ...shadows(argument, true)[0] });
|
|
6813
|
+
else throw new Error("Unsupported note filter");
|
|
6814
|
+
value = value.slice(end).trim();
|
|
6815
|
+
}
|
|
6816
|
+
return result;
|
|
6817
|
+
};
|
|
6818
|
+
const surface = (node) => {
|
|
6819
|
+
const style = getComputedStyle(node);
|
|
6820
|
+
if ([style.borderTopLeftRadius, style.borderTopRightRadius, style.borderBottomRightRadius, style.borderBottomLeftRadius].some((value) => !/^[\d.]+px$/.test(value)) || ["Top", "Right", "Bottom", "Left"].some((side) => !["none", "solid"].includes(style[`border${side}Style`]))) throw new Error("Unsupported foreground border");
|
|
6821
|
+
const gradient = style.backgroundImage === "none" ? [] : split(style.backgroundImage.slice(16, -1)).slice(1).map((stop) => {
|
|
6822
|
+
const match = stop.match(/^(.*?)(?: ([\d.]+)%)?$/);
|
|
6823
|
+
return { color: match[1], at: match[2] === void 0 ? void 0 : Number(match[2]) / 100 };
|
|
6824
|
+
});
|
|
6825
|
+
if (style.backgroundImage !== "none" && !style.backgroundImage.startsWith("linear-gradient(90deg,")) throw new Error("Unsupported note background");
|
|
6826
|
+
if (gradient.length) {
|
|
6827
|
+
gradient[0].at ??= 0;
|
|
6828
|
+
gradient.at(-1).at ??= 1;
|
|
6829
|
+
for (let i = 1; i < gradient.length - 1; i++) if (gradient[i].at === void 0) {
|
|
6830
|
+
let end = i;
|
|
6831
|
+
while (gradient[end].at === void 0) end++;
|
|
6832
|
+
const start = gradient[i - 1].at, count = end - i + 1;
|
|
6833
|
+
for (let j = i; j < end; j++) gradient[j].at = start + (gradient[end].at - start) * (j - i + 1) / count;
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
return {
|
|
6837
|
+
gradient,
|
|
6838
|
+
backgroundColor: style.backgroundColor,
|
|
6839
|
+
color: style.color,
|
|
6840
|
+
filter: filters(style.filter),
|
|
6841
|
+
shadows: shadows(style.boxShadow),
|
|
6842
|
+
radius: [style.borderTopLeftRadius, style.borderTopRightRadius, style.borderBottomRightRadius, style.borderBottomLeftRadius].map(parseFloat),
|
|
6843
|
+
borders: ["Top", "Right", "Bottom", "Left"].map((side) => ({ width: parseFloat(style[`border${side}Width`]), color: style[`border${side}Color`] }))
|
|
6844
|
+
};
|
|
6845
|
+
};
|
|
6846
|
+
const lettering = (node, right = false) => {
|
|
6847
|
+
const style = getComputedStyle(node);
|
|
6848
|
+
if (style.fontFeatureSettings !== "normal" || style.fontVariationSettings !== "normal" || style.fontVariant !== "normal" || style.textTransform !== "none" || style.direction !== "ltr" || !["normal", "100%"].includes(style.fontStretch)) throw new Error("Unsupported foreground typography");
|
|
6849
|
+
const parent = node.parentElement, parentStyle = getComputedStyle(parent);
|
|
6850
|
+
const marker = document.createElement("i");
|
|
6851
|
+
Object.assign(marker.style, { display: "inline-block", width: "0", height: "0", padding: "0", border: "0", margin: "0", verticalAlign: "baseline" });
|
|
6852
|
+
node.append(marker);
|
|
6853
|
+
const parentBox = parent.getBoundingClientRect(), labelBox = node.getBoundingClientRect();
|
|
6854
|
+
const baseline = marker.getBoundingClientRect().top - parentBox.top - parseFloat(parentStyle.borderTopWidth);
|
|
6855
|
+
const offset = labelBox.left - parentBox.left - parseFloat(parentStyle.borderLeftWidth);
|
|
6856
|
+
marker.remove();
|
|
6857
|
+
return {
|
|
6858
|
+
font: `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`,
|
|
6859
|
+
color: style.color,
|
|
6860
|
+
baseline,
|
|
6861
|
+
offset,
|
|
6862
|
+
letterSpacing: style.letterSpacing === "normal" ? "0px" : style.letterSpacing,
|
|
6863
|
+
padding: parseFloat(right ? getComputedStyle(node.parentElement).paddingRight : style.paddingLeft),
|
|
6864
|
+
textShadows: shadows(style.textShadow, true)
|
|
6865
|
+
};
|
|
6866
|
+
};
|
|
6867
|
+
function configure() {
|
|
6868
|
+
if (disposed || !root.clientWidth || !root.clientHeight) return;
|
|
6869
|
+
const temporary = [];
|
|
6870
|
+
try {
|
|
6871
|
+
const probe = document.createElement("div");
|
|
6872
|
+
temporary.push(probe);
|
|
6873
|
+
probe.className = root.className;
|
|
6874
|
+
probe.style.cssText = root.style.cssText;
|
|
6875
|
+
probe.style.setProperty("--ad-roll-beat", "1");
|
|
6876
|
+
Object.assign(probe.style, { position: "absolute", visibility: "hidden", pointerEvents: "none" });
|
|
6877
|
+
root.append(probe);
|
|
6878
|
+
const style = getComputedStyle(probe);
|
|
6879
|
+
const shadow = style.boxShadow.match(/^(.*) 0px 0px ([\d.]+)px 0px$/);
|
|
6880
|
+
const radius2 = [style.borderTopLeftRadius, style.borderTopRightRadius, style.borderBottomRightRadius, style.borderBottomLeftRadius];
|
|
6881
|
+
const duration = parseFloat(style.transitionDuration) * 1e3;
|
|
6882
|
+
const rootStyle = getComputedStyle(root);
|
|
6883
|
+
const width = parseFloat(rootStyle.width), height = parseFloat(rootStyle.height);
|
|
6884
|
+
probe.remove();
|
|
6885
|
+
if (!shadow || radius2.some((value) => !/^[\d.]+px$/.test(value)) || !Number.isFinite(width + height)) {
|
|
6886
|
+
dispose();
|
|
6887
|
+
return;
|
|
6888
|
+
}
|
|
6889
|
+
const blur = Number(shadow[2]), pad = Math.ceil(blur * 2);
|
|
6890
|
+
Object.assign(canvas.style, { left: `${-pad}px`, top: `${-pad}px`, width: `${width + pad * 2}px`, height: `${height + pad * 2}px` });
|
|
6891
|
+
const lightStyle = getComputedStyle(root.querySelector(".ad-melody-roll-light"));
|
|
6892
|
+
if (!lightStyle.backgroundImage.startsWith("radial-gradient(80% 120% at 25% 50%,")) throw new Error("Unsupported melody light");
|
|
6893
|
+
const lightColors = lightStyle.backgroundImage.slice(lightStyle.backgroundImage.indexOf(",") + 1, -1).split(/,\s*(?![^()]*\))/).map((color3) => color3.trim().replace(/\s+[\d.]+%$/, ""));
|
|
6894
|
+
const lightBorder = lightStyle.borderTopColor, lightWidth = parseFloat(lightStyle.borderTopWidth);
|
|
6895
|
+
const headStyle = getComputedStyle(lane.querySelector(".ad-melody-playhead-light"));
|
|
6896
|
+
const headShadows = headStyle.boxShadow.split(/,\s*(?![^()]*\))/).map((shadow2) => {
|
|
6897
|
+
const match = shadow2.match(/^(.*) 0px 0px ([\d.]+)px 0px$/);
|
|
6898
|
+
return { color: match[1], blur: Number(match[2]) };
|
|
6899
|
+
});
|
|
6900
|
+
const headWidth = parseFloat(getComputedStyle(lane.querySelector(".ad-melody-playhead")).width);
|
|
6901
|
+
const marker = document.createElement("span");
|
|
6902
|
+
temporary.push(marker);
|
|
6903
|
+
marker.className = "ad-melody-voice";
|
|
6904
|
+
marker.style.visibility = "hidden";
|
|
6905
|
+
lane.append(marker);
|
|
6906
|
+
const voiceDuration = parseFloat(getComputedStyle(marker).transitionDuration) * 1e3;
|
|
6907
|
+
marker.style.transition = "none";
|
|
6908
|
+
marker.setAttribute("data-hit", "");
|
|
6909
|
+
const hitStyle = getComputedStyle(marker);
|
|
6910
|
+
const hitColor = hitStyle.backgroundColor, hitDuration = parseFloat(hitStyle.animationDuration) * 1e3;
|
|
6911
|
+
marker.removeAttribute("data-hit");
|
|
6912
|
+
marker.style.animation = "none";
|
|
6913
|
+
const samples = [0, 1].map((value) => {
|
|
6914
|
+
marker.style.setProperty("--ad-roll-level", value);
|
|
6915
|
+
marker.style.setProperty("--ad-roll-accuracy", value);
|
|
6916
|
+
const computed = getComputedStyle(marker);
|
|
6917
|
+
if (computed.borderRadius !== "50%" || computed.width !== computed.height || computed.backgroundImage !== "none") throw new Error("Unsupported melody marker");
|
|
6918
|
+
const shadows2 = computed.boxShadow.split(/,\s*(?![^()]*\))/).map((shadow2) => {
|
|
6919
|
+
const match = shadow2.match(/^(.*) 0px 0px ([\d.]+)px ([\d.]+)px$/);
|
|
6920
|
+
if (!match) throw new Error("Unsupported melody shadow");
|
|
6921
|
+
return { color: match[1], blur: Number(match[2]), spread: Number(match[3]) };
|
|
6922
|
+
});
|
|
6923
|
+
return { size: parseFloat(computed.width), color: computed.backgroundColor, shadows: shadows2, border: parseFloat(computed.borderTopWidth), borderColor: computed.borderTopColor };
|
|
6924
|
+
});
|
|
6925
|
+
marker.remove();
|
|
6926
|
+
const voiceStyle = { size: samples.map((sample) => sample.size), color: samples.map((sample) => sample.color), shadows: samples.map((sample) => sample.shadows), border: samples[0].border, borderColor: samples[0].borderColor, hitColor, hitDuration, duration: voiceDuration };
|
|
6927
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
6928
|
+
temporary.push(svg);
|
|
6929
|
+
svg.setAttribute("class", "ad-melody-trail");
|
|
6930
|
+
svg.style.visibility = "hidden";
|
|
6931
|
+
lane.append(svg);
|
|
6932
|
+
const trailStyle = ["wide", "near", ""].map((halo) => {
|
|
6933
|
+
const path = document.createElementNS(svg.namespaceURI, "path");
|
|
6934
|
+
if (halo) path.setAttribute("data-halo", halo);
|
|
6935
|
+
svg.append(path);
|
|
6936
|
+
const color3 = [0, 1].map((value) => {
|
|
6937
|
+
svg.style.setProperty("--ad-roll-accuracy", value);
|
|
6938
|
+
return getComputedStyle(path).stroke;
|
|
6939
|
+
});
|
|
6940
|
+
const computed = getComputedStyle(path);
|
|
6941
|
+
return { color: color3, width: parseFloat(computed.strokeWidth), opacity: Number(computed.opacity) };
|
|
6942
|
+
});
|
|
6943
|
+
svg.remove();
|
|
6944
|
+
const noteProbe = document.createElement("span");
|
|
6945
|
+
temporary.push(noteProbe);
|
|
6946
|
+
noteProbe.className = "ad-melody-note";
|
|
6947
|
+
noteProbe.dataset.renderProbe = "";
|
|
6948
|
+
noteProbe.style.width = "100px";
|
|
6949
|
+
noteProbe.style.visibility = "hidden";
|
|
6950
|
+
const label = document.createElement("span");
|
|
6951
|
+
label.textContent = "M";
|
|
6952
|
+
noteProbe.append(label);
|
|
6953
|
+
root.querySelector(".ad-melody-roll-strip").append(noteProbe);
|
|
6954
|
+
const noteDurations = getComputedStyle(noteProbe).transitionDuration.split(",").map((value) => parseFloat(value) * 1e3);
|
|
6955
|
+
noteProbe.style.transition = "none";
|
|
6956
|
+
noteProbe.style.animation = "none";
|
|
6957
|
+
const noteStyle = { base: surface(noteProbe), height: parseFloat(getComputedStyle(noteProbe).height), text: lettering(label), filterDuration: noteDurations[0], opacityDuration: noteDurations[1] ?? noteDurations[0] };
|
|
6958
|
+
noteProbe.dataset.now = "";
|
|
6959
|
+
noteStyle.current = surface(noteProbe);
|
|
6960
|
+
delete noteProbe.dataset.now;
|
|
6961
|
+
noteProbe.dataset.past = "";
|
|
6962
|
+
noteStyle.pastOpacity = Number(getComputedStyle(noteProbe).opacity);
|
|
6963
|
+
delete noteProbe.dataset.past;
|
|
6964
|
+
noteProbe.dataset.hit = "";
|
|
6965
|
+
noteStyle.hit = surface(noteProbe);
|
|
6966
|
+
noteProbe.style.animation = "";
|
|
6967
|
+
noteStyle.hitDuration = parseFloat(getComputedStyle(noteProbe).animationDuration) * 1e3;
|
|
6968
|
+
const noteAnimation = noteProbe.getAnimations().find((animation2) => animation2.animationName === "ad-melody-hit");
|
|
6969
|
+
if (noteAnimation) {
|
|
6970
|
+
noteAnimation.pause();
|
|
6971
|
+
noteAnimation.currentTime = noteStyle.hitDuration * 0.28;
|
|
6972
|
+
}
|
|
6973
|
+
noteStyle.peakFilter = filters(getComputedStyle(noteProbe).filter);
|
|
6974
|
+
noteStyle.filterPad = Math.ceil(Math.max(0, ...[noteStyle.base.filter, noteStyle.current.filter, noteStyle.hit.filter, noteStyle.peakFilter].map((filter) => filter.reduce((pad2, part2) => pad2 + (part2.kind === "drop-shadow" ? Math.max(Math.abs(part2.x), Math.abs(part2.y)) + part2.blur * 3 : 0), 0))));
|
|
6975
|
+
noteProbe.remove();
|
|
6976
|
+
const board = keyboard.cloneNode(true);
|
|
6977
|
+
temporary.push(board);
|
|
6978
|
+
board.dataset.renderProbe = "";
|
|
6979
|
+
for (const layer of board.querySelectorAll("canvas")) layer.remove();
|
|
6980
|
+
Object.assign(board.style, { position: "absolute", width: `${keyboard.clientWidth}px`, height: `${keyboard.clientHeight}px`, visibility: "hidden", pointerEvents: "none" });
|
|
6981
|
+
root.append(board);
|
|
6982
|
+
const boardTop = board.getBoundingClientRect().top;
|
|
6983
|
+
const keys = [...board.querySelectorAll(".ad-piano-key")].map((node) => {
|
|
6984
|
+
delete node.dataset.active;
|
|
6985
|
+
delete node.dataset.hit;
|
|
6986
|
+
delete node.dataset.pressed;
|
|
6987
|
+
const css = getComputedStyle(node), durations = css.transitionDuration.split(",").map((value) => parseFloat(value) * 1e3);
|
|
6988
|
+
node.style.transition = "none";
|
|
6989
|
+
node.style.animation = "none";
|
|
6990
|
+
const bounds = node.getBoundingClientRect();
|
|
6991
|
+
const key = { midi: Number(node.dataset.midi), label: node.textContent, top: bounds.top - boardTop, width: bounds.width, height: bounds.height, text: lettering(node.querySelector("span") ?? node, true), duration: durations[0], pressDuration: durations[3] ?? durations[0] };
|
|
6992
|
+
key.base = surface(node);
|
|
6993
|
+
key.text.color = key.base.color;
|
|
6994
|
+
node.dataset.pressed = "";
|
|
6995
|
+
key.pressed = surface(node);
|
|
6996
|
+
key.pressShift = parseFloat(getComputedStyle(node).translate);
|
|
6997
|
+
delete node.dataset.pressed;
|
|
6998
|
+
node.dataset.active = "";
|
|
6999
|
+
key.active = surface(node);
|
|
7000
|
+
node.dataset.hit = "";
|
|
7001
|
+
key.hit = surface(node);
|
|
7002
|
+
node.style.animation = "";
|
|
7003
|
+
const hitCSS = getComputedStyle(node);
|
|
7004
|
+
key.hitDuration = parseFloat(hitCSS.animationDuration) * 1e3;
|
|
7005
|
+
key.hitIterations = hitCSS.animationIterationCount === "infinite" ? Infinity : Number(hitCSS.animationIterationCount);
|
|
7006
|
+
node.style.animation = "none";
|
|
7007
|
+
key.pad = Math.ceil(Math.max(1, ...[key.base, key.pressed, key.active, key.hit].flatMap((style2) => style2.shadows.map((shadow2) => Math.max(Math.abs(shadow2.x), Math.abs(shadow2.y)) + shadow2.blur * 2 + Math.abs(shadow2.spread)))));
|
|
7008
|
+
return key;
|
|
7009
|
+
});
|
|
7010
|
+
board.remove();
|
|
7011
|
+
const fonts = [];
|
|
7012
|
+
const fontNames = [noteStyle.text.font, ...keys.map((key) => key.text.font)].join(",");
|
|
7013
|
+
const readFonts = (sheet) => {
|
|
7014
|
+
let rules;
|
|
7015
|
+
try {
|
|
7016
|
+
rules = sheet.cssRules;
|
|
7017
|
+
} catch {
|
|
7018
|
+
return;
|
|
7019
|
+
}
|
|
7020
|
+
for (const rule of rules) {
|
|
7021
|
+
if (rule.styleSheet) readFonts(rule.styleSheet);
|
|
7022
|
+
if (rule.type !== 5) continue;
|
|
7023
|
+
const family = rule.style.fontFamily.replaceAll('"', "").replaceAll("'", "");
|
|
7024
|
+
if (!fontNames.includes(family)) continue;
|
|
7025
|
+
fonts.push({
|
|
7026
|
+
family,
|
|
7027
|
+
src: rule.style.getPropertyValue("src").replace(/url\(["']?([^"')]+)["']?\)/g, (_, url2) => `url("${new URL(url2, sheet.href ?? document.baseURI)}")`),
|
|
7028
|
+
descriptors: { ...Object.fromEntries(["weight", "style", "stretch"].map((key) => [key, rule.style.getPropertyValue(`font-${key}`) || "normal"])), unicodeRange: rule.style.getPropertyValue("unicode-range") || "U+0-10FFFF" }
|
|
7029
|
+
});
|
|
7030
|
+
}
|
|
7031
|
+
};
|
|
7032
|
+
for (const sheet of document.styleSheets) readFonts(sheet);
|
|
7033
|
+
for (const face of document.fonts ?? []) if (fontNames.includes(face.family.replaceAll('"', "").replaceAll("'", "")) && !fonts.some((font) => font.family === face.family.replaceAll('"', "").replaceAll("'", ""))) throw new Error("Font source is unavailable to the worker");
|
|
7034
|
+
const gridCSS = getComputedStyle(lane.querySelector(".ad-melody-roll-seconds")).backgroundImage;
|
|
7035
|
+
const gridStop = split(gridCSS.slice(16, -1))[1].match(/^(.*) ([\d.]+)px$/);
|
|
7036
|
+
if (!gridStop) throw new Error("Unsupported melody grid");
|
|
7037
|
+
const foreground = { note: noteStyle, keys, fonts, grid: { color: gridStop[1].trim(), width: Number(gridStop[2]) }, keyWidth: keyboard.clientWidth, keyHeight: keyboard.clientHeight };
|
|
7038
|
+
const config = { type: "configure", id, width, height, pad, blur, color: shadow[1], radius: radius2.map(parseFloat), dpr: devicePixelRatio || 1, duration, voiceStyle, trailStyle, lightColors, lightBorder, lightWidth, headShadows, headWidth, foreground, laneWidth: lane.clientWidth, laneHeight: lane.clientHeight };
|
|
7039
|
+
const nextSignature = JSON.stringify(config);
|
|
7040
|
+
if (nextSignature === signature) return;
|
|
7041
|
+
signature = nextSignature;
|
|
7042
|
+
const time = performance.timeOrigin + performance.now();
|
|
7043
|
+
const started = (node, name, duration2) => time - Number(node?.getAnimations().find((animation2) => animation2.animationName === name)?.currentTime ?? duration2);
|
|
7044
|
+
const noteTimes = !transferred ? Object.fromEntries([...root.querySelectorAll(".ad-melody-note")].map((node, i) => [state.notes?.[i]?.id, started(node, "ad-melody-hit", noteStyle.hitDuration)])) : void 0;
|
|
7045
|
+
const keyTimes = !transferred ? Object.fromEntries([...keyboard.querySelectorAll(".ad-piano-key")].map((node) => [node.dataset.midi, started(node, "ad-piano-key-hit", 0)])) : void 0;
|
|
7046
|
+
const animation = !transferred && state.hit ? lane.querySelector(".ad-melody-voice")?.getAnimations().find((animation2) => animation2.animationName === "ad-melody-voice-hit") : void 0;
|
|
7047
|
+
const message = {
|
|
7048
|
+
...config,
|
|
7049
|
+
state,
|
|
7050
|
+
visible: visible(),
|
|
7051
|
+
revision: ++revision,
|
|
7052
|
+
time,
|
|
7053
|
+
noteTimes,
|
|
7054
|
+
keyTimes,
|
|
7055
|
+
hitStartedAt: !transferred && state.hit ? time - Number(animation?.currentTime ?? hitDuration) : void 0
|
|
7056
|
+
};
|
|
7057
|
+
if (!transferred) {
|
|
7058
|
+
const offscreen = canvas.transferControlToOffscreen();
|
|
7059
|
+
const voiceOffscreen = voiceCanvas.transferControlToOffscreen();
|
|
7060
|
+
const trailOffscreen = trailCanvas.transferControlToOffscreen();
|
|
7061
|
+
const noteOffscreen = noteCanvas.transferControlToOffscreen(), keyOffscreen = keyCanvas.transferControlToOffscreen();
|
|
7062
|
+
worker.postMessage({ ...message, canvas: offscreen, voiceCanvas: voiceOffscreen, trailCanvas: trailOffscreen, noteCanvas: noteOffscreen, keyCanvas: keyOffscreen }, [offscreen, voiceOffscreen, trailOffscreen, noteOffscreen, keyOffscreen]);
|
|
7063
|
+
transferred = true;
|
|
7064
|
+
} else worker.postMessage(message);
|
|
7065
|
+
} catch {
|
|
7066
|
+
dispose();
|
|
7067
|
+
} finally {
|
|
7068
|
+
temporary.forEach((node) => node.remove());
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
function dispose() {
|
|
7072
|
+
if (disposed) return;
|
|
7073
|
+
disposed = true;
|
|
7074
|
+
resize.disconnect();
|
|
7075
|
+
intersection.disconnect();
|
|
7076
|
+
tokens.disconnect();
|
|
7077
|
+
keyObserver.disconnect();
|
|
7078
|
+
keyboard.removeEventListener("pointerover", keysChanged);
|
|
7079
|
+
keyboard.removeEventListener("pointerout", keysChanged);
|
|
7080
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
7081
|
+
window.removeEventListener("resize", configure);
|
|
7082
|
+
reduced.removeEventListener("change", configure);
|
|
7083
|
+
document.fonts?.removeEventListener("loadingdone", configure);
|
|
7084
|
+
root.removeAttribute("data-ad-glow");
|
|
7085
|
+
root.removeAttribute("data-ad-foreground");
|
|
7086
|
+
onReady(false);
|
|
7087
|
+
canvas.remove();
|
|
7088
|
+
voiceCanvas.remove();
|
|
7089
|
+
trailCanvas.remove();
|
|
7090
|
+
noteCanvas.remove();
|
|
7091
|
+
keyCanvas.remove();
|
|
7092
|
+
worker.postMessage({ type: "remove", id });
|
|
7093
|
+
clients.delete(id);
|
|
7094
|
+
if (!clients.size) {
|
|
7095
|
+
worker.terminate();
|
|
7096
|
+
URL.revokeObjectURL(url);
|
|
7097
|
+
thread = url = null;
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7100
|
+
const renderer = {
|
|
7101
|
+
update(next) {
|
|
7102
|
+
if (!disposed) {
|
|
7103
|
+
const sent = { ...next };
|
|
7104
|
+
if (next.notes === state.notes) delete sent.notes;
|
|
7105
|
+
if (next.hits === state.hits) delete sent.hits;
|
|
7106
|
+
state = next;
|
|
7107
|
+
worker.postMessage({ type: "state", id, state: sent, time: performance.timeOrigin + performance.now() });
|
|
7108
|
+
}
|
|
7109
|
+
},
|
|
7110
|
+
ready(data) {
|
|
7111
|
+
if (!disposed && data.revision === revision) {
|
|
7112
|
+
canvas.style.visibility = voiceCanvas.style.visibility = trailCanvas.style.visibility = noteCanvas.style.visibility = keyCanvas.style.visibility = "";
|
|
7113
|
+
root.setAttribute("data-ad-glow", "");
|
|
7114
|
+
root.setAttribute("data-ad-foreground", "");
|
|
7115
|
+
onReady(true);
|
|
7116
|
+
}
|
|
7117
|
+
},
|
|
7118
|
+
dispose
|
|
7119
|
+
};
|
|
7120
|
+
clients.set(id, renderer);
|
|
7121
|
+
configure();
|
|
7122
|
+
return renderer;
|
|
7123
|
+
}
|
|
7124
|
+
|
|
7125
|
+
// src/components/media/MelodyRoll/MelodyRoll.tsx
|
|
6151
7126
|
import { jsx as jsx50, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
6152
7127
|
var DEMO2 = [
|
|
6153
7128
|
{ id: "1", start: 0.4, end: 1.1, pitch: 64, lyric: "\u041D\u043E\u0447\u044C" },
|
|
@@ -6181,6 +7156,9 @@ var MelodyRoll = ({
|
|
|
6181
7156
|
...p
|
|
6182
7157
|
}) => {
|
|
6183
7158
|
const tr2 = useTr();
|
|
7159
|
+
const root = useRef28(null);
|
|
7160
|
+
const glow3 = useRef28(null);
|
|
7161
|
+
const [glowReady, setGlowReady] = useState13(false);
|
|
6184
7162
|
const [span, setSpan] = useControllable(windowProp, defaultWindow, onWindowChange);
|
|
6185
7163
|
const indexed = useMemo5(() => {
|
|
6186
7164
|
let low2 = 60, high2 = 64;
|
|
@@ -6204,14 +7182,52 @@ var MelodyRoll = ({
|
|
|
6204
7182
|
const from = position - span * lead;
|
|
6205
7183
|
const origin = Math.floor(from / span) * span;
|
|
6206
7184
|
const x = (seconds) => (seconds - from) / span * 100;
|
|
6207
|
-
const
|
|
6208
|
-
const
|
|
7185
|
+
const rangeStart = glowReady ? origin : from;
|
|
7186
|
+
const rangeEnd = glowReady ? origin + span * 2 : from + span;
|
|
7187
|
+
const visible = useMemo5(() => indexed.blocks.filter((block) => block.end >= rangeStart && block.start <= rangeEnd).flatMap((block) => block.notes.filter((note) => note.end >= rangeStart && note.start <= rangeEnd)), [indexed, rangeStart, rangeEnd]);
|
|
7188
|
+
const phases = glowReady ? "" : visible.map((note) => note.end < from || note.start > from + span ? "x" : position >= note.start && position <= note.end ? "n" : position > note.end ? "p" : "-").join("");
|
|
7189
|
+
const hits = hitIds?.size ? visible.map((note) => hitIds.has(note.id) ? "h" : "-").join("") : "";
|
|
7190
|
+
const glowState = { beat: clamp(beat, 0, 1), pitch: livePitch, high, rows, lead, accuracy: clamp(accuracy, 0, 1), level: clamp(level, 0, 1), hit, position, span, notes: visible, hits, showLyrics };
|
|
7191
|
+
useEffect14(() => {
|
|
7192
|
+
if (!root.current) return;
|
|
7193
|
+
glow3.current = createMelodyGlow(root.current, { ...glowState, trail: trail.current }, setGlowReady);
|
|
7194
|
+
return () => {
|
|
7195
|
+
glow3.current?.dispose();
|
|
7196
|
+
glow3.current = null;
|
|
7197
|
+
};
|
|
7198
|
+
}, []);
|
|
7199
|
+
useLayoutEffect5(() => {
|
|
7200
|
+
glow3.current?.update(glowState);
|
|
7201
|
+
}, [beat, livePitch, high, rows, lead, accuracy, level, hit, position, span, visible, hits, showLyrics]);
|
|
7202
|
+
const nativeHits = glowReady ? "" : hits;
|
|
7203
|
+
const noteElements = useMemo5(() => glowReady ? void 0 : visible.map((note, i) => {
|
|
7204
|
+
const hidden = phases[i] === "x";
|
|
7205
|
+
return /* @__PURE__ */ jsx50(
|
|
7206
|
+
"span",
|
|
7207
|
+
{
|
|
7208
|
+
className: "ad-melody-note",
|
|
7209
|
+
"data-hit": !hidden && nativeHits[i] === "h" || void 0,
|
|
7210
|
+
"data-now": phases[i] === "n" || void 0,
|
|
7211
|
+
"data-past": phases[i] === "p" || void 0,
|
|
7212
|
+
style: {
|
|
7213
|
+
visibility: hidden ? "hidden" : void 0,
|
|
7214
|
+
left: `${(note.start - origin) / span * 100}%`,
|
|
7215
|
+
width: `${Math.max(0.6, (note.end - note.start) / span * 100)}%`,
|
|
7216
|
+
top: `${(high - note.pitch) / rows * 100}%`
|
|
7217
|
+
},
|
|
7218
|
+
children: showLyrics && note.lyric && /* @__PURE__ */ jsx50("span", { children: note.lyric })
|
|
7219
|
+
},
|
|
7220
|
+
note.id
|
|
7221
|
+
);
|
|
7222
|
+
}), [glowReady, visible, phases, nativeHits, showLyrics, origin, span, high, rows]);
|
|
6209
7223
|
const trail = useRef28([]);
|
|
6210
7224
|
if (trail.current.at(-1)?.t !== position) {
|
|
6211
|
-
|
|
6212
|
-
trail.current
|
|
7225
|
+
if (position < (trail.current.at(-1)?.t ?? position)) trail.current = trail.current.filter((point) => point.t < position);
|
|
7226
|
+
const expired = trail.current.findIndex((point) => point.t > position - TRAIL);
|
|
7227
|
+
trail.current.splice(0, expired < 0 ? trail.current.length : expired);
|
|
7228
|
+
trail.current.push({ t: position, pitch: livePitch });
|
|
6213
7229
|
}
|
|
6214
|
-
const segments = trail.current.reduce((paths, point, i, all) => {
|
|
7230
|
+
const segments = glowReady ? [] : trail.current.reduce((paths, point, i, all) => {
|
|
6215
7231
|
if (point.pitch === void 0) return paths;
|
|
6216
7232
|
const joined = i > 0 && all[i - 1]?.pitch !== void 0;
|
|
6217
7233
|
const at = `${x(point.t).toFixed(2)},${((high - point.pitch + 0.5) / rows * 100).toFixed(2)}`;
|
|
@@ -6223,65 +7239,56 @@ var MelodyRoll = ({
|
|
|
6223
7239
|
if (!event.deltaY) return;
|
|
6224
7240
|
setSpan(clamp(span * (event.deltaY > 0 ? 1.12 : 1 / 1.12), 2, 20));
|
|
6225
7241
|
};
|
|
7242
|
+
const nativeState = glowReady ? null : glowState;
|
|
7243
|
+
const content = useMemo5(() => /* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-content", children: [
|
|
7244
|
+
/* @__PURE__ */ jsx50(
|
|
7245
|
+
PianoKeyboard,
|
|
7246
|
+
{
|
|
7247
|
+
className: "ad-melody-roll-keys",
|
|
7248
|
+
minMidi: low,
|
|
7249
|
+
maxMidi: high,
|
|
7250
|
+
activeMidi: glowReady || livePitch === void 0 ? void 0 : Math.round(livePitch),
|
|
7251
|
+
hit: !glowReady && hit,
|
|
7252
|
+
labels: rows > 30 ? "octaves" : "all",
|
|
7253
|
+
onKeyPress
|
|
7254
|
+
}
|
|
7255
|
+
),
|
|
7256
|
+
/* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-lane", "aria-hidden": "true", children: [
|
|
7257
|
+
/* @__PURE__ */ jsx50("span", { className: "ad-melody-roll-seconds", style: glowReady ? void 0 : { "--ad-roll-shift": `${-((from % 1 + 1) % 1) / span * 100}cqw` } }),
|
|
7258
|
+
/* @__PURE__ */ jsx50("div", { className: "ad-melody-roll-strip", style: glowReady ? void 0 : { transform: `translateX(${((origin - from) / span * 100).toFixed(3)}%)` }, children: noteElements }),
|
|
7259
|
+
!glowReady && /* @__PURE__ */ jsx50("svg", { className: "ad-melody-trail", viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { "--ad-roll-accuracy": clamp(accuracy, 0, 1) }, children: segments.flatMap((d, i) => [
|
|
7260
|
+
/* @__PURE__ */ jsx50("path", { "data-halo": "wide", d, vectorEffect: "non-scaling-stroke" }, `${i}-wide`),
|
|
7261
|
+
/* @__PURE__ */ jsx50("path", { "data-halo": "near", d, vectorEffect: "non-scaling-stroke" }, `${i}-near`),
|
|
7262
|
+
/* @__PURE__ */ jsx50("path", { d, vectorEffect: "non-scaling-stroke" }, i)
|
|
7263
|
+
]) }),
|
|
7264
|
+
/* @__PURE__ */ jsx50("span", { className: "ad-melody-playhead", style: { left: `${lead * 100}%` }, children: /* @__PURE__ */ jsx50("span", { className: "ad-melody-playhead-light", style: { opacity: glowReady ? 0 : clamp(beat, 0, 1) } }) }),
|
|
7265
|
+
!glowReady && livePitch !== void 0 && /* @__PURE__ */ jsx50(
|
|
7266
|
+
"span",
|
|
7267
|
+
{
|
|
7268
|
+
className: "ad-melody-voice",
|
|
7269
|
+
"data-hit": hit || void 0,
|
|
7270
|
+
style: { left: `${lead * 100}%`, top: `${(high - livePitch + 0.5) / rows * 100}%`, "--ad-roll-level": clamp(level, 0, 1), "--ad-roll-accuracy": clamp(accuracy, 0, 1) }
|
|
7271
|
+
}
|
|
7272
|
+
)
|
|
7273
|
+
] })
|
|
7274
|
+
] }), [glowReady, noteElements, low, high, rows, onKeyPress, lead, nativeState]);
|
|
6226
7275
|
return /* @__PURE__ */ jsxs41(
|
|
6227
7276
|
"div",
|
|
6228
7277
|
{
|
|
6229
7278
|
...mark("MelodyRoll", p),
|
|
7279
|
+
ref: root,
|
|
6230
7280
|
role: "img",
|
|
6231
7281
|
"aria-label": label ?? tr2("\u041C\u0435\u043B\u043E\u0434\u0438\u044F"),
|
|
6232
7282
|
onWheel: zoom,
|
|
6233
7283
|
style: {
|
|
6234
7284
|
...p.style,
|
|
6235
7285
|
"--ad-roll-rows": rows,
|
|
6236
|
-
"--ad-roll-beat": clamp(beat, 0, 1),
|
|
7286
|
+
"--ad-roll-beat": glowReady ? 0 : clamp(beat, 0, 1),
|
|
6237
7287
|
"--ad-roll-second": `${100 / span}cqw`
|
|
6238
7288
|
},
|
|
6239
7289
|
children: [
|
|
6240
|
-
/* @__PURE__ */ jsx50(
|
|
6241
|
-
|
|
6242
|
-
{
|
|
6243
|
-
className: "ad-melody-roll-keys",
|
|
6244
|
-
minMidi: low,
|
|
6245
|
-
maxMidi: high,
|
|
6246
|
-
activeMidi: livePitch === void 0 ? void 0 : Math.round(livePitch),
|
|
6247
|
-
hit,
|
|
6248
|
-
labels: rows > 30 ? "octaves" : "all",
|
|
6249
|
-
onKeyPress
|
|
6250
|
-
}
|
|
6251
|
-
),
|
|
6252
|
-
/* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-lane", "aria-hidden": "true", children: [
|
|
6253
|
-
/* @__PURE__ */ jsx50("span", { className: "ad-melody-roll-seconds", style: { "--ad-roll-shift": `${-((from % 1 + 1) % 1) / span * 100}cqw` } }),
|
|
6254
|
-
/* @__PURE__ */ jsx50("div", { className: "ad-melody-roll-strip", style: { transform: `translateX(${((origin - from) / span * 100).toFixed(3)}%)` }, children: visible.map((note) => /* @__PURE__ */ jsx50(
|
|
6255
|
-
"span",
|
|
6256
|
-
{
|
|
6257
|
-
className: "ad-melody-note",
|
|
6258
|
-
"data-hit": hitIds?.has(note.id) || void 0,
|
|
6259
|
-
"data-now": position >= note.start && position <= note.end || void 0,
|
|
6260
|
-
"data-past": position > note.end || void 0,
|
|
6261
|
-
style: {
|
|
6262
|
-
left: `${(note.start - origin) / span * 100}%`,
|
|
6263
|
-
width: `${Math.max(0.6, (note.end - note.start) / span * 100)}%`,
|
|
6264
|
-
top: `${y(note.pitch)}%`
|
|
6265
|
-
},
|
|
6266
|
-
children: showLyrics && note.lyric && /* @__PURE__ */ jsx50("span", { children: note.lyric })
|
|
6267
|
-
},
|
|
6268
|
-
note.id
|
|
6269
|
-
)) }),
|
|
6270
|
-
/* @__PURE__ */ jsx50("svg", { className: "ad-melody-trail", viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { "--ad-roll-accuracy": clamp(accuracy, 0, 1) }, children: segments.flatMap((d, i) => [
|
|
6271
|
-
/* @__PURE__ */ jsx50("path", { "data-halo": "wide", d, vectorEffect: "non-scaling-stroke" }, `${i}-wide`),
|
|
6272
|
-
/* @__PURE__ */ jsx50("path", { "data-halo": "near", d, vectorEffect: "non-scaling-stroke" }, `${i}-near`),
|
|
6273
|
-
/* @__PURE__ */ jsx50("path", { d, vectorEffect: "non-scaling-stroke" }, i)
|
|
6274
|
-
]) }),
|
|
6275
|
-
/* @__PURE__ */ jsx50("span", { className: "ad-melody-playhead", style: { left: `${lead * 100}%`, "--ad-roll-beat": clamp(beat, 0, 1) } }),
|
|
6276
|
-
livePitch !== void 0 && /* @__PURE__ */ jsx50(
|
|
6277
|
-
"span",
|
|
6278
|
-
{
|
|
6279
|
-
className: "ad-melody-voice",
|
|
6280
|
-
"data-hit": hit || void 0,
|
|
6281
|
-
style: { left: `${lead * 100}%`, top: `${(high - livePitch + 0.5) / rows * 100}%`, "--ad-roll-level": clamp(level, 0, 1), "--ad-roll-accuracy": clamp(accuracy, 0, 1) }
|
|
6282
|
-
}
|
|
6283
|
-
)
|
|
6284
|
-
] })
|
|
7290
|
+
/* @__PURE__ */ jsx50("span", { className: "ad-melody-roll-light", "aria-hidden": "true", style: { opacity: glowReady ? 0 : clamp(beat, 0, 1) } }),
|
|
7291
|
+
content
|
|
6285
7292
|
]
|
|
6286
7293
|
}
|
|
6287
7294
|
);
|
|
@@ -6291,24 +7298,24 @@ var MelodyRoll = ({
|
|
|
6291
7298
|
import { useRef as useRef30 } from "react";
|
|
6292
7299
|
|
|
6293
7300
|
// src/components/layout/FloatingPanel/useFloatingPanel.ts
|
|
6294
|
-
import { useCallback as useCallback2, useEffect as
|
|
7301
|
+
import { useCallback as useCallback2, useEffect as useEffect15, useLayoutEffect as useLayoutEffect6, useRef as useRef29, useState as useState14 } from "react";
|
|
6295
7302
|
var resizeEdges = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
6296
7303
|
var threshold = 3;
|
|
6297
7304
|
var panelControls = "button, a, input, select, textarea, [role=slider], [role=button], [role=switch], [role=listbox], [contenteditable=true], .ad-rotary-knob";
|
|
6298
7305
|
var useFloatingPanel = (frameRef, options = {}) => {
|
|
6299
|
-
const [active, setActive] =
|
|
6300
|
-
const [live, setLive] =
|
|
7306
|
+
const [active, setActive] = useState14(false);
|
|
7307
|
+
const [live, setLive] = useState14(null);
|
|
6301
7308
|
const drag = useRef29(null);
|
|
6302
7309
|
const moved = useRef29(false);
|
|
6303
7310
|
const latest = useRef29(options);
|
|
6304
7311
|
latest.current = options;
|
|
6305
7312
|
const saved = options.layout;
|
|
6306
|
-
|
|
7313
|
+
useLayoutEffect6(() => {
|
|
6307
7314
|
drag.current = null;
|
|
6308
7315
|
setLive(null);
|
|
6309
7316
|
}, [saved?.left, saved?.top, saved?.width, saved?.height]);
|
|
6310
|
-
const [, setViewport] =
|
|
6311
|
-
|
|
7317
|
+
const [, setViewport] = useState14(0);
|
|
7318
|
+
useEffect15(() => {
|
|
6312
7319
|
const onResize = () => setViewport((n) => n + 1);
|
|
6313
7320
|
window.addEventListener("resize", onResize);
|
|
6314
7321
|
onResize();
|
|
@@ -6418,7 +7425,7 @@ var useFloatingPanel = (frameRef, options = {}) => {
|
|
|
6418
7425
|
latest.current.onLayoutChange?.(next);
|
|
6419
7426
|
};
|
|
6420
7427
|
const deactivate = useCallback2(() => setActive(false), []);
|
|
6421
|
-
|
|
7428
|
+
useEffect15(() => {
|
|
6422
7429
|
if (!active) return;
|
|
6423
7430
|
const onPointerDown = (event) => {
|
|
6424
7431
|
if (!(event.target instanceof Node) || !frameRef.current?.contains(event.target)) deactivate();
|