@signalsandsorcery/plugin-sdk 2.25.1 → 2.28.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 +235 -6
- package/dist/index.d.ts +235 -6
- package/dist/index.js +702 -185
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +695 -185
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47,6 +47,8 @@ __export(index_exports, {
|
|
|
47
47
|
FX_DISPLAY_LABELS: () => FX_DISPLAY_LABELS,
|
|
48
48
|
FX_ENGINE_PLUGIN_NAMES: () => FX_ENGINE_PLUGIN_NAMES,
|
|
49
49
|
FX_PRESET_CONFIGS: () => FX_PRESET_CONFIGS,
|
|
50
|
+
FadeModal: () => FadeModal,
|
|
51
|
+
FadeTrackRow: () => FadeTrackRow,
|
|
50
52
|
FxToggleBar: () => FxToggleBar,
|
|
51
53
|
GUTTER_W: () => GUTTER_W,
|
|
52
54
|
ImportTrackModal: () => ImportTrackModal,
|
|
@@ -65,6 +67,7 @@ __export(index_exports, {
|
|
|
65
67
|
SamplePackCTACard: () => SamplePackCTACard,
|
|
66
68
|
ScrollingWaveform: () => ScrollingWaveform,
|
|
67
69
|
SorceryProgressBar: () => SorceryProgressBar,
|
|
70
|
+
TEXTURAL_ROLES: () => TEXTURAL_ROLES,
|
|
68
71
|
TrackDrawer: () => TrackDrawer,
|
|
69
72
|
TrackMeterStrip: () => TrackMeterStrip,
|
|
70
73
|
TrackRow: () => TrackRow,
|
|
@@ -72,17 +75,21 @@ __export(index_exports, {
|
|
|
72
75
|
WaveformView: () => WaveformView,
|
|
73
76
|
analyzeWavPeak: () => analyzeWavPeak,
|
|
74
77
|
asCrossfadeMeta: () => asCrossfadeMeta,
|
|
78
|
+
asFadeMeta: () => asFadeMeta,
|
|
75
79
|
buildCrossfadeInpaintPrompt: () => buildCrossfadeInpaintPrompt,
|
|
76
80
|
buildCrossfadeVolumeCurves: () => buildCrossfadeVolumeCurves,
|
|
81
|
+
buildFadeVolumeCurve: () => buildFadeVolumeCurve,
|
|
77
82
|
calculateTimeBasedTarget: () => calculateTimeBasedTarget,
|
|
78
83
|
cellToPx: () => cellToPx,
|
|
79
84
|
centerScrollTop: () => centerScrollTop,
|
|
80
85
|
computePeaks: () => computePeaks,
|
|
81
86
|
dbToSlider: () => dbToSlider,
|
|
87
|
+
defaultFadeGesture: () => defaultFadeGesture,
|
|
82
88
|
drawWaveform: () => drawWaveform,
|
|
83
89
|
formatConcurrentTracks: () => formatConcurrentTracks,
|
|
84
90
|
moveItem: () => moveItem,
|
|
85
91
|
parseCrossfadePairs: () => parseCrossfadePairs,
|
|
92
|
+
parseFades: () => parseFades,
|
|
86
93
|
pickTopKWeighted: () => pickTopKWeighted,
|
|
87
94
|
pitchToName: () => pitchToName,
|
|
88
95
|
pxToCell: () => pxToCell,
|
|
@@ -2570,6 +2577,8 @@ function parseCrossfadePairs(sceneData) {
|
|
|
2570
2577
|
sliderPos: g.origin.meta.sliderPos,
|
|
2571
2578
|
originDbId: g.origin.dbId,
|
|
2572
2579
|
targetDbId: g.target.dbId,
|
|
2580
|
+
originSourceDbId: g.origin.meta.sourceTrackDbId,
|
|
2581
|
+
targetSourceDbId: g.target.meta.sourceTrackDbId,
|
|
2573
2582
|
originSourceName: g.origin.meta.sourceName,
|
|
2574
2583
|
originSoundLabel: g.origin.meta.soundLabel,
|
|
2575
2584
|
targetSourceName: g.target.meta.sourceName,
|
|
@@ -2673,9 +2682,448 @@ function buildCrossfadeInpaintPrompt(input) {
|
|
|
2673
2682
|
return lines.join("\n");
|
|
2674
2683
|
}
|
|
2675
2684
|
|
|
2676
|
-
// src/
|
|
2677
|
-
|
|
2685
|
+
// src/fade-meta.ts
|
|
2686
|
+
function asFadeMeta(val) {
|
|
2687
|
+
if (!val || typeof val !== "object") return null;
|
|
2688
|
+
const m = val;
|
|
2689
|
+
if (m.direction !== "in" && m.direction !== "out") return null;
|
|
2690
|
+
if (m.gesture !== "volume" && m.gesture !== "build") return null;
|
|
2691
|
+
return {
|
|
2692
|
+
direction: m.direction,
|
|
2693
|
+
gesture: m.gesture,
|
|
2694
|
+
sourceTrackDbId: typeof m.sourceTrackDbId === "string" ? m.sourceTrackDbId : "",
|
|
2695
|
+
sourceSceneId: typeof m.sourceSceneId === "string" ? m.sourceSceneId : "",
|
|
2696
|
+
sourceName: typeof m.sourceName === "string" ? m.sourceName : "",
|
|
2697
|
+
soundLabel: typeof m.soundLabel === "string" ? m.soundLabel : "",
|
|
2698
|
+
sliderPos: typeof m.sliderPos === "number" ? m.sliderPos : 0.5
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
function parseFades(sceneData) {
|
|
2702
|
+
const out = [];
|
|
2703
|
+
for (const [key, val] of Object.entries(sceneData)) {
|
|
2704
|
+
const match = /^track:(.+):fade$/.exec(key);
|
|
2705
|
+
if (!match) continue;
|
|
2706
|
+
const meta = asFadeMeta(val);
|
|
2707
|
+
if (!meta) continue;
|
|
2708
|
+
out.push({ dbId: match[1], meta });
|
|
2709
|
+
}
|
|
2710
|
+
return out;
|
|
2711
|
+
}
|
|
2712
|
+
function buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture, steps = 32) {
|
|
2713
|
+
const durationSeconds = bars * 4 * 60 / Math.max(1, bpm);
|
|
2714
|
+
if (gesture === "build") {
|
|
2715
|
+
return [
|
|
2716
|
+
{ time: 0, db: 0 },
|
|
2717
|
+
{ time: Math.round(durationSeconds * 1e3) / 1e3, db: 0 }
|
|
2718
|
+
];
|
|
2719
|
+
}
|
|
2720
|
+
const s = Math.min(0.98, Math.max(0.02, sliderPos));
|
|
2721
|
+
const round = (n) => Math.round(n * 1e3) / 1e3;
|
|
2722
|
+
const points = [];
|
|
2723
|
+
for (let i = 0; i <= steps; i++) {
|
|
2724
|
+
const x = i / steps;
|
|
2725
|
+
const time = round(x * durationSeconds);
|
|
2726
|
+
const theta = x <= s ? x / s * (Math.PI / 4) : Math.PI / 4 + (x - s) / (1 - s) * (Math.PI / 4);
|
|
2727
|
+
const gain = direction === "out" ? Math.cos(theta) : Math.sin(theta);
|
|
2728
|
+
points.push({ time, db: Math.round(gainToDb(gain) * 100) / 100 });
|
|
2729
|
+
}
|
|
2730
|
+
return points;
|
|
2731
|
+
}
|
|
2732
|
+
var TEXTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
2733
|
+
"pads",
|
|
2734
|
+
"pad",
|
|
2735
|
+
"strings",
|
|
2736
|
+
"atmospheres",
|
|
2737
|
+
"atmosphere",
|
|
2738
|
+
"atmos",
|
|
2739
|
+
"drones",
|
|
2740
|
+
"drone",
|
|
2741
|
+
"soundscapes",
|
|
2742
|
+
"soundscape"
|
|
2743
|
+
]);
|
|
2744
|
+
function defaultFadeGesture(role) {
|
|
2745
|
+
if (!role) return "build";
|
|
2746
|
+
const norm = role.toLowerCase().replace(/[\s_-]+/g, " ").trim();
|
|
2747
|
+
if (TEXTURAL_ROLES.has(norm)) return "volume";
|
|
2748
|
+
for (const token of norm.split(" ")) {
|
|
2749
|
+
if (TEXTURAL_ROLES.has(token)) return "volume";
|
|
2750
|
+
}
|
|
2751
|
+
return "build";
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/components/FadeTrackRow.tsx
|
|
2755
|
+
var import_react11 = __toESM(require("react"));
|
|
2678
2756
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
2757
|
+
function FadeCaption({
|
|
2758
|
+
layer,
|
|
2759
|
+
direction,
|
|
2760
|
+
gesture
|
|
2761
|
+
}) {
|
|
2762
|
+
const tag = direction === "in" ? "Fade in" : "Fade out";
|
|
2763
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
|
|
2764
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
|
|
2765
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
|
|
2766
|
+
layer.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
|
|
2767
|
+
"\xB7 ",
|
|
2768
|
+
layer.soundLabel
|
|
2769
|
+
] }),
|
|
2770
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", title: `Fade gesture: ${gesture}`, children: [
|
|
2771
|
+
"\xB7 ",
|
|
2772
|
+
gesture
|
|
2773
|
+
] })
|
|
2774
|
+
] });
|
|
2775
|
+
}
|
|
2776
|
+
function FadeTrackRow({
|
|
2777
|
+
layer,
|
|
2778
|
+
direction,
|
|
2779
|
+
gesture,
|
|
2780
|
+
sliderPos = 0.5,
|
|
2781
|
+
onMuteToggle,
|
|
2782
|
+
onSoloToggle,
|
|
2783
|
+
onVolumeChange,
|
|
2784
|
+
onPanChange,
|
|
2785
|
+
onDelete,
|
|
2786
|
+
onSliderChange,
|
|
2787
|
+
levels,
|
|
2788
|
+
accentColor = "#9333EA"
|
|
2789
|
+
}) {
|
|
2790
|
+
const [confirmDelete, setConfirmDelete] = import_react11.default.useState(false);
|
|
2791
|
+
const leftLabel = direction === "in" ? "(silent)" : layer.sourceName ?? layer.name;
|
|
2792
|
+
const rightLabel = direction === "in" ? layer.sourceName ?? layer.name : "(silent)";
|
|
2793
|
+
const badge = direction === "in" ? "\u2197 Fade in" : "\u2198 Fade out";
|
|
2794
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2795
|
+
"div",
|
|
2796
|
+
{
|
|
2797
|
+
"data-testid": "fade-track-row",
|
|
2798
|
+
className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
|
|
2799
|
+
style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
|
|
2800
|
+
children: [
|
|
2801
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
|
|
2802
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2803
|
+
"span",
|
|
2804
|
+
{
|
|
2805
|
+
"data-testid": "fade-direction-badge",
|
|
2806
|
+
className: "text-[10px] font-bold uppercase tracking-wide",
|
|
2807
|
+
style: { color: accentColor },
|
|
2808
|
+
children: badge
|
|
2809
|
+
}
|
|
2810
|
+
),
|
|
2811
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2812
|
+
"button",
|
|
2813
|
+
{
|
|
2814
|
+
"data-testid": "fade-delete-button",
|
|
2815
|
+
onClick: () => setConfirmDelete(true),
|
|
2816
|
+
className: "text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm",
|
|
2817
|
+
title: "Delete fade",
|
|
2818
|
+
"aria-label": "Delete fade",
|
|
2819
|
+
children: "x"
|
|
2820
|
+
}
|
|
2821
|
+
)
|
|
2822
|
+
] }),
|
|
2823
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2824
|
+
TrackRow,
|
|
2825
|
+
{
|
|
2826
|
+
track: { id: layer.trackId, name: "", role: layer.role },
|
|
2827
|
+
runtimeState: layer.runtimeState,
|
|
2828
|
+
fxDetailState: EMPTY_FX_DETAIL_STATE,
|
|
2829
|
+
drawerOpen: false,
|
|
2830
|
+
drawerTab: "fx",
|
|
2831
|
+
levels,
|
|
2832
|
+
accentColor,
|
|
2833
|
+
contentSlot: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FadeCaption, { layer, direction, gesture }),
|
|
2834
|
+
onMuteToggle,
|
|
2835
|
+
onSoloToggle,
|
|
2836
|
+
onVolumeChange,
|
|
2837
|
+
onPanChange
|
|
2838
|
+
}
|
|
2839
|
+
),
|
|
2840
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
|
|
2841
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2842
|
+
"span",
|
|
2843
|
+
{
|
|
2844
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
|
|
2845
|
+
title: leftLabel,
|
|
2846
|
+
children: leftLabel
|
|
2847
|
+
}
|
|
2848
|
+
),
|
|
2849
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2850
|
+
"input",
|
|
2851
|
+
{
|
|
2852
|
+
type: "range",
|
|
2853
|
+
"data-testid": "fade-slider",
|
|
2854
|
+
min: 0,
|
|
2855
|
+
max: 1,
|
|
2856
|
+
step: 0.01,
|
|
2857
|
+
value: sliderPos,
|
|
2858
|
+
disabled: !onSliderChange,
|
|
2859
|
+
onChange: onSliderChange ? (e) => onSliderChange(Number(e.target.value)) : void 0,
|
|
2860
|
+
style: { accentColor },
|
|
2861
|
+
className: "flex-1 disabled:opacity-60 disabled:cursor-not-allowed",
|
|
2862
|
+
"aria-label": "Fade position"
|
|
2863
|
+
}
|
|
2864
|
+
),
|
|
2865
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2866
|
+
"span",
|
|
2867
|
+
{
|
|
2868
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
|
|
2869
|
+
title: rightLabel,
|
|
2870
|
+
children: rightLabel
|
|
2871
|
+
}
|
|
2872
|
+
)
|
|
2873
|
+
] }),
|
|
2874
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2875
|
+
ConfirmDialog,
|
|
2876
|
+
{
|
|
2877
|
+
open: confirmDelete,
|
|
2878
|
+
title: "Delete fade?",
|
|
2879
|
+
message: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: "This fade track will be permanently removed from this scene. This cannot be undone." }),
|
|
2880
|
+
confirmLabel: "Delete",
|
|
2881
|
+
onConfirm: () => {
|
|
2882
|
+
setConfirmDelete(false);
|
|
2883
|
+
onDelete();
|
|
2884
|
+
},
|
|
2885
|
+
onCancel: () => setConfirmDelete(false),
|
|
2886
|
+
testIdPrefix: "fade-delete-confirm"
|
|
2887
|
+
}
|
|
2888
|
+
)
|
|
2889
|
+
]
|
|
2890
|
+
}
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/components/FadeModal.tsx
|
|
2895
|
+
var import_react12 = require("react");
|
|
2896
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2897
|
+
function shortId(dbId) {
|
|
2898
|
+
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
2899
|
+
}
|
|
2900
|
+
var normRole = (r) => (r ?? "").toLowerCase().trim();
|
|
2901
|
+
function computeOrphans(from, to, excludeSet) {
|
|
2902
|
+
const bucket = (list) => {
|
|
2903
|
+
const m = /* @__PURE__ */ new Map();
|
|
2904
|
+
for (const t of list) {
|
|
2905
|
+
const k = normRole(t.role);
|
|
2906
|
+
const arr = m.get(k);
|
|
2907
|
+
if (arr) arr.push(t);
|
|
2908
|
+
else m.set(k, [t]);
|
|
2909
|
+
}
|
|
2910
|
+
return m;
|
|
2911
|
+
};
|
|
2912
|
+
const fromByRole = bucket(from);
|
|
2913
|
+
const toByRole = bucket(to);
|
|
2914
|
+
const roles = /* @__PURE__ */ new Set([...fromByRole.keys(), ...toByRole.keys()]);
|
|
2915
|
+
const fadeOut = [];
|
|
2916
|
+
const fadeIn = [];
|
|
2917
|
+
for (const role of roles) {
|
|
2918
|
+
const f = fromByRole.get(role) ?? [];
|
|
2919
|
+
const t = toByRole.get(role) ?? [];
|
|
2920
|
+
const shared = Math.min(f.length, t.length);
|
|
2921
|
+
fadeOut.push(...f.slice(shared));
|
|
2922
|
+
fadeIn.push(...t.slice(shared));
|
|
2923
|
+
}
|
|
2924
|
+
return {
|
|
2925
|
+
fadeOut: fadeOut.filter((x) => !excludeSet.has(x.dbId)),
|
|
2926
|
+
fadeIn: fadeIn.filter((x) => !excludeSet.has(x.dbId))
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
function OrphanRow({
|
|
2930
|
+
track,
|
|
2931
|
+
gesture,
|
|
2932
|
+
selected,
|
|
2933
|
+
disabled,
|
|
2934
|
+
onSelect,
|
|
2935
|
+
testId
|
|
2936
|
+
}) {
|
|
2937
|
+
const primary = track.prompt?.trim() || track.name;
|
|
2938
|
+
const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
|
|
2939
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
2940
|
+
"button",
|
|
2941
|
+
{
|
|
2942
|
+
type: "button",
|
|
2943
|
+
role: "radio",
|
|
2944
|
+
"aria-checked": selected,
|
|
2945
|
+
"data-testid": testId,
|
|
2946
|
+
"data-value": track.dbId,
|
|
2947
|
+
onClick: onSelect,
|
|
2948
|
+
disabled,
|
|
2949
|
+
className: `w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${selected ? "bg-sas-accent/15 border-sas-accent" : "bg-sas-panel border-sas-border hover:border-sas-accent/50"}`,
|
|
2950
|
+
children: [
|
|
2951
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
2952
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
2953
|
+
]
|
|
2954
|
+
}
|
|
2955
|
+
);
|
|
2956
|
+
}
|
|
2957
|
+
function FadeModal({
|
|
2958
|
+
host,
|
|
2959
|
+
open,
|
|
2960
|
+
fromSceneId,
|
|
2961
|
+
toSceneId,
|
|
2962
|
+
fromSceneName,
|
|
2963
|
+
toSceneName,
|
|
2964
|
+
excludeSourceDbIds,
|
|
2965
|
+
onClose,
|
|
2966
|
+
onCreate,
|
|
2967
|
+
testIdPrefix = "fade-modal"
|
|
2968
|
+
}) {
|
|
2969
|
+
const [load, setLoad] = (0, import_react12.useState)({ status: "loading" });
|
|
2970
|
+
const [selectedDbId, setSelectedDbId] = (0, import_react12.useState)("");
|
|
2971
|
+
const [isCreating, setIsCreating] = (0, import_react12.useState)(false);
|
|
2972
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
2973
|
+
const [fromName, setFromName] = (0, import_react12.useState)(null);
|
|
2974
|
+
const [toName, setToName] = (0, import_react12.useState)(null);
|
|
2975
|
+
const cancelRef = (0, import_react12.useRef)(null);
|
|
2976
|
+
const refresh = (0, import_react12.useCallback)(async () => {
|
|
2977
|
+
if (!host.listSceneFamilyTracks) {
|
|
2978
|
+
setLoad({ status: "error", message: "This host does not support fades." });
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
setLoad({ status: "loading" });
|
|
2982
|
+
try {
|
|
2983
|
+
const [from, to, fName, tName] = await Promise.all([
|
|
2984
|
+
host.listSceneFamilyTracks(fromSceneId),
|
|
2985
|
+
host.listSceneFamilyTracks(toSceneId),
|
|
2986
|
+
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
2987
|
+
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null)
|
|
2988
|
+
]);
|
|
2989
|
+
setFromName(fName);
|
|
2990
|
+
setToName(tName);
|
|
2991
|
+
setLoad({ status: "ready", from, to });
|
|
2992
|
+
} catch (err) {
|
|
2993
|
+
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
2994
|
+
}
|
|
2995
|
+
}, [host, fromSceneId, toSceneId]);
|
|
2996
|
+
(0, import_react12.useEffect)(() => {
|
|
2997
|
+
if (open) {
|
|
2998
|
+
setError(null);
|
|
2999
|
+
setIsCreating(false);
|
|
3000
|
+
setSelectedDbId("");
|
|
3001
|
+
void refresh();
|
|
3002
|
+
}
|
|
3003
|
+
}, [open, refresh]);
|
|
3004
|
+
const excludeSet = (0, import_react12.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3005
|
+
const { fadeOut, fadeIn } = (0, import_react12.useMemo)(
|
|
3006
|
+
() => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
|
|
3007
|
+
[load, excludeSet]
|
|
3008
|
+
);
|
|
3009
|
+
const allOrphans = (0, import_react12.useMemo)(
|
|
3010
|
+
() => [
|
|
3011
|
+
...fadeOut.map((t) => ({ track: t, direction: "out" })),
|
|
3012
|
+
...fadeIn.map((t) => ({ track: t, direction: "in" }))
|
|
3013
|
+
],
|
|
3014
|
+
[fadeOut, fadeIn]
|
|
3015
|
+
);
|
|
3016
|
+
(0, import_react12.useEffect)(() => {
|
|
3017
|
+
if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
|
|
3018
|
+
setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
|
|
3019
|
+
}
|
|
3020
|
+
}, [allOrphans, selectedDbId]);
|
|
3021
|
+
const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
|
|
3022
|
+
const canCreate = !isCreating && !!selected;
|
|
3023
|
+
const handleClose = (0, import_react12.useCallback)(() => {
|
|
3024
|
+
if (!isCreating) onClose();
|
|
3025
|
+
}, [isCreating, onClose]);
|
|
3026
|
+
const handleCreate = (0, import_react12.useCallback)(async () => {
|
|
3027
|
+
if (!selected) return;
|
|
3028
|
+
setIsCreating(true);
|
|
3029
|
+
setError(null);
|
|
3030
|
+
try {
|
|
3031
|
+
await onCreate(
|
|
3032
|
+
{ dbId: selected.track.dbId, name: selected.track.name, role: selected.track.role },
|
|
3033
|
+
selected.direction,
|
|
3034
|
+
defaultFadeGesture(selected.track.role)
|
|
3035
|
+
);
|
|
3036
|
+
onClose();
|
|
3037
|
+
} catch (err) {
|
|
3038
|
+
setError(err instanceof Error ? err.message : "Failed to create fade.");
|
|
3039
|
+
setIsCreating(false);
|
|
3040
|
+
}
|
|
3041
|
+
}, [selected, onCreate, onClose]);
|
|
3042
|
+
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
3043
|
+
const toLabel = toName ?? toSceneName ?? null;
|
|
3044
|
+
if (!open) return null;
|
|
3045
|
+
const renderSection = (heading, list, section) => {
|
|
3046
|
+
if (list.length === 0) return null;
|
|
3047
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "block", children: [
|
|
3048
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
|
|
3049
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3050
|
+
"div",
|
|
3051
|
+
{
|
|
3052
|
+
role: "radiogroup",
|
|
3053
|
+
"aria-label": heading,
|
|
3054
|
+
"data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
|
|
3055
|
+
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3056
|
+
children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3057
|
+
OrphanRow,
|
|
3058
|
+
{
|
|
3059
|
+
track: t,
|
|
3060
|
+
gesture: defaultFadeGesture(t.role),
|
|
3061
|
+
selected: t.dbId === selectedDbId,
|
|
3062
|
+
disabled: isCreating,
|
|
3063
|
+
onSelect: () => setSelectedDbId(t.dbId),
|
|
3064
|
+
testId: `${testIdPrefix}-option-${t.dbId}`
|
|
3065
|
+
},
|
|
3066
|
+
t.dbId
|
|
3067
|
+
))
|
|
3068
|
+
}
|
|
3069
|
+
)
|
|
3070
|
+
] });
|
|
3071
|
+
};
|
|
3072
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
3073
|
+
"div",
|
|
3074
|
+
{
|
|
3075
|
+
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
3076
|
+
onClick: (e) => e.stopPropagation(),
|
|
3077
|
+
"data-testid": `${testIdPrefix}-box`,
|
|
3078
|
+
children: [
|
|
3079
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
|
|
3080
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
3081
|
+
"Tracks with no counterpart between",
|
|
3082
|
+
" ",
|
|
3083
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
3084
|
+
" and",
|
|
3085
|
+
" ",
|
|
3086
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
3087
|
+
" can gracefully fade out (leaving) or fade in (entering) across this transition."
|
|
3088
|
+
] }),
|
|
3089
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3090
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3091
|
+
load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", "data-testid": `${testIdPrefix}-empty`, children: "Every track has a counterpart in the other scene \u2014 nothing to fade. Use \u201C+ Crossfade\u201D to bridge matching tracks." }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
3092
|
+
renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
|
|
3093
|
+
renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
|
|
3094
|
+
] })),
|
|
3095
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
3096
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
3097
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3098
|
+
"button",
|
|
3099
|
+
{
|
|
3100
|
+
ref: cancelRef,
|
|
3101
|
+
"data-testid": `${testIdPrefix}-cancel`,
|
|
3102
|
+
onClick: onClose,
|
|
3103
|
+
disabled: isCreating,
|
|
3104
|
+
className: "px-3 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-text disabled:opacity-50",
|
|
3105
|
+
children: "Cancel"
|
|
3106
|
+
}
|
|
3107
|
+
),
|
|
3108
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3109
|
+
"button",
|
|
3110
|
+
{
|
|
3111
|
+
"data-testid": `${testIdPrefix}-confirm`,
|
|
3112
|
+
onClick: handleCreate,
|
|
3113
|
+
disabled: !canCreate,
|
|
3114
|
+
className: `px-3 py-1 text-xs rounded-sm border transition-colors ${canCreate ? "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"}`,
|
|
3115
|
+
children: isCreating ? "Generating fade\u2026" : "Create fade"
|
|
3116
|
+
}
|
|
3117
|
+
)
|
|
3118
|
+
] })
|
|
3119
|
+
]
|
|
3120
|
+
}
|
|
3121
|
+
) });
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
// src/components/ImportTrackModal.tsx
|
|
3125
|
+
var import_react13 = require("react");
|
|
3126
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2679
3127
|
function ImportTrackModal({
|
|
2680
3128
|
host,
|
|
2681
3129
|
open,
|
|
@@ -2687,10 +3135,10 @@ function ImportTrackModal({
|
|
|
2687
3135
|
onPick,
|
|
2688
3136
|
onPortTrack
|
|
2689
3137
|
}) {
|
|
2690
|
-
const [load, setLoad] = (0,
|
|
2691
|
-
const [selectedSceneId, setSelectedSceneId] = (0,
|
|
2692
|
-
const [importingTrackId, setImportingTrackId] = (0,
|
|
2693
|
-
const refresh = (0,
|
|
3138
|
+
const [load, setLoad] = (0, import_react13.useState)({ status: "loading" });
|
|
3139
|
+
const [selectedSceneId, setSelectedSceneId] = (0, import_react13.useState)(null);
|
|
3140
|
+
const [importingTrackId, setImportingTrackId] = (0, import_react13.useState)(null);
|
|
3141
|
+
const refresh = (0, import_react13.useCallback)(async () => {
|
|
2694
3142
|
if (!host.listImportableTracks) {
|
|
2695
3143
|
setLoad({ status: "error", message: "This host does not support importing tracks." });
|
|
2696
3144
|
return;
|
|
@@ -2706,14 +3154,14 @@ function ImportTrackModal({
|
|
|
2706
3154
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
|
|
2707
3155
|
}
|
|
2708
3156
|
}, [host, mode, onPortTrack]);
|
|
2709
|
-
(0,
|
|
3157
|
+
(0, import_react13.useEffect)(() => {
|
|
2710
3158
|
if (open) {
|
|
2711
3159
|
setSelectedSceneId(null);
|
|
2712
3160
|
setImportingTrackId(null);
|
|
2713
3161
|
void refresh();
|
|
2714
3162
|
}
|
|
2715
3163
|
}, [open, refresh]);
|
|
2716
|
-
const handleImport = (0,
|
|
3164
|
+
const handleImport = (0, import_react13.useCallback)(
|
|
2717
3165
|
async (track, sourceSceneId, sceneName, isSameScene) => {
|
|
2718
3166
|
if (isSameScene && onPortTrack) {
|
|
2719
3167
|
if (!track.importable) return;
|
|
@@ -2754,16 +3202,16 @@ function ImportTrackModal({
|
|
|
2754
3202
|
if (!open) return null;
|
|
2755
3203
|
const scenes = load.status === "ready" ? load.scenes : [];
|
|
2756
3204
|
const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
|
|
2757
|
-
return /* @__PURE__ */ (0,
|
|
3205
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
2758
3206
|
"div",
|
|
2759
3207
|
{
|
|
2760
3208
|
className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
|
|
2761
3209
|
onClick: (e) => e.stopPropagation(),
|
|
2762
3210
|
"data-testid": `${testIdPrefix}-modal`,
|
|
2763
3211
|
children: [
|
|
2764
|
-
/* @__PURE__ */ (0,
|
|
2765
|
-
/* @__PURE__ */ (0,
|
|
2766
|
-
selectedScene && /* @__PURE__ */ (0,
|
|
3212
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
|
|
3213
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3214
|
+
selectedScene && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2767
3215
|
"button",
|
|
2768
3216
|
{
|
|
2769
3217
|
className: "text-sas-muted hover:text-sas-accent text-xs",
|
|
@@ -2772,9 +3220,9 @@ function ImportTrackModal({
|
|
|
2772
3220
|
children: "\u2190"
|
|
2773
3221
|
}
|
|
2774
3222
|
),
|
|
2775
|
-
/* @__PURE__ */ (0,
|
|
3223
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
|
|
2776
3224
|
] }),
|
|
2777
|
-
/* @__PURE__ */ (0,
|
|
3225
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2778
3226
|
"button",
|
|
2779
3227
|
{
|
|
2780
3228
|
className: "text-sas-muted hover:text-sas-accent text-sm",
|
|
@@ -2784,30 +3232,30 @@ function ImportTrackModal({
|
|
|
2784
3232
|
}
|
|
2785
3233
|
)
|
|
2786
3234
|
] }),
|
|
2787
|
-
/* @__PURE__ */ (0,
|
|
2788
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
2789
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
2790
|
-
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0,
|
|
2791
|
-
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0,
|
|
3235
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "overflow-y-auto p-2 flex-1", children: [
|
|
3236
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
|
|
3237
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
3238
|
+
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-empty`, children: mode === "sound" ? "No other scenes have a sound to import." : "No other scenes have a compatible track to import." }),
|
|
3239
|
+
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
2792
3240
|
"button",
|
|
2793
3241
|
{
|
|
2794
3242
|
className: "w-full flex items-center justify-between px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt text-left text-xs text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors",
|
|
2795
3243
|
onClick: () => setSelectedSceneId(scene.sceneId),
|
|
2796
3244
|
"data-testid": `${testIdPrefix}-scene`,
|
|
2797
3245
|
children: [
|
|
2798
|
-
/* @__PURE__ */ (0,
|
|
2799
|
-
/* @__PURE__ */ (0,
|
|
3246
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate", children: scene.sceneName }),
|
|
3247
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-sas-muted", children: [
|
|
2800
3248
|
scene.tracks.length,
|
|
2801
3249
|
" \u2192"
|
|
2802
3250
|
] })
|
|
2803
3251
|
]
|
|
2804
3252
|
}
|
|
2805
3253
|
) }, scene.sceneId)) }),
|
|
2806
|
-
selectedScene && /* @__PURE__ */ (0,
|
|
3254
|
+
selectedScene && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
|
|
2807
3255
|
const busy = importingTrackId === track.trackId;
|
|
2808
3256
|
const gated = mode === "track" && !track.importable;
|
|
2809
3257
|
const disabled = gated || busy;
|
|
2810
|
-
return /* @__PURE__ */ (0,
|
|
3258
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
2811
3259
|
"button",
|
|
2812
3260
|
{
|
|
2813
3261
|
className: `w-full flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${disabled ? "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-text hover:border-sas-accent hover:text-sas-accent"}`,
|
|
@@ -2817,14 +3265,14 @@ function ImportTrackModal({
|
|
|
2817
3265
|
"data-testid": `${testIdPrefix}-track`,
|
|
2818
3266
|
"data-importable": mode === "sound" || track.importable ? "true" : "false",
|
|
2819
3267
|
children: [
|
|
2820
|
-
/* @__PURE__ */ (0,
|
|
3268
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "truncate", children: [
|
|
2821
3269
|
track.name,
|
|
2822
|
-
track.role ? /* @__PURE__ */ (0,
|
|
3270
|
+
track.role ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-sas-muted", children: [
|
|
2823
3271
|
" \xB7 ",
|
|
2824
3272
|
track.role
|
|
2825
3273
|
] }) : null
|
|
2826
3274
|
] }),
|
|
2827
|
-
busy ? /* @__PURE__ */ (0,
|
|
3275
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sas-muted", children: "\u2298" }) : null
|
|
2828
3276
|
]
|
|
2829
3277
|
}
|
|
2830
3278
|
) }, track.dbId);
|
|
@@ -2836,8 +3284,38 @@ function ImportTrackModal({
|
|
|
2836
3284
|
}
|
|
2837
3285
|
|
|
2838
3286
|
// src/components/CrossfadeModal.tsx
|
|
2839
|
-
var
|
|
2840
|
-
var
|
|
3287
|
+
var import_react14 = require("react");
|
|
3288
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3289
|
+
function shortId2(dbId) {
|
|
3290
|
+
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3291
|
+
}
|
|
3292
|
+
function CandidateRow({
|
|
3293
|
+
track,
|
|
3294
|
+
selected,
|
|
3295
|
+
disabled,
|
|
3296
|
+
onSelect,
|
|
3297
|
+
testId
|
|
3298
|
+
}) {
|
|
3299
|
+
const primary = track.prompt?.trim() || track.name;
|
|
3300
|
+
const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
|
|
3301
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3302
|
+
"button",
|
|
3303
|
+
{
|
|
3304
|
+
type: "button",
|
|
3305
|
+
role: "radio",
|
|
3306
|
+
"aria-checked": selected,
|
|
3307
|
+
"data-testid": testId,
|
|
3308
|
+
"data-value": track.dbId,
|
|
3309
|
+
onClick: onSelect,
|
|
3310
|
+
disabled,
|
|
3311
|
+
className: `w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${selected ? "bg-sas-accent/15 border-sas-accent" : "bg-sas-panel border-sas-border hover:border-sas-accent/50"}`,
|
|
3312
|
+
children: [
|
|
3313
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
3314
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
3315
|
+
]
|
|
3316
|
+
}
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
2841
3319
|
function CrossfadeModal({
|
|
2842
3320
|
host,
|
|
2843
3321
|
open,
|
|
@@ -2845,34 +3323,40 @@ function CrossfadeModal({
|
|
|
2845
3323
|
toSceneId,
|
|
2846
3324
|
fromSceneName,
|
|
2847
3325
|
toSceneName,
|
|
3326
|
+
excludeSourceDbIds,
|
|
2848
3327
|
onClose,
|
|
2849
3328
|
onCreate,
|
|
2850
3329
|
testIdPrefix = "crossfade-modal"
|
|
2851
3330
|
}) {
|
|
2852
|
-
const [load, setLoad] = (0,
|
|
2853
|
-
const [originDbId, setOriginDbId] = (0,
|
|
2854
|
-
const [targetDbId, setTargetDbId] = (0,
|
|
2855
|
-
const [isCreating, setIsCreating] = (0,
|
|
2856
|
-
const [error, setError] = (0,
|
|
2857
|
-
const
|
|
2858
|
-
const
|
|
3331
|
+
const [load, setLoad] = (0, import_react14.useState)({ status: "loading" });
|
|
3332
|
+
const [originDbId, setOriginDbId] = (0, import_react14.useState)("");
|
|
3333
|
+
const [targetDbId, setTargetDbId] = (0, import_react14.useState)("");
|
|
3334
|
+
const [isCreating, setIsCreating] = (0, import_react14.useState)(false);
|
|
3335
|
+
const [error, setError] = (0, import_react14.useState)(null);
|
|
3336
|
+
const [fromName, setFromName] = (0, import_react14.useState)(null);
|
|
3337
|
+
const [toName, setToName] = (0, import_react14.useState)(null);
|
|
3338
|
+
const cancelRef = (0, import_react14.useRef)(null);
|
|
3339
|
+
const refresh = (0, import_react14.useCallback)(async () => {
|
|
2859
3340
|
if (!host.listSceneFamilyTracks) {
|
|
2860
3341
|
setLoad({ status: "error", message: "This host does not support crossfade tracks." });
|
|
2861
3342
|
return;
|
|
2862
3343
|
}
|
|
2863
3344
|
setLoad({ status: "loading" });
|
|
2864
3345
|
try {
|
|
2865
|
-
const [origin, target] = await Promise.all([
|
|
3346
|
+
const [origin, target, fName, tName] = await Promise.all([
|
|
2866
3347
|
host.listSceneFamilyTracks(fromSceneId),
|
|
2867
|
-
host.listSceneFamilyTracks(toSceneId)
|
|
3348
|
+
host.listSceneFamilyTracks(toSceneId),
|
|
3349
|
+
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
3350
|
+
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null)
|
|
2868
3351
|
]);
|
|
3352
|
+
setFromName(fName);
|
|
3353
|
+
setToName(tName);
|
|
2869
3354
|
setLoad({ status: "ready", origin, target });
|
|
2870
|
-
setOriginDbId(origin[0]?.dbId ?? "");
|
|
2871
3355
|
} catch (err) {
|
|
2872
3356
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
2873
3357
|
}
|
|
2874
3358
|
}, [host, fromSceneId, toSceneId]);
|
|
2875
|
-
(0,
|
|
3359
|
+
(0, import_react14.useEffect)(() => {
|
|
2876
3360
|
if (open) {
|
|
2877
3361
|
setError(null);
|
|
2878
3362
|
setIsCreating(false);
|
|
@@ -2881,27 +3365,32 @@ function CrossfadeModal({
|
|
|
2881
3365
|
void refresh();
|
|
2882
3366
|
}
|
|
2883
3367
|
}, [open, refresh]);
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2886
|
-
|
|
3368
|
+
const excludeSet = (0, import_react14.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3369
|
+
const originCandidates = (0, import_react14.useMemo)(
|
|
3370
|
+
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3371
|
+
[load, excludeSet]
|
|
2887
3372
|
);
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3373
|
+
const targetCandidates = (0, import_react14.useMemo)(
|
|
3374
|
+
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3375
|
+
[load, excludeSet]
|
|
3376
|
+
);
|
|
3377
|
+
(0, import_react14.useEffect)(() => {
|
|
3378
|
+
if (!originCandidates.some((t) => t.dbId === originDbId)) {
|
|
3379
|
+
setOriginDbId(originCandidates[0]?.dbId ?? "");
|
|
3380
|
+
}
|
|
3381
|
+
}, [originCandidates, originDbId]);
|
|
3382
|
+
(0, import_react14.useEffect)(() => {
|
|
2895
3383
|
if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
|
|
2896
3384
|
setTargetDbId(targetCandidates[0]?.dbId ?? "");
|
|
2897
3385
|
}
|
|
2898
3386
|
}, [targetCandidates, targetDbId]);
|
|
3387
|
+
const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
|
|
2899
3388
|
const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
|
|
2900
3389
|
const canCreate = !isCreating && !!originTrack && !!targetTrack;
|
|
2901
|
-
const handleClose = (0,
|
|
3390
|
+
const handleClose = (0, import_react14.useCallback)(() => {
|
|
2902
3391
|
if (!isCreating) onClose();
|
|
2903
3392
|
}, [isCreating, onClose]);
|
|
2904
|
-
const handleCreate = (0,
|
|
3393
|
+
const handleCreate = (0, import_react14.useCallback)(async () => {
|
|
2905
3394
|
if (!originTrack || !targetTrack) return;
|
|
2906
3395
|
setIsCreating(true);
|
|
2907
3396
|
setError(null);
|
|
@@ -2916,79 +3405,100 @@ function CrossfadeModal({
|
|
|
2916
3405
|
setIsCreating(false);
|
|
2917
3406
|
}
|
|
2918
3407
|
}, [originTrack, targetTrack, onCreate, onClose]);
|
|
3408
|
+
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
3409
|
+
const toLabel = toName ?? toSceneName ?? null;
|
|
2919
3410
|
if (!open) return null;
|
|
2920
|
-
return /* @__PURE__ */ (0,
|
|
3411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
2921
3412
|
"div",
|
|
2922
3413
|
{
|
|
2923
3414
|
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
2924
3415
|
onClick: (e) => e.stopPropagation(),
|
|
2925
3416
|
"data-testid": `${testIdPrefix}-box`,
|
|
2926
3417
|
children: [
|
|
2927
|
-
/* @__PURE__ */ (0,
|
|
2928
|
-
/* @__PURE__ */ (0,
|
|
3418
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
|
|
3419
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
2929
3420
|
"Bridge a track from",
|
|
2930
3421
|
" ",
|
|
2931
|
-
/* @__PURE__ */ (0,
|
|
3422
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
2932
3423
|
" into one from",
|
|
2933
3424
|
" ",
|
|
2934
|
-
/* @__PURE__ */ (0,
|
|
3425
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
2935
3426
|
". Both layers share one generated part; each keeps its own preset."
|
|
2936
3427
|
] }),
|
|
2937
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
2938
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
2939
|
-
load.status === "ready" && (
|
|
3428
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3429
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3430
|
+
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
2940
3431
|
"div",
|
|
2941
3432
|
{
|
|
2942
3433
|
className: "text-xs text-sas-muted py-4 text-center",
|
|
2943
3434
|
"data-testid": `${testIdPrefix}-empty-origin`,
|
|
2944
|
-
children:
|
|
3435
|
+
children: [
|
|
3436
|
+
"No available tracks in ",
|
|
3437
|
+
fromLabel ?? "the origin scene",
|
|
3438
|
+
". Add one (or free one from another crossfade) first."
|
|
3439
|
+
]
|
|
2945
3440
|
}
|
|
2946
|
-
) : /* @__PURE__ */ (0,
|
|
2947
|
-
/* @__PURE__ */ (0,
|
|
2948
|
-
/* @__PURE__ */ (0,
|
|
2949
|
-
|
|
2950
|
-
"
|
|
3441
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
3442
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
|
|
3443
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
3444
|
+
"Origin ",
|
|
3445
|
+
fromLabel ? `(${fromLabel})` : "(top)"
|
|
3446
|
+
] }),
|
|
3447
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3448
|
+
"div",
|
|
2951
3449
|
{
|
|
2952
|
-
"
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3450
|
+
role: "radiogroup",
|
|
3451
|
+
"aria-label": "Origin track",
|
|
3452
|
+
"data-testid": `${testIdPrefix}-origin-list`,
|
|
3453
|
+
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3454
|
+
children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3455
|
+
CandidateRow,
|
|
3456
|
+
{
|
|
3457
|
+
track: t,
|
|
3458
|
+
selected: t.dbId === originDbId,
|
|
3459
|
+
disabled: isCreating,
|
|
3460
|
+
onSelect: () => setOriginDbId(t.dbId),
|
|
3461
|
+
testId: `${testIdPrefix}-origin-option-${t.dbId}`
|
|
3462
|
+
},
|
|
3463
|
+
t.dbId
|
|
3464
|
+
))
|
|
2961
3465
|
}
|
|
2962
3466
|
)
|
|
2963
3467
|
] }),
|
|
2964
|
-
/* @__PURE__ */ (0,
|
|
2965
|
-
/* @__PURE__ */ (0,
|
|
2966
|
-
"Target
|
|
2967
|
-
|
|
3468
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
|
|
3469
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
3470
|
+
"Target ",
|
|
3471
|
+
toLabel ? `(${toLabel})` : "(bottom)"
|
|
2968
3472
|
] }),
|
|
2969
|
-
targetCandidates.length === 0 ? /* @__PURE__ */ (0,
|
|
2970
|
-
"No ",
|
|
2971
|
-
|
|
2972
|
-
"
|
|
2973
|
-
] }) : /* @__PURE__ */ (0,
|
|
2974
|
-
"
|
|
3473
|
+
targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
|
|
3474
|
+
"No available tracks in ",
|
|
3475
|
+
toLabel ?? "the target scene",
|
|
3476
|
+
" to crossfade into."
|
|
3477
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3478
|
+
"div",
|
|
2975
3479
|
{
|
|
2976
|
-
"
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3480
|
+
role: "radiogroup",
|
|
3481
|
+
"aria-label": "Target track",
|
|
3482
|
+
"data-testid": `${testIdPrefix}-target-list`,
|
|
3483
|
+
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3484
|
+
children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3485
|
+
CandidateRow,
|
|
3486
|
+
{
|
|
3487
|
+
track: t,
|
|
3488
|
+
selected: t.dbId === targetDbId,
|
|
3489
|
+
disabled: isCreating,
|
|
3490
|
+
onSelect: () => setTargetDbId(t.dbId),
|
|
3491
|
+
testId: `${testIdPrefix}-target-option-${t.dbId}`
|
|
3492
|
+
},
|
|
3493
|
+
t.dbId
|
|
3494
|
+
))
|
|
2985
3495
|
}
|
|
2986
3496
|
)
|
|
2987
3497
|
] })
|
|
2988
3498
|
] })),
|
|
2989
|
-
error && /* @__PURE__ */ (0,
|
|
2990
|
-
/* @__PURE__ */ (0,
|
|
2991
|
-
/* @__PURE__ */ (0,
|
|
3499
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
3500
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
3501
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
2992
3502
|
"button",
|
|
2993
3503
|
{
|
|
2994
3504
|
ref: cancelRef,
|
|
@@ -2999,7 +3509,7 @@ function CrossfadeModal({
|
|
|
2999
3509
|
children: "Cancel"
|
|
3000
3510
|
}
|
|
3001
3511
|
),
|
|
3002
|
-
/* @__PURE__ */ (0,
|
|
3512
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3003
3513
|
"button",
|
|
3004
3514
|
{
|
|
3005
3515
|
"data-testid": `${testIdPrefix}-confirm`,
|
|
@@ -3016,8 +3526,8 @@ function CrossfadeModal({
|
|
|
3016
3526
|
}
|
|
3017
3527
|
|
|
3018
3528
|
// src/components/DownloadPackButton.tsx
|
|
3019
|
-
var
|
|
3020
|
-
var
|
|
3529
|
+
var import_react15 = require("react");
|
|
3530
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
3021
3531
|
function formatSize(bytes) {
|
|
3022
3532
|
if (!bytes || bytes <= 0) return "";
|
|
3023
3533
|
const gb = bytes / 1024 ** 3;
|
|
@@ -3033,10 +3543,10 @@ var DownloadPackButton = ({
|
|
|
3033
3543
|
variant = "compact",
|
|
3034
3544
|
onDownloadComplete
|
|
3035
3545
|
}) => {
|
|
3036
|
-
const [status, setStatus] = (0,
|
|
3037
|
-
const [progress, setProgress] = (0,
|
|
3038
|
-
const [errorMessage, setErrorMessage] = (0,
|
|
3039
|
-
(0,
|
|
3546
|
+
const [status, setStatus] = (0, import_react15.useState)("idle");
|
|
3547
|
+
const [progress, setProgress] = (0, import_react15.useState)(0);
|
|
3548
|
+
const [errorMessage, setErrorMessage] = (0, import_react15.useState)(null);
|
|
3549
|
+
(0, import_react15.useEffect)(() => {
|
|
3040
3550
|
const unsub = host.onSamplePackProgress(packId, (p) => {
|
|
3041
3551
|
setStatus(p.status);
|
|
3042
3552
|
setProgress(p.progress);
|
|
@@ -3051,7 +3561,7 @@ var DownloadPackButton = ({
|
|
|
3051
3561
|
});
|
|
3052
3562
|
return unsub;
|
|
3053
3563
|
}, [host, packId, onDownloadComplete]);
|
|
3054
|
-
const handleClick = (0,
|
|
3564
|
+
const handleClick = (0, import_react15.useCallback)(async () => {
|
|
3055
3565
|
if (status !== "idle" && status !== "error") return;
|
|
3056
3566
|
try {
|
|
3057
3567
|
setStatus("downloading");
|
|
@@ -3105,8 +3615,8 @@ var DownloadPackButton = ({
|
|
|
3105
3615
|
} else {
|
|
3106
3616
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
3107
3617
|
}
|
|
3108
|
-
return /* @__PURE__ */ (0,
|
|
3109
|
-
/* @__PURE__ */ (0,
|
|
3618
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
|
|
3619
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
3110
3620
|
"button",
|
|
3111
3621
|
{
|
|
3112
3622
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -3117,12 +3627,12 @@ var DownloadPackButton = ({
|
|
|
3117
3627
|
children: buttonLabel
|
|
3118
3628
|
}
|
|
3119
3629
|
),
|
|
3120
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0,
|
|
3630
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
3121
3631
|
] });
|
|
3122
3632
|
};
|
|
3123
3633
|
|
|
3124
3634
|
// src/components/SamplePackCTACard.tsx
|
|
3125
|
-
var
|
|
3635
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3126
3636
|
var SamplePackCTACard = ({
|
|
3127
3637
|
host,
|
|
3128
3638
|
pack,
|
|
@@ -3130,7 +3640,7 @@ var SamplePackCTACard = ({
|
|
|
3130
3640
|
onDownloadComplete
|
|
3131
3641
|
}) => {
|
|
3132
3642
|
if (status === "checking") {
|
|
3133
|
-
return /* @__PURE__ */ (0,
|
|
3643
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3134
3644
|
"div",
|
|
3135
3645
|
{
|
|
3136
3646
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -3141,16 +3651,16 @@ var SamplePackCTACard = ({
|
|
|
3141
3651
|
}
|
|
3142
3652
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
3143
3653
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
3144
|
-
return /* @__PURE__ */ (0,
|
|
3654
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3145
3655
|
"div",
|
|
3146
3656
|
{
|
|
3147
3657
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
3148
3658
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
3149
3659
|
children: [
|
|
3150
|
-
/* @__PURE__ */ (0,
|
|
3151
|
-
/* @__PURE__ */ (0,
|
|
3152
|
-
/* @__PURE__ */ (0,
|
|
3153
|
-
/* @__PURE__ */ (0,
|
|
3660
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
3661
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
3662
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
3663
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3154
3664
|
DownloadPackButton,
|
|
3155
3665
|
{
|
|
3156
3666
|
host,
|
|
@@ -3167,7 +3677,7 @@ var SamplePackCTACard = ({
|
|
|
3167
3677
|
};
|
|
3168
3678
|
|
|
3169
3679
|
// src/components/WaveformView.tsx
|
|
3170
|
-
var
|
|
3680
|
+
var import_react16 = require("react");
|
|
3171
3681
|
|
|
3172
3682
|
// src/components/waveform.ts
|
|
3173
3683
|
function computePeaks(audioBuffer, bins, targetSamples) {
|
|
@@ -3230,7 +3740,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
3230
3740
|
}
|
|
3231
3741
|
|
|
3232
3742
|
// src/components/WaveformView.tsx
|
|
3233
|
-
var
|
|
3743
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
3234
3744
|
var WaveformView = ({
|
|
3235
3745
|
host,
|
|
3236
3746
|
filePath,
|
|
@@ -3239,9 +3749,9 @@ var WaveformView = ({
|
|
|
3239
3749
|
fillStyle,
|
|
3240
3750
|
targetSamples
|
|
3241
3751
|
}) => {
|
|
3242
|
-
const canvasRef = (0,
|
|
3243
|
-
const [peaks, setPeaks] = (0,
|
|
3244
|
-
(0,
|
|
3752
|
+
const canvasRef = (0, import_react16.useRef)(null);
|
|
3753
|
+
const [peaks, setPeaks] = (0, import_react16.useState)(null);
|
|
3754
|
+
(0, import_react16.useEffect)(() => {
|
|
3245
3755
|
let cancelled = false;
|
|
3246
3756
|
let audioContext = null;
|
|
3247
3757
|
(async () => {
|
|
@@ -3267,7 +3777,7 @@ var WaveformView = ({
|
|
|
3267
3777
|
cancelled = true;
|
|
3268
3778
|
};
|
|
3269
3779
|
}, [host, filePath, bins, targetSamples]);
|
|
3270
|
-
(0,
|
|
3780
|
+
(0, import_react16.useEffect)(() => {
|
|
3271
3781
|
if (!peaks) return;
|
|
3272
3782
|
const canvas = canvasRef.current;
|
|
3273
3783
|
if (!canvas) return;
|
|
@@ -3278,7 +3788,7 @@ var WaveformView = ({
|
|
|
3278
3788
|
observer.observe(canvas);
|
|
3279
3789
|
return () => observer.disconnect();
|
|
3280
3790
|
}, [peaks, fillStyle]);
|
|
3281
|
-
return /* @__PURE__ */ (0,
|
|
3791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
3282
3792
|
"canvas",
|
|
3283
3793
|
{
|
|
3284
3794
|
ref: canvasRef,
|
|
@@ -3289,8 +3799,8 @@ var WaveformView = ({
|
|
|
3289
3799
|
};
|
|
3290
3800
|
|
|
3291
3801
|
// src/components/ScrollingWaveform.tsx
|
|
3292
|
-
var
|
|
3293
|
-
var
|
|
3802
|
+
var import_react17 = require("react");
|
|
3803
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
3294
3804
|
var ScrollingWaveform = ({
|
|
3295
3805
|
getPeakDb,
|
|
3296
3806
|
active,
|
|
@@ -3298,11 +3808,11 @@ var ScrollingWaveform = ({
|
|
|
3298
3808
|
className,
|
|
3299
3809
|
fillStyle
|
|
3300
3810
|
}) => {
|
|
3301
|
-
const canvasRef = (0,
|
|
3302
|
-
const ringRef = (0,
|
|
3303
|
-
const writeIdxRef = (0,
|
|
3304
|
-
const rafRef = (0,
|
|
3305
|
-
(0,
|
|
3811
|
+
const canvasRef = (0, import_react17.useRef)(null);
|
|
3812
|
+
const ringRef = (0, import_react17.useRef)(new Float32Array(columns));
|
|
3813
|
+
const writeIdxRef = (0, import_react17.useRef)(0);
|
|
3814
|
+
const rafRef = (0, import_react17.useRef)(null);
|
|
3815
|
+
(0, import_react17.useEffect)(() => {
|
|
3306
3816
|
if (ringRef.current.length !== columns) {
|
|
3307
3817
|
const next = new Float32Array(columns);
|
|
3308
3818
|
const prev = ringRef.current;
|
|
@@ -3314,7 +3824,7 @@ var ScrollingWaveform = ({
|
|
|
3314
3824
|
writeIdxRef.current = writeIdxRef.current % columns;
|
|
3315
3825
|
}
|
|
3316
3826
|
}, [columns]);
|
|
3317
|
-
(0,
|
|
3827
|
+
(0, import_react17.useEffect)(() => {
|
|
3318
3828
|
if (!active) {
|
|
3319
3829
|
if (rafRef.current !== null) {
|
|
3320
3830
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -3366,7 +3876,7 @@ var ScrollingWaveform = ({
|
|
|
3366
3876
|
}
|
|
3367
3877
|
};
|
|
3368
3878
|
}, [active, getPeakDb, fillStyle]);
|
|
3369
|
-
return /* @__PURE__ */ (0,
|
|
3879
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
3370
3880
|
"canvas",
|
|
3371
3881
|
{
|
|
3372
3882
|
ref: canvasRef,
|
|
@@ -3377,8 +3887,8 @@ var ScrollingWaveform = ({
|
|
|
3377
3887
|
};
|
|
3378
3888
|
|
|
3379
3889
|
// src/components/OffsetScrubber.tsx
|
|
3380
|
-
var
|
|
3381
|
-
var
|
|
3890
|
+
var import_react18 = require("react");
|
|
3891
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
3382
3892
|
var SLIDER_HEIGHT_PX = 28;
|
|
3383
3893
|
var TICK_HEIGHT_PX = 14;
|
|
3384
3894
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -3391,40 +3901,40 @@ function OffsetScrubber({
|
|
|
3391
3901
|
onChange,
|
|
3392
3902
|
disabled = false
|
|
3393
3903
|
}) {
|
|
3394
|
-
const trackRef = (0,
|
|
3395
|
-
const [draftOffset, setDraftOffset] = (0,
|
|
3396
|
-
const [isDragging, setIsDragging] = (0,
|
|
3397
|
-
(0,
|
|
3904
|
+
const trackRef = (0, import_react18.useRef)(null);
|
|
3905
|
+
const [draftOffset, setDraftOffset] = (0, import_react18.useState)(offsetSamples);
|
|
3906
|
+
const [isDragging, setIsDragging] = (0, import_react18.useState)(false);
|
|
3907
|
+
(0, import_react18.useEffect)(() => {
|
|
3398
3908
|
if (!isDragging) setDraftOffset(offsetSamples);
|
|
3399
3909
|
}, [offsetSamples, isDragging]);
|
|
3400
3910
|
const sampleRate = cuePoints?.sample_rate ?? 44100;
|
|
3401
3911
|
const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
|
|
3402
|
-
const beatsForRange = (0,
|
|
3912
|
+
const beatsForRange = (0, import_react18.useMemo)(() => {
|
|
3403
3913
|
return Math.round(60 / projectBpm * sampleRate);
|
|
3404
3914
|
}, [projectBpm, sampleRate]);
|
|
3405
3915
|
const rangeSamples = beatsForRange * meter;
|
|
3406
|
-
const sampleToFraction = (0,
|
|
3916
|
+
const sampleToFraction = (0, import_react18.useCallback)(
|
|
3407
3917
|
(sample) => {
|
|
3408
3918
|
const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
|
|
3409
3919
|
return (clamped + rangeSamples) / (2 * rangeSamples);
|
|
3410
3920
|
},
|
|
3411
3921
|
[rangeSamples]
|
|
3412
3922
|
);
|
|
3413
|
-
const fractionToSample = (0,
|
|
3923
|
+
const fractionToSample = (0, import_react18.useCallback)(
|
|
3414
3924
|
(fraction) => {
|
|
3415
3925
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
3416
3926
|
return Math.round(clamped * 2 * rangeSamples - rangeSamples);
|
|
3417
3927
|
},
|
|
3418
3928
|
[rangeSamples]
|
|
3419
3929
|
);
|
|
3420
|
-
const snapTargets = (0,
|
|
3930
|
+
const snapTargets = (0, import_react18.useMemo)(() => {
|
|
3421
3931
|
if (!cuePoints || cuePoints.beats.length === 0) return [];
|
|
3422
3932
|
const downbeat = cuePoints.beats[0];
|
|
3423
3933
|
const positives = cuePoints.beats.map((b) => b - downbeat);
|
|
3424
3934
|
const negatives = positives.slice(1).map((p) => -p);
|
|
3425
3935
|
return [...negatives, ...positives].sort((a, b) => a - b);
|
|
3426
3936
|
}, [cuePoints]);
|
|
3427
|
-
const snapToBeat = (0,
|
|
3937
|
+
const snapToBeat = (0, import_react18.useCallback)(
|
|
3428
3938
|
(sample) => {
|
|
3429
3939
|
if (snapTargets.length === 0) return sample;
|
|
3430
3940
|
let best = snapTargets[0];
|
|
@@ -3440,7 +3950,7 @@ function OffsetScrubber({
|
|
|
3440
3950
|
},
|
|
3441
3951
|
[snapTargets]
|
|
3442
3952
|
);
|
|
3443
|
-
const handlePointerDown = (0,
|
|
3953
|
+
const handlePointerDown = (0, import_react18.useCallback)(
|
|
3444
3954
|
(e) => {
|
|
3445
3955
|
if (disabled || !cuePoints) return;
|
|
3446
3956
|
e.preventDefault();
|
|
@@ -3474,7 +3984,7 @@ function OffsetScrubber({
|
|
|
3474
3984
|
},
|
|
3475
3985
|
[disabled, cuePoints, fractionToSample, onChange, snapToBeat]
|
|
3476
3986
|
);
|
|
3477
|
-
const handleResetToZero = (0,
|
|
3987
|
+
const handleResetToZero = (0, import_react18.useCallback)(() => {
|
|
3478
3988
|
if (disabled) return;
|
|
3479
3989
|
setDraftOffset(0);
|
|
3480
3990
|
onChange(0);
|
|
@@ -3482,7 +3992,7 @@ function OffsetScrubber({
|
|
|
3482
3992
|
const thumbFraction = sampleToFraction(draftOffset);
|
|
3483
3993
|
const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
|
|
3484
3994
|
const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
|
|
3485
|
-
const ticks = (0,
|
|
3995
|
+
const ticks = (0, import_react18.useMemo)(() => {
|
|
3486
3996
|
if (!cuePoints) return [];
|
|
3487
3997
|
const downbeat = cuePoints.beats[0] ?? 0;
|
|
3488
3998
|
return cuePoints.beats.map((b, i) => {
|
|
@@ -3493,9 +4003,9 @@ function OffsetScrubber({
|
|
|
3493
4003
|
});
|
|
3494
4004
|
}, [cuePoints, sampleToFraction]);
|
|
3495
4005
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
3496
|
-
return /* @__PURE__ */ (0,
|
|
3497
|
-
/* @__PURE__ */ (0,
|
|
3498
|
-
/* @__PURE__ */ (0,
|
|
4006
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
4007
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
4008
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
3499
4009
|
"div",
|
|
3500
4010
|
{
|
|
3501
4011
|
ref: trackRef,
|
|
@@ -3511,7 +4021,7 @@ function OffsetScrubber({
|
|
|
3511
4021
|
"aria-valuenow": draftOffset,
|
|
3512
4022
|
"aria-disabled": isDisabled,
|
|
3513
4023
|
children: [
|
|
3514
|
-
/* @__PURE__ */ (0,
|
|
4024
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3515
4025
|
"div",
|
|
3516
4026
|
{
|
|
3517
4027
|
"aria-hidden": "true",
|
|
@@ -3519,7 +4029,7 @@ function OffsetScrubber({
|
|
|
3519
4029
|
style: { left: "50%" }
|
|
3520
4030
|
}
|
|
3521
4031
|
),
|
|
3522
|
-
ticks.map((t) => /* @__PURE__ */ (0,
|
|
4032
|
+
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3523
4033
|
"div",
|
|
3524
4034
|
{
|
|
3525
4035
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -3534,7 +4044,7 @@ function OffsetScrubber({
|
|
|
3534
4044
|
},
|
|
3535
4045
|
t.i
|
|
3536
4046
|
)),
|
|
3537
|
-
/* @__PURE__ */ (0,
|
|
4047
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3538
4048
|
"div",
|
|
3539
4049
|
{
|
|
3540
4050
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -3551,7 +4061,7 @@ function OffsetScrubber({
|
|
|
3551
4061
|
]
|
|
3552
4062
|
}
|
|
3553
4063
|
),
|
|
3554
|
-
/* @__PURE__ */ (0,
|
|
4064
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3555
4065
|
"span",
|
|
3556
4066
|
{
|
|
3557
4067
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -3559,7 +4069,7 @@ function OffsetScrubber({
|
|
|
3559
4069
|
children: formatOffset(draftOffset, sampleRate)
|
|
3560
4070
|
}
|
|
3561
4071
|
),
|
|
3562
|
-
/* @__PURE__ */ (0,
|
|
4072
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3563
4073
|
"button",
|
|
3564
4074
|
{
|
|
3565
4075
|
type: "button",
|
|
@@ -3571,7 +4081,7 @@ function OffsetScrubber({
|
|
|
3571
4081
|
children: "\u2316"
|
|
3572
4082
|
}
|
|
3573
4083
|
),
|
|
3574
|
-
bpmMismatch && /* @__PURE__ */ (0,
|
|
4084
|
+
bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3575
4085
|
"span",
|
|
3576
4086
|
{
|
|
3577
4087
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -3643,13 +4153,13 @@ function synthesizeCuePoints({
|
|
|
3643
4153
|
}
|
|
3644
4154
|
|
|
3645
4155
|
// src/hooks/useSceneState.ts
|
|
3646
|
-
var
|
|
4156
|
+
var import_react19 = require("react");
|
|
3647
4157
|
function useSceneState(activeSceneId, initialValue) {
|
|
3648
|
-
const [stateMap, setStateMap] = (0,
|
|
3649
|
-
const activeSceneIdRef = (0,
|
|
4158
|
+
const [stateMap, setStateMap] = (0, import_react19.useState)(() => /* @__PURE__ */ new Map());
|
|
4159
|
+
const activeSceneIdRef = (0, import_react19.useRef)(activeSceneId);
|
|
3650
4160
|
activeSceneIdRef.current = activeSceneId;
|
|
3651
4161
|
const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
|
|
3652
|
-
const setForCurrentScene = (0,
|
|
4162
|
+
const setForCurrentScene = (0, import_react19.useCallback)((value) => {
|
|
3653
4163
|
const sid = activeSceneIdRef.current;
|
|
3654
4164
|
if (sid === null) return;
|
|
3655
4165
|
setStateMap((prev) => {
|
|
@@ -3660,7 +4170,7 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
3660
4170
|
return newMap;
|
|
3661
4171
|
});
|
|
3662
4172
|
}, [initialValue]);
|
|
3663
|
-
const setForScene = (0,
|
|
4173
|
+
const setForScene = (0, import_react19.useCallback)((sceneId, value) => {
|
|
3664
4174
|
setStateMap((prev) => {
|
|
3665
4175
|
const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
|
|
3666
4176
|
const next = typeof value === "function" ? value(current) : value;
|
|
@@ -3673,10 +4183,10 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
3673
4183
|
}
|
|
3674
4184
|
|
|
3675
4185
|
// src/hooks/useAnySolo.ts
|
|
3676
|
-
var
|
|
4186
|
+
var import_react20 = require("react");
|
|
3677
4187
|
function useAnySolo(host) {
|
|
3678
|
-
const [anySolo, setAnySolo] = (0,
|
|
3679
|
-
(0,
|
|
4188
|
+
const [anySolo, setAnySolo] = (0, import_react20.useState)(false);
|
|
4189
|
+
(0, import_react20.useEffect)(() => {
|
|
3680
4190
|
let active = true;
|
|
3681
4191
|
const refresh = () => {
|
|
3682
4192
|
host.isAnySoloActive().then((v) => {
|
|
@@ -3695,7 +4205,7 @@ function useAnySolo(host) {
|
|
|
3695
4205
|
}
|
|
3696
4206
|
|
|
3697
4207
|
// src/hooks/useSoundHistory.ts
|
|
3698
|
-
var
|
|
4208
|
+
var import_react21 = require("react");
|
|
3699
4209
|
var EMPTY = { entries: [], cursor: -1 };
|
|
3700
4210
|
function sameDescriptor(a, b) {
|
|
3701
4211
|
if (a === b) return true;
|
|
@@ -3707,14 +4217,14 @@ function sameDescriptor(a, b) {
|
|
|
3707
4217
|
}
|
|
3708
4218
|
function useSoundHistory(applySound, opts = {}) {
|
|
3709
4219
|
const max = Math.max(2, opts.max ?? 24);
|
|
3710
|
-
const applyRef = (0,
|
|
4220
|
+
const applyRef = (0, import_react21.useRef)(applySound);
|
|
3711
4221
|
applyRef.current = applySound;
|
|
3712
|
-
const onChangeRef = (0,
|
|
4222
|
+
const onChangeRef = (0, import_react21.useRef)(opts.onChange);
|
|
3713
4223
|
onChangeRef.current = opts.onChange;
|
|
3714
|
-
const dataRef = (0,
|
|
3715
|
-
const [, setVersion] = (0,
|
|
3716
|
-
const bump = (0,
|
|
3717
|
-
const commit = (0,
|
|
4224
|
+
const dataRef = (0, import_react21.useRef)({});
|
|
4225
|
+
const [, setVersion] = (0, import_react21.useState)(0);
|
|
4226
|
+
const bump = (0, import_react21.useCallback)(() => setVersion((v) => v + 1), []);
|
|
4227
|
+
const commit = (0, import_react21.useCallback)(
|
|
3718
4228
|
(trackId, next, notify) => {
|
|
3719
4229
|
dataRef.current = { ...dataRef.current, [trackId]: next };
|
|
3720
4230
|
bump();
|
|
@@ -3722,7 +4232,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3722
4232
|
},
|
|
3723
4233
|
[bump]
|
|
3724
4234
|
);
|
|
3725
|
-
const record = (0,
|
|
4235
|
+
const record = (0, import_react21.useCallback)(
|
|
3726
4236
|
(trackId, descriptor, label) => {
|
|
3727
4237
|
const h = dataRef.current[trackId];
|
|
3728
4238
|
const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
|
|
@@ -3737,7 +4247,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3737
4247
|
},
|
|
3738
4248
|
[max, commit]
|
|
3739
4249
|
);
|
|
3740
|
-
const restoreTo = (0,
|
|
4250
|
+
const restoreTo = (0, import_react21.useCallback)(
|
|
3741
4251
|
async (trackId, index) => {
|
|
3742
4252
|
const h = dataRef.current[trackId];
|
|
3743
4253
|
if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
|
|
@@ -3747,7 +4257,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3747
4257
|
},
|
|
3748
4258
|
[commit]
|
|
3749
4259
|
);
|
|
3750
|
-
const undo = (0,
|
|
4260
|
+
const undo = (0, import_react21.useCallback)(
|
|
3751
4261
|
(trackId) => {
|
|
3752
4262
|
const h = dataRef.current[trackId];
|
|
3753
4263
|
if (!h || h.cursor <= 0) return Promise.resolve(false);
|
|
@@ -3755,7 +4265,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3755
4265
|
},
|
|
3756
4266
|
[restoreTo]
|
|
3757
4267
|
);
|
|
3758
|
-
const toggleFavorite = (0,
|
|
4268
|
+
const toggleFavorite = (0, import_react21.useCallback)(
|
|
3759
4269
|
(trackId, index) => {
|
|
3760
4270
|
const h = dataRef.current[trackId];
|
|
3761
4271
|
if (!h || index < 0 || index >= h.entries.length) return;
|
|
@@ -3764,7 +4274,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3764
4274
|
},
|
|
3765
4275
|
[commit]
|
|
3766
4276
|
);
|
|
3767
|
-
const restore = (0,
|
|
4277
|
+
const restore = (0, import_react21.useCallback)(
|
|
3768
4278
|
(trackId, state) => {
|
|
3769
4279
|
const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
|
|
3770
4280
|
const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
|
|
@@ -3773,15 +4283,15 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3773
4283
|
},
|
|
3774
4284
|
[commit]
|
|
3775
4285
|
);
|
|
3776
|
-
const list = (0,
|
|
4286
|
+
const list = (0, import_react21.useCallback)(
|
|
3777
4287
|
(trackId) => dataRef.current[trackId] ?? EMPTY,
|
|
3778
4288
|
[]
|
|
3779
4289
|
);
|
|
3780
|
-
const canUndo = (0,
|
|
4290
|
+
const canUndo = (0, import_react21.useCallback)((trackId) => {
|
|
3781
4291
|
const h = dataRef.current[trackId];
|
|
3782
4292
|
return !!h && h.cursor > 0;
|
|
3783
4293
|
}, []);
|
|
3784
|
-
const clear = (0,
|
|
4294
|
+
const clear = (0, import_react21.useCallback)(
|
|
3785
4295
|
(trackId) => {
|
|
3786
4296
|
if (dataRef.current[trackId]) {
|
|
3787
4297
|
const next = { ...dataRef.current };
|
|
@@ -3793,18 +4303,18 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3793
4303
|
},
|
|
3794
4304
|
[bump]
|
|
3795
4305
|
);
|
|
3796
|
-
const reset = (0,
|
|
4306
|
+
const reset = (0, import_react21.useCallback)(() => {
|
|
3797
4307
|
dataRef.current = {};
|
|
3798
4308
|
bump();
|
|
3799
4309
|
}, [bump]);
|
|
3800
|
-
return (0,
|
|
4310
|
+
return (0, import_react21.useMemo)(
|
|
3801
4311
|
() => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
|
|
3802
4312
|
[record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
|
|
3803
4313
|
);
|
|
3804
4314
|
}
|
|
3805
4315
|
|
|
3806
4316
|
// src/hooks/useTrackReorder.ts
|
|
3807
|
-
var
|
|
4317
|
+
var import_react22 = require("react");
|
|
3808
4318
|
function moveItem(arr, from, to) {
|
|
3809
4319
|
const next = arr.slice();
|
|
3810
4320
|
if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
|
|
@@ -3821,12 +4331,12 @@ function useTrackReorder({
|
|
|
3821
4331
|
getId,
|
|
3822
4332
|
onError
|
|
3823
4333
|
}) {
|
|
3824
|
-
const [draggingIndex, setDraggingIndex] = (0,
|
|
3825
|
-
const [dragOverIndex, setDragOverIndex] = (0,
|
|
3826
|
-
const fromRef = (0,
|
|
3827
|
-
const itemsRef = (0,
|
|
4334
|
+
const [draggingIndex, setDraggingIndex] = (0, import_react22.useState)(null);
|
|
4335
|
+
const [dragOverIndex, setDragOverIndex] = (0, import_react22.useState)(null);
|
|
4336
|
+
const fromRef = (0, import_react22.useRef)(null);
|
|
4337
|
+
const itemsRef = (0, import_react22.useRef)(items);
|
|
3828
4338
|
itemsRef.current = items;
|
|
3829
|
-
const dragPropsFor = (0,
|
|
4339
|
+
const dragPropsFor = (0, import_react22.useCallback)(
|
|
3830
4340
|
(index) => ({
|
|
3831
4341
|
handleProps: {
|
|
3832
4342
|
draggable: true,
|
|
@@ -3888,7 +4398,7 @@ function useTrackReorder({
|
|
|
3888
4398
|
}
|
|
3889
4399
|
|
|
3890
4400
|
// src/constants/sdk-version.ts
|
|
3891
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
4401
|
+
var PLUGIN_SDK_VERSION = "2.28.0";
|
|
3892
4402
|
|
|
3893
4403
|
// src/utils/format-concurrent-tracks.ts
|
|
3894
4404
|
function formatConcurrentTracks(ctx) {
|
|
@@ -4049,6 +4559,8 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
4049
4559
|
FX_DISPLAY_LABELS,
|
|
4050
4560
|
FX_ENGINE_PLUGIN_NAMES,
|
|
4051
4561
|
FX_PRESET_CONFIGS,
|
|
4562
|
+
FadeModal,
|
|
4563
|
+
FadeTrackRow,
|
|
4052
4564
|
FxToggleBar,
|
|
4053
4565
|
GUTTER_W,
|
|
4054
4566
|
ImportTrackModal,
|
|
@@ -4067,6 +4579,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
4067
4579
|
SamplePackCTACard,
|
|
4068
4580
|
ScrollingWaveform,
|
|
4069
4581
|
SorceryProgressBar,
|
|
4582
|
+
TEXTURAL_ROLES,
|
|
4070
4583
|
TrackDrawer,
|
|
4071
4584
|
TrackMeterStrip,
|
|
4072
4585
|
TrackRow,
|
|
@@ -4074,17 +4587,21 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
4074
4587
|
WaveformView,
|
|
4075
4588
|
analyzeWavPeak,
|
|
4076
4589
|
asCrossfadeMeta,
|
|
4590
|
+
asFadeMeta,
|
|
4077
4591
|
buildCrossfadeInpaintPrompt,
|
|
4078
4592
|
buildCrossfadeVolumeCurves,
|
|
4593
|
+
buildFadeVolumeCurve,
|
|
4079
4594
|
calculateTimeBasedTarget,
|
|
4080
4595
|
cellToPx,
|
|
4081
4596
|
centerScrollTop,
|
|
4082
4597
|
computePeaks,
|
|
4083
4598
|
dbToSlider,
|
|
4599
|
+
defaultFadeGesture,
|
|
4084
4600
|
drawWaveform,
|
|
4085
4601
|
formatConcurrentTracks,
|
|
4086
4602
|
moveItem,
|
|
4087
4603
|
parseCrossfadePairs,
|
|
4604
|
+
parseFades,
|
|
4088
4605
|
pickTopKWeighted,
|
|
4089
4606
|
pitchToName,
|
|
4090
4607
|
pxToCell,
|