@signalsandsorcery/plugin-sdk 2.28.1 → 2.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +234 -3
- package/dist/index.d.ts +234 -3
- package/dist/index.js +831 -168
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +815 -169
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1343,6 +1343,7 @@ var LevelMeter = ({
|
|
|
1343
1343
|
|
|
1344
1344
|
// src/hooks/useTrackLevels.ts
|
|
1345
1345
|
import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
|
|
1346
|
+
var meterDiagRLast = /* @__PURE__ */ new Map();
|
|
1346
1347
|
var POLL_INTERVAL_MS = 33;
|
|
1347
1348
|
var HIDDEN_RECHECK_MS = 250;
|
|
1348
1349
|
var METER_FLOOR_DB = -120;
|
|
@@ -1474,6 +1475,11 @@ function useTrackMeter(handle, trackId) {
|
|
|
1474
1475
|
}
|
|
1475
1476
|
const update = () => {
|
|
1476
1477
|
const level = handle.getLevel(trackId);
|
|
1478
|
+
const dNow = Date.now();
|
|
1479
|
+
if ((meterDiagRLast.get(trackId) ?? 0) < dNow - 3e3) {
|
|
1480
|
+
meterDiagRLast.set(trackId, dNow);
|
|
1481
|
+
console.log(`[MeterDiagR] lookup trackId=${trackId} \u2192 ${level === null ? "MISS (no level for this id)" : "hit"}`);
|
|
1482
|
+
}
|
|
1477
1483
|
const now = performance.now();
|
|
1478
1484
|
const dtSec = lastTickRef.current ? Math.max(0, (now - lastTickRef.current) / 1e3) : 0;
|
|
1479
1485
|
lastTickRef.current = now;
|
|
@@ -2175,8 +2181,7 @@ function TrackRow({
|
|
|
2175
2181
|
{
|
|
2176
2182
|
"data-testid": "sdk-mute-button",
|
|
2177
2183
|
onClick: onMuteToggle,
|
|
2178
|
-
|
|
2179
|
-
className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${isGenerating ? "bg-sas-panel text-sas-muted/50 cursor-not-allowed" : isMuted ? "bg-red-600 text-white" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"}`,
|
|
2184
|
+
className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${isMuted ? "bg-red-600 text-white" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"}`,
|
|
2180
2185
|
title: isMuted ? "Unmute track" : "Mute track",
|
|
2181
2186
|
children: "M"
|
|
2182
2187
|
}
|
|
@@ -2427,6 +2432,18 @@ function CrossfadeTrackRow({
|
|
|
2427
2432
|
}
|
|
2428
2433
|
|
|
2429
2434
|
// src/crossfade-meta.ts
|
|
2435
|
+
function hashString(s) {
|
|
2436
|
+
let h = 5381;
|
|
2437
|
+
for (let i = 0; i < s.length; i++) h = (h << 5) + h ^ s.charCodeAt(i) | 0;
|
|
2438
|
+
return (h >>> 0).toString(36);
|
|
2439
|
+
}
|
|
2440
|
+
function soundIdentity(snap) {
|
|
2441
|
+
if (!snap) return "";
|
|
2442
|
+
if (snap.kind === "preset") return `p:${hashString(snap.state)}`;
|
|
2443
|
+
if (snap.kind === "sample") return `s:${snap.samplePath}`;
|
|
2444
|
+
if (snap.kind === "instrument") return `i:${snap.instrumentId ?? ""}:${hashString(JSON.stringify(snap.zones))}`;
|
|
2445
|
+
return "";
|
|
2446
|
+
}
|
|
2430
2447
|
var EQUAL_POWER_GAIN = 0.707;
|
|
2431
2448
|
function asCrossfadeMeta(val) {
|
|
2432
2449
|
if (!val || typeof val !== "object") return null;
|
|
@@ -2576,9 +2593,11 @@ function asFadeMeta(val) {
|
|
|
2576
2593
|
const m = val;
|
|
2577
2594
|
if (m.direction !== "in" && m.direction !== "out") return null;
|
|
2578
2595
|
if (m.gesture !== "volume" && m.gesture !== "build") return null;
|
|
2596
|
+
const effect = m.effect === "stutter" || m.effect === "chopped" || m.effect === "delay" || m.effect === "fade" ? m.effect : void 0;
|
|
2579
2597
|
return {
|
|
2580
2598
|
direction: m.direction,
|
|
2581
2599
|
gesture: m.gesture,
|
|
2600
|
+
effect,
|
|
2582
2601
|
sourceTrackDbId: typeof m.sourceTrackDbId === "string" ? m.sourceTrackDbId : "",
|
|
2583
2602
|
sourceSceneId: typeof m.sourceSceneId === "string" ? m.sourceSceneId : "",
|
|
2584
2603
|
sourceName: typeof m.sourceName === "string" ? m.sourceName : "",
|
|
@@ -2665,6 +2684,7 @@ function FadeTrackRow({
|
|
|
2665
2684
|
layer,
|
|
2666
2685
|
direction,
|
|
2667
2686
|
gesture,
|
|
2687
|
+
effect,
|
|
2668
2688
|
sliderPos = 0.5,
|
|
2669
2689
|
onMuteToggle,
|
|
2670
2690
|
onSoloToggle,
|
|
@@ -2678,7 +2698,8 @@ function FadeTrackRow({
|
|
|
2678
2698
|
const [confirmDelete, setConfirmDelete] = React10.useState(false);
|
|
2679
2699
|
const leftLabel = direction === "in" ? "(silent)" : layer.sourceName ?? layer.name;
|
|
2680
2700
|
const rightLabel = direction === "in" ? layer.sourceName ?? layer.name : "(silent)";
|
|
2681
|
-
const
|
|
2701
|
+
const verb = effect && effect !== "fade" ? effect.charAt(0).toUpperCase() + effect.slice(1) : "Fade";
|
|
2702
|
+
const badge = direction === "in" ? `\u2197 ${verb} in` : `\u2198 ${verb} out`;
|
|
2682
2703
|
return /* @__PURE__ */ jsxs9(
|
|
2683
2704
|
"div",
|
|
2684
2705
|
{
|
|
@@ -3413,9 +3434,701 @@ function CrossfadeModal({
|
|
|
3413
3434
|
) });
|
|
3414
3435
|
}
|
|
3415
3436
|
|
|
3416
|
-
// src/components/
|
|
3417
|
-
import { useCallback as
|
|
3437
|
+
// src/components/TransitionDesigner.tsx
|
|
3438
|
+
import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef10, useState as useState11 } from "react";
|
|
3439
|
+
|
|
3440
|
+
// src/hooks/useTrackReorder.ts
|
|
3441
|
+
import { useCallback as useCallback7, useRef as useRef9, useState as useState10 } from "react";
|
|
3442
|
+
function moveItem(arr, from, to) {
|
|
3443
|
+
const next = arr.slice();
|
|
3444
|
+
if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
|
|
3445
|
+
return next;
|
|
3446
|
+
}
|
|
3447
|
+
const [moved] = next.splice(from, 1);
|
|
3448
|
+
next.splice(to, 0, moved);
|
|
3449
|
+
return next;
|
|
3450
|
+
}
|
|
3451
|
+
function useTrackReorder({
|
|
3452
|
+
host,
|
|
3453
|
+
items,
|
|
3454
|
+
setItems,
|
|
3455
|
+
getId,
|
|
3456
|
+
onError
|
|
3457
|
+
}) {
|
|
3458
|
+
const [draggingIndex, setDraggingIndex] = useState10(null);
|
|
3459
|
+
const [dragOverIndex, setDragOverIndex] = useState10(null);
|
|
3460
|
+
const fromRef = useRef9(null);
|
|
3461
|
+
const itemsRef = useRef9(items);
|
|
3462
|
+
itemsRef.current = items;
|
|
3463
|
+
const dragPropsFor = useCallback7(
|
|
3464
|
+
(index) => ({
|
|
3465
|
+
handleProps: {
|
|
3466
|
+
draggable: true,
|
|
3467
|
+
onDragStart: (e) => {
|
|
3468
|
+
fromRef.current = index;
|
|
3469
|
+
setDraggingIndex(index);
|
|
3470
|
+
if (e.dataTransfer) {
|
|
3471
|
+
e.dataTransfer.effectAllowed = "move";
|
|
3472
|
+
try {
|
|
3473
|
+
e.dataTransfer.setData("text/plain", String(index));
|
|
3474
|
+
} catch {
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
onDragEnd: () => {
|
|
3479
|
+
fromRef.current = null;
|
|
3480
|
+
setDraggingIndex(null);
|
|
3481
|
+
setDragOverIndex(null);
|
|
3482
|
+
}
|
|
3483
|
+
},
|
|
3484
|
+
rowProps: {
|
|
3485
|
+
onDragEnter: (e) => {
|
|
3486
|
+
if (fromRef.current === null) return;
|
|
3487
|
+
e.preventDefault();
|
|
3488
|
+
setDragOverIndex(index);
|
|
3489
|
+
},
|
|
3490
|
+
onDragOver: (e) => {
|
|
3491
|
+
if (fromRef.current === null) return;
|
|
3492
|
+
e.preventDefault();
|
|
3493
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
3494
|
+
setDragOverIndex((cur) => cur === index ? cur : index);
|
|
3495
|
+
},
|
|
3496
|
+
onDragLeave: () => {
|
|
3497
|
+
setDragOverIndex((cur) => cur === index ? null : cur);
|
|
3498
|
+
},
|
|
3499
|
+
onDrop: (e) => {
|
|
3500
|
+
e.preventDefault();
|
|
3501
|
+
const from = fromRef.current;
|
|
3502
|
+
fromRef.current = null;
|
|
3503
|
+
setDraggingIndex(null);
|
|
3504
|
+
setDragOverIndex(null);
|
|
3505
|
+
if (from === null || from === index) return;
|
|
3506
|
+
const prev = itemsRef.current;
|
|
3507
|
+
const next = moveItem(prev, from, index);
|
|
3508
|
+
setItems(next);
|
|
3509
|
+
const ids = next.map(getId);
|
|
3510
|
+
Promise.resolve(host.reorderTracks(ids)).catch((err) => {
|
|
3511
|
+
setItems(prev);
|
|
3512
|
+
onError?.(err);
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
},
|
|
3516
|
+
isDragging: draggingIndex === index,
|
|
3517
|
+
isDragTarget: dragOverIndex === index && draggingIndex !== index
|
|
3518
|
+
}),
|
|
3519
|
+
[host, setItems, getId, onError, draggingIndex, dragOverIndex]
|
|
3520
|
+
);
|
|
3521
|
+
return { dragPropsFor, draggingIndex, dragOverIndex };
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
// src/transition-designer-meta.ts
|
|
3525
|
+
var TRANSITION_DESIGNER_DRAFT_KEY = "transitionDesigner:draft";
|
|
3526
|
+
var AUDIO_EFFECTS = ["fade", "stutter", "chopped", "delay"];
|
|
3527
|
+
var AUDIO_EFFECT_LABEL = {
|
|
3528
|
+
fade: "Fade",
|
|
3529
|
+
stutter: "Stutter",
|
|
3530
|
+
chopped: "Chopped",
|
|
3531
|
+
delay: "Delay"
|
|
3532
|
+
};
|
|
3533
|
+
function asAudioEffect(v) {
|
|
3534
|
+
return v === "fade" || v === "stutter" || v === "chopped" || v === "delay" ? v : null;
|
|
3535
|
+
}
|
|
3536
|
+
function rowType(hasOrigin, hasTarget) {
|
|
3537
|
+
if (hasOrigin && hasTarget) return "crossfade";
|
|
3538
|
+
if (hasOrigin) return "fade-out";
|
|
3539
|
+
if (hasTarget) return "fade-in";
|
|
3540
|
+
return null;
|
|
3541
|
+
}
|
|
3542
|
+
function asTransitionDesignerDraft(val) {
|
|
3543
|
+
if (!val || typeof val !== "object") return null;
|
|
3544
|
+
const d = val;
|
|
3545
|
+
const clean = (a) => Array.isArray(a) ? a.filter((x) => x === null || typeof x === "string") : [];
|
|
3546
|
+
const cleanEffects = (e) => {
|
|
3547
|
+
const out = {};
|
|
3548
|
+
if (e && typeof e === "object") {
|
|
3549
|
+
for (const [k, v] of Object.entries(e)) {
|
|
3550
|
+
const eff = asAudioEffect(v);
|
|
3551
|
+
if (eff) out[k] = eff;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
return out;
|
|
3555
|
+
};
|
|
3556
|
+
return {
|
|
3557
|
+
originOrder: clean(d.originOrder),
|
|
3558
|
+
targetOrder: clean(d.targetOrder),
|
|
3559
|
+
rowEffects: cleanEffects(d.rowEffects)
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
function reconcileSlots(saved, poolIds) {
|
|
3563
|
+
const pool = new Set(poolIds);
|
|
3564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3565
|
+
const out = [];
|
|
3566
|
+
for (const slot of saved ?? []) {
|
|
3567
|
+
if (slot === null) {
|
|
3568
|
+
out.push(null);
|
|
3569
|
+
continue;
|
|
3570
|
+
}
|
|
3571
|
+
if (pool.has(slot) && !seen.has(slot)) {
|
|
3572
|
+
out.push(slot);
|
|
3573
|
+
seen.add(slot);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
for (const id of poolIds) {
|
|
3577
|
+
if (!seen.has(id)) {
|
|
3578
|
+
out.push(id);
|
|
3579
|
+
seen.add(id);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
return out;
|
|
3583
|
+
}
|
|
3584
|
+
function buildRowSlots(originSlots, targetSlots) {
|
|
3585
|
+
const n = Math.max(originSlots.length, targetSlots.length);
|
|
3586
|
+
const rows = [];
|
|
3587
|
+
for (let i = 0; i < n; i++) {
|
|
3588
|
+
const originId = originSlots[i] ?? null;
|
|
3589
|
+
const targetId = targetSlots[i] ?? null;
|
|
3590
|
+
rows.push({ originId, targetId, type: rowType(originId !== null, targetId !== null) });
|
|
3591
|
+
}
|
|
3592
|
+
return rows;
|
|
3593
|
+
}
|
|
3594
|
+
function normalizeSlots(originSlots, targetSlots) {
|
|
3595
|
+
const rows = buildRowSlots(originSlots, targetSlots).filter(
|
|
3596
|
+
(r) => r.originId !== null || r.targetId !== null
|
|
3597
|
+
);
|
|
3598
|
+
const trimTrailing = (a) => {
|
|
3599
|
+
let end = a.length;
|
|
3600
|
+
while (end > 0 && a[end - 1] === null) end--;
|
|
3601
|
+
return a.slice(0, end);
|
|
3602
|
+
};
|
|
3603
|
+
return {
|
|
3604
|
+
originOrder: trimTrailing(rows.map((r) => r.originId)),
|
|
3605
|
+
targetOrder: trimTrailing(rows.map((r) => r.targetId))
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3608
|
+
function padSlots(slots, n) {
|
|
3609
|
+
if (slots.length >= n) return slots.slice();
|
|
3610
|
+
return [...slots, ...new Array(n - slots.length).fill(null)];
|
|
3611
|
+
}
|
|
3612
|
+
function padPair(originSlots, targetSlots) {
|
|
3613
|
+
const n = Math.max(originSlots.length, targetSlots.length);
|
|
3614
|
+
return [padSlots(originSlots, n), padSlots(targetSlots, n)];
|
|
3615
|
+
}
|
|
3616
|
+
function slotsEqual(a, b) {
|
|
3617
|
+
if (a.length !== b.length) return false;
|
|
3618
|
+
for (let i = 0; i < a.length; i++) {
|
|
3619
|
+
if (a[i] !== b[i]) return false;
|
|
3620
|
+
}
|
|
3621
|
+
return true;
|
|
3622
|
+
}
|
|
3623
|
+
function rowKey(row) {
|
|
3624
|
+
if (row.type === "crossfade") return `xf:${row.originId}|${row.targetId}`;
|
|
3625
|
+
if (row.type === "fade-out") return `fo:${row.originId}`;
|
|
3626
|
+
if (row.type === "fade-in") return `fi:${row.targetId}`;
|
|
3627
|
+
return null;
|
|
3628
|
+
}
|
|
3629
|
+
function dbIdsFromKeys(keys) {
|
|
3630
|
+
const out = /* @__PURE__ */ new Set();
|
|
3631
|
+
for (const k of keys) {
|
|
3632
|
+
const body = k.slice(3);
|
|
3633
|
+
if (k.startsWith("xf:")) {
|
|
3634
|
+
const sep = body.indexOf("|");
|
|
3635
|
+
out.add(body.slice(0, sep));
|
|
3636
|
+
out.add(body.slice(sep + 1));
|
|
3637
|
+
} else {
|
|
3638
|
+
out.add(body);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
return out;
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
// src/components/TransitionDesigner.tsx
|
|
3418
3645
|
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3646
|
+
var CROSSFADE_ESTIMATE_MS = 15e3;
|
|
3647
|
+
var FADE_ESTIMATE_MS = 11e3;
|
|
3648
|
+
var CREATE_ALL_CONCURRENCY = 5;
|
|
3649
|
+
var TYPE_LABEL = {
|
|
3650
|
+
crossfade: "Crossfade",
|
|
3651
|
+
"fade-out": "Fade out",
|
|
3652
|
+
"fade-in": "Fade in"
|
|
3653
|
+
};
|
|
3654
|
+
function shortId3(dbId) {
|
|
3655
|
+
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3656
|
+
}
|
|
3657
|
+
function TransitionDesigner({
|
|
3658
|
+
host,
|
|
3659
|
+
fromSceneId,
|
|
3660
|
+
toSceneId,
|
|
3661
|
+
transitionSceneId,
|
|
3662
|
+
excludeSourceDbIds,
|
|
3663
|
+
onCreateCrossfade,
|
|
3664
|
+
onCreateFade,
|
|
3665
|
+
onCreateAudioTransition,
|
|
3666
|
+
familyLabel,
|
|
3667
|
+
testIdPrefix = "transition-designer"
|
|
3668
|
+
}) {
|
|
3669
|
+
const [load, setLoad] = useState11({ status: "loading" });
|
|
3670
|
+
const [fromName, setFromName] = useState11(null);
|
|
3671
|
+
const [toName, setToName] = useState11(null);
|
|
3672
|
+
const [originSlots, setOriginSlots] = useState11([]);
|
|
3673
|
+
const [targetSlots, setTargetSlots] = useState11([]);
|
|
3674
|
+
const [creatingKeys, setCreatingKeys] = useState11(() => /* @__PURE__ */ new Set());
|
|
3675
|
+
const [rowErrors, setRowErrors] = useState11({});
|
|
3676
|
+
const [rowEffects, setRowEffects] = useState11({});
|
|
3677
|
+
const rowEffectsRef = useRef10(rowEffects);
|
|
3678
|
+
rowEffectsRef.current = rowEffects;
|
|
3679
|
+
const audioEffectsEnabled = !!onCreateAudioTransition;
|
|
3680
|
+
const excludeRef = useRef10(excludeSourceDbIds);
|
|
3681
|
+
excludeRef.current = excludeSourceDbIds;
|
|
3682
|
+
const originSlotsRef = useRef10(originSlots);
|
|
3683
|
+
originSlotsRef.current = originSlots;
|
|
3684
|
+
const targetSlotsRef = useRef10(targetSlots);
|
|
3685
|
+
targetSlotsRef.current = targetSlots;
|
|
3686
|
+
const creatingKeysRef = useRef10(creatingKeys);
|
|
3687
|
+
creatingKeysRef.current = creatingKeys;
|
|
3688
|
+
const dragRef = useRef10(null);
|
|
3689
|
+
const [dragging, setDragging] = useState11(null);
|
|
3690
|
+
const [dragOver, setDragOver] = useState11(null);
|
|
3691
|
+
const excludeSet = useMemo5(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3692
|
+
const originPool = useMemo5(
|
|
3693
|
+
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3694
|
+
[load, excludeSet]
|
|
3695
|
+
);
|
|
3696
|
+
const targetPool = useMemo5(
|
|
3697
|
+
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3698
|
+
[load, excludeSet]
|
|
3699
|
+
);
|
|
3700
|
+
const originById = useMemo5(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
|
|
3701
|
+
const targetById = useMemo5(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
|
|
3702
|
+
const originByIdRef = useRef10(originById);
|
|
3703
|
+
originByIdRef.current = originById;
|
|
3704
|
+
const targetByIdRef = useRef10(targetById);
|
|
3705
|
+
targetByIdRef.current = targetById;
|
|
3706
|
+
const refresh = useCallback8(async () => {
|
|
3707
|
+
if (!host.listSceneFamilyTracks) {
|
|
3708
|
+
setLoad({ status: "error", message: "This host does not support transition tracks." });
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
setLoad({ status: "loading" });
|
|
3712
|
+
try {
|
|
3713
|
+
const [origin, target, fName, tName, draftRaw] = await Promise.all([
|
|
3714
|
+
host.listSceneFamilyTracks(fromSceneId),
|
|
3715
|
+
host.listSceneFamilyTracks(toSceneId),
|
|
3716
|
+
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
3717
|
+
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),
|
|
3718
|
+
host.getSceneData ? host.getSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY) : Promise.resolve(null)
|
|
3719
|
+
]);
|
|
3720
|
+
const draft = asTransitionDesignerDraft(draftRaw);
|
|
3721
|
+
const exSet = new Set(excludeRef.current ?? []);
|
|
3722
|
+
const originIds = origin.filter((t) => !exSet.has(t.dbId)).map((t) => t.dbId);
|
|
3723
|
+
const targetIds = target.filter((t) => !exSet.has(t.dbId)).map((t) => t.dbId);
|
|
3724
|
+
const [po, pt] = padPair(
|
|
3725
|
+
reconcileSlots(draft?.originOrder, originIds),
|
|
3726
|
+
reconcileSlots(draft?.targetOrder, targetIds)
|
|
3727
|
+
);
|
|
3728
|
+
setOriginSlots(po);
|
|
3729
|
+
setTargetSlots(pt);
|
|
3730
|
+
setRowEffects(draft?.rowEffects ?? {});
|
|
3731
|
+
setFromName(fName);
|
|
3732
|
+
setToName(tName);
|
|
3733
|
+
setLoad({ status: "ready", origin, target });
|
|
3734
|
+
} catch (err) {
|
|
3735
|
+
setLoad({
|
|
3736
|
+
status: "error",
|
|
3737
|
+
message: err instanceof Error ? err.message : "Failed to load tracks."
|
|
3738
|
+
});
|
|
3739
|
+
}
|
|
3740
|
+
}, [host, fromSceneId, toSceneId, transitionSceneId]);
|
|
3741
|
+
useEffect9(() => {
|
|
3742
|
+
void refresh();
|
|
3743
|
+
}, [refresh]);
|
|
3744
|
+
useEffect9(() => {
|
|
3745
|
+
if (load.status !== "ready") return;
|
|
3746
|
+
const [po, pt] = padPair(
|
|
3747
|
+
reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),
|
|
3748
|
+
reconcileSlots(targetSlotsRef.current, targetPool.map((t) => t.dbId))
|
|
3749
|
+
);
|
|
3750
|
+
if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);
|
|
3751
|
+
if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);
|
|
3752
|
+
}, [originPool, targetPool, load.status]);
|
|
3753
|
+
const mutate = useCallback8(
|
|
3754
|
+
(nextOrigin, nextTarget) => {
|
|
3755
|
+
const norm = normalizeSlots(nextOrigin, nextTarget);
|
|
3756
|
+
const [po, pt] = padPair(norm.originOrder, norm.targetOrder);
|
|
3757
|
+
setOriginSlots(po);
|
|
3758
|
+
setTargetSlots(pt);
|
|
3759
|
+
if (host.setSceneData) {
|
|
3760
|
+
host.setSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY, { ...norm, rowEffects: rowEffectsRef.current }).catch(() => {
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
},
|
|
3764
|
+
[host, transitionSceneId]
|
|
3765
|
+
);
|
|
3766
|
+
const setRowEffect = useCallback8(
|
|
3767
|
+
(sourceDbId, effect) => {
|
|
3768
|
+
setRowEffects((prev) => {
|
|
3769
|
+
const next = { ...prev, [sourceDbId]: effect };
|
|
3770
|
+
if (host.setSceneData) {
|
|
3771
|
+
const norm = normalizeSlots(originSlotsRef.current, targetSlotsRef.current);
|
|
3772
|
+
host.setSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY, { ...norm, rowEffects: next }).catch(() => {
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
return next;
|
|
3776
|
+
});
|
|
3777
|
+
},
|
|
3778
|
+
[host, transitionSceneId]
|
|
3779
|
+
);
|
|
3780
|
+
const insertGapAbove = useCallback8(
|
|
3781
|
+
(col, index) => {
|
|
3782
|
+
const slots = col === "origin" ? originSlots : targetSlots;
|
|
3783
|
+
const next = [...slots.slice(0, index), null, ...slots.slice(index)];
|
|
3784
|
+
if (col === "origin") mutate(next, targetSlots);
|
|
3785
|
+
else mutate(originSlots, next);
|
|
3786
|
+
},
|
|
3787
|
+
[originSlots, targetSlots, mutate]
|
|
3788
|
+
);
|
|
3789
|
+
const removeGap = useCallback8(
|
|
3790
|
+
(col, index) => {
|
|
3791
|
+
const slots = col === "origin" ? originSlots : targetSlots;
|
|
3792
|
+
const next = slots.filter((_, i) => i !== index);
|
|
3793
|
+
if (col === "origin") mutate(next, targetSlots);
|
|
3794
|
+
else mutate(originSlots, next);
|
|
3795
|
+
},
|
|
3796
|
+
[originSlots, targetSlots, mutate]
|
|
3797
|
+
);
|
|
3798
|
+
const handleDrop = useCallback8(
|
|
3799
|
+
(col, to) => {
|
|
3800
|
+
const from = dragRef.current;
|
|
3801
|
+
dragRef.current = null;
|
|
3802
|
+
setDragging(null);
|
|
3803
|
+
setDragOver(null);
|
|
3804
|
+
if (!from || from.col !== col || from.index === to) return;
|
|
3805
|
+
if (col === "origin") mutate(moveItem(originSlots, from.index, to), targetSlots);
|
|
3806
|
+
else mutate(originSlots, moveItem(targetSlots, from.index, to));
|
|
3807
|
+
},
|
|
3808
|
+
[originSlots, targetSlots, mutate]
|
|
3809
|
+
);
|
|
3810
|
+
const rows = useMemo5(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
|
|
3811
|
+
const creatingDbIds = useMemo5(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
|
|
3812
|
+
const eligibleCount = useMemo5(
|
|
3813
|
+
() => rows.filter((r) => {
|
|
3814
|
+
const k = rowKey(r);
|
|
3815
|
+
return k !== null && !creatingKeys.has(k);
|
|
3816
|
+
}).length,
|
|
3817
|
+
[rows, creatingKeys]
|
|
3818
|
+
);
|
|
3819
|
+
const createRow = useCallback8(
|
|
3820
|
+
async (row) => {
|
|
3821
|
+
const key = rowKey(row);
|
|
3822
|
+
if (!key || !row.type || creatingKeysRef.current.has(key)) return;
|
|
3823
|
+
setCreatingKeys((prev) => new Set(prev).add(key));
|
|
3824
|
+
setRowErrors((prev) => {
|
|
3825
|
+
if (!(key in prev)) return prev;
|
|
3826
|
+
const next = { ...prev };
|
|
3827
|
+
delete next[key];
|
|
3828
|
+
return next;
|
|
3829
|
+
});
|
|
3830
|
+
try {
|
|
3831
|
+
if (row.type === "crossfade") {
|
|
3832
|
+
const o = row.originId ? originByIdRef.current.get(row.originId) : void 0;
|
|
3833
|
+
const t = row.targetId ? targetByIdRef.current.get(row.targetId) : void 0;
|
|
3834
|
+
if (!o || !t) throw new Error("Track is no longer available \u2014 refresh and retry.");
|
|
3835
|
+
await onCreateCrossfade(
|
|
3836
|
+
{ dbId: o.dbId, name: o.name, role: o.role },
|
|
3837
|
+
{ dbId: t.dbId, name: t.name, role: t.role }
|
|
3838
|
+
);
|
|
3839
|
+
} else if (row.type === "fade-out") {
|
|
3840
|
+
const o = row.originId ? originByIdRef.current.get(row.originId) : void 0;
|
|
3841
|
+
if (!o) throw new Error("Track is no longer available \u2014 refresh and retry.");
|
|
3842
|
+
const eff = rowEffectsRef.current[o.dbId] ?? "fade";
|
|
3843
|
+
if (eff !== "fade" && onCreateAudioTransition) {
|
|
3844
|
+
await onCreateAudioTransition({ dbId: o.dbId, name: o.name, role: o.role }, "out", eff);
|
|
3845
|
+
} else {
|
|
3846
|
+
await onCreateFade({ dbId: o.dbId, name: o.name, role: o.role }, "out", defaultFadeGesture(o.role));
|
|
3847
|
+
}
|
|
3848
|
+
} else {
|
|
3849
|
+
const t = row.targetId ? targetByIdRef.current.get(row.targetId) : void 0;
|
|
3850
|
+
if (!t) throw new Error("Track is no longer available \u2014 refresh and retry.");
|
|
3851
|
+
const eff = rowEffectsRef.current[t.dbId] ?? "fade";
|
|
3852
|
+
if (eff !== "fade" && onCreateAudioTransition) {
|
|
3853
|
+
await onCreateAudioTransition({ dbId: t.dbId, name: t.name, role: t.role }, "in", eff);
|
|
3854
|
+
} else {
|
|
3855
|
+
await onCreateFade({ dbId: t.dbId, name: t.name, role: t.role }, "in", defaultFadeGesture(t.role));
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
} catch (err) {
|
|
3859
|
+
setRowErrors((prev) => ({
|
|
3860
|
+
...prev,
|
|
3861
|
+
[key]: err instanceof Error ? err.message : "Failed to create transition."
|
|
3862
|
+
}));
|
|
3863
|
+
} finally {
|
|
3864
|
+
setCreatingKeys((prev) => {
|
|
3865
|
+
const next = new Set(prev);
|
|
3866
|
+
next.delete(key);
|
|
3867
|
+
return next;
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
},
|
|
3871
|
+
[onCreateCrossfade, onCreateFade, onCreateAudioTransition]
|
|
3872
|
+
);
|
|
3873
|
+
const createAll = useCallback8(async () => {
|
|
3874
|
+
const eligible = buildRowSlots(originSlotsRef.current, targetSlotsRef.current).filter((r) => {
|
|
3875
|
+
const k = rowKey(r);
|
|
3876
|
+
return k !== null && !creatingKeysRef.current.has(k);
|
|
3877
|
+
});
|
|
3878
|
+
if (eligible.length === 0) return;
|
|
3879
|
+
let cursor = 0;
|
|
3880
|
+
const worker = async () => {
|
|
3881
|
+
while (cursor < eligible.length) {
|
|
3882
|
+
const row = eligible[cursor];
|
|
3883
|
+
cursor += 1;
|
|
3884
|
+
await createRow(row);
|
|
3885
|
+
}
|
|
3886
|
+
};
|
|
3887
|
+
await Promise.all(
|
|
3888
|
+
Array.from({ length: Math.min(CREATE_ALL_CONCURRENCY, eligible.length) }, () => worker())
|
|
3889
|
+
);
|
|
3890
|
+
}, [createRow]);
|
|
3891
|
+
const fromLabel = fromName ?? "origin";
|
|
3892
|
+
const toLabel = toName ?? "target";
|
|
3893
|
+
const cellDragProps = (col, index, locked) => ({
|
|
3894
|
+
draggable: !locked,
|
|
3895
|
+
onDragStart: (e) => {
|
|
3896
|
+
if (locked) return;
|
|
3897
|
+
dragRef.current = { col, index };
|
|
3898
|
+
setDragging({ col, index });
|
|
3899
|
+
if (e.dataTransfer) {
|
|
3900
|
+
e.dataTransfer.effectAllowed = "move";
|
|
3901
|
+
try {
|
|
3902
|
+
e.dataTransfer.setData("text/plain", String(index));
|
|
3903
|
+
} catch {
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
},
|
|
3907
|
+
onDragEnd: () => {
|
|
3908
|
+
dragRef.current = null;
|
|
3909
|
+
setDragging(null);
|
|
3910
|
+
setDragOver(null);
|
|
3911
|
+
},
|
|
3912
|
+
onDragEnter: (e) => {
|
|
3913
|
+
const d = dragRef.current;
|
|
3914
|
+
if (!d || d.col !== col) return;
|
|
3915
|
+
e.preventDefault();
|
|
3916
|
+
setDragOver({ col, index });
|
|
3917
|
+
},
|
|
3918
|
+
onDragOver: (e) => {
|
|
3919
|
+
const d = dragRef.current;
|
|
3920
|
+
if (!d || d.col !== col) return;
|
|
3921
|
+
e.preventDefault();
|
|
3922
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
3923
|
+
},
|
|
3924
|
+
onDragLeave: () => {
|
|
3925
|
+
setDragOver((cur) => cur && cur.col === col && cur.index === index ? null : cur);
|
|
3926
|
+
},
|
|
3927
|
+
onDrop: (e) => {
|
|
3928
|
+
e.preventDefault();
|
|
3929
|
+
handleDrop(col, index);
|
|
3930
|
+
}
|
|
3931
|
+
});
|
|
3932
|
+
const renderCell = (col, index, slotId) => {
|
|
3933
|
+
const byId = col === "origin" ? originById : targetById;
|
|
3934
|
+
const track = slotId ? byId.get(slotId) : void 0;
|
|
3935
|
+
const locked = slotId !== null && creatingDbIds.has(slotId);
|
|
3936
|
+
const isDragging = dragging?.col === col && dragging.index === index;
|
|
3937
|
+
const isDragTarget = dragOver?.col === col && dragOver.index === index && !isDragging;
|
|
3938
|
+
const base = "group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none";
|
|
3939
|
+
const tone = isDragTarget ? "border-sas-accent bg-sas-accent/10" : "border-sas-border bg-sas-panel";
|
|
3940
|
+
if (slotId === null) {
|
|
3941
|
+
return /* @__PURE__ */ jsxs13(
|
|
3942
|
+
"div",
|
|
3943
|
+
{
|
|
3944
|
+
...cellDragProps(col, index, false),
|
|
3945
|
+
"data-testid": `${testIdPrefix}-${col}-gap-${index}`,
|
|
3946
|
+
className: `${base} ${tone} border-dashed flex items-center justify-between ${isDragging ? "opacity-40" : "opacity-70"}`,
|
|
3947
|
+
children: [
|
|
3948
|
+
/* @__PURE__ */ jsx17("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
|
|
3949
|
+
/* @__PURE__ */ jsx17(
|
|
3950
|
+
"button",
|
|
3951
|
+
{
|
|
3952
|
+
type: "button",
|
|
3953
|
+
"data-testid": `${testIdPrefix}-${col}-remove-gap-${index}`,
|
|
3954
|
+
onClick: () => removeGap(col, index),
|
|
3955
|
+
title: "Remove gap",
|
|
3956
|
+
className: "text-[10px] text-sas-muted hover:text-sas-danger",
|
|
3957
|
+
children: "\u2715"
|
|
3958
|
+
}
|
|
3959
|
+
)
|
|
3960
|
+
]
|
|
3961
|
+
}
|
|
3962
|
+
);
|
|
3963
|
+
}
|
|
3964
|
+
const primary = track ? track.prompt?.trim() || track.name : slotId;
|
|
3965
|
+
const meta = track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing";
|
|
3966
|
+
return /* @__PURE__ */ jsx17(
|
|
3967
|
+
"div",
|
|
3968
|
+
{
|
|
3969
|
+
...cellDragProps(col, index, locked),
|
|
3970
|
+
"data-testid": `${testIdPrefix}-${col}-cell-${slotId}`,
|
|
3971
|
+
"data-value": slotId,
|
|
3972
|
+
className: `${base} ${tone} ${isDragging ? "opacity-40" : ""} ${locked ? "opacity-60" : "cursor-grab active:cursor-grabbing"}`,
|
|
3973
|
+
title: track ? track.dbId : "Track no longer available",
|
|
3974
|
+
children: /* @__PURE__ */ jsxs13("div", { className: "flex items-start gap-1", children: [
|
|
3975
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
|
|
3976
|
+
/* @__PURE__ */ jsxs13("div", { className: "min-w-0 flex-1", children: [
|
|
3977
|
+
/* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-text truncate", children: primary }),
|
|
3978
|
+
meta && /* @__PURE__ */ jsx17("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
|
|
3979
|
+
] }),
|
|
3980
|
+
/* @__PURE__ */ jsx17(
|
|
3981
|
+
"button",
|
|
3982
|
+
{
|
|
3983
|
+
type: "button",
|
|
3984
|
+
"data-testid": `${testIdPrefix}-${col}-insert-gap-${index}`,
|
|
3985
|
+
onClick: () => insertGapAbove(col, index),
|
|
3986
|
+
disabled: locked,
|
|
3987
|
+
title: "Insert a gap above (make this a fade)",
|
|
3988
|
+
className: "text-[10px] text-sas-muted opacity-0 group-hover:opacity-100 hover:text-sas-accent disabled:opacity-30",
|
|
3989
|
+
children: "+gap"
|
|
3990
|
+
}
|
|
3991
|
+
)
|
|
3992
|
+
] })
|
|
3993
|
+
}
|
|
3994
|
+
);
|
|
3995
|
+
};
|
|
3996
|
+
return /* @__PURE__ */ jsxs13("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
|
|
3997
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
|
|
3998
|
+
/* @__PURE__ */ jsxs13("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
|
|
3999
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sas-text", children: fromLabel }),
|
|
4000
|
+
" \u2192",
|
|
4001
|
+
" ",
|
|
4002
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sas-text", children: toLabel }),
|
|
4003
|
+
familyLabel ? ` \xB7 ${familyLabel}` : "",
|
|
4004
|
+
" \xB7 line up a track on each side to crossfade; leave one blank (or insert a gap) to fade."
|
|
4005
|
+
] }),
|
|
4006
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4007
|
+
creatingKeys.size > 0 && /* @__PURE__ */ jsxs13("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
|
|
4008
|
+
creatingKeys.size,
|
|
4009
|
+
" creating\u2026"
|
|
4010
|
+
] }),
|
|
4011
|
+
/* @__PURE__ */ jsxs13(
|
|
4012
|
+
"button",
|
|
4013
|
+
{
|
|
4014
|
+
type: "button",
|
|
4015
|
+
"data-testid": `${testIdPrefix}-create-all`,
|
|
4016
|
+
onClick: createAll,
|
|
4017
|
+
disabled: eligibleCount === 0,
|
|
4018
|
+
title: "Create every staged transition at once (runs several concurrently)",
|
|
4019
|
+
className: `px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors whitespace-nowrap ${eligibleCount > 0 ? "bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg" : "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed"}`,
|
|
4020
|
+
children: [
|
|
4021
|
+
"Create all",
|
|
4022
|
+
eligibleCount > 0 ? ` (${eligibleCount})` : ""
|
|
4023
|
+
]
|
|
4024
|
+
}
|
|
4025
|
+
)
|
|
4026
|
+
] })
|
|
4027
|
+
] }),
|
|
4028
|
+
/* @__PURE__ */ jsxs13("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
|
|
4029
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
|
|
4030
|
+
"Origin (",
|
|
4031
|
+
fromLabel,
|
|
4032
|
+
")"
|
|
4033
|
+
] }),
|
|
4034
|
+
/* @__PURE__ */ jsx17("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
|
|
4035
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
|
|
4036
|
+
"Target (",
|
|
4037
|
+
toLabel,
|
|
4038
|
+
")"
|
|
4039
|
+
] })
|
|
4040
|
+
] }),
|
|
4041
|
+
load.status === "loading" && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
|
|
4042
|
+
load.status === "error" && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
4043
|
+
load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ jsxs13("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
|
|
4044
|
+
"No tracks to arrange in this panel for either scene. Add tracks to ",
|
|
4045
|
+
fromLabel,
|
|
4046
|
+
" or ",
|
|
4047
|
+
toLabel,
|
|
4048
|
+
" ",
|
|
4049
|
+
"first (or free one by deleting an existing crossfade/fade)."
|
|
4050
|
+
] }) : /* @__PURE__ */ jsx17("div", { className: "space-y-2", children: rows.map((row, i) => {
|
|
4051
|
+
const key = rowKey(row);
|
|
4052
|
+
const isCreatingThis = key !== null && creatingKeys.has(key);
|
|
4053
|
+
const errMsg = key !== null ? rowErrors[key] : void 0;
|
|
4054
|
+
return /* @__PURE__ */ jsxs13(
|
|
4055
|
+
"div",
|
|
4056
|
+
{
|
|
4057
|
+
"data-testid": `${testIdPrefix}-row-${i}`,
|
|
4058
|
+
className: "grid grid-cols-[1fr_auto_1fr] gap-2 items-center",
|
|
4059
|
+
children: [
|
|
4060
|
+
renderCell("origin", i, row.originId),
|
|
4061
|
+
/* @__PURE__ */ jsxs13("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
|
|
4062
|
+
!row.type ? /* @__PURE__ */ jsx17("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ jsx17(
|
|
4063
|
+
"span",
|
|
4064
|
+
{
|
|
4065
|
+
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
4066
|
+
className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent",
|
|
4067
|
+
children: TYPE_LABEL[row.type]
|
|
4068
|
+
}
|
|
4069
|
+
) : audioEffectsEnabled ? /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
|
|
4070
|
+
/* @__PURE__ */ jsx17(
|
|
4071
|
+
"select",
|
|
4072
|
+
{
|
|
4073
|
+
"data-testid": `${testIdPrefix}-effect-${i}`,
|
|
4074
|
+
value: rowEffects[row.originId ?? row.targetId] ?? "fade",
|
|
4075
|
+
onChange: (e) => {
|
|
4076
|
+
const id = row.originId ?? row.targetId;
|
|
4077
|
+
if (id) setRowEffect(id, e.target.value);
|
|
4078
|
+
},
|
|
4079
|
+
className: "text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text",
|
|
4080
|
+
children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ jsx17("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
|
|
4081
|
+
}
|
|
4082
|
+
),
|
|
4083
|
+
/* @__PURE__ */ jsx17("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
|
|
4084
|
+
] }) : /* @__PURE__ */ jsx17(
|
|
4085
|
+
"span",
|
|
4086
|
+
{
|
|
4087
|
+
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
4088
|
+
className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-border text-sas-muted",
|
|
4089
|
+
children: TYPE_LABEL[row.type]
|
|
4090
|
+
}
|
|
4091
|
+
),
|
|
4092
|
+
isCreatingThis ? /* @__PURE__ */ jsx17("div", { className: "w-full", children: /* @__PURE__ */ jsx17(
|
|
4093
|
+
SorceryProgressBar,
|
|
4094
|
+
{
|
|
4095
|
+
isLoading: true,
|
|
4096
|
+
heightClass: "h-5",
|
|
4097
|
+
statusText: "CREATING",
|
|
4098
|
+
estimatedDurationMs: row.type === "crossfade" ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS
|
|
4099
|
+
}
|
|
4100
|
+
) }) : /* @__PURE__ */ jsx17(
|
|
4101
|
+
"button",
|
|
4102
|
+
{
|
|
4103
|
+
type: "button",
|
|
4104
|
+
"data-testid": `${testIdPrefix}-create-${i}`,
|
|
4105
|
+
onClick: () => createRow(row),
|
|
4106
|
+
disabled: !row.type,
|
|
4107
|
+
className: `w-full px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${row.type ? "bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg" : "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed"}`,
|
|
4108
|
+
children: "Create"
|
|
4109
|
+
}
|
|
4110
|
+
),
|
|
4111
|
+
errMsg && /* @__PURE__ */ jsx17(
|
|
4112
|
+
"span",
|
|
4113
|
+
{
|
|
4114
|
+
"data-testid": `${testIdPrefix}-row-error-${i}`,
|
|
4115
|
+
className: "text-[10px] text-sas-danger text-center leading-tight",
|
|
4116
|
+
children: errMsg
|
|
4117
|
+
}
|
|
4118
|
+
)
|
|
4119
|
+
] }),
|
|
4120
|
+
renderCell("target", i, row.targetId)
|
|
4121
|
+
]
|
|
4122
|
+
},
|
|
4123
|
+
i
|
|
4124
|
+
);
|
|
4125
|
+
}) }))
|
|
4126
|
+
] });
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
// src/components/DownloadPackButton.tsx
|
|
4130
|
+
import { useCallback as useCallback9, useEffect as useEffect10, useState as useState12 } from "react";
|
|
4131
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3419
4132
|
function formatSize(bytes) {
|
|
3420
4133
|
if (!bytes || bytes <= 0) return "";
|
|
3421
4134
|
const gb = bytes / 1024 ** 3;
|
|
@@ -3431,10 +4144,10 @@ var DownloadPackButton = ({
|
|
|
3431
4144
|
variant = "compact",
|
|
3432
4145
|
onDownloadComplete
|
|
3433
4146
|
}) => {
|
|
3434
|
-
const [status, setStatus] =
|
|
3435
|
-
const [progress, setProgress] =
|
|
3436
|
-
const [errorMessage, setErrorMessage] =
|
|
3437
|
-
|
|
4147
|
+
const [status, setStatus] = useState12("idle");
|
|
4148
|
+
const [progress, setProgress] = useState12(0);
|
|
4149
|
+
const [errorMessage, setErrorMessage] = useState12(null);
|
|
4150
|
+
useEffect10(() => {
|
|
3438
4151
|
const unsub = host.onSamplePackProgress(packId, (p) => {
|
|
3439
4152
|
setStatus(p.status);
|
|
3440
4153
|
setProgress(p.progress);
|
|
@@ -3449,7 +4162,7 @@ var DownloadPackButton = ({
|
|
|
3449
4162
|
});
|
|
3450
4163
|
return unsub;
|
|
3451
4164
|
}, [host, packId, onDownloadComplete]);
|
|
3452
|
-
const handleClick =
|
|
4165
|
+
const handleClick = useCallback9(async () => {
|
|
3453
4166
|
if (status !== "idle" && status !== "error") return;
|
|
3454
4167
|
try {
|
|
3455
4168
|
setStatus("downloading");
|
|
@@ -3503,8 +4216,8 @@ var DownloadPackButton = ({
|
|
|
3503
4216
|
} else {
|
|
3504
4217
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
3505
4218
|
}
|
|
3506
|
-
return /* @__PURE__ */
|
|
3507
|
-
/* @__PURE__ */
|
|
4219
|
+
return /* @__PURE__ */ jsxs14("div", { children: [
|
|
4220
|
+
/* @__PURE__ */ jsx18(
|
|
3508
4221
|
"button",
|
|
3509
4222
|
{
|
|
3510
4223
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -3515,12 +4228,12 @@ var DownloadPackButton = ({
|
|
|
3515
4228
|
children: buttonLabel
|
|
3516
4229
|
}
|
|
3517
4230
|
),
|
|
3518
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */
|
|
4231
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
3519
4232
|
] });
|
|
3520
4233
|
};
|
|
3521
4234
|
|
|
3522
4235
|
// src/components/SamplePackCTACard.tsx
|
|
3523
|
-
import { jsx as
|
|
4236
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3524
4237
|
var SamplePackCTACard = ({
|
|
3525
4238
|
host,
|
|
3526
4239
|
pack,
|
|
@@ -3528,7 +4241,7 @@ var SamplePackCTACard = ({
|
|
|
3528
4241
|
onDownloadComplete
|
|
3529
4242
|
}) => {
|
|
3530
4243
|
if (status === "checking") {
|
|
3531
|
-
return /* @__PURE__ */
|
|
4244
|
+
return /* @__PURE__ */ jsx19(
|
|
3532
4245
|
"div",
|
|
3533
4246
|
{
|
|
3534
4247
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -3539,16 +4252,16 @@ var SamplePackCTACard = ({
|
|
|
3539
4252
|
}
|
|
3540
4253
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
3541
4254
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
3542
|
-
return /* @__PURE__ */
|
|
4255
|
+
return /* @__PURE__ */ jsxs15(
|
|
3543
4256
|
"div",
|
|
3544
4257
|
{
|
|
3545
4258
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
3546
4259
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
3547
4260
|
children: [
|
|
3548
|
-
/* @__PURE__ */
|
|
3549
|
-
/* @__PURE__ */
|
|
3550
|
-
/* @__PURE__ */
|
|
3551
|
-
/* @__PURE__ */
|
|
4261
|
+
/* @__PURE__ */ jsx19("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
4262
|
+
/* @__PURE__ */ jsx19("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
4263
|
+
/* @__PURE__ */ jsx19("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
4264
|
+
/* @__PURE__ */ jsx19(
|
|
3552
4265
|
DownloadPackButton,
|
|
3553
4266
|
{
|
|
3554
4267
|
host,
|
|
@@ -3565,7 +4278,7 @@ var SamplePackCTACard = ({
|
|
|
3565
4278
|
};
|
|
3566
4279
|
|
|
3567
4280
|
// src/components/WaveformView.tsx
|
|
3568
|
-
import { useEffect as
|
|
4281
|
+
import { useEffect as useEffect11, useRef as useRef11, useState as useState13 } from "react";
|
|
3569
4282
|
|
|
3570
4283
|
// src/components/waveform.ts
|
|
3571
4284
|
function computePeaks(audioBuffer, bins, targetSamples) {
|
|
@@ -3628,7 +4341,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
3628
4341
|
}
|
|
3629
4342
|
|
|
3630
4343
|
// src/components/WaveformView.tsx
|
|
3631
|
-
import { jsx as
|
|
4344
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
3632
4345
|
var WaveformView = ({
|
|
3633
4346
|
host,
|
|
3634
4347
|
filePath,
|
|
@@ -3637,9 +4350,9 @@ var WaveformView = ({
|
|
|
3637
4350
|
fillStyle,
|
|
3638
4351
|
targetSamples
|
|
3639
4352
|
}) => {
|
|
3640
|
-
const canvasRef =
|
|
3641
|
-
const [peaks, setPeaks] =
|
|
3642
|
-
|
|
4353
|
+
const canvasRef = useRef11(null);
|
|
4354
|
+
const [peaks, setPeaks] = useState13(null);
|
|
4355
|
+
useEffect11(() => {
|
|
3643
4356
|
let cancelled = false;
|
|
3644
4357
|
let audioContext = null;
|
|
3645
4358
|
(async () => {
|
|
@@ -3665,7 +4378,7 @@ var WaveformView = ({
|
|
|
3665
4378
|
cancelled = true;
|
|
3666
4379
|
};
|
|
3667
4380
|
}, [host, filePath, bins, targetSamples]);
|
|
3668
|
-
|
|
4381
|
+
useEffect11(() => {
|
|
3669
4382
|
if (!peaks) return;
|
|
3670
4383
|
const canvas = canvasRef.current;
|
|
3671
4384
|
if (!canvas) return;
|
|
@@ -3676,7 +4389,7 @@ var WaveformView = ({
|
|
|
3676
4389
|
observer.observe(canvas);
|
|
3677
4390
|
return () => observer.disconnect();
|
|
3678
4391
|
}, [peaks, fillStyle]);
|
|
3679
|
-
return /* @__PURE__ */
|
|
4392
|
+
return /* @__PURE__ */ jsx20(
|
|
3680
4393
|
"canvas",
|
|
3681
4394
|
{
|
|
3682
4395
|
ref: canvasRef,
|
|
@@ -3687,8 +4400,8 @@ var WaveformView = ({
|
|
|
3687
4400
|
};
|
|
3688
4401
|
|
|
3689
4402
|
// src/components/ScrollingWaveform.tsx
|
|
3690
|
-
import { useEffect as
|
|
3691
|
-
import { jsx as
|
|
4403
|
+
import { useEffect as useEffect12, useRef as useRef12 } from "react";
|
|
4404
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
3692
4405
|
var ScrollingWaveform = ({
|
|
3693
4406
|
getPeakDb,
|
|
3694
4407
|
active,
|
|
@@ -3696,11 +4409,11 @@ var ScrollingWaveform = ({
|
|
|
3696
4409
|
className,
|
|
3697
4410
|
fillStyle
|
|
3698
4411
|
}) => {
|
|
3699
|
-
const canvasRef =
|
|
3700
|
-
const ringRef =
|
|
3701
|
-
const writeIdxRef =
|
|
3702
|
-
const rafRef =
|
|
3703
|
-
|
|
4412
|
+
const canvasRef = useRef12(null);
|
|
4413
|
+
const ringRef = useRef12(new Float32Array(columns));
|
|
4414
|
+
const writeIdxRef = useRef12(0);
|
|
4415
|
+
const rafRef = useRef12(null);
|
|
4416
|
+
useEffect12(() => {
|
|
3704
4417
|
if (ringRef.current.length !== columns) {
|
|
3705
4418
|
const next = new Float32Array(columns);
|
|
3706
4419
|
const prev = ringRef.current;
|
|
@@ -3712,7 +4425,7 @@ var ScrollingWaveform = ({
|
|
|
3712
4425
|
writeIdxRef.current = writeIdxRef.current % columns;
|
|
3713
4426
|
}
|
|
3714
4427
|
}, [columns]);
|
|
3715
|
-
|
|
4428
|
+
useEffect12(() => {
|
|
3716
4429
|
if (!active) {
|
|
3717
4430
|
if (rafRef.current !== null) {
|
|
3718
4431
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -3764,7 +4477,7 @@ var ScrollingWaveform = ({
|
|
|
3764
4477
|
}
|
|
3765
4478
|
};
|
|
3766
4479
|
}, [active, getPeakDb, fillStyle]);
|
|
3767
|
-
return /* @__PURE__ */
|
|
4480
|
+
return /* @__PURE__ */ jsx21(
|
|
3768
4481
|
"canvas",
|
|
3769
4482
|
{
|
|
3770
4483
|
ref: canvasRef,
|
|
@@ -3775,8 +4488,8 @@ var ScrollingWaveform = ({
|
|
|
3775
4488
|
};
|
|
3776
4489
|
|
|
3777
4490
|
// src/components/OffsetScrubber.tsx
|
|
3778
|
-
import { useCallback as
|
|
3779
|
-
import { jsx as
|
|
4491
|
+
import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo6, useRef as useRef13, useState as useState14 } from "react";
|
|
4492
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3780
4493
|
var SLIDER_HEIGHT_PX = 28;
|
|
3781
4494
|
var TICK_HEIGHT_PX = 14;
|
|
3782
4495
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -3789,40 +4502,40 @@ function OffsetScrubber({
|
|
|
3789
4502
|
onChange,
|
|
3790
4503
|
disabled = false
|
|
3791
4504
|
}) {
|
|
3792
|
-
const trackRef =
|
|
3793
|
-
const [draftOffset, setDraftOffset] =
|
|
3794
|
-
const [isDragging, setIsDragging] =
|
|
3795
|
-
|
|
4505
|
+
const trackRef = useRef13(null);
|
|
4506
|
+
const [draftOffset, setDraftOffset] = useState14(offsetSamples);
|
|
4507
|
+
const [isDragging, setIsDragging] = useState14(false);
|
|
4508
|
+
useEffect13(() => {
|
|
3796
4509
|
if (!isDragging) setDraftOffset(offsetSamples);
|
|
3797
4510
|
}, [offsetSamples, isDragging]);
|
|
3798
4511
|
const sampleRate = cuePoints?.sample_rate ?? 44100;
|
|
3799
4512
|
const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
|
|
3800
|
-
const beatsForRange =
|
|
4513
|
+
const beatsForRange = useMemo6(() => {
|
|
3801
4514
|
return Math.round(60 / projectBpm * sampleRate);
|
|
3802
4515
|
}, [projectBpm, sampleRate]);
|
|
3803
4516
|
const rangeSamples = beatsForRange * meter;
|
|
3804
|
-
const sampleToFraction =
|
|
4517
|
+
const sampleToFraction = useCallback10(
|
|
3805
4518
|
(sample) => {
|
|
3806
4519
|
const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
|
|
3807
4520
|
return (clamped + rangeSamples) / (2 * rangeSamples);
|
|
3808
4521
|
},
|
|
3809
4522
|
[rangeSamples]
|
|
3810
4523
|
);
|
|
3811
|
-
const fractionToSample =
|
|
4524
|
+
const fractionToSample = useCallback10(
|
|
3812
4525
|
(fraction) => {
|
|
3813
4526
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
3814
4527
|
return Math.round(clamped * 2 * rangeSamples - rangeSamples);
|
|
3815
4528
|
},
|
|
3816
4529
|
[rangeSamples]
|
|
3817
4530
|
);
|
|
3818
|
-
const snapTargets =
|
|
4531
|
+
const snapTargets = useMemo6(() => {
|
|
3819
4532
|
if (!cuePoints || cuePoints.beats.length === 0) return [];
|
|
3820
4533
|
const downbeat = cuePoints.beats[0];
|
|
3821
4534
|
const positives = cuePoints.beats.map((b) => b - downbeat);
|
|
3822
4535
|
const negatives = positives.slice(1).map((p) => -p);
|
|
3823
4536
|
return [...negatives, ...positives].sort((a, b) => a - b);
|
|
3824
4537
|
}, [cuePoints]);
|
|
3825
|
-
const snapToBeat =
|
|
4538
|
+
const snapToBeat = useCallback10(
|
|
3826
4539
|
(sample) => {
|
|
3827
4540
|
if (snapTargets.length === 0) return sample;
|
|
3828
4541
|
let best = snapTargets[0];
|
|
@@ -3838,7 +4551,7 @@ function OffsetScrubber({
|
|
|
3838
4551
|
},
|
|
3839
4552
|
[snapTargets]
|
|
3840
4553
|
);
|
|
3841
|
-
const handlePointerDown =
|
|
4554
|
+
const handlePointerDown = useCallback10(
|
|
3842
4555
|
(e) => {
|
|
3843
4556
|
if (disabled || !cuePoints) return;
|
|
3844
4557
|
e.preventDefault();
|
|
@@ -3872,7 +4585,7 @@ function OffsetScrubber({
|
|
|
3872
4585
|
},
|
|
3873
4586
|
[disabled, cuePoints, fractionToSample, onChange, snapToBeat]
|
|
3874
4587
|
);
|
|
3875
|
-
const handleResetToZero =
|
|
4588
|
+
const handleResetToZero = useCallback10(() => {
|
|
3876
4589
|
if (disabled) return;
|
|
3877
4590
|
setDraftOffset(0);
|
|
3878
4591
|
onChange(0);
|
|
@@ -3880,7 +4593,7 @@ function OffsetScrubber({
|
|
|
3880
4593
|
const thumbFraction = sampleToFraction(draftOffset);
|
|
3881
4594
|
const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
|
|
3882
4595
|
const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
|
|
3883
|
-
const ticks =
|
|
4596
|
+
const ticks = useMemo6(() => {
|
|
3884
4597
|
if (!cuePoints) return [];
|
|
3885
4598
|
const downbeat = cuePoints.beats[0] ?? 0;
|
|
3886
4599
|
return cuePoints.beats.map((b, i) => {
|
|
@@ -3891,9 +4604,9 @@ function OffsetScrubber({
|
|
|
3891
4604
|
});
|
|
3892
4605
|
}, [cuePoints, sampleToFraction]);
|
|
3893
4606
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
3894
|
-
return /* @__PURE__ */
|
|
3895
|
-
/* @__PURE__ */
|
|
3896
|
-
/* @__PURE__ */
|
|
4607
|
+
return /* @__PURE__ */ jsxs16("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
4608
|
+
/* @__PURE__ */ jsx22("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
4609
|
+
/* @__PURE__ */ jsxs16(
|
|
3897
4610
|
"div",
|
|
3898
4611
|
{
|
|
3899
4612
|
ref: trackRef,
|
|
@@ -3909,7 +4622,7 @@ function OffsetScrubber({
|
|
|
3909
4622
|
"aria-valuenow": draftOffset,
|
|
3910
4623
|
"aria-disabled": isDisabled,
|
|
3911
4624
|
children: [
|
|
3912
|
-
/* @__PURE__ */
|
|
4625
|
+
/* @__PURE__ */ jsx22(
|
|
3913
4626
|
"div",
|
|
3914
4627
|
{
|
|
3915
4628
|
"aria-hidden": "true",
|
|
@@ -3917,7 +4630,7 @@ function OffsetScrubber({
|
|
|
3917
4630
|
style: { left: "50%" }
|
|
3918
4631
|
}
|
|
3919
4632
|
),
|
|
3920
|
-
ticks.map((t) => /* @__PURE__ */
|
|
4633
|
+
ticks.map((t) => /* @__PURE__ */ jsx22(
|
|
3921
4634
|
"div",
|
|
3922
4635
|
{
|
|
3923
4636
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -3932,7 +4645,7 @@ function OffsetScrubber({
|
|
|
3932
4645
|
},
|
|
3933
4646
|
t.i
|
|
3934
4647
|
)),
|
|
3935
|
-
/* @__PURE__ */
|
|
4648
|
+
/* @__PURE__ */ jsx22(
|
|
3936
4649
|
"div",
|
|
3937
4650
|
{
|
|
3938
4651
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -3949,7 +4662,7 @@ function OffsetScrubber({
|
|
|
3949
4662
|
]
|
|
3950
4663
|
}
|
|
3951
4664
|
),
|
|
3952
|
-
/* @__PURE__ */
|
|
4665
|
+
/* @__PURE__ */ jsx22(
|
|
3953
4666
|
"span",
|
|
3954
4667
|
{
|
|
3955
4668
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -3957,7 +4670,7 @@ function OffsetScrubber({
|
|
|
3957
4670
|
children: formatOffset(draftOffset, sampleRate)
|
|
3958
4671
|
}
|
|
3959
4672
|
),
|
|
3960
|
-
/* @__PURE__ */
|
|
4673
|
+
/* @__PURE__ */ jsx22(
|
|
3961
4674
|
"button",
|
|
3962
4675
|
{
|
|
3963
4676
|
type: "button",
|
|
@@ -3969,7 +4682,7 @@ function OffsetScrubber({
|
|
|
3969
4682
|
children: "\u2316"
|
|
3970
4683
|
}
|
|
3971
4684
|
),
|
|
3972
|
-
bpmMismatch && /* @__PURE__ */
|
|
4685
|
+
bpmMismatch && /* @__PURE__ */ jsx22(
|
|
3973
4686
|
"span",
|
|
3974
4687
|
{
|
|
3975
4688
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -4041,13 +4754,13 @@ function synthesizeCuePoints({
|
|
|
4041
4754
|
}
|
|
4042
4755
|
|
|
4043
4756
|
// src/hooks/useSceneState.ts
|
|
4044
|
-
import { useState as
|
|
4757
|
+
import { useState as useState15, useCallback as useCallback11, useRef as useRef14 } from "react";
|
|
4045
4758
|
function useSceneState(activeSceneId, initialValue) {
|
|
4046
|
-
const [stateMap, setStateMap] =
|
|
4047
|
-
const activeSceneIdRef =
|
|
4759
|
+
const [stateMap, setStateMap] = useState15(() => /* @__PURE__ */ new Map());
|
|
4760
|
+
const activeSceneIdRef = useRef14(activeSceneId);
|
|
4048
4761
|
activeSceneIdRef.current = activeSceneId;
|
|
4049
4762
|
const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
|
|
4050
|
-
const setForCurrentScene =
|
|
4763
|
+
const setForCurrentScene = useCallback11((value) => {
|
|
4051
4764
|
const sid = activeSceneIdRef.current;
|
|
4052
4765
|
if (sid === null) return;
|
|
4053
4766
|
setStateMap((prev) => {
|
|
@@ -4058,7 +4771,7 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
4058
4771
|
return newMap;
|
|
4059
4772
|
});
|
|
4060
4773
|
}, [initialValue]);
|
|
4061
|
-
const setForScene =
|
|
4774
|
+
const setForScene = useCallback11((sceneId, value) => {
|
|
4062
4775
|
setStateMap((prev) => {
|
|
4063
4776
|
const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
|
|
4064
4777
|
const next = typeof value === "function" ? value(current) : value;
|
|
@@ -4071,10 +4784,10 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
4071
4784
|
}
|
|
4072
4785
|
|
|
4073
4786
|
// src/hooks/useAnySolo.ts
|
|
4074
|
-
import { useEffect as
|
|
4787
|
+
import { useEffect as useEffect14, useState as useState16 } from "react";
|
|
4075
4788
|
function useAnySolo(host) {
|
|
4076
|
-
const [anySolo, setAnySolo] =
|
|
4077
|
-
|
|
4789
|
+
const [anySolo, setAnySolo] = useState16(false);
|
|
4790
|
+
useEffect14(() => {
|
|
4078
4791
|
let active = true;
|
|
4079
4792
|
const refresh = () => {
|
|
4080
4793
|
host.isAnySoloActive().then((v) => {
|
|
@@ -4093,7 +4806,7 @@ function useAnySolo(host) {
|
|
|
4093
4806
|
}
|
|
4094
4807
|
|
|
4095
4808
|
// src/hooks/useSoundHistory.ts
|
|
4096
|
-
import { useCallback as
|
|
4809
|
+
import { useCallback as useCallback12, useMemo as useMemo7, useRef as useRef15, useState as useState17 } from "react";
|
|
4097
4810
|
var EMPTY = { entries: [], cursor: -1 };
|
|
4098
4811
|
function sameDescriptor(a, b) {
|
|
4099
4812
|
if (a === b) return true;
|
|
@@ -4105,14 +4818,14 @@ function sameDescriptor(a, b) {
|
|
|
4105
4818
|
}
|
|
4106
4819
|
function useSoundHistory(applySound, opts = {}) {
|
|
4107
4820
|
const max = Math.max(2, opts.max ?? 24);
|
|
4108
|
-
const applyRef =
|
|
4821
|
+
const applyRef = useRef15(applySound);
|
|
4109
4822
|
applyRef.current = applySound;
|
|
4110
|
-
const onChangeRef =
|
|
4823
|
+
const onChangeRef = useRef15(opts.onChange);
|
|
4111
4824
|
onChangeRef.current = opts.onChange;
|
|
4112
|
-
const dataRef =
|
|
4113
|
-
const [, setVersion] =
|
|
4114
|
-
const bump =
|
|
4115
|
-
const commit =
|
|
4825
|
+
const dataRef = useRef15({});
|
|
4826
|
+
const [, setVersion] = useState17(0);
|
|
4827
|
+
const bump = useCallback12(() => setVersion((v) => v + 1), []);
|
|
4828
|
+
const commit = useCallback12(
|
|
4116
4829
|
(trackId, next, notify) => {
|
|
4117
4830
|
dataRef.current = { ...dataRef.current, [trackId]: next };
|
|
4118
4831
|
bump();
|
|
@@ -4120,7 +4833,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4120
4833
|
},
|
|
4121
4834
|
[bump]
|
|
4122
4835
|
);
|
|
4123
|
-
const record =
|
|
4836
|
+
const record = useCallback12(
|
|
4124
4837
|
(trackId, descriptor, label) => {
|
|
4125
4838
|
const h = dataRef.current[trackId];
|
|
4126
4839
|
const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
|
|
@@ -4135,7 +4848,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4135
4848
|
},
|
|
4136
4849
|
[max, commit]
|
|
4137
4850
|
);
|
|
4138
|
-
const restoreTo =
|
|
4851
|
+
const restoreTo = useCallback12(
|
|
4139
4852
|
async (trackId, index) => {
|
|
4140
4853
|
const h = dataRef.current[trackId];
|
|
4141
4854
|
if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
|
|
@@ -4145,7 +4858,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4145
4858
|
},
|
|
4146
4859
|
[commit]
|
|
4147
4860
|
);
|
|
4148
|
-
const undo =
|
|
4861
|
+
const undo = useCallback12(
|
|
4149
4862
|
(trackId) => {
|
|
4150
4863
|
const h = dataRef.current[trackId];
|
|
4151
4864
|
if (!h || h.cursor <= 0) return Promise.resolve(false);
|
|
@@ -4153,7 +4866,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4153
4866
|
},
|
|
4154
4867
|
[restoreTo]
|
|
4155
4868
|
);
|
|
4156
|
-
const toggleFavorite =
|
|
4869
|
+
const toggleFavorite = useCallback12(
|
|
4157
4870
|
(trackId, index) => {
|
|
4158
4871
|
const h = dataRef.current[trackId];
|
|
4159
4872
|
if (!h || index < 0 || index >= h.entries.length) return;
|
|
@@ -4162,7 +4875,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4162
4875
|
},
|
|
4163
4876
|
[commit]
|
|
4164
4877
|
);
|
|
4165
|
-
const restore =
|
|
4878
|
+
const restore = useCallback12(
|
|
4166
4879
|
(trackId, state) => {
|
|
4167
4880
|
const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
|
|
4168
4881
|
const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
|
|
@@ -4171,15 +4884,15 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4171
4884
|
},
|
|
4172
4885
|
[commit]
|
|
4173
4886
|
);
|
|
4174
|
-
const list =
|
|
4887
|
+
const list = useCallback12(
|
|
4175
4888
|
(trackId) => dataRef.current[trackId] ?? EMPTY,
|
|
4176
4889
|
[]
|
|
4177
4890
|
);
|
|
4178
|
-
const canUndo =
|
|
4891
|
+
const canUndo = useCallback12((trackId) => {
|
|
4179
4892
|
const h = dataRef.current[trackId];
|
|
4180
4893
|
return !!h && h.cursor > 0;
|
|
4181
4894
|
}, []);
|
|
4182
|
-
const clear =
|
|
4895
|
+
const clear = useCallback12(
|
|
4183
4896
|
(trackId) => {
|
|
4184
4897
|
if (dataRef.current[trackId]) {
|
|
4185
4898
|
const next = { ...dataRef.current };
|
|
@@ -4191,102 +4904,18 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4191
4904
|
},
|
|
4192
4905
|
[bump]
|
|
4193
4906
|
);
|
|
4194
|
-
const reset =
|
|
4907
|
+
const reset = useCallback12(() => {
|
|
4195
4908
|
dataRef.current = {};
|
|
4196
4909
|
bump();
|
|
4197
4910
|
}, [bump]);
|
|
4198
|
-
return
|
|
4911
|
+
return useMemo7(
|
|
4199
4912
|
() => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
|
|
4200
4913
|
[record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
|
|
4201
4914
|
);
|
|
4202
4915
|
}
|
|
4203
4916
|
|
|
4204
|
-
// src/hooks/useTrackReorder.ts
|
|
4205
|
-
import { useCallback as useCallback11, useRef as useRef14, useState as useState16 } from "react";
|
|
4206
|
-
function moveItem(arr, from, to) {
|
|
4207
|
-
const next = arr.slice();
|
|
4208
|
-
if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
|
|
4209
|
-
return next;
|
|
4210
|
-
}
|
|
4211
|
-
const [moved] = next.splice(from, 1);
|
|
4212
|
-
next.splice(to, 0, moved);
|
|
4213
|
-
return next;
|
|
4214
|
-
}
|
|
4215
|
-
function useTrackReorder({
|
|
4216
|
-
host,
|
|
4217
|
-
items,
|
|
4218
|
-
setItems,
|
|
4219
|
-
getId,
|
|
4220
|
-
onError
|
|
4221
|
-
}) {
|
|
4222
|
-
const [draggingIndex, setDraggingIndex] = useState16(null);
|
|
4223
|
-
const [dragOverIndex, setDragOverIndex] = useState16(null);
|
|
4224
|
-
const fromRef = useRef14(null);
|
|
4225
|
-
const itemsRef = useRef14(items);
|
|
4226
|
-
itemsRef.current = items;
|
|
4227
|
-
const dragPropsFor = useCallback11(
|
|
4228
|
-
(index) => ({
|
|
4229
|
-
handleProps: {
|
|
4230
|
-
draggable: true,
|
|
4231
|
-
onDragStart: (e) => {
|
|
4232
|
-
fromRef.current = index;
|
|
4233
|
-
setDraggingIndex(index);
|
|
4234
|
-
if (e.dataTransfer) {
|
|
4235
|
-
e.dataTransfer.effectAllowed = "move";
|
|
4236
|
-
try {
|
|
4237
|
-
e.dataTransfer.setData("text/plain", String(index));
|
|
4238
|
-
} catch {
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
},
|
|
4242
|
-
onDragEnd: () => {
|
|
4243
|
-
fromRef.current = null;
|
|
4244
|
-
setDraggingIndex(null);
|
|
4245
|
-
setDragOverIndex(null);
|
|
4246
|
-
}
|
|
4247
|
-
},
|
|
4248
|
-
rowProps: {
|
|
4249
|
-
onDragEnter: (e) => {
|
|
4250
|
-
if (fromRef.current === null) return;
|
|
4251
|
-
e.preventDefault();
|
|
4252
|
-
setDragOverIndex(index);
|
|
4253
|
-
},
|
|
4254
|
-
onDragOver: (e) => {
|
|
4255
|
-
if (fromRef.current === null) return;
|
|
4256
|
-
e.preventDefault();
|
|
4257
|
-
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
4258
|
-
setDragOverIndex((cur) => cur === index ? cur : index);
|
|
4259
|
-
},
|
|
4260
|
-
onDragLeave: () => {
|
|
4261
|
-
setDragOverIndex((cur) => cur === index ? null : cur);
|
|
4262
|
-
},
|
|
4263
|
-
onDrop: (e) => {
|
|
4264
|
-
e.preventDefault();
|
|
4265
|
-
const from = fromRef.current;
|
|
4266
|
-
fromRef.current = null;
|
|
4267
|
-
setDraggingIndex(null);
|
|
4268
|
-
setDragOverIndex(null);
|
|
4269
|
-
if (from === null || from === index) return;
|
|
4270
|
-
const prev = itemsRef.current;
|
|
4271
|
-
const next = moveItem(prev, from, index);
|
|
4272
|
-
setItems(next);
|
|
4273
|
-
const ids = next.map(getId);
|
|
4274
|
-
Promise.resolve(host.reorderTracks(ids)).catch((err) => {
|
|
4275
|
-
setItems(prev);
|
|
4276
|
-
onError?.(err);
|
|
4277
|
-
});
|
|
4278
|
-
}
|
|
4279
|
-
},
|
|
4280
|
-
isDragging: draggingIndex === index,
|
|
4281
|
-
isDragTarget: dragOverIndex === index && draggingIndex !== index
|
|
4282
|
-
}),
|
|
4283
|
-
[host, setItems, getId, onError, draggingIndex, dragOverIndex]
|
|
4284
|
-
);
|
|
4285
|
-
return { dragPropsFor, draggingIndex, dragOverIndex };
|
|
4286
|
-
}
|
|
4287
|
-
|
|
4288
4917
|
// src/constants/sdk-version.ts
|
|
4289
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
4918
|
+
var PLUGIN_SDK_VERSION = "2.34.0";
|
|
4290
4919
|
|
|
4291
4920
|
// src/utils/format-concurrent-tracks.ts
|
|
4292
4921
|
function formatConcurrentTracks(ctx) {
|
|
@@ -4429,6 +5058,8 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
4429
5058
|
return top[top.length - 1].item;
|
|
4430
5059
|
}
|
|
4431
5060
|
export {
|
|
5061
|
+
AUDIO_EFFECTS,
|
|
5062
|
+
AUDIO_EFFECT_LABEL,
|
|
4432
5063
|
ConfirmDialog,
|
|
4433
5064
|
CrossfadeModal,
|
|
4434
5065
|
CrossfadeTrackRow,
|
|
@@ -4467,34 +5098,49 @@ export {
|
|
|
4467
5098
|
ScrollingWaveform,
|
|
4468
5099
|
SorceryProgressBar,
|
|
4469
5100
|
TEXTURAL_ROLES,
|
|
5101
|
+
TRANSITION_DESIGNER_DRAFT_KEY,
|
|
4470
5102
|
TrackDrawer,
|
|
4471
5103
|
TrackMeterStrip,
|
|
4472
5104
|
TrackRow,
|
|
5105
|
+
TransitionDesigner,
|
|
4473
5106
|
VolumeSlider,
|
|
4474
5107
|
WaveformView,
|
|
4475
5108
|
analyzeWavPeak,
|
|
5109
|
+
asAudioEffect,
|
|
4476
5110
|
asCrossfadeMeta,
|
|
4477
5111
|
asFadeMeta,
|
|
5112
|
+
asTransitionDesignerDraft,
|
|
4478
5113
|
buildCrossfadeInpaintPrompt,
|
|
4479
5114
|
buildCrossfadeVolumeCurves,
|
|
4480
5115
|
buildFadeVolumeCurve,
|
|
5116
|
+
buildRowSlots,
|
|
4481
5117
|
calculateTimeBasedTarget,
|
|
4482
5118
|
cellToPx,
|
|
4483
5119
|
centerScrollTop,
|
|
4484
5120
|
computePeaks,
|
|
5121
|
+
dbIdsFromKeys,
|
|
4485
5122
|
dbToSlider,
|
|
4486
5123
|
defaultFadeGesture,
|
|
4487
5124
|
drawWaveform,
|
|
4488
5125
|
formatConcurrentTracks,
|
|
5126
|
+
hashString,
|
|
4489
5127
|
moveItem,
|
|
5128
|
+
normalizeSlots,
|
|
5129
|
+
padPair,
|
|
5130
|
+
padSlots,
|
|
4490
5131
|
parseCrossfadePairs,
|
|
4491
5132
|
parseFades,
|
|
4492
5133
|
pickTopKWeighted,
|
|
4493
5134
|
pitchToName,
|
|
4494
5135
|
pxToCell,
|
|
5136
|
+
reconcileSlots,
|
|
4495
5137
|
resizeNoteDuration,
|
|
5138
|
+
rowKey,
|
|
5139
|
+
rowType,
|
|
4496
5140
|
scorePromptMatch,
|
|
4497
5141
|
sliderToDb,
|
|
5142
|
+
slotsEqual,
|
|
5143
|
+
soundIdentity,
|
|
4498
5144
|
synthesizeCuePoints,
|
|
4499
5145
|
tokenizePrompt,
|
|
4500
5146
|
transposeNotes,
|