@signalsandsorcery/plugin-sdk 2.35.4 → 2.35.6
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 +539 -6
- package/dist/index.d.ts +539 -6
- package/dist/index.js +1574 -472
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1299 -215
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,6 +43,9 @@ __export(index_exports, {
|
|
|
43
43
|
DownloadPackButton: () => DownloadPackButton,
|
|
44
44
|
EMPTY_FX_DETAIL_STATE: () => EMPTY_FX_DETAIL_STATE,
|
|
45
45
|
EMPTY_FX_STATE: () => EMPTY_FX_STATE,
|
|
46
|
+
ENSEMBLE_MAX_VOICES: () => ENSEMBLE_MAX_VOICES,
|
|
47
|
+
ENSEMBLE_MIN_VOICES: () => ENSEMBLE_MIN_VOICES,
|
|
48
|
+
ENSEMBLE_STYLES: () => ENSEMBLE_STYLES,
|
|
46
49
|
EQUAL_POWER_GAIN: () => EQUAL_POWER_GAIN,
|
|
47
50
|
FX_CATEGORIES: () => FX_CATEGORIES,
|
|
48
51
|
FX_CHAIN_ORDER: () => FX_CHAIN_ORDER,
|
|
@@ -54,9 +57,11 @@ __export(index_exports, {
|
|
|
54
57
|
FxToggleBar: () => FxToggleBar,
|
|
55
58
|
GUTTER_W: () => GUTTER_W,
|
|
56
59
|
GeneratorPanelShell: () => GeneratorPanelShell,
|
|
60
|
+
GroupFadeTrackRow: () => GroupFadeTrackRow,
|
|
57
61
|
ImportTrackModal: () => ImportTrackModal,
|
|
58
62
|
InstrumentDrawer: () => TrackDrawer,
|
|
59
63
|
LevelMeter: () => LevelMeter,
|
|
64
|
+
MIN_NOTE_DURATION_BEATS: () => MIN_NOTE_DURATION_BEATS,
|
|
60
65
|
Modal: () => Modal,
|
|
61
66
|
OffsetScrubber: () => OffsetScrubber,
|
|
62
67
|
PLUGIN_SDK_VERSION: () => PLUGIN_SDK_VERSION,
|
|
@@ -68,6 +73,8 @@ __export(index_exports, {
|
|
|
68
73
|
RESIZE_HANDLE_PX: () => RESIZE_HANDLE_PX,
|
|
69
74
|
ROW_HEIGHT: () => ROW_HEIGHT,
|
|
70
75
|
SLIDER_UNITY: () => SLIDER_UNITY,
|
|
76
|
+
STYLE_RULES: () => STYLE_RULES,
|
|
77
|
+
SUBMIT_ENSEMBLE_TOOL_NAME: () => SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
71
78
|
SamplePackCTACard: () => SamplePackCTACard,
|
|
72
79
|
ScrollingWaveform: () => ScrollingWaveform,
|
|
73
80
|
SorceryProgressBar: () => SorceryProgressBar,
|
|
@@ -80,6 +87,7 @@ __export(index_exports, {
|
|
|
80
87
|
TransitionDesigner: () => TransitionDesigner,
|
|
81
88
|
VolumeSlider: () => VolumeSlider,
|
|
82
89
|
WaveformView: () => WaveformView,
|
|
90
|
+
analyzeEnsemble: () => analyzeEnsemble,
|
|
83
91
|
analyzeWavPeak: () => analyzeWavPeak,
|
|
84
92
|
asAudioEffect: () => asAudioEffect,
|
|
85
93
|
asCrossfadeMeta: () => asCrossfadeMeta,
|
|
@@ -87,8 +95,11 @@ __export(index_exports, {
|
|
|
87
95
|
asTransitionDesignerDraft: () => asTransitionDesignerDraft,
|
|
88
96
|
buildCrossfadeInpaintPrompt: () => buildCrossfadeInpaintPrompt,
|
|
89
97
|
buildCrossfadeVolumeCurves: () => buildCrossfadeVolumeCurves,
|
|
98
|
+
buildEnsembleSystemPrompt: () => buildEnsembleSystemPrompt,
|
|
90
99
|
buildFadeVolumeCurve: () => buildFadeVolumeCurve,
|
|
91
100
|
buildRowSlots: () => buildRowSlots,
|
|
101
|
+
buildSubmitEnsembleParameters: () => buildSubmitEnsembleParameters,
|
|
102
|
+
buildViolationRetrySuffix: () => buildViolationRetrySuffix,
|
|
92
103
|
calculateTimeBasedTarget: () => calculateTimeBasedTarget,
|
|
93
104
|
cellToPx: () => cellToPx,
|
|
94
105
|
centerScrollTop: () => centerScrollTop,
|
|
@@ -97,15 +108,21 @@ __export(index_exports, {
|
|
|
97
108
|
dbIdsFromKeys: () => dbIdsFromKeys,
|
|
98
109
|
dbToSlider: () => dbToSlider,
|
|
99
110
|
defaultFadeGesture: () => defaultFadeGesture,
|
|
111
|
+
defaultVoiceSpecs: () => defaultVoiceSpecs,
|
|
112
|
+
describeViolations: () => describeViolations,
|
|
100
113
|
drawWaveform: () => drawWaveform,
|
|
114
|
+
enforceVoice: () => enforceVoice,
|
|
115
|
+
foldPitchToRegister: () => foldPitchToRegister,
|
|
101
116
|
formatConcurrentTracks: () => formatConcurrentTracks,
|
|
102
117
|
hashString: () => hashString,
|
|
103
118
|
moveItem: () => moveItem,
|
|
119
|
+
nearestPitchWithPc: () => nearestPitchWithPc,
|
|
104
120
|
newTrackState: () => newTrackState,
|
|
105
121
|
normalizeSlots: () => normalizeSlots,
|
|
106
122
|
padPair: () => padPair,
|
|
107
123
|
padSlots: () => padSlots,
|
|
108
124
|
parseCrossfadePairs: () => parseCrossfadePairs,
|
|
125
|
+
parseEnsembleArgs: () => parseEnsembleArgs,
|
|
109
126
|
parseFades: () => parseFades,
|
|
110
127
|
parseLLMNoteResponse: () => parseLLMNoteResponse,
|
|
111
128
|
parseTrackGroups: () => parseTrackGroups,
|
|
@@ -122,6 +139,7 @@ __export(index_exports, {
|
|
|
122
139
|
sliderToDb: () => sliderToDb,
|
|
123
140
|
slotsEqual: () => slotsEqual,
|
|
124
141
|
soundIdentity: () => soundIdentity,
|
|
142
|
+
splitFadeEntries: () => splitFadeEntries,
|
|
125
143
|
synthesizeCuePoints: () => synthesizeCuePoints,
|
|
126
144
|
tokenizePrompt: () => tokenizePrompt,
|
|
127
145
|
trackDataKey: () => trackDataKey,
|
|
@@ -2991,7 +3009,10 @@ function asFadeMeta(val) {
|
|
|
2991
3009
|
sourceSceneId: typeof m.sourceSceneId === "string" ? m.sourceSceneId : "",
|
|
2992
3010
|
sourceName: typeof m.sourceName === "string" ? m.sourceName : "",
|
|
2993
3011
|
soundLabel: typeof m.soundLabel === "string" ? m.soundLabel : "",
|
|
2994
|
-
sliderPos: typeof m.sliderPos === "number" ? m.sliderPos : 0.5
|
|
3012
|
+
sliderPos: typeof m.sliderPos === "number" ? m.sliderPos : 0.5,
|
|
3013
|
+
groupId: typeof m.groupId === "string" && m.groupId ? m.groupId : void 0,
|
|
3014
|
+
memberIndex: typeof m.memberIndex === "number" ? m.memberIndex : void 0,
|
|
3015
|
+
memberLabel: typeof m.memberLabel === "string" && m.memberLabel ? m.memberLabel : void 0
|
|
2995
3016
|
};
|
|
2996
3017
|
}
|
|
2997
3018
|
function parseFades(sceneData) {
|
|
@@ -3005,6 +3026,33 @@ function parseFades(sceneData) {
|
|
|
3005
3026
|
}
|
|
3006
3027
|
return out;
|
|
3007
3028
|
}
|
|
3029
|
+
function splitFadeEntries(entries) {
|
|
3030
|
+
const singles = [];
|
|
3031
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
3032
|
+
for (const entry of entries) {
|
|
3033
|
+
const gid = entry.meta.groupId;
|
|
3034
|
+
if (!gid) {
|
|
3035
|
+
singles.push(entry);
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
const list = byGroup.get(gid) ?? [];
|
|
3039
|
+
list.push(entry);
|
|
3040
|
+
byGroup.set(gid, list);
|
|
3041
|
+
}
|
|
3042
|
+
const groups = [];
|
|
3043
|
+
for (const [groupId, members] of byGroup) {
|
|
3044
|
+
members.sort((a, b) => (a.meta.memberIndex ?? 0) - (b.meta.memberIndex ?? 0));
|
|
3045
|
+
const head = members[0].meta;
|
|
3046
|
+
groups.push({
|
|
3047
|
+
groupId,
|
|
3048
|
+
direction: head.direction,
|
|
3049
|
+
gesture: head.gesture,
|
|
3050
|
+
sliderPos: head.sliderPos,
|
|
3051
|
+
members
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
return { singles, groups };
|
|
3055
|
+
}
|
|
3008
3056
|
function buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture, steps = 32) {
|
|
3009
3057
|
const durationSeconds = bars * 4 * 60 / Math.max(1, bpm);
|
|
3010
3058
|
if (gesture === "build") {
|
|
@@ -3189,9 +3237,191 @@ function FadeTrackRow({
|
|
|
3189
3237
|
);
|
|
3190
3238
|
}
|
|
3191
3239
|
|
|
3192
|
-
// src/components/
|
|
3193
|
-
var import_react14 = require("react");
|
|
3240
|
+
// src/components/GroupFadeTrackRow.tsx
|
|
3241
|
+
var import_react14 = __toESM(require("react"));
|
|
3194
3242
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3243
|
+
function MemberCaption({
|
|
3244
|
+
member,
|
|
3245
|
+
direction
|
|
3246
|
+
}) {
|
|
3247
|
+
const tag = direction === "in" ? "Fade in" : "Fade out";
|
|
3248
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
|
|
3249
|
+
member.memberLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: member.memberLabel }),
|
|
3250
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: member.sourceName ?? member.name, children: member.sourceName ?? member.name }),
|
|
3251
|
+
member.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: member.soundLabel, children: [
|
|
3252
|
+
"\xB7 ",
|
|
3253
|
+
member.soundLabel
|
|
3254
|
+
] }),
|
|
3255
|
+
!member.memberLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: [
|
|
3256
|
+
"\xB7 ",
|
|
3257
|
+
tag
|
|
3258
|
+
] })
|
|
3259
|
+
] });
|
|
3260
|
+
}
|
|
3261
|
+
function GroupFadeTrackRow({
|
|
3262
|
+
groupLabel,
|
|
3263
|
+
direction,
|
|
3264
|
+
gesture,
|
|
3265
|
+
sliderPos = 0.5,
|
|
3266
|
+
members,
|
|
3267
|
+
onMemberMuteToggle,
|
|
3268
|
+
onMemberSoloToggle,
|
|
3269
|
+
onMemberVolumeChange,
|
|
3270
|
+
onMemberPanChange,
|
|
3271
|
+
onMuteAll,
|
|
3272
|
+
onSoloAll,
|
|
3273
|
+
onDelete,
|
|
3274
|
+
onSliderChange,
|
|
3275
|
+
levels,
|
|
3276
|
+
accentColor = "#9333EA"
|
|
3277
|
+
}) {
|
|
3278
|
+
const [confirmDelete, setConfirmDelete] = import_react14.default.useState(false);
|
|
3279
|
+
const allMuted = members.length > 0 && members.every((m) => m.runtimeState.muted);
|
|
3280
|
+
const allSolo = members.length > 0 && members.every((m) => m.runtimeState.solo);
|
|
3281
|
+
const badge = direction === "in" ? "\u2197 Fade in" : "\u2198 Fade out";
|
|
3282
|
+
const leftLabel = direction === "in" ? "(silent)" : groupLabel;
|
|
3283
|
+
const rightLabel = direction === "in" ? groupLabel : "(silent)";
|
|
3284
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3285
|
+
"div",
|
|
3286
|
+
{
|
|
3287
|
+
"data-testid": "group-fade-track-row",
|
|
3288
|
+
className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
|
|
3289
|
+
style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
|
|
3290
|
+
children: [
|
|
3291
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60 gap-2", children: [
|
|
3292
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
3293
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3294
|
+
"span",
|
|
3295
|
+
{
|
|
3296
|
+
"data-testid": "group-fade-direction-badge",
|
|
3297
|
+
className: "text-[10px] font-bold uppercase tracking-wide flex-shrink-0",
|
|
3298
|
+
style: { color: accentColor },
|
|
3299
|
+
children: badge
|
|
3300
|
+
}
|
|
3301
|
+
),
|
|
3302
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3303
|
+
"span",
|
|
3304
|
+
{
|
|
3305
|
+
"data-testid": "group-fade-label",
|
|
3306
|
+
className: "text-[11px] text-sas-text truncate",
|
|
3307
|
+
title: `${groupLabel} \xB7 ${gesture}`,
|
|
3308
|
+
children: groupLabel
|
|
3309
|
+
}
|
|
3310
|
+
)
|
|
3311
|
+
] }),
|
|
3312
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
|
|
3313
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3314
|
+
"button",
|
|
3315
|
+
{
|
|
3316
|
+
"data-testid": "group-fade-mute-button",
|
|
3317
|
+
onClick: onMuteAll,
|
|
3318
|
+
className: `px-1.5 py-0.5 text-[9px] font-bold rounded-sm border transition-colors ${allMuted ? "bg-sas-danger/20 border-sas-danger text-sas-danger" : "border-sas-border text-sas-muted hover:text-sas-text"}`,
|
|
3319
|
+
title: allMuted ? "Unmute all members" : "Mute all members",
|
|
3320
|
+
children: "M"
|
|
3321
|
+
}
|
|
3322
|
+
),
|
|
3323
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3324
|
+
"button",
|
|
3325
|
+
{
|
|
3326
|
+
"data-testid": "group-fade-solo-button",
|
|
3327
|
+
onClick: onSoloAll,
|
|
3328
|
+
className: `px-1.5 py-0.5 text-[9px] font-bold rounded-sm border transition-colors ${allSolo ? "bg-sas-accent/20 border-sas-accent text-sas-accent" : "border-sas-border text-sas-muted hover:text-sas-text"}`,
|
|
3329
|
+
title: allSolo ? "Unsolo all members" : "Solo all members",
|
|
3330
|
+
children: "S"
|
|
3331
|
+
}
|
|
3332
|
+
),
|
|
3333
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3334
|
+
"button",
|
|
3335
|
+
{
|
|
3336
|
+
"data-testid": "group-fade-delete-button",
|
|
3337
|
+
onClick: () => setConfirmDelete(true),
|
|
3338
|
+
className: "text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm",
|
|
3339
|
+
title: "Delete group fade",
|
|
3340
|
+
"aria-label": "Delete group fade",
|
|
3341
|
+
children: "x"
|
|
3342
|
+
}
|
|
3343
|
+
)
|
|
3344
|
+
] })
|
|
3345
|
+
] }),
|
|
3346
|
+
members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3347
|
+
TrackRow,
|
|
3348
|
+
{
|
|
3349
|
+
track: { id: member.trackId, name: "", role: member.role },
|
|
3350
|
+
runtimeState: member.runtimeState,
|
|
3351
|
+
fxDetailState: EMPTY_FX_DETAIL_STATE,
|
|
3352
|
+
drawerOpen: false,
|
|
3353
|
+
drawerTab: "fx",
|
|
3354
|
+
levels,
|
|
3355
|
+
accentColor,
|
|
3356
|
+
contentSlot: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MemberCaption, { member, direction }),
|
|
3357
|
+
onMuteToggle: () => onMemberMuteToggle(member.trackId),
|
|
3358
|
+
onSoloToggle: () => onMemberSoloToggle(member.trackId),
|
|
3359
|
+
onVolumeChange: (vol) => onMemberVolumeChange(member.trackId, vol),
|
|
3360
|
+
onPanChange: (pan) => onMemberPanChange(member.trackId, pan)
|
|
3361
|
+
},
|
|
3362
|
+
member.trackId
|
|
3363
|
+
)),
|
|
3364
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "group-fade-slider-row", children: [
|
|
3365
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3366
|
+
"span",
|
|
3367
|
+
{
|
|
3368
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
|
|
3369
|
+
title: leftLabel,
|
|
3370
|
+
children: leftLabel
|
|
3371
|
+
}
|
|
3372
|
+
),
|
|
3373
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3374
|
+
"input",
|
|
3375
|
+
{
|
|
3376
|
+
type: "range",
|
|
3377
|
+
"data-testid": "group-fade-slider",
|
|
3378
|
+
min: 0,
|
|
3379
|
+
max: 1,
|
|
3380
|
+
step: 0.01,
|
|
3381
|
+
value: sliderPos,
|
|
3382
|
+
disabled: !onSliderChange,
|
|
3383
|
+
onChange: onSliderChange ? (e) => onSliderChange(Number(e.target.value)) : void 0,
|
|
3384
|
+
style: { accentColor },
|
|
3385
|
+
className: "flex-1 disabled:opacity-60 disabled:cursor-not-allowed",
|
|
3386
|
+
"aria-label": "Group fade position"
|
|
3387
|
+
}
|
|
3388
|
+
),
|
|
3389
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3390
|
+
"span",
|
|
3391
|
+
{
|
|
3392
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
|
|
3393
|
+
title: rightLabel,
|
|
3394
|
+
children: rightLabel
|
|
3395
|
+
}
|
|
3396
|
+
)
|
|
3397
|
+
] }),
|
|
3398
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3399
|
+
ConfirmDialog,
|
|
3400
|
+
{
|
|
3401
|
+
open: confirmDelete,
|
|
3402
|
+
title: "Delete group fade?",
|
|
3403
|
+
message: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
3404
|
+
"All ",
|
|
3405
|
+
members.length,
|
|
3406
|
+
" member tracks of this fade will be permanently removed from this scene. This cannot be undone."
|
|
3407
|
+
] }),
|
|
3408
|
+
confirmLabel: "Delete",
|
|
3409
|
+
onConfirm: () => {
|
|
3410
|
+
setConfirmDelete(false);
|
|
3411
|
+
onDelete();
|
|
3412
|
+
},
|
|
3413
|
+
onCancel: () => setConfirmDelete(false),
|
|
3414
|
+
testIdPrefix: "group-fade-delete-confirm"
|
|
3415
|
+
}
|
|
3416
|
+
)
|
|
3417
|
+
]
|
|
3418
|
+
}
|
|
3419
|
+
);
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
// src/components/FadeModal.tsx
|
|
3423
|
+
var import_react15 = require("react");
|
|
3424
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3195
3425
|
function shortId(dbId) {
|
|
3196
3426
|
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3197
3427
|
}
|
|
@@ -3234,7 +3464,7 @@ function OrphanRow({
|
|
|
3234
3464
|
}) {
|
|
3235
3465
|
const primary = track.prompt?.trim() || track.name;
|
|
3236
3466
|
const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
|
|
3237
|
-
return /* @__PURE__ */ (0,
|
|
3467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3238
3468
|
"button",
|
|
3239
3469
|
{
|
|
3240
3470
|
type: "button",
|
|
@@ -3246,8 +3476,8 @@ function OrphanRow({
|
|
|
3246
3476
|
disabled,
|
|
3247
3477
|
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"}`,
|
|
3248
3478
|
children: [
|
|
3249
|
-
/* @__PURE__ */ (0,
|
|
3250
|
-
meta && /* @__PURE__ */ (0,
|
|
3479
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
3480
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
3251
3481
|
]
|
|
3252
3482
|
}
|
|
3253
3483
|
);
|
|
@@ -3264,14 +3494,14 @@ function FadeModal({
|
|
|
3264
3494
|
onCreate,
|
|
3265
3495
|
testIdPrefix = "fade-modal"
|
|
3266
3496
|
}) {
|
|
3267
|
-
const [load, setLoad] = (0,
|
|
3268
|
-
const [selectedDbId, setSelectedDbId] = (0,
|
|
3269
|
-
const [isCreating, setIsCreating] = (0,
|
|
3270
|
-
const [error, setError] = (0,
|
|
3271
|
-
const [fromName, setFromName] = (0,
|
|
3272
|
-
const [toName, setToName] = (0,
|
|
3273
|
-
const cancelRef = (0,
|
|
3274
|
-
const refresh = (0,
|
|
3497
|
+
const [load, setLoad] = (0, import_react15.useState)({ status: "loading" });
|
|
3498
|
+
const [selectedDbId, setSelectedDbId] = (0, import_react15.useState)("");
|
|
3499
|
+
const [isCreating, setIsCreating] = (0, import_react15.useState)(false);
|
|
3500
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
3501
|
+
const [fromName, setFromName] = (0, import_react15.useState)(null);
|
|
3502
|
+
const [toName, setToName] = (0, import_react15.useState)(null);
|
|
3503
|
+
const cancelRef = (0, import_react15.useRef)(null);
|
|
3504
|
+
const refresh = (0, import_react15.useCallback)(async () => {
|
|
3275
3505
|
if (!host.listSceneFamilyTracks) {
|
|
3276
3506
|
setLoad({ status: "error", message: "This host does not support fades." });
|
|
3277
3507
|
return;
|
|
@@ -3291,7 +3521,7 @@ function FadeModal({
|
|
|
3291
3521
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
3292
3522
|
}
|
|
3293
3523
|
}, [host, fromSceneId, toSceneId]);
|
|
3294
|
-
(0,
|
|
3524
|
+
(0, import_react15.useEffect)(() => {
|
|
3295
3525
|
if (open) {
|
|
3296
3526
|
setError(null);
|
|
3297
3527
|
setIsCreating(false);
|
|
@@ -3299,29 +3529,29 @@ function FadeModal({
|
|
|
3299
3529
|
void refresh();
|
|
3300
3530
|
}
|
|
3301
3531
|
}, [open, refresh]);
|
|
3302
|
-
const excludeSet = (0,
|
|
3303
|
-
const { fadeOut, fadeIn } = (0,
|
|
3532
|
+
const excludeSet = (0, import_react15.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3533
|
+
const { fadeOut, fadeIn } = (0, import_react15.useMemo)(
|
|
3304
3534
|
() => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
|
|
3305
3535
|
[load, excludeSet]
|
|
3306
3536
|
);
|
|
3307
|
-
const allOrphans = (0,
|
|
3537
|
+
const allOrphans = (0, import_react15.useMemo)(
|
|
3308
3538
|
() => [
|
|
3309
3539
|
...fadeOut.map((t) => ({ track: t, direction: "out" })),
|
|
3310
3540
|
...fadeIn.map((t) => ({ track: t, direction: "in" }))
|
|
3311
3541
|
],
|
|
3312
3542
|
[fadeOut, fadeIn]
|
|
3313
3543
|
);
|
|
3314
|
-
(0,
|
|
3544
|
+
(0, import_react15.useEffect)(() => {
|
|
3315
3545
|
if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
|
|
3316
3546
|
setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
|
|
3317
3547
|
}
|
|
3318
3548
|
}, [allOrphans, selectedDbId]);
|
|
3319
3549
|
const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
|
|
3320
3550
|
const canCreate = !isCreating && !!selected;
|
|
3321
|
-
const handleClose = (0,
|
|
3551
|
+
const handleClose = (0, import_react15.useCallback)(() => {
|
|
3322
3552
|
if (!isCreating) onClose();
|
|
3323
3553
|
}, [isCreating, onClose]);
|
|
3324
|
-
const handleCreate = (0,
|
|
3554
|
+
const handleCreate = (0, import_react15.useCallback)(async () => {
|
|
3325
3555
|
if (!selected) return;
|
|
3326
3556
|
setIsCreating(true);
|
|
3327
3557
|
setError(null);
|
|
@@ -3342,16 +3572,16 @@ function FadeModal({
|
|
|
3342
3572
|
if (!open) return null;
|
|
3343
3573
|
const renderSection = (heading, list, section) => {
|
|
3344
3574
|
if (list.length === 0) return null;
|
|
3345
|
-
return /* @__PURE__ */ (0,
|
|
3346
|
-
/* @__PURE__ */ (0,
|
|
3347
|
-
/* @__PURE__ */ (0,
|
|
3575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
|
|
3576
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
|
|
3577
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3348
3578
|
"div",
|
|
3349
3579
|
{
|
|
3350
3580
|
role: "radiogroup",
|
|
3351
3581
|
"aria-label": heading,
|
|
3352
3582
|
"data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
|
|
3353
3583
|
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3354
|
-
children: list.map((t) => /* @__PURE__ */ (0,
|
|
3584
|
+
children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3355
3585
|
OrphanRow,
|
|
3356
3586
|
{
|
|
3357
3587
|
track: t,
|
|
@@ -3367,32 +3597,32 @@ function FadeModal({
|
|
|
3367
3597
|
)
|
|
3368
3598
|
] });
|
|
3369
3599
|
};
|
|
3370
|
-
return /* @__PURE__ */ (0,
|
|
3600
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3371
3601
|
"div",
|
|
3372
3602
|
{
|
|
3373
3603
|
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
3374
3604
|
onClick: (e) => e.stopPropagation(),
|
|
3375
3605
|
"data-testid": `${testIdPrefix}-box`,
|
|
3376
3606
|
children: [
|
|
3377
|
-
/* @__PURE__ */ (0,
|
|
3378
|
-
/* @__PURE__ */ (0,
|
|
3607
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
|
|
3608
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
3379
3609
|
"Tracks with no counterpart between",
|
|
3380
3610
|
" ",
|
|
3381
|
-
/* @__PURE__ */ (0,
|
|
3611
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
3382
3612
|
" and",
|
|
3383
3613
|
" ",
|
|
3384
|
-
/* @__PURE__ */ (0,
|
|
3614
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
3385
3615
|
" can gracefully fade out (leaving) or fade in (entering) across this transition."
|
|
3386
3616
|
] }),
|
|
3387
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
3388
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
3389
|
-
load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0,
|
|
3617
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3618
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3619
|
+
load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.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_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
3390
3620
|
renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
|
|
3391
3621
|
renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
|
|
3392
3622
|
] })),
|
|
3393
|
-
error && /* @__PURE__ */ (0,
|
|
3394
|
-
/* @__PURE__ */ (0,
|
|
3395
|
-
/* @__PURE__ */ (0,
|
|
3623
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
3624
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
3625
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3396
3626
|
"button",
|
|
3397
3627
|
{
|
|
3398
3628
|
ref: cancelRef,
|
|
@@ -3403,7 +3633,7 @@ function FadeModal({
|
|
|
3403
3633
|
children: "Cancel"
|
|
3404
3634
|
}
|
|
3405
3635
|
),
|
|
3406
|
-
/* @__PURE__ */ (0,
|
|
3636
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3407
3637
|
"button",
|
|
3408
3638
|
{
|
|
3409
3639
|
"data-testid": `${testIdPrefix}-confirm`,
|
|
@@ -3420,8 +3650,8 @@ function FadeModal({
|
|
|
3420
3650
|
}
|
|
3421
3651
|
|
|
3422
3652
|
// src/components/ImportTrackModal.tsx
|
|
3423
|
-
var
|
|
3424
|
-
var
|
|
3653
|
+
var import_react16 = require("react");
|
|
3654
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
3425
3655
|
function ImportTrackModal({
|
|
3426
3656
|
host,
|
|
3427
3657
|
open,
|
|
@@ -3433,10 +3663,10 @@ function ImportTrackModal({
|
|
|
3433
3663
|
onPick,
|
|
3434
3664
|
onPortTrack
|
|
3435
3665
|
}) {
|
|
3436
|
-
const [load, setLoad] = (0,
|
|
3437
|
-
const [selectedSceneId, setSelectedSceneId] = (0,
|
|
3438
|
-
const [importingTrackId, setImportingTrackId] = (0,
|
|
3439
|
-
const refresh = (0,
|
|
3666
|
+
const [load, setLoad] = (0, import_react16.useState)({ status: "loading" });
|
|
3667
|
+
const [selectedSceneId, setSelectedSceneId] = (0, import_react16.useState)(null);
|
|
3668
|
+
const [importingTrackId, setImportingTrackId] = (0, import_react16.useState)(null);
|
|
3669
|
+
const refresh = (0, import_react16.useCallback)(async () => {
|
|
3440
3670
|
if (!host.listImportableTracks) {
|
|
3441
3671
|
setLoad({ status: "error", message: "This host does not support importing tracks." });
|
|
3442
3672
|
return;
|
|
@@ -3452,14 +3682,14 @@ function ImportTrackModal({
|
|
|
3452
3682
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
|
|
3453
3683
|
}
|
|
3454
3684
|
}, [host, mode, onPortTrack]);
|
|
3455
|
-
(0,
|
|
3685
|
+
(0, import_react16.useEffect)(() => {
|
|
3456
3686
|
if (open) {
|
|
3457
3687
|
setSelectedSceneId(null);
|
|
3458
3688
|
setImportingTrackId(null);
|
|
3459
3689
|
void refresh();
|
|
3460
3690
|
}
|
|
3461
3691
|
}, [open, refresh]);
|
|
3462
|
-
const handleImport = (0,
|
|
3692
|
+
const handleImport = (0, import_react16.useCallback)(
|
|
3463
3693
|
async (track, sourceSceneId, sceneName, isSameScene) => {
|
|
3464
3694
|
if (isSameScene && onPortTrack) {
|
|
3465
3695
|
if (!track.importable) return;
|
|
@@ -3500,16 +3730,16 @@ function ImportTrackModal({
|
|
|
3500
3730
|
if (!open) return null;
|
|
3501
3731
|
const scenes = load.status === "ready" ? load.scenes : [];
|
|
3502
3732
|
const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
|
|
3503
|
-
return /* @__PURE__ */ (0,
|
|
3733
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
3504
3734
|
"div",
|
|
3505
3735
|
{
|
|
3506
3736
|
className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
|
|
3507
3737
|
onClick: (e) => e.stopPropagation(),
|
|
3508
3738
|
"data-testid": `${testIdPrefix}-modal`,
|
|
3509
3739
|
children: [
|
|
3510
|
-
/* @__PURE__ */ (0,
|
|
3511
|
-
/* @__PURE__ */ (0,
|
|
3512
|
-
selectedScene && /* @__PURE__ */ (0,
|
|
3740
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
|
|
3741
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3742
|
+
selectedScene && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
3513
3743
|
"button",
|
|
3514
3744
|
{
|
|
3515
3745
|
className: "text-sas-muted hover:text-sas-accent text-xs",
|
|
@@ -3518,9 +3748,9 @@ function ImportTrackModal({
|
|
|
3518
3748
|
children: "\u2190"
|
|
3519
3749
|
}
|
|
3520
3750
|
),
|
|
3521
|
-
/* @__PURE__ */ (0,
|
|
3751
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
|
|
3522
3752
|
] }),
|
|
3523
|
-
/* @__PURE__ */ (0,
|
|
3753
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
3524
3754
|
"button",
|
|
3525
3755
|
{
|
|
3526
3756
|
className: "text-sas-muted hover:text-sas-accent text-sm",
|
|
@@ -3530,30 +3760,30 @@ function ImportTrackModal({
|
|
|
3530
3760
|
}
|
|
3531
3761
|
)
|
|
3532
3762
|
] }),
|
|
3533
|
-
/* @__PURE__ */ (0,
|
|
3534
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
3535
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
3536
|
-
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0,
|
|
3537
|
-
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0,
|
|
3763
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "overflow-y-auto p-2 flex-1", children: [
|
|
3764
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
|
|
3765
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
3766
|
+
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime17.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." }),
|
|
3767
|
+
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
3538
3768
|
"button",
|
|
3539
3769
|
{
|
|
3540
3770
|
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",
|
|
3541
3771
|
onClick: () => setSelectedSceneId(scene.sceneId),
|
|
3542
3772
|
"data-testid": `${testIdPrefix}-scene`,
|
|
3543
3773
|
children: [
|
|
3544
|
-
/* @__PURE__ */ (0,
|
|
3545
|
-
/* @__PURE__ */ (0,
|
|
3774
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: scene.sceneName }),
|
|
3775
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-sas-muted", children: [
|
|
3546
3776
|
scene.tracks.length,
|
|
3547
3777
|
" \u2192"
|
|
3548
3778
|
] })
|
|
3549
3779
|
]
|
|
3550
3780
|
}
|
|
3551
3781
|
) }, scene.sceneId)) }),
|
|
3552
|
-
selectedScene && /* @__PURE__ */ (0,
|
|
3782
|
+
selectedScene && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
|
|
3553
3783
|
const busy = importingTrackId === track.trackId;
|
|
3554
3784
|
const gated = mode === "track" && !track.importable;
|
|
3555
3785
|
const disabled = gated || busy;
|
|
3556
|
-
return /* @__PURE__ */ (0,
|
|
3786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
3557
3787
|
"button",
|
|
3558
3788
|
{
|
|
3559
3789
|
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"}`,
|
|
@@ -3563,14 +3793,14 @@ function ImportTrackModal({
|
|
|
3563
3793
|
"data-testid": `${testIdPrefix}-track`,
|
|
3564
3794
|
"data-importable": mode === "sound" || track.importable ? "true" : "false",
|
|
3565
3795
|
children: [
|
|
3566
|
-
/* @__PURE__ */ (0,
|
|
3796
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "truncate", children: [
|
|
3567
3797
|
track.name,
|
|
3568
|
-
track.role ? /* @__PURE__ */ (0,
|
|
3798
|
+
track.role ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-sas-muted", children: [
|
|
3569
3799
|
" \xB7 ",
|
|
3570
3800
|
track.role
|
|
3571
3801
|
] }) : null
|
|
3572
3802
|
] }),
|
|
3573
|
-
busy ? /* @__PURE__ */ (0,
|
|
3803
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-muted", children: "\u2298" }) : null
|
|
3574
3804
|
]
|
|
3575
3805
|
}
|
|
3576
3806
|
) }, track.dbId);
|
|
@@ -3582,8 +3812,8 @@ function ImportTrackModal({
|
|
|
3582
3812
|
}
|
|
3583
3813
|
|
|
3584
3814
|
// src/components/CrossfadeModal.tsx
|
|
3585
|
-
var
|
|
3586
|
-
var
|
|
3815
|
+
var import_react17 = require("react");
|
|
3816
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3587
3817
|
function shortId2(dbId) {
|
|
3588
3818
|
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3589
3819
|
}
|
|
@@ -3596,7 +3826,7 @@ function CandidateRow({
|
|
|
3596
3826
|
}) {
|
|
3597
3827
|
const primary = track.prompt?.trim() || track.name;
|
|
3598
3828
|
const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
|
|
3599
|
-
return /* @__PURE__ */ (0,
|
|
3829
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3600
3830
|
"button",
|
|
3601
3831
|
{
|
|
3602
3832
|
type: "button",
|
|
@@ -3608,8 +3838,8 @@ function CandidateRow({
|
|
|
3608
3838
|
disabled,
|
|
3609
3839
|
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"}`,
|
|
3610
3840
|
children: [
|
|
3611
|
-
/* @__PURE__ */ (0,
|
|
3612
|
-
meta && /* @__PURE__ */ (0,
|
|
3841
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
3842
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
3613
3843
|
]
|
|
3614
3844
|
}
|
|
3615
3845
|
);
|
|
@@ -3626,15 +3856,15 @@ function CrossfadeModal({
|
|
|
3626
3856
|
onCreate,
|
|
3627
3857
|
testIdPrefix = "crossfade-modal"
|
|
3628
3858
|
}) {
|
|
3629
|
-
const [load, setLoad] = (0,
|
|
3630
|
-
const [originDbId, setOriginDbId] = (0,
|
|
3631
|
-
const [targetDbId, setTargetDbId] = (0,
|
|
3632
|
-
const [isCreating, setIsCreating] = (0,
|
|
3633
|
-
const [error, setError] = (0,
|
|
3634
|
-
const [fromName, setFromName] = (0,
|
|
3635
|
-
const [toName, setToName] = (0,
|
|
3636
|
-
const cancelRef = (0,
|
|
3637
|
-
const refresh = (0,
|
|
3859
|
+
const [load, setLoad] = (0, import_react17.useState)({ status: "loading" });
|
|
3860
|
+
const [originDbId, setOriginDbId] = (0, import_react17.useState)("");
|
|
3861
|
+
const [targetDbId, setTargetDbId] = (0, import_react17.useState)("");
|
|
3862
|
+
const [isCreating, setIsCreating] = (0, import_react17.useState)(false);
|
|
3863
|
+
const [error, setError] = (0, import_react17.useState)(null);
|
|
3864
|
+
const [fromName, setFromName] = (0, import_react17.useState)(null);
|
|
3865
|
+
const [toName, setToName] = (0, import_react17.useState)(null);
|
|
3866
|
+
const cancelRef = (0, import_react17.useRef)(null);
|
|
3867
|
+
const refresh = (0, import_react17.useCallback)(async () => {
|
|
3638
3868
|
if (!host.listSceneFamilyTracks) {
|
|
3639
3869
|
setLoad({ status: "error", message: "This host does not support crossfade tracks." });
|
|
3640
3870
|
return;
|
|
@@ -3654,7 +3884,7 @@ function CrossfadeModal({
|
|
|
3654
3884
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
3655
3885
|
}
|
|
3656
3886
|
}, [host, fromSceneId, toSceneId]);
|
|
3657
|
-
(0,
|
|
3887
|
+
(0, import_react17.useEffect)(() => {
|
|
3658
3888
|
if (open) {
|
|
3659
3889
|
setError(null);
|
|
3660
3890
|
setIsCreating(false);
|
|
@@ -3663,21 +3893,21 @@ function CrossfadeModal({
|
|
|
3663
3893
|
void refresh();
|
|
3664
3894
|
}
|
|
3665
3895
|
}, [open, refresh]);
|
|
3666
|
-
const excludeSet = (0,
|
|
3667
|
-
const originCandidates = (0,
|
|
3896
|
+
const excludeSet = (0, import_react17.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3897
|
+
const originCandidates = (0, import_react17.useMemo)(
|
|
3668
3898
|
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3669
3899
|
[load, excludeSet]
|
|
3670
3900
|
);
|
|
3671
|
-
const targetCandidates = (0,
|
|
3901
|
+
const targetCandidates = (0, import_react17.useMemo)(
|
|
3672
3902
|
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3673
3903
|
[load, excludeSet]
|
|
3674
3904
|
);
|
|
3675
|
-
(0,
|
|
3905
|
+
(0, import_react17.useEffect)(() => {
|
|
3676
3906
|
if (!originCandidates.some((t) => t.dbId === originDbId)) {
|
|
3677
3907
|
setOriginDbId(originCandidates[0]?.dbId ?? "");
|
|
3678
3908
|
}
|
|
3679
3909
|
}, [originCandidates, originDbId]);
|
|
3680
|
-
(0,
|
|
3910
|
+
(0, import_react17.useEffect)(() => {
|
|
3681
3911
|
if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
|
|
3682
3912
|
setTargetDbId(targetCandidates[0]?.dbId ?? "");
|
|
3683
3913
|
}
|
|
@@ -3685,10 +3915,10 @@ function CrossfadeModal({
|
|
|
3685
3915
|
const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
|
|
3686
3916
|
const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
|
|
3687
3917
|
const canCreate = !isCreating && !!originTrack && !!targetTrack;
|
|
3688
|
-
const handleClose = (0,
|
|
3918
|
+
const handleClose = (0, import_react17.useCallback)(() => {
|
|
3689
3919
|
if (!isCreating) onClose();
|
|
3690
3920
|
}, [isCreating, onClose]);
|
|
3691
|
-
const handleCreate = (0,
|
|
3921
|
+
const handleCreate = (0, import_react17.useCallback)(async () => {
|
|
3692
3922
|
if (!originTrack || !targetTrack) return;
|
|
3693
3923
|
setIsCreating(true);
|
|
3694
3924
|
setError(null);
|
|
@@ -3706,26 +3936,26 @@ function CrossfadeModal({
|
|
|
3706
3936
|
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
3707
3937
|
const toLabel = toName ?? toSceneName ?? null;
|
|
3708
3938
|
if (!open) return null;
|
|
3709
|
-
return /* @__PURE__ */ (0,
|
|
3939
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3710
3940
|
"div",
|
|
3711
3941
|
{
|
|
3712
3942
|
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
3713
3943
|
onClick: (e) => e.stopPropagation(),
|
|
3714
3944
|
"data-testid": `${testIdPrefix}-box`,
|
|
3715
3945
|
children: [
|
|
3716
|
-
/* @__PURE__ */ (0,
|
|
3717
|
-
/* @__PURE__ */ (0,
|
|
3946
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
|
|
3947
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
3718
3948
|
"Bridge a track from",
|
|
3719
3949
|
" ",
|
|
3720
|
-
/* @__PURE__ */ (0,
|
|
3950
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
3721
3951
|
" into one from",
|
|
3722
3952
|
" ",
|
|
3723
|
-
/* @__PURE__ */ (0,
|
|
3953
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
3724
3954
|
". Both layers share one generated part; each keeps its own preset."
|
|
3725
3955
|
] }),
|
|
3726
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
3727
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
3728
|
-
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0,
|
|
3956
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3957
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3958
|
+
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3729
3959
|
"div",
|
|
3730
3960
|
{
|
|
3731
3961
|
className: "text-xs text-sas-muted py-4 text-center",
|
|
@@ -3736,20 +3966,20 @@ function CrossfadeModal({
|
|
|
3736
3966
|
". Add one (or free one from another crossfade) first."
|
|
3737
3967
|
]
|
|
3738
3968
|
}
|
|
3739
|
-
) : /* @__PURE__ */ (0,
|
|
3740
|
-
/* @__PURE__ */ (0,
|
|
3741
|
-
/* @__PURE__ */ (0,
|
|
3969
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
3970
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "block", children: [
|
|
3971
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
3742
3972
|
"Origin ",
|
|
3743
3973
|
fromLabel ? `(${fromLabel})` : "(top)"
|
|
3744
3974
|
] }),
|
|
3745
|
-
/* @__PURE__ */ (0,
|
|
3975
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3746
3976
|
"div",
|
|
3747
3977
|
{
|
|
3748
3978
|
role: "radiogroup",
|
|
3749
3979
|
"aria-label": "Origin track",
|
|
3750
3980
|
"data-testid": `${testIdPrefix}-origin-list`,
|
|
3751
3981
|
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3752
|
-
children: originCandidates.map((t) => /* @__PURE__ */ (0,
|
|
3982
|
+
children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3753
3983
|
CandidateRow,
|
|
3754
3984
|
{
|
|
3755
3985
|
track: t,
|
|
@@ -3763,23 +3993,23 @@ function CrossfadeModal({
|
|
|
3763
3993
|
}
|
|
3764
3994
|
)
|
|
3765
3995
|
] }),
|
|
3766
|
-
/* @__PURE__ */ (0,
|
|
3767
|
-
/* @__PURE__ */ (0,
|
|
3996
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "block", children: [
|
|
3997
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
3768
3998
|
"Target ",
|
|
3769
3999
|
toLabel ? `(${toLabel})` : "(bottom)"
|
|
3770
4000
|
] }),
|
|
3771
|
-
targetCandidates.length === 0 ? /* @__PURE__ */ (0,
|
|
4001
|
+
targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
|
|
3772
4002
|
"No available tracks in ",
|
|
3773
4003
|
toLabel ?? "the target scene",
|
|
3774
4004
|
" to crossfade into."
|
|
3775
|
-
] }) : /* @__PURE__ */ (0,
|
|
4005
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3776
4006
|
"div",
|
|
3777
4007
|
{
|
|
3778
4008
|
role: "radiogroup",
|
|
3779
4009
|
"aria-label": "Target track",
|
|
3780
4010
|
"data-testid": `${testIdPrefix}-target-list`,
|
|
3781
4011
|
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3782
|
-
children: targetCandidates.map((t) => /* @__PURE__ */ (0,
|
|
4012
|
+
children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3783
4013
|
CandidateRow,
|
|
3784
4014
|
{
|
|
3785
4015
|
track: t,
|
|
@@ -3794,9 +4024,9 @@ function CrossfadeModal({
|
|
|
3794
4024
|
)
|
|
3795
4025
|
] })
|
|
3796
4026
|
] })),
|
|
3797
|
-
error && /* @__PURE__ */ (0,
|
|
3798
|
-
/* @__PURE__ */ (0,
|
|
3799
|
-
/* @__PURE__ */ (0,
|
|
4027
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
4028
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
4029
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3800
4030
|
"button",
|
|
3801
4031
|
{
|
|
3802
4032
|
ref: cancelRef,
|
|
@@ -3807,7 +4037,7 @@ function CrossfadeModal({
|
|
|
3807
4037
|
children: "Cancel"
|
|
3808
4038
|
}
|
|
3809
4039
|
),
|
|
3810
|
-
/* @__PURE__ */ (0,
|
|
4040
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3811
4041
|
"button",
|
|
3812
4042
|
{
|
|
3813
4043
|
"data-testid": `${testIdPrefix}-confirm`,
|
|
@@ -3824,10 +4054,10 @@ function CrossfadeModal({
|
|
|
3824
4054
|
}
|
|
3825
4055
|
|
|
3826
4056
|
// src/components/TransitionDesigner.tsx
|
|
3827
|
-
var
|
|
4057
|
+
var import_react19 = require("react");
|
|
3828
4058
|
|
|
3829
4059
|
// src/hooks/useTrackReorder.ts
|
|
3830
|
-
var
|
|
4060
|
+
var import_react18 = require("react");
|
|
3831
4061
|
function moveItem(arr, from, to) {
|
|
3832
4062
|
const next = arr.slice();
|
|
3833
4063
|
if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
|
|
@@ -3844,12 +4074,12 @@ function useTrackReorder({
|
|
|
3844
4074
|
getId,
|
|
3845
4075
|
onError
|
|
3846
4076
|
}) {
|
|
3847
|
-
const [draggingIndex, setDraggingIndex] = (0,
|
|
3848
|
-
const [dragOverIndex, setDragOverIndex] = (0,
|
|
3849
|
-
const fromRef = (0,
|
|
3850
|
-
const itemsRef = (0,
|
|
4077
|
+
const [draggingIndex, setDraggingIndex] = (0, import_react18.useState)(null);
|
|
4078
|
+
const [dragOverIndex, setDragOverIndex] = (0, import_react18.useState)(null);
|
|
4079
|
+
const fromRef = (0, import_react18.useRef)(null);
|
|
4080
|
+
const itemsRef = (0, import_react18.useRef)(items);
|
|
3851
4081
|
itemsRef.current = items;
|
|
3852
|
-
const dragPropsFor = (0,
|
|
4082
|
+
const dragPropsFor = (0, import_react18.useCallback)(
|
|
3853
4083
|
(index) => ({
|
|
3854
4084
|
handleProps: {
|
|
3855
4085
|
draggable: true,
|
|
@@ -4031,7 +4261,7 @@ function dbIdsFromKeys(keys) {
|
|
|
4031
4261
|
}
|
|
4032
4262
|
|
|
4033
4263
|
// src/components/TransitionDesigner.tsx
|
|
4034
|
-
var
|
|
4264
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
4035
4265
|
var CROSSFADE_ESTIMATE_MS = 15e3;
|
|
4036
4266
|
var FADE_ESTIMATE_MS = 11e3;
|
|
4037
4267
|
var CREATE_ALL_CONCURRENCY = 5;
|
|
@@ -4052,56 +4282,67 @@ function TransitionDesigner({
|
|
|
4052
4282
|
onCreateCrossfade,
|
|
4053
4283
|
onCreateFade,
|
|
4054
4284
|
onCreateAudioTransition,
|
|
4285
|
+
mapColumnSubjects,
|
|
4286
|
+
fadeEstimateMs,
|
|
4055
4287
|
familyLabel,
|
|
4056
4288
|
testIdPrefix = "transition-designer"
|
|
4057
4289
|
}) {
|
|
4058
|
-
const
|
|
4059
|
-
const [
|
|
4060
|
-
const [
|
|
4061
|
-
const [
|
|
4062
|
-
const [
|
|
4063
|
-
const [
|
|
4064
|
-
const [
|
|
4065
|
-
const [
|
|
4066
|
-
const
|
|
4290
|
+
const fadeOnly = !onCreateCrossfade;
|
|
4291
|
+
const [load, setLoad] = (0, import_react19.useState)({ status: "loading" });
|
|
4292
|
+
const [fromName, setFromName] = (0, import_react19.useState)(null);
|
|
4293
|
+
const [toName, setToName] = (0, import_react19.useState)(null);
|
|
4294
|
+
const [originSlots, setOriginSlots] = (0, import_react19.useState)([]);
|
|
4295
|
+
const [targetSlots, setTargetSlots] = (0, import_react19.useState)([]);
|
|
4296
|
+
const [creatingKeys, setCreatingKeys] = (0, import_react19.useState)(() => /* @__PURE__ */ new Set());
|
|
4297
|
+
const [rowErrors, setRowErrors] = (0, import_react19.useState)({});
|
|
4298
|
+
const [rowEffects, setRowEffects] = (0, import_react19.useState)({});
|
|
4299
|
+
const rowEffectsRef = (0, import_react19.useRef)(rowEffects);
|
|
4067
4300
|
rowEffectsRef.current = rowEffects;
|
|
4068
4301
|
const audioEffectsEnabled = !!onCreateAudioTransition;
|
|
4069
|
-
const excludeRef = (0,
|
|
4302
|
+
const excludeRef = (0, import_react19.useRef)(excludeSourceDbIds);
|
|
4070
4303
|
excludeRef.current = excludeSourceDbIds;
|
|
4071
|
-
const originSlotsRef = (0,
|
|
4304
|
+
const originSlotsRef = (0, import_react19.useRef)(originSlots);
|
|
4072
4305
|
originSlotsRef.current = originSlots;
|
|
4073
|
-
const targetSlotsRef = (0,
|
|
4306
|
+
const targetSlotsRef = (0, import_react19.useRef)(targetSlots);
|
|
4074
4307
|
targetSlotsRef.current = targetSlots;
|
|
4075
|
-
const creatingKeysRef = (0,
|
|
4308
|
+
const creatingKeysRef = (0, import_react19.useRef)(creatingKeys);
|
|
4076
4309
|
creatingKeysRef.current = creatingKeys;
|
|
4077
|
-
const dragRef = (0,
|
|
4078
|
-
const [dragging, setDragging] = (0,
|
|
4079
|
-
const [dragOver, setDragOver] = (0,
|
|
4080
|
-
const excludeSet = (0,
|
|
4081
|
-
const originPool = (0,
|
|
4310
|
+
const dragRef = (0, import_react19.useRef)(null);
|
|
4311
|
+
const [dragging, setDragging] = (0, import_react19.useState)(null);
|
|
4312
|
+
const [dragOver, setDragOver] = (0, import_react19.useState)(null);
|
|
4313
|
+
const excludeSet = (0, import_react19.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
4314
|
+
const originPool = (0, import_react19.useMemo)(
|
|
4082
4315
|
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
4083
4316
|
[load, excludeSet]
|
|
4084
4317
|
);
|
|
4085
|
-
const targetPool = (0,
|
|
4318
|
+
const targetPool = (0, import_react19.useMemo)(
|
|
4086
4319
|
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
4087
4320
|
[load, excludeSet]
|
|
4088
4321
|
);
|
|
4089
|
-
const originById = (0,
|
|
4090
|
-
const targetById = (0,
|
|
4091
|
-
const originByIdRef = (0,
|
|
4322
|
+
const originById = (0, import_react19.useMemo)(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
|
|
4323
|
+
const targetById = (0, import_react19.useMemo)(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
|
|
4324
|
+
const originByIdRef = (0, import_react19.useRef)(originById);
|
|
4092
4325
|
originByIdRef.current = originById;
|
|
4093
|
-
const targetByIdRef = (0,
|
|
4326
|
+
const targetByIdRef = (0, import_react19.useRef)(targetById);
|
|
4094
4327
|
targetByIdRef.current = targetById;
|
|
4095
|
-
const refresh = (0,
|
|
4328
|
+
const refresh = (0, import_react19.useCallback)(async () => {
|
|
4096
4329
|
if (!host.listSceneFamilyTracks) {
|
|
4097
4330
|
setLoad({ status: "error", message: "This host does not support transition tracks." });
|
|
4098
4331
|
return;
|
|
4099
4332
|
}
|
|
4100
4333
|
setLoad({ status: "loading" });
|
|
4101
4334
|
try {
|
|
4102
|
-
|
|
4335
|
+
let [origin, target] = await Promise.all([
|
|
4103
4336
|
host.listSceneFamilyTracks(fromSceneId),
|
|
4104
|
-
host.listSceneFamilyTracks(toSceneId)
|
|
4337
|
+
host.listSceneFamilyTracks(toSceneId)
|
|
4338
|
+
]);
|
|
4339
|
+
if (mapColumnSubjects) {
|
|
4340
|
+
[origin, target] = await Promise.all([
|
|
4341
|
+
mapColumnSubjects(fromSceneId, origin),
|
|
4342
|
+
mapColumnSubjects(toSceneId, target)
|
|
4343
|
+
]);
|
|
4344
|
+
}
|
|
4345
|
+
const [fName, tName, draftRaw] = await Promise.all([
|
|
4105
4346
|
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
4106
4347
|
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),
|
|
4107
4348
|
host.getSceneData ? host.getSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY) : Promise.resolve(null)
|
|
@@ -4126,11 +4367,11 @@ function TransitionDesigner({
|
|
|
4126
4367
|
message: err instanceof Error ? err.message : "Failed to load tracks."
|
|
4127
4368
|
});
|
|
4128
4369
|
}
|
|
4129
|
-
}, [host, fromSceneId, toSceneId, transitionSceneId]);
|
|
4130
|
-
(0,
|
|
4370
|
+
}, [host, fromSceneId, toSceneId, transitionSceneId, mapColumnSubjects]);
|
|
4371
|
+
(0, import_react19.useEffect)(() => {
|
|
4131
4372
|
void refresh();
|
|
4132
4373
|
}, [refresh]);
|
|
4133
|
-
(0,
|
|
4374
|
+
(0, import_react19.useEffect)(() => {
|
|
4134
4375
|
if (load.status !== "ready") return;
|
|
4135
4376
|
const [po, pt] = padPair(
|
|
4136
4377
|
reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),
|
|
@@ -4139,7 +4380,7 @@ function TransitionDesigner({
|
|
|
4139
4380
|
if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);
|
|
4140
4381
|
if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);
|
|
4141
4382
|
}, [originPool, targetPool, load.status]);
|
|
4142
|
-
const mutate = (0,
|
|
4383
|
+
const mutate = (0, import_react19.useCallback)(
|
|
4143
4384
|
(nextOrigin, nextTarget) => {
|
|
4144
4385
|
const norm = normalizeSlots(nextOrigin, nextTarget);
|
|
4145
4386
|
const [po, pt] = padPair(norm.originOrder, norm.targetOrder);
|
|
@@ -4152,7 +4393,7 @@ function TransitionDesigner({
|
|
|
4152
4393
|
},
|
|
4153
4394
|
[host, transitionSceneId]
|
|
4154
4395
|
);
|
|
4155
|
-
const setRowEffect = (0,
|
|
4396
|
+
const setRowEffect = (0, import_react19.useCallback)(
|
|
4156
4397
|
(sourceDbId, effect) => {
|
|
4157
4398
|
setRowEffects((prev) => {
|
|
4158
4399
|
const next = { ...prev, [sourceDbId]: effect };
|
|
@@ -4166,7 +4407,7 @@ function TransitionDesigner({
|
|
|
4166
4407
|
},
|
|
4167
4408
|
[host, transitionSceneId]
|
|
4168
4409
|
);
|
|
4169
|
-
const insertGapAbove = (0,
|
|
4410
|
+
const insertGapAbove = (0, import_react19.useCallback)(
|
|
4170
4411
|
(col, index) => {
|
|
4171
4412
|
const slots = col === "origin" ? originSlots : targetSlots;
|
|
4172
4413
|
const next = [...slots.slice(0, index), null, ...slots.slice(index)];
|
|
@@ -4175,7 +4416,7 @@ function TransitionDesigner({
|
|
|
4175
4416
|
},
|
|
4176
4417
|
[originSlots, targetSlots, mutate]
|
|
4177
4418
|
);
|
|
4178
|
-
const removeGap = (0,
|
|
4419
|
+
const removeGap = (0, import_react19.useCallback)(
|
|
4179
4420
|
(col, index) => {
|
|
4180
4421
|
const slots = col === "origin" ? originSlots : targetSlots;
|
|
4181
4422
|
const next = slots.filter((_, i) => i !== index);
|
|
@@ -4184,7 +4425,7 @@ function TransitionDesigner({
|
|
|
4184
4425
|
},
|
|
4185
4426
|
[originSlots, targetSlots, mutate]
|
|
4186
4427
|
);
|
|
4187
|
-
const handleDrop = (0,
|
|
4428
|
+
const handleDrop = (0, import_react19.useCallback)(
|
|
4188
4429
|
(col, to) => {
|
|
4189
4430
|
const from = dragRef.current;
|
|
4190
4431
|
dragRef.current = null;
|
|
@@ -4196,16 +4437,34 @@ function TransitionDesigner({
|
|
|
4196
4437
|
},
|
|
4197
4438
|
[originSlots, targetSlots, mutate]
|
|
4198
4439
|
);
|
|
4199
|
-
const rows = (0,
|
|
4200
|
-
const
|
|
4201
|
-
|
|
4202
|
-
|
|
4440
|
+
const rows = (0, import_react19.useMemo)(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
|
|
4441
|
+
const fadeOnlyRows = (0, import_react19.useMemo)(
|
|
4442
|
+
() => fadeOnly ? [
|
|
4443
|
+
...originPool.map((t) => ({
|
|
4444
|
+
originId: t.dbId,
|
|
4445
|
+
targetId: null,
|
|
4446
|
+
type: "fade-out"
|
|
4447
|
+
})),
|
|
4448
|
+
...targetPool.map((t) => ({
|
|
4449
|
+
originId: null,
|
|
4450
|
+
targetId: t.dbId,
|
|
4451
|
+
type: "fade-in"
|
|
4452
|
+
}))
|
|
4453
|
+
] : [],
|
|
4454
|
+
[fadeOnly, originPool, targetPool]
|
|
4455
|
+
);
|
|
4456
|
+
const fadeOnlyRowsRef = (0, import_react19.useRef)(fadeOnlyRows);
|
|
4457
|
+
fadeOnlyRowsRef.current = fadeOnlyRows;
|
|
4458
|
+
const effectiveRows = fadeOnly ? fadeOnlyRows : rows;
|
|
4459
|
+
const creatingDbIds = (0, import_react19.useMemo)(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
|
|
4460
|
+
const eligibleCount = (0, import_react19.useMemo)(
|
|
4461
|
+
() => effectiveRows.filter((r) => {
|
|
4203
4462
|
const k = rowKey(r);
|
|
4204
4463
|
return k !== null && !creatingKeys.has(k);
|
|
4205
4464
|
}).length,
|
|
4206
|
-
[
|
|
4465
|
+
[effectiveRows, creatingKeys]
|
|
4207
4466
|
);
|
|
4208
|
-
const createRow = (0,
|
|
4467
|
+
const createRow = (0, import_react19.useCallback)(
|
|
4209
4468
|
async (row) => {
|
|
4210
4469
|
const key = rowKey(row);
|
|
4211
4470
|
if (!key || !row.type || creatingKeysRef.current.has(key)) return;
|
|
@@ -4221,6 +4480,7 @@ function TransitionDesigner({
|
|
|
4221
4480
|
const o = row.originId ? originByIdRef.current.get(row.originId) : void 0;
|
|
4222
4481
|
const t = row.targetId ? targetByIdRef.current.get(row.targetId) : void 0;
|
|
4223
4482
|
if (!o || !t) throw new Error("Track is no longer available \u2014 refresh and retry.");
|
|
4483
|
+
if (!onCreateCrossfade) throw new Error("This panel does not support crossfades.");
|
|
4224
4484
|
await onCreateCrossfade(
|
|
4225
4485
|
{ dbId: o.dbId, name: o.name, role: o.role },
|
|
4226
4486
|
{ dbId: t.dbId, name: t.name, role: t.role }
|
|
@@ -4259,8 +4519,9 @@ function TransitionDesigner({
|
|
|
4259
4519
|
},
|
|
4260
4520
|
[onCreateCrossfade, onCreateFade, onCreateAudioTransition]
|
|
4261
4521
|
);
|
|
4262
|
-
const createAll = (0,
|
|
4263
|
-
const
|
|
4522
|
+
const createAll = (0, import_react19.useCallback)(async () => {
|
|
4523
|
+
const source = fadeOnly ? fadeOnlyRowsRef.current : buildRowSlots(originSlotsRef.current, targetSlotsRef.current);
|
|
4524
|
+
const eligible = source.filter((r) => {
|
|
4264
4525
|
const k = rowKey(r);
|
|
4265
4526
|
return k !== null && !creatingKeysRef.current.has(k);
|
|
4266
4527
|
});
|
|
@@ -4276,7 +4537,7 @@ function TransitionDesigner({
|
|
|
4276
4537
|
await Promise.all(
|
|
4277
4538
|
Array.from({ length: Math.min(CREATE_ALL_CONCURRENCY, eligible.length) }, () => worker())
|
|
4278
4539
|
);
|
|
4279
|
-
}, [createRow]);
|
|
4540
|
+
}, [createRow, fadeOnly]);
|
|
4280
4541
|
const fromLabel = fromName ?? "origin";
|
|
4281
4542
|
const toLabel = toName ?? "target";
|
|
4282
4543
|
const cellDragProps = (col, index, locked) => ({
|
|
@@ -4327,15 +4588,15 @@ function TransitionDesigner({
|
|
|
4327
4588
|
const base = "group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none";
|
|
4328
4589
|
const tone = isDragTarget ? "border-sas-accent bg-sas-accent/10" : "border-sas-border bg-sas-panel";
|
|
4329
4590
|
if (slotId === null) {
|
|
4330
|
-
return /* @__PURE__ */ (0,
|
|
4591
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4331
4592
|
"div",
|
|
4332
4593
|
{
|
|
4333
4594
|
...cellDragProps(col, index, false),
|
|
4334
4595
|
"data-testid": `${testIdPrefix}-${col}-gap-${index}`,
|
|
4335
4596
|
className: `${base} ${tone} border-dashed flex items-center justify-between ${isDragging ? "opacity-40" : "opacity-70"}`,
|
|
4336
4597
|
children: [
|
|
4337
|
-
/* @__PURE__ */ (0,
|
|
4338
|
-
/* @__PURE__ */ (0,
|
|
4598
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
|
|
4599
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4339
4600
|
"button",
|
|
4340
4601
|
{
|
|
4341
4602
|
type: "button",
|
|
@@ -4352,7 +4613,7 @@ function TransitionDesigner({
|
|
|
4352
4613
|
}
|
|
4353
4614
|
const primary = track ? track.prompt?.trim() || track.name : slotId;
|
|
4354
4615
|
const meta = track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing";
|
|
4355
|
-
return /* @__PURE__ */ (0,
|
|
4616
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4356
4617
|
"div",
|
|
4357
4618
|
{
|
|
4358
4619
|
...cellDragProps(col, index, locked),
|
|
@@ -4360,13 +4621,13 @@ function TransitionDesigner({
|
|
|
4360
4621
|
"data-value": slotId,
|
|
4361
4622
|
className: `${base} ${tone} ${isDragging ? "opacity-40" : ""} ${locked ? "opacity-60" : "cursor-grab active:cursor-grabbing"}`,
|
|
4362
4623
|
title: track ? track.dbId : "Track no longer available",
|
|
4363
|
-
children: /* @__PURE__ */ (0,
|
|
4364
|
-
/* @__PURE__ */ (0,
|
|
4365
|
-
/* @__PURE__ */ (0,
|
|
4366
|
-
/* @__PURE__ */ (0,
|
|
4367
|
-
meta && /* @__PURE__ */ (0,
|
|
4624
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-start gap-1", children: [
|
|
4625
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
|
|
4626
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
4627
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-text truncate", children: primary }),
|
|
4628
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
|
|
4368
4629
|
] }),
|
|
4369
|
-
/* @__PURE__ */ (0,
|
|
4630
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4370
4631
|
"button",
|
|
4371
4632
|
{
|
|
4372
4633
|
type: "button",
|
|
@@ -4382,22 +4643,159 @@ function TransitionDesigner({
|
|
|
4382
4643
|
}
|
|
4383
4644
|
);
|
|
4384
4645
|
};
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4646
|
+
const renderFadeOnlyRow = (row, i) => {
|
|
4647
|
+
const slotId = row.originId ?? row.targetId;
|
|
4648
|
+
const byId = row.type === "fade-out" ? originById : targetById;
|
|
4649
|
+
const track = byId.get(slotId);
|
|
4650
|
+
const key = rowKey(row);
|
|
4651
|
+
const isCreatingThis = key !== null && creatingKeys.has(key);
|
|
4652
|
+
const errMsg = key !== null ? rowErrors[key] : void 0;
|
|
4653
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4654
|
+
"div",
|
|
4655
|
+
{
|
|
4656
|
+
"data-testid": `${testIdPrefix}-row-${i}`,
|
|
4657
|
+
className: "grid grid-cols-[1fr_auto] gap-2 items-center",
|
|
4658
|
+
children: [
|
|
4659
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4660
|
+
"div",
|
|
4661
|
+
{
|
|
4662
|
+
"data-testid": `${testIdPrefix}-subject-${slotId}`,
|
|
4663
|
+
className: `rounded-sm border border-sas-border bg-sas-panel px-2 py-1.5 ${isCreatingThis ? "opacity-60" : ""}`,
|
|
4664
|
+
title: track ? track.dbId : "Track no longer available",
|
|
4665
|
+
children: [
|
|
4666
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-text truncate", children: track ? track.prompt?.trim() || track.name : slotId }),
|
|
4667
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing" })
|
|
4668
|
+
]
|
|
4669
|
+
}
|
|
4670
|
+
),
|
|
4671
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
|
|
4672
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4673
|
+
"span",
|
|
4674
|
+
{
|
|
4675
|
+
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
4676
|
+
className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-border text-sas-muted",
|
|
4677
|
+
children: row.type ? TYPE_LABEL[row.type] : "\u2014"
|
|
4678
|
+
}
|
|
4679
|
+
),
|
|
4680
|
+
isCreatingThis ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4681
|
+
SorceryProgressBar,
|
|
4682
|
+
{
|
|
4683
|
+
isLoading: true,
|
|
4684
|
+
heightClass: "h-5",
|
|
4685
|
+
statusText: "CREATING",
|
|
4686
|
+
estimatedDurationMs: fadeEstimateMs ?? FADE_ESTIMATE_MS
|
|
4687
|
+
}
|
|
4688
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4689
|
+
"button",
|
|
4690
|
+
{
|
|
4691
|
+
type: "button",
|
|
4692
|
+
"data-testid": `${testIdPrefix}-create-${i}`,
|
|
4693
|
+
onClick: () => createRow(row),
|
|
4694
|
+
className: "w-full px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg",
|
|
4695
|
+
children: "Create"
|
|
4696
|
+
}
|
|
4697
|
+
),
|
|
4698
|
+
errMsg && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4699
|
+
"span",
|
|
4700
|
+
{
|
|
4701
|
+
"data-testid": `${testIdPrefix}-row-error-${i}`,
|
|
4702
|
+
className: "text-[10px] text-sas-danger text-center leading-tight",
|
|
4703
|
+
children: errMsg
|
|
4704
|
+
}
|
|
4705
|
+
)
|
|
4706
|
+
] })
|
|
4707
|
+
]
|
|
4708
|
+
},
|
|
4709
|
+
slotId
|
|
4710
|
+
);
|
|
4711
|
+
};
|
|
4712
|
+
if (fadeOnly) {
|
|
4713
|
+
const outRows = fadeOnlyRows.filter((r) => r.type === "fade-out");
|
|
4714
|
+
const inRows = fadeOnlyRows.filter((r) => r.type === "fade-in");
|
|
4715
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
|
|
4716
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
|
|
4717
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
|
|
4718
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: fromLabel }),
|
|
4719
|
+
" \u2192",
|
|
4720
|
+
" ",
|
|
4721
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: toLabel }),
|
|
4722
|
+
familyLabel ? ` \xB7 ${familyLabel}` : "",
|
|
4723
|
+
" \xB7 each entry fades on its own \u2014 origin fades out, target fades in."
|
|
4724
|
+
] }),
|
|
4725
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4726
|
+
creatingKeys.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4727
|
+
"span",
|
|
4728
|
+
{
|
|
4729
|
+
className: "text-[10px] text-sas-accent whitespace-nowrap",
|
|
4730
|
+
"data-testid": `${testIdPrefix}-creating-count`,
|
|
4731
|
+
children: [
|
|
4732
|
+
creatingKeys.size,
|
|
4733
|
+
" creating\u2026"
|
|
4734
|
+
]
|
|
4735
|
+
}
|
|
4736
|
+
),
|
|
4737
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4738
|
+
"button",
|
|
4739
|
+
{
|
|
4740
|
+
type: "button",
|
|
4741
|
+
"data-testid": `${testIdPrefix}-create-all`,
|
|
4742
|
+
onClick: createAll,
|
|
4743
|
+
disabled: eligibleCount === 0,
|
|
4744
|
+
title: "Create every fade at once (runs several concurrently)",
|
|
4745
|
+
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"}`,
|
|
4746
|
+
children: [
|
|
4747
|
+
"Create all",
|
|
4748
|
+
eligibleCount > 0 ? ` (${eligibleCount})` : ""
|
|
4749
|
+
]
|
|
4750
|
+
}
|
|
4751
|
+
)
|
|
4752
|
+
] })
|
|
4753
|
+
] }),
|
|
4754
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
|
|
4755
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
4756
|
+
load.status === "ready" && (fadeOnlyRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
|
|
4757
|
+
"Nothing to fade in this panel for either scene. Add tracks to ",
|
|
4758
|
+
fromLabel,
|
|
4759
|
+
" or ",
|
|
4760
|
+
toLabel,
|
|
4761
|
+
" ",
|
|
4762
|
+
"first (or free one by deleting an existing fade)."
|
|
4763
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-3", children: [
|
|
4764
|
+
outRows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-fade-out-section`, children: [
|
|
4765
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
4766
|
+
"Origin (",
|
|
4767
|
+
fromLabel,
|
|
4768
|
+
") \u2014 fades out"
|
|
4769
|
+
] }),
|
|
4770
|
+
outRows.map((row, i) => renderFadeOnlyRow(row, i))
|
|
4771
|
+
] }),
|
|
4772
|
+
inRows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-fade-in-section`, children: [
|
|
4773
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
4774
|
+
"Target (",
|
|
4775
|
+
toLabel,
|
|
4776
|
+
") \u2014 fades in"
|
|
4777
|
+
] }),
|
|
4778
|
+
inRows.map((row, j) => renderFadeOnlyRow(row, outRows.length + j))
|
|
4779
|
+
] })
|
|
4780
|
+
] }))
|
|
4781
|
+
] });
|
|
4782
|
+
}
|
|
4783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
|
|
4784
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
|
|
4785
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
|
|
4786
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: fromLabel }),
|
|
4389
4787
|
" \u2192",
|
|
4390
4788
|
" ",
|
|
4391
|
-
/* @__PURE__ */ (0,
|
|
4789
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: toLabel }),
|
|
4392
4790
|
familyLabel ? ` \xB7 ${familyLabel}` : "",
|
|
4393
4791
|
" \xB7 line up a track on each side to crossfade; leave one blank (or insert a gap) to fade."
|
|
4394
4792
|
] }),
|
|
4395
|
-
/* @__PURE__ */ (0,
|
|
4396
|
-
creatingKeys.size > 0 && /* @__PURE__ */ (0,
|
|
4793
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4794
|
+
creatingKeys.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
|
|
4397
4795
|
creatingKeys.size,
|
|
4398
4796
|
" creating\u2026"
|
|
4399
4797
|
] }),
|
|
4400
|
-
/* @__PURE__ */ (0,
|
|
4798
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4401
4799
|
"button",
|
|
4402
4800
|
{
|
|
4403
4801
|
type: "button",
|
|
@@ -4414,49 +4812,49 @@ function TransitionDesigner({
|
|
|
4414
4812
|
)
|
|
4415
4813
|
] })
|
|
4416
4814
|
] }),
|
|
4417
|
-
/* @__PURE__ */ (0,
|
|
4418
|
-
/* @__PURE__ */ (0,
|
|
4815
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
|
|
4816
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
|
|
4419
4817
|
"Origin (",
|
|
4420
4818
|
fromLabel,
|
|
4421
4819
|
")"
|
|
4422
4820
|
] }),
|
|
4423
|
-
/* @__PURE__ */ (0,
|
|
4424
|
-
/* @__PURE__ */ (0,
|
|
4821
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
|
|
4822
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
|
|
4425
4823
|
"Target (",
|
|
4426
4824
|
toLabel,
|
|
4427
4825
|
")"
|
|
4428
4826
|
] })
|
|
4429
4827
|
] }),
|
|
4430
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
4431
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
4432
|
-
load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ (0,
|
|
4828
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
|
|
4829
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
4830
|
+
load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
|
|
4433
4831
|
"No tracks to arrange in this panel for either scene. Add tracks to ",
|
|
4434
4832
|
fromLabel,
|
|
4435
4833
|
" or ",
|
|
4436
4834
|
toLabel,
|
|
4437
4835
|
" ",
|
|
4438
4836
|
"first (or free one by deleting an existing crossfade/fade)."
|
|
4439
|
-
] }) : /* @__PURE__ */ (0,
|
|
4837
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "space-y-2", children: rows.map((row, i) => {
|
|
4440
4838
|
const key = rowKey(row);
|
|
4441
4839
|
const isCreatingThis = key !== null && creatingKeys.has(key);
|
|
4442
4840
|
const errMsg = key !== null ? rowErrors[key] : void 0;
|
|
4443
|
-
return /* @__PURE__ */ (0,
|
|
4841
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4444
4842
|
"div",
|
|
4445
4843
|
{
|
|
4446
4844
|
"data-testid": `${testIdPrefix}-row-${i}`,
|
|
4447
4845
|
className: "grid grid-cols-[1fr_auto_1fr] gap-2 items-center",
|
|
4448
4846
|
children: [
|
|
4449
4847
|
renderCell("origin", i, row.originId),
|
|
4450
|
-
/* @__PURE__ */ (0,
|
|
4451
|
-
!row.type ? /* @__PURE__ */ (0,
|
|
4848
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
|
|
4849
|
+
!row.type ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4452
4850
|
"span",
|
|
4453
4851
|
{
|
|
4454
4852
|
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
4455
4853
|
className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent",
|
|
4456
4854
|
children: TYPE_LABEL[row.type]
|
|
4457
4855
|
}
|
|
4458
|
-
) : audioEffectsEnabled ? /* @__PURE__ */ (0,
|
|
4459
|
-
/* @__PURE__ */ (0,
|
|
4856
|
+
) : audioEffectsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
|
|
4857
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4460
4858
|
"select",
|
|
4461
4859
|
{
|
|
4462
4860
|
"data-testid": `${testIdPrefix}-effect-${i}`,
|
|
@@ -4466,11 +4864,11 @@ function TransitionDesigner({
|
|
|
4466
4864
|
if (id) setRowEffect(id, e.target.value);
|
|
4467
4865
|
},
|
|
4468
4866
|
className: "text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text",
|
|
4469
|
-
children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ (0,
|
|
4867
|
+
children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
|
|
4470
4868
|
}
|
|
4471
4869
|
),
|
|
4472
|
-
/* @__PURE__ */ (0,
|
|
4473
|
-
] }) : /* @__PURE__ */ (0,
|
|
4870
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
|
|
4871
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4474
4872
|
"span",
|
|
4475
4873
|
{
|
|
4476
4874
|
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
@@ -4478,7 +4876,7 @@ function TransitionDesigner({
|
|
|
4478
4876
|
children: TYPE_LABEL[row.type]
|
|
4479
4877
|
}
|
|
4480
4878
|
),
|
|
4481
|
-
isCreatingThis ? /* @__PURE__ */ (0,
|
|
4879
|
+
isCreatingThis ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4482
4880
|
SorceryProgressBar,
|
|
4483
4881
|
{
|
|
4484
4882
|
isLoading: true,
|
|
@@ -4486,7 +4884,7 @@ function TransitionDesigner({
|
|
|
4486
4884
|
statusText: "CREATING",
|
|
4487
4885
|
estimatedDurationMs: row.type === "crossfade" ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS
|
|
4488
4886
|
}
|
|
4489
|
-
) }) : /* @__PURE__ */ (0,
|
|
4887
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4490
4888
|
"button",
|
|
4491
4889
|
{
|
|
4492
4890
|
type: "button",
|
|
@@ -4497,7 +4895,7 @@ function TransitionDesigner({
|
|
|
4497
4895
|
children: "Create"
|
|
4498
4896
|
}
|
|
4499
4897
|
),
|
|
4500
|
-
errMsg && /* @__PURE__ */ (0,
|
|
4898
|
+
errMsg && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4501
4899
|
"span",
|
|
4502
4900
|
{
|
|
4503
4901
|
"data-testid": `${testIdPrefix}-row-error-${i}`,
|
|
@@ -4516,8 +4914,8 @@ function TransitionDesigner({
|
|
|
4516
4914
|
}
|
|
4517
4915
|
|
|
4518
4916
|
// src/components/PanelMasterStrip.tsx
|
|
4519
|
-
var
|
|
4520
|
-
var
|
|
4917
|
+
var import_react20 = require("react");
|
|
4918
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4521
4919
|
function PanelMasterStrip({
|
|
4522
4920
|
bus,
|
|
4523
4921
|
levels = null,
|
|
@@ -4536,22 +4934,22 @@ function PanelMasterStrip({
|
|
|
4536
4934
|
onToggleFxEnabled,
|
|
4537
4935
|
onShowFxEditor
|
|
4538
4936
|
}) {
|
|
4539
|
-
const [search, setSearch] = (0,
|
|
4540
|
-
const filtered = (0,
|
|
4937
|
+
const [search, setSearch] = (0, import_react20.useState)("");
|
|
4938
|
+
const filtered = (0, import_react20.useMemo)(() => {
|
|
4541
4939
|
const q = search.trim().toLowerCase();
|
|
4542
4940
|
if (!q) return availableFx;
|
|
4543
4941
|
return availableFx.filter(
|
|
4544
4942
|
(fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
|
|
4545
4943
|
);
|
|
4546
4944
|
}, [availableFx, search]);
|
|
4547
|
-
return /* @__PURE__ */ (0,
|
|
4945
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4548
4946
|
"div",
|
|
4549
4947
|
{
|
|
4550
4948
|
"data-testid": "panel-master-strip",
|
|
4551
4949
|
className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border border-l-2 border-l-sas-accent/50 bg-sas-accent/5 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
|
|
4552
4950
|
children: [
|
|
4553
|
-
/* @__PURE__ */ (0,
|
|
4554
|
-
/* @__PURE__ */ (0,
|
|
4951
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4952
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4555
4953
|
"span",
|
|
4556
4954
|
{
|
|
4557
4955
|
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
@@ -4559,8 +4957,8 @@ function PanelMasterStrip({
|
|
|
4559
4957
|
children: "BUS"
|
|
4560
4958
|
}
|
|
4561
4959
|
),
|
|
4562
|
-
/* @__PURE__ */ (0,
|
|
4563
|
-
/* @__PURE__ */ (0,
|
|
4960
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
|
|
4961
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4564
4962
|
VolumeSlider,
|
|
4565
4963
|
{
|
|
4566
4964
|
value: dbToSlider(bus.volume),
|
|
@@ -4568,8 +4966,8 @@ function PanelMasterStrip({
|
|
|
4568
4966
|
disabled
|
|
4569
4967
|
}
|
|
4570
4968
|
),
|
|
4571
|
-
/* @__PURE__ */ (0,
|
|
4572
|
-
/* @__PURE__ */ (0,
|
|
4969
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
|
|
4970
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4573
4971
|
LevelMeter,
|
|
4574
4972
|
{
|
|
4575
4973
|
peakDb: levels?.leftDb ?? -120,
|
|
@@ -4579,7 +4977,7 @@ function PanelMasterStrip({
|
|
|
4579
4977
|
"data-testid": "bus-meter-left"
|
|
4580
4978
|
}
|
|
4581
4979
|
),
|
|
4582
|
-
/* @__PURE__ */ (0,
|
|
4980
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4583
4981
|
LevelMeter,
|
|
4584
4982
|
{
|
|
4585
4983
|
peakDb: levels?.rightDb ?? -120,
|
|
@@ -4590,7 +4988,7 @@ function PanelMasterStrip({
|
|
|
4590
4988
|
)
|
|
4591
4989
|
] })
|
|
4592
4990
|
] }),
|
|
4593
|
-
/* @__PURE__ */ (0,
|
|
4991
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4594
4992
|
"button",
|
|
4595
4993
|
{
|
|
4596
4994
|
"data-testid": "bus-mute-button",
|
|
@@ -4601,7 +4999,7 @@ function PanelMasterStrip({
|
|
|
4601
4999
|
children: "M"
|
|
4602
5000
|
}
|
|
4603
5001
|
),
|
|
4604
|
-
/* @__PURE__ */ (0,
|
|
5002
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4605
5003
|
"button",
|
|
4606
5004
|
{
|
|
4607
5005
|
"data-testid": "bus-solo-button",
|
|
@@ -4612,14 +5010,14 @@ function PanelMasterStrip({
|
|
|
4612
5010
|
children: "S"
|
|
4613
5011
|
}
|
|
4614
5012
|
),
|
|
4615
|
-
/* @__PURE__ */ (0,
|
|
5013
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center gap-1 max-w-[45%] min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4616
5014
|
"span",
|
|
4617
5015
|
{
|
|
4618
5016
|
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
4619
5017
|
className: `flex items-center gap-1 px-1.5 py-0.5 rounded-sm border text-[10px] whitespace-nowrap ${fx.enabled ? "border-sas-accent/60 text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted/50 bg-sas-panel"}`,
|
|
4620
5018
|
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
4621
5019
|
children: [
|
|
4622
|
-
/* @__PURE__ */ (0,
|
|
5020
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4623
5021
|
"button",
|
|
4624
5022
|
{
|
|
4625
5023
|
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
@@ -4630,7 +5028,7 @@ function PanelMasterStrip({
|
|
|
4630
5028
|
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
4631
5029
|
}
|
|
4632
5030
|
),
|
|
4633
|
-
onShowFxEditor ? /* @__PURE__ */ (0,
|
|
5031
|
+
onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4634
5032
|
"button",
|
|
4635
5033
|
{
|
|
4636
5034
|
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
@@ -4640,8 +5038,8 @@ function PanelMasterStrip({
|
|
|
4640
5038
|
title: `Open ${fx.name} editor`,
|
|
4641
5039
|
children: fx.name
|
|
4642
5040
|
}
|
|
4643
|
-
) : /* @__PURE__ */ (0,
|
|
4644
|
-
/* @__PURE__ */ (0,
|
|
5041
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
5042
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4645
5043
|
"button",
|
|
4646
5044
|
{
|
|
4647
5045
|
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
@@ -4656,7 +5054,7 @@ function PanelMasterStrip({
|
|
|
4656
5054
|
},
|
|
4657
5055
|
`${fx.index}:${fx.pluginId}`
|
|
4658
5056
|
)) }),
|
|
4659
|
-
/* @__PURE__ */ (0,
|
|
5057
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4660
5058
|
"button",
|
|
4661
5059
|
{
|
|
4662
5060
|
"data-testid": "bus-fx-add-button",
|
|
@@ -4668,9 +5066,9 @@ function PanelMasterStrip({
|
|
|
4668
5066
|
}
|
|
4669
5067
|
)
|
|
4670
5068
|
] }),
|
|
4671
|
-
fxPickerOpen && /* @__PURE__ */ (0,
|
|
4672
|
-
/* @__PURE__ */ (0,
|
|
4673
|
-
/* @__PURE__ */ (0,
|
|
5069
|
+
fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
5070
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
5071
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4674
5072
|
"input",
|
|
4675
5073
|
{
|
|
4676
5074
|
type: "text",
|
|
@@ -4680,7 +5078,7 @@ function PanelMasterStrip({
|
|
|
4680
5078
|
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
4681
5079
|
}
|
|
4682
5080
|
),
|
|
4683
|
-
onRefreshFx && /* @__PURE__ */ (0,
|
|
5081
|
+
onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4684
5082
|
"button",
|
|
4685
5083
|
{
|
|
4686
5084
|
onClick: () => onRefreshFx(),
|
|
@@ -4691,8 +5089,8 @@ function PanelMasterStrip({
|
|
|
4691
5089
|
}
|
|
4692
5090
|
)
|
|
4693
5091
|
] }),
|
|
4694
|
-
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0,
|
|
4695
|
-
filtered.map((fx) => /* @__PURE__ */ (0,
|
|
5092
|
+
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
|
|
5093
|
+
filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4696
5094
|
"button",
|
|
4697
5095
|
{
|
|
4698
5096
|
"data-testid": `bus-fx-pick-${fx.pluginId}`,
|
|
@@ -4700,13 +5098,13 @@ function PanelMasterStrip({
|
|
|
4700
5098
|
className: "flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent",
|
|
4701
5099
|
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
4702
5100
|
children: [
|
|
4703
|
-
/* @__PURE__ */ (0,
|
|
4704
|
-
/* @__PURE__ */ (0,
|
|
5101
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
5102
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
4705
5103
|
]
|
|
4706
5104
|
},
|
|
4707
5105
|
fx.pluginId
|
|
4708
5106
|
)),
|
|
4709
|
-
filtered.length === 0 && /* @__PURE__ */ (0,
|
|
5107
|
+
filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
|
|
4710
5108
|
] })
|
|
4711
5109
|
] })
|
|
4712
5110
|
]
|
|
@@ -4715,18 +5113,18 @@ function PanelMasterStrip({
|
|
|
4715
5113
|
}
|
|
4716
5114
|
|
|
4717
5115
|
// src/hooks/usePanelBus.ts
|
|
4718
|
-
var
|
|
5116
|
+
var import_react21 = require("react");
|
|
4719
5117
|
var LEVELS_POLL_MS = 66;
|
|
4720
5118
|
function usePanelBus(host, activeSceneId) {
|
|
4721
5119
|
const supported = typeof host.getPanelBusState === "function";
|
|
4722
|
-
const [bus, setBus] = (0,
|
|
4723
|
-
const [levels, setLevels] = (0,
|
|
4724
|
-
const [availableFx, setAvailableFx] = (0,
|
|
4725
|
-
const [fxLoading, setFxLoading] = (0,
|
|
4726
|
-
const [fxPickerOpen, setFxPickerOpen] = (0,
|
|
4727
|
-
const fxLoadedRef = (0,
|
|
4728
|
-
const loadSeqRef = (0,
|
|
4729
|
-
const reload = (0,
|
|
5120
|
+
const [bus, setBus] = (0, import_react21.useState)(null);
|
|
5121
|
+
const [levels, setLevels] = (0, import_react21.useState)(null);
|
|
5122
|
+
const [availableFx, setAvailableFx] = (0, import_react21.useState)([]);
|
|
5123
|
+
const [fxLoading, setFxLoading] = (0, import_react21.useState)(false);
|
|
5124
|
+
const [fxPickerOpen, setFxPickerOpen] = (0, import_react21.useState)(false);
|
|
5125
|
+
const fxLoadedRef = (0, import_react21.useRef)(false);
|
|
5126
|
+
const loadSeqRef = (0, import_react21.useRef)(0);
|
|
5127
|
+
const reload = (0, import_react21.useCallback)(async () => {
|
|
4730
5128
|
if (!supported || !activeSceneId || !host.getPanelBusState) {
|
|
4731
5129
|
setBus(null);
|
|
4732
5130
|
return;
|
|
@@ -4738,12 +5136,12 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4738
5136
|
} catch {
|
|
4739
5137
|
}
|
|
4740
5138
|
}, [host, activeSceneId, supported]);
|
|
4741
|
-
(0,
|
|
5139
|
+
(0, import_react21.useEffect)(() => {
|
|
4742
5140
|
setBus(null);
|
|
4743
5141
|
setFxPickerOpen(false);
|
|
4744
5142
|
void reload();
|
|
4745
5143
|
}, [reload]);
|
|
4746
|
-
(0,
|
|
5144
|
+
(0, import_react21.useEffect)(() => {
|
|
4747
5145
|
if (!supported || !activeSceneId || !bus?.engaged || !host.getPanelBusLevels) {
|
|
4748
5146
|
setLevels(null);
|
|
4749
5147
|
return;
|
|
@@ -4765,7 +5163,7 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4765
5163
|
clearInterval(id);
|
|
4766
5164
|
};
|
|
4767
5165
|
}, [supported, activeSceneId, bus?.engaged, host]);
|
|
4768
|
-
const loadFxList = (0,
|
|
5166
|
+
const loadFxList = (0, import_react21.useCallback)(
|
|
4769
5167
|
async (opts) => {
|
|
4770
5168
|
if (!supported || !host.getAvailableFx) return;
|
|
4771
5169
|
if (fxLoadedRef.current && !opts.force && !opts.rescan) return;
|
|
@@ -4781,14 +5179,14 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4781
5179
|
},
|
|
4782
5180
|
[host, supported]
|
|
4783
5181
|
);
|
|
4784
|
-
const openPicker = (0,
|
|
5182
|
+
const openPicker = (0, import_react21.useCallback)(
|
|
4785
5183
|
(open) => {
|
|
4786
5184
|
setFxPickerOpen(open);
|
|
4787
5185
|
if (open) void loadFxList({});
|
|
4788
5186
|
},
|
|
4789
5187
|
[loadFxList]
|
|
4790
5188
|
);
|
|
4791
|
-
const mutate = (0,
|
|
5189
|
+
const mutate = (0, import_react21.useCallback)(
|
|
4792
5190
|
(fn) => {
|
|
4793
5191
|
if (!fn || !activeSceneId) return;
|
|
4794
5192
|
void (async () => {
|
|
@@ -4832,8 +5230,8 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4832
5230
|
}
|
|
4833
5231
|
|
|
4834
5232
|
// src/components/DownloadPackButton.tsx
|
|
4835
|
-
var
|
|
4836
|
-
var
|
|
5233
|
+
var import_react22 = require("react");
|
|
5234
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
4837
5235
|
function formatSize(bytes) {
|
|
4838
5236
|
if (!bytes || bytes <= 0) return "";
|
|
4839
5237
|
const gb = bytes / 1024 ** 3;
|
|
@@ -4849,10 +5247,10 @@ var DownloadPackButton = ({
|
|
|
4849
5247
|
variant = "compact",
|
|
4850
5248
|
onDownloadComplete
|
|
4851
5249
|
}) => {
|
|
4852
|
-
const [status, setStatus] = (0,
|
|
4853
|
-
const [progress, setProgress] = (0,
|
|
4854
|
-
const [errorMessage, setErrorMessage] = (0,
|
|
4855
|
-
(0,
|
|
5250
|
+
const [status, setStatus] = (0, import_react22.useState)("idle");
|
|
5251
|
+
const [progress, setProgress] = (0, import_react22.useState)(0);
|
|
5252
|
+
const [errorMessage, setErrorMessage] = (0, import_react22.useState)(null);
|
|
5253
|
+
(0, import_react22.useEffect)(() => {
|
|
4856
5254
|
const unsub = host.onSamplePackProgress(packId, (p) => {
|
|
4857
5255
|
setStatus(p.status);
|
|
4858
5256
|
setProgress(p.progress);
|
|
@@ -4867,7 +5265,7 @@ var DownloadPackButton = ({
|
|
|
4867
5265
|
});
|
|
4868
5266
|
return unsub;
|
|
4869
5267
|
}, [host, packId, onDownloadComplete]);
|
|
4870
|
-
const handleClick = (0,
|
|
5268
|
+
const handleClick = (0, import_react22.useCallback)(async () => {
|
|
4871
5269
|
if (status !== "idle" && status !== "error") return;
|
|
4872
5270
|
try {
|
|
4873
5271
|
setStatus("downloading");
|
|
@@ -4921,8 +5319,8 @@ var DownloadPackButton = ({
|
|
|
4921
5319
|
} else {
|
|
4922
5320
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
4923
5321
|
}
|
|
4924
|
-
return /* @__PURE__ */ (0,
|
|
4925
|
-
/* @__PURE__ */ (0,
|
|
5322
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { children: [
|
|
5323
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4926
5324
|
"button",
|
|
4927
5325
|
{
|
|
4928
5326
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -4933,12 +5331,12 @@ var DownloadPackButton = ({
|
|
|
4933
5331
|
children: buttonLabel
|
|
4934
5332
|
}
|
|
4935
5333
|
),
|
|
4936
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0,
|
|
5334
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
4937
5335
|
] });
|
|
4938
5336
|
};
|
|
4939
5337
|
|
|
4940
5338
|
// src/components/SamplePackCTACard.tsx
|
|
4941
|
-
var
|
|
5339
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
4942
5340
|
var SamplePackCTACard = ({
|
|
4943
5341
|
host,
|
|
4944
5342
|
pack,
|
|
@@ -4946,7 +5344,7 @@ var SamplePackCTACard = ({
|
|
|
4946
5344
|
onDownloadComplete
|
|
4947
5345
|
}) => {
|
|
4948
5346
|
if (status === "checking") {
|
|
4949
|
-
return /* @__PURE__ */ (0,
|
|
5347
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4950
5348
|
"div",
|
|
4951
5349
|
{
|
|
4952
5350
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -4957,16 +5355,16 @@ var SamplePackCTACard = ({
|
|
|
4957
5355
|
}
|
|
4958
5356
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
4959
5357
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
4960
|
-
return /* @__PURE__ */ (0,
|
|
5358
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
4961
5359
|
"div",
|
|
4962
5360
|
{
|
|
4963
5361
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
4964
5362
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
4965
5363
|
children: [
|
|
4966
|
-
/* @__PURE__ */ (0,
|
|
4967
|
-
/* @__PURE__ */ (0,
|
|
4968
|
-
/* @__PURE__ */ (0,
|
|
4969
|
-
/* @__PURE__ */ (0,
|
|
5364
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
5365
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
5366
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
5367
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4970
5368
|
DownloadPackButton,
|
|
4971
5369
|
{
|
|
4972
5370
|
host,
|
|
@@ -4983,7 +5381,7 @@ var SamplePackCTACard = ({
|
|
|
4983
5381
|
};
|
|
4984
5382
|
|
|
4985
5383
|
// src/components/WaveformView.tsx
|
|
4986
|
-
var
|
|
5384
|
+
var import_react23 = require("react");
|
|
4987
5385
|
|
|
4988
5386
|
// src/components/waveform.ts
|
|
4989
5387
|
function computePeaks(audioBuffer, bins, targetSamples) {
|
|
@@ -5046,7 +5444,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
5046
5444
|
}
|
|
5047
5445
|
|
|
5048
5446
|
// src/components/WaveformView.tsx
|
|
5049
|
-
var
|
|
5447
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
5050
5448
|
var WaveformView = ({
|
|
5051
5449
|
host,
|
|
5052
5450
|
filePath,
|
|
@@ -5055,9 +5453,9 @@ var WaveformView = ({
|
|
|
5055
5453
|
fillStyle,
|
|
5056
5454
|
targetSamples
|
|
5057
5455
|
}) => {
|
|
5058
|
-
const canvasRef = (0,
|
|
5059
|
-
const [peaks, setPeaks] = (0,
|
|
5060
|
-
(0,
|
|
5456
|
+
const canvasRef = (0, import_react23.useRef)(null);
|
|
5457
|
+
const [peaks, setPeaks] = (0, import_react23.useState)(null);
|
|
5458
|
+
(0, import_react23.useEffect)(() => {
|
|
5061
5459
|
let cancelled = false;
|
|
5062
5460
|
let audioContext = null;
|
|
5063
5461
|
(async () => {
|
|
@@ -5083,7 +5481,7 @@ var WaveformView = ({
|
|
|
5083
5481
|
cancelled = true;
|
|
5084
5482
|
};
|
|
5085
5483
|
}, [host, filePath, bins, targetSamples]);
|
|
5086
|
-
(0,
|
|
5484
|
+
(0, import_react23.useEffect)(() => {
|
|
5087
5485
|
if (!peaks) return;
|
|
5088
5486
|
const canvas = canvasRef.current;
|
|
5089
5487
|
if (!canvas) return;
|
|
@@ -5094,7 +5492,7 @@ var WaveformView = ({
|
|
|
5094
5492
|
observer.observe(canvas);
|
|
5095
5493
|
return () => observer.disconnect();
|
|
5096
5494
|
}, [peaks, fillStyle]);
|
|
5097
|
-
return /* @__PURE__ */ (0,
|
|
5495
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5098
5496
|
"canvas",
|
|
5099
5497
|
{
|
|
5100
5498
|
ref: canvasRef,
|
|
@@ -5105,8 +5503,8 @@ var WaveformView = ({
|
|
|
5105
5503
|
};
|
|
5106
5504
|
|
|
5107
5505
|
// src/components/ScrollingWaveform.tsx
|
|
5108
|
-
var
|
|
5109
|
-
var
|
|
5506
|
+
var import_react24 = require("react");
|
|
5507
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
5110
5508
|
var ScrollingWaveform = ({
|
|
5111
5509
|
getPeakDb,
|
|
5112
5510
|
active,
|
|
@@ -5114,11 +5512,11 @@ var ScrollingWaveform = ({
|
|
|
5114
5512
|
className,
|
|
5115
5513
|
fillStyle
|
|
5116
5514
|
}) => {
|
|
5117
|
-
const canvasRef = (0,
|
|
5118
|
-
const ringRef = (0,
|
|
5119
|
-
const writeIdxRef = (0,
|
|
5120
|
-
const rafRef = (0,
|
|
5121
|
-
(0,
|
|
5515
|
+
const canvasRef = (0, import_react24.useRef)(null);
|
|
5516
|
+
const ringRef = (0, import_react24.useRef)(new Float32Array(columns));
|
|
5517
|
+
const writeIdxRef = (0, import_react24.useRef)(0);
|
|
5518
|
+
const rafRef = (0, import_react24.useRef)(null);
|
|
5519
|
+
(0, import_react24.useEffect)(() => {
|
|
5122
5520
|
if (ringRef.current.length !== columns) {
|
|
5123
5521
|
const next = new Float32Array(columns);
|
|
5124
5522
|
const prev = ringRef.current;
|
|
@@ -5130,7 +5528,7 @@ var ScrollingWaveform = ({
|
|
|
5130
5528
|
writeIdxRef.current = writeIdxRef.current % columns;
|
|
5131
5529
|
}
|
|
5132
5530
|
}, [columns]);
|
|
5133
|
-
(0,
|
|
5531
|
+
(0, import_react24.useEffect)(() => {
|
|
5134
5532
|
if (!active) {
|
|
5135
5533
|
if (rafRef.current !== null) {
|
|
5136
5534
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -5182,7 +5580,7 @@ var ScrollingWaveform = ({
|
|
|
5182
5580
|
}
|
|
5183
5581
|
};
|
|
5184
5582
|
}, [active, getPeakDb, fillStyle]);
|
|
5185
|
-
return /* @__PURE__ */ (0,
|
|
5583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5186
5584
|
"canvas",
|
|
5187
5585
|
{
|
|
5188
5586
|
ref: canvasRef,
|
|
@@ -5193,8 +5591,8 @@ var ScrollingWaveform = ({
|
|
|
5193
5591
|
};
|
|
5194
5592
|
|
|
5195
5593
|
// src/components/OffsetScrubber.tsx
|
|
5196
|
-
var
|
|
5197
|
-
var
|
|
5594
|
+
var import_react25 = require("react");
|
|
5595
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
5198
5596
|
var SLIDER_HEIGHT_PX = 28;
|
|
5199
5597
|
var TICK_HEIGHT_PX = 14;
|
|
5200
5598
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -5207,40 +5605,40 @@ function OffsetScrubber({
|
|
|
5207
5605
|
onChange,
|
|
5208
5606
|
disabled = false
|
|
5209
5607
|
}) {
|
|
5210
|
-
const trackRef = (0,
|
|
5211
|
-
const [draftOffset, setDraftOffset] = (0,
|
|
5212
|
-
const [isDragging, setIsDragging] = (0,
|
|
5213
|
-
(0,
|
|
5608
|
+
const trackRef = (0, import_react25.useRef)(null);
|
|
5609
|
+
const [draftOffset, setDraftOffset] = (0, import_react25.useState)(offsetSamples);
|
|
5610
|
+
const [isDragging, setIsDragging] = (0, import_react25.useState)(false);
|
|
5611
|
+
(0, import_react25.useEffect)(() => {
|
|
5214
5612
|
if (!isDragging) setDraftOffset(offsetSamples);
|
|
5215
5613
|
}, [offsetSamples, isDragging]);
|
|
5216
5614
|
const sampleRate = cuePoints?.sample_rate ?? 44100;
|
|
5217
5615
|
const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
|
|
5218
|
-
const beatsForRange = (0,
|
|
5616
|
+
const beatsForRange = (0, import_react25.useMemo)(() => {
|
|
5219
5617
|
return Math.round(60 / projectBpm * sampleRate);
|
|
5220
5618
|
}, [projectBpm, sampleRate]);
|
|
5221
5619
|
const rangeSamples = beatsForRange * meter;
|
|
5222
|
-
const sampleToFraction = (0,
|
|
5620
|
+
const sampleToFraction = (0, import_react25.useCallback)(
|
|
5223
5621
|
(sample) => {
|
|
5224
5622
|
const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
|
|
5225
5623
|
return (clamped + rangeSamples) / (2 * rangeSamples);
|
|
5226
5624
|
},
|
|
5227
5625
|
[rangeSamples]
|
|
5228
5626
|
);
|
|
5229
|
-
const fractionToSample = (0,
|
|
5627
|
+
const fractionToSample = (0, import_react25.useCallback)(
|
|
5230
5628
|
(fraction) => {
|
|
5231
5629
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
5232
5630
|
return Math.round(clamped * 2 * rangeSamples - rangeSamples);
|
|
5233
5631
|
},
|
|
5234
5632
|
[rangeSamples]
|
|
5235
5633
|
);
|
|
5236
|
-
const snapTargets = (0,
|
|
5634
|
+
const snapTargets = (0, import_react25.useMemo)(() => {
|
|
5237
5635
|
if (!cuePoints || cuePoints.beats.length === 0) return [];
|
|
5238
5636
|
const downbeat = cuePoints.beats[0];
|
|
5239
5637
|
const positives = cuePoints.beats.map((b) => b - downbeat);
|
|
5240
5638
|
const negatives = positives.slice(1).map((p) => -p);
|
|
5241
5639
|
return [...negatives, ...positives].sort((a, b) => a - b);
|
|
5242
5640
|
}, [cuePoints]);
|
|
5243
|
-
const snapToBeat = (0,
|
|
5641
|
+
const snapToBeat = (0, import_react25.useCallback)(
|
|
5244
5642
|
(sample) => {
|
|
5245
5643
|
if (snapTargets.length === 0) return sample;
|
|
5246
5644
|
let best = snapTargets[0];
|
|
@@ -5256,7 +5654,7 @@ function OffsetScrubber({
|
|
|
5256
5654
|
},
|
|
5257
5655
|
[snapTargets]
|
|
5258
5656
|
);
|
|
5259
|
-
const handlePointerDown = (0,
|
|
5657
|
+
const handlePointerDown = (0, import_react25.useCallback)(
|
|
5260
5658
|
(e) => {
|
|
5261
5659
|
if (disabled || !cuePoints) return;
|
|
5262
5660
|
e.preventDefault();
|
|
@@ -5290,7 +5688,7 @@ function OffsetScrubber({
|
|
|
5290
5688
|
},
|
|
5291
5689
|
[disabled, cuePoints, fractionToSample, onChange, snapToBeat]
|
|
5292
5690
|
);
|
|
5293
|
-
const handleResetToZero = (0,
|
|
5691
|
+
const handleResetToZero = (0, import_react25.useCallback)(() => {
|
|
5294
5692
|
if (disabled) return;
|
|
5295
5693
|
setDraftOffset(0);
|
|
5296
5694
|
onChange(0);
|
|
@@ -5298,7 +5696,7 @@ function OffsetScrubber({
|
|
|
5298
5696
|
const thumbFraction = sampleToFraction(draftOffset);
|
|
5299
5697
|
const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
|
|
5300
5698
|
const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
|
|
5301
|
-
const ticks = (0,
|
|
5699
|
+
const ticks = (0, import_react25.useMemo)(() => {
|
|
5302
5700
|
if (!cuePoints) return [];
|
|
5303
5701
|
const downbeat = cuePoints.beats[0] ?? 0;
|
|
5304
5702
|
return cuePoints.beats.map((b, i) => {
|
|
@@ -5309,9 +5707,9 @@ function OffsetScrubber({
|
|
|
5309
5707
|
});
|
|
5310
5708
|
}, [cuePoints, sampleToFraction]);
|
|
5311
5709
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
5312
|
-
return /* @__PURE__ */ (0,
|
|
5313
|
-
/* @__PURE__ */ (0,
|
|
5314
|
-
/* @__PURE__ */ (0,
|
|
5710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
5711
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
5712
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
5315
5713
|
"div",
|
|
5316
5714
|
{
|
|
5317
5715
|
ref: trackRef,
|
|
@@ -5327,7 +5725,7 @@ function OffsetScrubber({
|
|
|
5327
5725
|
"aria-valuenow": draftOffset,
|
|
5328
5726
|
"aria-disabled": isDisabled,
|
|
5329
5727
|
children: [
|
|
5330
|
-
/* @__PURE__ */ (0,
|
|
5728
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5331
5729
|
"div",
|
|
5332
5730
|
{
|
|
5333
5731
|
"aria-hidden": "true",
|
|
@@ -5335,7 +5733,7 @@ function OffsetScrubber({
|
|
|
5335
5733
|
style: { left: "50%" }
|
|
5336
5734
|
}
|
|
5337
5735
|
),
|
|
5338
|
-
ticks.map((t) => /* @__PURE__ */ (0,
|
|
5736
|
+
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5339
5737
|
"div",
|
|
5340
5738
|
{
|
|
5341
5739
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -5350,7 +5748,7 @@ function OffsetScrubber({
|
|
|
5350
5748
|
},
|
|
5351
5749
|
t.i
|
|
5352
5750
|
)),
|
|
5353
|
-
/* @__PURE__ */ (0,
|
|
5751
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5354
5752
|
"div",
|
|
5355
5753
|
{
|
|
5356
5754
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -5367,7 +5765,7 @@ function OffsetScrubber({
|
|
|
5367
5765
|
]
|
|
5368
5766
|
}
|
|
5369
5767
|
),
|
|
5370
|
-
/* @__PURE__ */ (0,
|
|
5768
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5371
5769
|
"span",
|
|
5372
5770
|
{
|
|
5373
5771
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -5375,7 +5773,7 @@ function OffsetScrubber({
|
|
|
5375
5773
|
children: formatOffset(draftOffset, sampleRate)
|
|
5376
5774
|
}
|
|
5377
5775
|
),
|
|
5378
|
-
/* @__PURE__ */ (0,
|
|
5776
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5379
5777
|
"button",
|
|
5380
5778
|
{
|
|
5381
5779
|
type: "button",
|
|
@@ -5387,7 +5785,7 @@ function OffsetScrubber({
|
|
|
5387
5785
|
children: "\u2316"
|
|
5388
5786
|
}
|
|
5389
5787
|
),
|
|
5390
|
-
bpmMismatch && /* @__PURE__ */ (0,
|
|
5788
|
+
bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5391
5789
|
"span",
|
|
5392
5790
|
{
|
|
5393
5791
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -5459,16 +5857,16 @@ function synthesizeCuePoints({
|
|
|
5459
5857
|
}
|
|
5460
5858
|
|
|
5461
5859
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
5462
|
-
var
|
|
5860
|
+
var import_react30 = require("react");
|
|
5463
5861
|
|
|
5464
5862
|
// src/hooks/useSceneState.ts
|
|
5465
|
-
var
|
|
5863
|
+
var import_react26 = require("react");
|
|
5466
5864
|
function useSceneState(activeSceneId, initialValue) {
|
|
5467
|
-
const [stateMap, setStateMap] = (0,
|
|
5468
|
-
const activeSceneIdRef = (0,
|
|
5865
|
+
const [stateMap, setStateMap] = (0, import_react26.useState)(() => /* @__PURE__ */ new Map());
|
|
5866
|
+
const activeSceneIdRef = (0, import_react26.useRef)(activeSceneId);
|
|
5469
5867
|
activeSceneIdRef.current = activeSceneId;
|
|
5470
5868
|
const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
|
|
5471
|
-
const setForCurrentScene = (0,
|
|
5869
|
+
const setForCurrentScene = (0, import_react26.useCallback)((value) => {
|
|
5472
5870
|
const sid = activeSceneIdRef.current;
|
|
5473
5871
|
if (sid === null) return;
|
|
5474
5872
|
setStateMap((prev) => {
|
|
@@ -5479,7 +5877,7 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
5479
5877
|
return newMap;
|
|
5480
5878
|
});
|
|
5481
5879
|
}, [initialValue]);
|
|
5482
|
-
const setForScene = (0,
|
|
5880
|
+
const setForScene = (0, import_react26.useCallback)((sceneId, value) => {
|
|
5483
5881
|
setStateMap((prev) => {
|
|
5484
5882
|
const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
|
|
5485
5883
|
const next = typeof value === "function" ? value(current) : value;
|
|
@@ -5492,10 +5890,10 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
5492
5890
|
}
|
|
5493
5891
|
|
|
5494
5892
|
// src/hooks/useAnySolo.ts
|
|
5495
|
-
var
|
|
5893
|
+
var import_react27 = require("react");
|
|
5496
5894
|
function useAnySolo(host) {
|
|
5497
|
-
const [anySolo, setAnySolo] = (0,
|
|
5498
|
-
(0,
|
|
5895
|
+
const [anySolo, setAnySolo] = (0, import_react27.useState)(false);
|
|
5896
|
+
(0, import_react27.useEffect)(() => {
|
|
5499
5897
|
let active = true;
|
|
5500
5898
|
const refresh = () => {
|
|
5501
5899
|
host.isAnySoloActive().then((v) => {
|
|
@@ -5514,7 +5912,7 @@ function useAnySolo(host) {
|
|
|
5514
5912
|
}
|
|
5515
5913
|
|
|
5516
5914
|
// src/hooks/useSoundHistory.ts
|
|
5517
|
-
var
|
|
5915
|
+
var import_react28 = require("react");
|
|
5518
5916
|
var EMPTY = { entries: [], cursor: -1 };
|
|
5519
5917
|
function sameDescriptor(a, b) {
|
|
5520
5918
|
if (a === b) return true;
|
|
@@ -5526,14 +5924,14 @@ function sameDescriptor(a, b) {
|
|
|
5526
5924
|
}
|
|
5527
5925
|
function useSoundHistory(applySound, opts = {}) {
|
|
5528
5926
|
const max = Math.max(2, opts.max ?? 24);
|
|
5529
|
-
const applyRef = (0,
|
|
5927
|
+
const applyRef = (0, import_react28.useRef)(applySound);
|
|
5530
5928
|
applyRef.current = applySound;
|
|
5531
|
-
const onChangeRef = (0,
|
|
5929
|
+
const onChangeRef = (0, import_react28.useRef)(opts.onChange);
|
|
5532
5930
|
onChangeRef.current = opts.onChange;
|
|
5533
|
-
const dataRef = (0,
|
|
5534
|
-
const [, setVersion] = (0,
|
|
5535
|
-
const bump = (0,
|
|
5536
|
-
const commit = (0,
|
|
5931
|
+
const dataRef = (0, import_react28.useRef)({});
|
|
5932
|
+
const [, setVersion] = (0, import_react28.useState)(0);
|
|
5933
|
+
const bump = (0, import_react28.useCallback)(() => setVersion((v) => v + 1), []);
|
|
5934
|
+
const commit = (0, import_react28.useCallback)(
|
|
5537
5935
|
(trackId, next, notify) => {
|
|
5538
5936
|
dataRef.current = { ...dataRef.current, [trackId]: next };
|
|
5539
5937
|
bump();
|
|
@@ -5541,7 +5939,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5541
5939
|
},
|
|
5542
5940
|
[bump]
|
|
5543
5941
|
);
|
|
5544
|
-
const record = (0,
|
|
5942
|
+
const record = (0, import_react28.useCallback)(
|
|
5545
5943
|
(trackId, descriptor, label) => {
|
|
5546
5944
|
const h = dataRef.current[trackId];
|
|
5547
5945
|
const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
|
|
@@ -5556,7 +5954,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5556
5954
|
},
|
|
5557
5955
|
[max, commit]
|
|
5558
5956
|
);
|
|
5559
|
-
const restoreTo = (0,
|
|
5957
|
+
const restoreTo = (0, import_react28.useCallback)(
|
|
5560
5958
|
async (trackId, index) => {
|
|
5561
5959
|
const h = dataRef.current[trackId];
|
|
5562
5960
|
if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
|
|
@@ -5566,7 +5964,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5566
5964
|
},
|
|
5567
5965
|
[commit]
|
|
5568
5966
|
);
|
|
5569
|
-
const undo = (0,
|
|
5967
|
+
const undo = (0, import_react28.useCallback)(
|
|
5570
5968
|
(trackId) => {
|
|
5571
5969
|
const h = dataRef.current[trackId];
|
|
5572
5970
|
if (!h || h.cursor <= 0) return Promise.resolve(false);
|
|
@@ -5574,7 +5972,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5574
5972
|
},
|
|
5575
5973
|
[restoreTo]
|
|
5576
5974
|
);
|
|
5577
|
-
const toggleFavorite = (0,
|
|
5975
|
+
const toggleFavorite = (0, import_react28.useCallback)(
|
|
5578
5976
|
(trackId, index) => {
|
|
5579
5977
|
const h = dataRef.current[trackId];
|
|
5580
5978
|
if (!h || index < 0 || index >= h.entries.length) return;
|
|
@@ -5583,7 +5981,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5583
5981
|
},
|
|
5584
5982
|
[commit]
|
|
5585
5983
|
);
|
|
5586
|
-
const restore = (0,
|
|
5984
|
+
const restore = (0, import_react28.useCallback)(
|
|
5587
5985
|
(trackId, state) => {
|
|
5588
5986
|
const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
|
|
5589
5987
|
const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
|
|
@@ -5592,15 +5990,15 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5592
5990
|
},
|
|
5593
5991
|
[commit]
|
|
5594
5992
|
);
|
|
5595
|
-
const list = (0,
|
|
5993
|
+
const list = (0, import_react28.useCallback)(
|
|
5596
5994
|
(trackId) => dataRef.current[trackId] ?? EMPTY,
|
|
5597
5995
|
[]
|
|
5598
5996
|
);
|
|
5599
|
-
const canUndo = (0,
|
|
5997
|
+
const canUndo = (0, import_react28.useCallback)((trackId) => {
|
|
5600
5998
|
const h = dataRef.current[trackId];
|
|
5601
5999
|
return !!h && h.cursor > 0;
|
|
5602
6000
|
}, []);
|
|
5603
|
-
const clear = (0,
|
|
6001
|
+
const clear = (0, import_react28.useCallback)(
|
|
5604
6002
|
(trackId) => {
|
|
5605
6003
|
if (dataRef.current[trackId]) {
|
|
5606
6004
|
const next = { ...dataRef.current };
|
|
@@ -5612,11 +6010,11 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5612
6010
|
},
|
|
5613
6011
|
[bump]
|
|
5614
6012
|
);
|
|
5615
|
-
const reset = (0,
|
|
6013
|
+
const reset = (0, import_react28.useCallback)(() => {
|
|
5616
6014
|
dataRef.current = {};
|
|
5617
6015
|
bump();
|
|
5618
6016
|
}, [bump]);
|
|
5619
|
-
return (0,
|
|
6017
|
+
return (0, import_react28.useMemo)(
|
|
5620
6018
|
() => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
|
|
5621
6019
|
[record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
|
|
5622
6020
|
);
|
|
@@ -5749,7 +6147,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
|
|
|
5749
6147
|
}
|
|
5750
6148
|
|
|
5751
6149
|
// src/panel-core/useTransitionOps.ts
|
|
5752
|
-
var
|
|
6150
|
+
var import_react29 = require("react");
|
|
5753
6151
|
function useTransitionOps({
|
|
5754
6152
|
host,
|
|
5755
6153
|
adapter,
|
|
@@ -5766,8 +6164,8 @@ function useTransitionOps({
|
|
|
5766
6164
|
resolvedFades
|
|
5767
6165
|
}) {
|
|
5768
6166
|
const { identity } = adapter;
|
|
5769
|
-
const appliedFadeAutomationRef = (0,
|
|
5770
|
-
const applyCrossfadeAutomation = (0,
|
|
6167
|
+
const appliedFadeAutomationRef = (0, import_react29.useRef)(/* @__PURE__ */ new Set());
|
|
6168
|
+
const applyCrossfadeAutomation = (0, import_react29.useCallback)(
|
|
5771
6169
|
async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
|
|
5772
6170
|
if (host.setTrackVolumeAutomation) {
|
|
5773
6171
|
const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
|
|
@@ -5784,7 +6182,7 @@ function useTransitionOps({
|
|
|
5784
6182
|
},
|
|
5785
6183
|
[host]
|
|
5786
6184
|
);
|
|
5787
|
-
const applyFadeAutomation = (0,
|
|
6185
|
+
const applyFadeAutomation = (0, import_react29.useCallback)(
|
|
5788
6186
|
async (trackId, direction, bars, bpm, sliderPos, gesture) => {
|
|
5789
6187
|
if (!host.setTrackVolumeAutomation) return;
|
|
5790
6188
|
const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
|
|
@@ -5793,8 +6191,25 @@ function useTransitionOps({
|
|
|
5793
6191
|
},
|
|
5794
6192
|
[host]
|
|
5795
6193
|
);
|
|
5796
|
-
const
|
|
5797
|
-
|
|
6194
|
+
const copyTrackFx = (0, import_react29.useCallback)(
|
|
6195
|
+
async (newTrackId, sourceDbId) => {
|
|
6196
|
+
if (typeof host.copyTrackFxFrom !== "function") return;
|
|
6197
|
+
try {
|
|
6198
|
+
const res = await host.copyTrackFxFrom(newTrackId, sourceDbId);
|
|
6199
|
+
if (res.externalMissing.length > 0) {
|
|
6200
|
+
host.showToast(
|
|
6201
|
+
"warning",
|
|
6202
|
+
"Some FX not copied",
|
|
6203
|
+
`Missing plugin(s): ${res.externalMissing.join(", ")}`
|
|
6204
|
+
);
|
|
6205
|
+
}
|
|
6206
|
+
} catch {
|
|
6207
|
+
}
|
|
6208
|
+
},
|
|
6209
|
+
[host]
|
|
6210
|
+
);
|
|
6211
|
+
const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react29.useState)(false);
|
|
6212
|
+
const handleCreateCrossfade = (0, import_react29.useCallback)(
|
|
5798
6213
|
async (origin, target) => {
|
|
5799
6214
|
const scene = activeSceneId;
|
|
5800
6215
|
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
@@ -5871,6 +6286,8 @@ function useTransitionOps({
|
|
|
5871
6286
|
};
|
|
5872
6287
|
const originLabel = await copySound(top.id, origin.dbId);
|
|
5873
6288
|
const targetLabel = await copySound(bottom.id, target.dbId);
|
|
6289
|
+
await copyTrackFx(top.id, origin.dbId);
|
|
6290
|
+
await copyTrackFx(bottom.id, target.dbId);
|
|
5874
6291
|
await applyCrossfadeAutomation(top.id, bottom.id, mc.bars, mc.bpm, 0.5);
|
|
5875
6292
|
const groupId = top.dbId;
|
|
5876
6293
|
const originMeta = {
|
|
@@ -5922,8 +6339,9 @@ function useTransitionOps({
|
|
|
5922
6339
|
loadTracks
|
|
5923
6340
|
]
|
|
5924
6341
|
);
|
|
5925
|
-
const [
|
|
5926
|
-
const
|
|
6342
|
+
const [isCreatingGroupFade, setIsCreatingGroupFade] = (0, import_react29.useState)(false);
|
|
6343
|
+
const [isCreatingFade, setIsCreatingFade] = (0, import_react29.useState)(false);
|
|
6344
|
+
const handleCreateFade = (0, import_react29.useCallback)(
|
|
5927
6345
|
async (selection, direction, gesture) => {
|
|
5928
6346
|
const scene = activeSceneId;
|
|
5929
6347
|
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
@@ -5990,6 +6408,7 @@ function useTransitionOps({
|
|
|
5990
6408
|
soundLabel = await adapter.sound.copySnapshot(track.id, snap);
|
|
5991
6409
|
}
|
|
5992
6410
|
}
|
|
6411
|
+
await copyTrackFx(track.id, selection.dbId);
|
|
5993
6412
|
await applyFadeAutomation(track.id, direction, mc.bars, mc.bpm, 0.5, gesture);
|
|
5994
6413
|
appliedFadeAutomationRef.current.add(track.id);
|
|
5995
6414
|
const meta = {
|
|
@@ -6033,7 +6452,7 @@ function useTransitionOps({
|
|
|
6033
6452
|
loadTracks
|
|
6034
6453
|
]
|
|
6035
6454
|
);
|
|
6036
|
-
const handleCrossfadeMute = (0,
|
|
6455
|
+
const handleCrossfadeMute = (0, import_react29.useCallback)(
|
|
6037
6456
|
(pair) => {
|
|
6038
6457
|
const newMuted = !pair.origin.runtimeState.muted;
|
|
6039
6458
|
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
@@ -6048,7 +6467,7 @@ function useTransitionOps({
|
|
|
6048
6467
|
},
|
|
6049
6468
|
[host, setTracks]
|
|
6050
6469
|
);
|
|
6051
|
-
const handleCrossfadeSolo = (0,
|
|
6470
|
+
const handleCrossfadeSolo = (0, import_react29.useCallback)(
|
|
6052
6471
|
(pair) => {
|
|
6053
6472
|
const newSolo = !pair.origin.runtimeState.solo;
|
|
6054
6473
|
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
@@ -6063,7 +6482,7 @@ function useTransitionOps({
|
|
|
6063
6482
|
},
|
|
6064
6483
|
[host, setTracks]
|
|
6065
6484
|
);
|
|
6066
|
-
const handleCrossfadeDelete = (0,
|
|
6485
|
+
const handleCrossfadeDelete = (0, import_react29.useCallback)(
|
|
6067
6486
|
async (pair) => {
|
|
6068
6487
|
try {
|
|
6069
6488
|
for (const member of [pair.origin, pair.target]) {
|
|
@@ -6089,8 +6508,8 @@ function useTransitionOps({
|
|
|
6089
6508
|
},
|
|
6090
6509
|
[host, activeSceneId, setCrossfadePairsMeta, setTracks]
|
|
6091
6510
|
);
|
|
6092
|
-
const crossfadeSliderTimers = (0,
|
|
6093
|
-
const handleCrossfadeSlider = (0,
|
|
6511
|
+
const crossfadeSliderTimers = (0, import_react29.useRef)({});
|
|
6512
|
+
const handleCrossfadeSlider = (0, import_react29.useCallback)(
|
|
6094
6513
|
(pair, pos) => {
|
|
6095
6514
|
setCrossfadePairsMeta(
|
|
6096
6515
|
(prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
|
|
@@ -6123,7 +6542,7 @@ function useTransitionOps({
|
|
|
6123
6542
|
},
|
|
6124
6543
|
[host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
|
|
6125
6544
|
);
|
|
6126
|
-
const handleFadeDelete = (0,
|
|
6545
|
+
const handleFadeDelete = (0, import_react29.useCallback)(
|
|
6127
6546
|
async (fade) => {
|
|
6128
6547
|
try {
|
|
6129
6548
|
await host.deleteTrack(fade.track.handle.id);
|
|
@@ -6143,8 +6562,8 @@ function useTransitionOps({
|
|
|
6143
6562
|
},
|
|
6144
6563
|
[host, activeSceneId, setFadesMeta, setTracks]
|
|
6145
6564
|
);
|
|
6146
|
-
const fadeSliderTimers = (0,
|
|
6147
|
-
const handleFadeSlider = (0,
|
|
6565
|
+
const fadeSliderTimers = (0, import_react29.useRef)({});
|
|
6566
|
+
const handleFadeSlider = (0, import_react29.useCallback)(
|
|
6148
6567
|
(fade, pos) => {
|
|
6149
6568
|
setFadesMeta(
|
|
6150
6569
|
(prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
|
|
@@ -6174,8 +6593,190 @@ function useTransitionOps({
|
|
|
6174
6593
|
},
|
|
6175
6594
|
[host, activeSceneId, applyFadeAutomation, setFadesMeta]
|
|
6176
6595
|
);
|
|
6177
|
-
const
|
|
6178
|
-
|
|
6596
|
+
const handleCreateVerbatimGroupFade = (0, import_react29.useCallback)(
|
|
6597
|
+
async (subject, direction) => {
|
|
6598
|
+
const groupAdapter = adapter.transitionGroup;
|
|
6599
|
+
if (!groupAdapter) throw new Error("This panel does not support group fades.");
|
|
6600
|
+
const scene = activeSceneId;
|
|
6601
|
+
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
6602
|
+
const toSceneId = sceneContext?.transitionToSceneId ?? "";
|
|
6603
|
+
if (!scene) throw new Error("No active scene.");
|
|
6604
|
+
if (!isConnected) throw new Error("Systems not connected.");
|
|
6605
|
+
const sourceSceneId = direction === "out" ? fromSceneId : toSceneId;
|
|
6606
|
+
const members = await groupAdapter.expandSubject(sourceSceneId, subject.dbId);
|
|
6607
|
+
if (members.length === 0) {
|
|
6608
|
+
throw new Error("Nothing to fade \u2014 the source group has no members.");
|
|
6609
|
+
}
|
|
6610
|
+
if (tracks.length + members.length > identity.maxTracks) {
|
|
6611
|
+
throw new Error(`Not enough track slots for this fade (needs ${members.length}).`);
|
|
6612
|
+
}
|
|
6613
|
+
setIsCreatingGroupFade(true);
|
|
6614
|
+
const created = [];
|
|
6615
|
+
try {
|
|
6616
|
+
const mc = await host.getMusicalContext();
|
|
6617
|
+
const sliderPos = groupAdapter.defaultSliderPos?.(direction) ?? 0.5;
|
|
6618
|
+
const gesture = "volume";
|
|
6619
|
+
const clipEnd = mc.bars * 4 * 60 / mc.bpm;
|
|
6620
|
+
const maxBeats = mc.bars * 4;
|
|
6621
|
+
for (const member of members) {
|
|
6622
|
+
const handle = await host.createTrack({
|
|
6623
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-fade-${direction}-v${member.memberIndex}`,
|
|
6624
|
+
...adapter.createTrackOptions()
|
|
6625
|
+
});
|
|
6626
|
+
created.push({ handle, member, soundLabel: "default" });
|
|
6627
|
+
const role = member.role ?? subject.role ?? "";
|
|
6628
|
+
if (role) await host.setTrackRole(handle.id, role).catch(() => {
|
|
6629
|
+
});
|
|
6630
|
+
const srcMidi = host.readImportableTrackMidi ? await host.readImportableTrackMidi(member.dbId) : { clips: [] };
|
|
6631
|
+
const srcNotes = srcMidi.clips[0]?.notes ?? [];
|
|
6632
|
+
const notes = srcNotes.filter((n) => n.startBeat < maxBeats).map((n) => ({
|
|
6633
|
+
...n,
|
|
6634
|
+
durationBeats: Math.min(n.durationBeats, maxBeats - n.startBeat)
|
|
6635
|
+
}));
|
|
6636
|
+
if (notes.length > 0) {
|
|
6637
|
+
const clip = { startTime: 0, endTime: clipEnd, tempo: mc.bpm, notes };
|
|
6638
|
+
await host.writeMidiClip(handle.id, clip);
|
|
6639
|
+
}
|
|
6640
|
+
if (host.getTrackSound) {
|
|
6641
|
+
const snap = await host.getTrackSound(member.dbId);
|
|
6642
|
+
if (snap && snap.kind === adapter.sound.acceptedSnapshotKind) {
|
|
6643
|
+
created[created.length - 1].soundLabel = await adapter.sound.copySnapshot(
|
|
6644
|
+
handle.id,
|
|
6645
|
+
snap
|
|
6646
|
+
);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
await copyTrackFx(handle.id, member.dbId);
|
|
6650
|
+
await applyFadeAutomation(handle.id, direction, mc.bars, mc.bpm, sliderPos, gesture);
|
|
6651
|
+
appliedFadeAutomationRef.current.add(handle.id);
|
|
6652
|
+
}
|
|
6653
|
+
const groupId = created[0].handle.dbId;
|
|
6654
|
+
for (const { handle, member, soundLabel } of created) {
|
|
6655
|
+
const meta = {
|
|
6656
|
+
direction,
|
|
6657
|
+
gesture,
|
|
6658
|
+
sourceTrackDbId: member.dbId,
|
|
6659
|
+
sourceSceneId,
|
|
6660
|
+
sourceName: member.name,
|
|
6661
|
+
soundLabel,
|
|
6662
|
+
sliderPos,
|
|
6663
|
+
groupId,
|
|
6664
|
+
memberIndex: member.memberIndex,
|
|
6665
|
+
memberLabel: member.memberLabel
|
|
6666
|
+
};
|
|
6667
|
+
await host.setSceneData(scene, `track:${handle.dbId}:fade`, meta);
|
|
6668
|
+
}
|
|
6669
|
+
await groupAdapter.writeGroupMetas(
|
|
6670
|
+
scene,
|
|
6671
|
+
created.map(({ handle, member }) => ({ newDbId: handle.dbId, member })),
|
|
6672
|
+
groupId
|
|
6673
|
+
);
|
|
6674
|
+
await loadTracks(true);
|
|
6675
|
+
host.showToast(
|
|
6676
|
+
"success",
|
|
6677
|
+
direction === "in" ? "Fade in created" : "Fade out created",
|
|
6678
|
+
subject.name
|
|
6679
|
+
);
|
|
6680
|
+
} catch (err) {
|
|
6681
|
+
for (const { handle } of [...created].reverse()) {
|
|
6682
|
+
try {
|
|
6683
|
+
await host.deleteTrack(handle.id);
|
|
6684
|
+
} catch {
|
|
6685
|
+
}
|
|
6686
|
+
await host.deleteSceneData(scene, `track:${handle.dbId}:fade`).catch(() => {
|
|
6687
|
+
});
|
|
6688
|
+
for (const suffix of groupAdapter.cleanupKeySuffixes) {
|
|
6689
|
+
await host.deleteSceneData(scene, `track:${handle.dbId}:${suffix}`).catch(() => {
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
6694
|
+
} finally {
|
|
6695
|
+
setIsCreatingGroupFade(false);
|
|
6696
|
+
}
|
|
6697
|
+
},
|
|
6698
|
+
[
|
|
6699
|
+
host,
|
|
6700
|
+
adapter,
|
|
6701
|
+
identity,
|
|
6702
|
+
activeSceneId,
|
|
6703
|
+
isConnected,
|
|
6704
|
+
tracks.length,
|
|
6705
|
+
sceneContext,
|
|
6706
|
+
applyFadeAutomation,
|
|
6707
|
+
copyTrackFx,
|
|
6708
|
+
loadTracks
|
|
6709
|
+
]
|
|
6710
|
+
);
|
|
6711
|
+
const groupFadeSliderTimers = (0, import_react29.useRef)({});
|
|
6712
|
+
const handleGroupFadeSlider = (0, import_react29.useCallback)(
|
|
6713
|
+
(group, pos) => {
|
|
6714
|
+
setFadesMeta(
|
|
6715
|
+
(prev) => prev.map(
|
|
6716
|
+
(f) => f.meta.groupId === group.groupId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f
|
|
6717
|
+
)
|
|
6718
|
+
);
|
|
6719
|
+
if (groupFadeSliderTimers.current[group.groupId]) {
|
|
6720
|
+
clearTimeout(groupFadeSliderTimers.current[group.groupId]);
|
|
6721
|
+
}
|
|
6722
|
+
groupFadeSliderTimers.current[group.groupId] = setTimeout(() => {
|
|
6723
|
+
void (async () => {
|
|
6724
|
+
const mc = await host.getMusicalContext();
|
|
6725
|
+
const sceneData = activeSceneId ? await host.getAllSceneData(activeSceneId) : null;
|
|
6726
|
+
for (const member of group.members) {
|
|
6727
|
+
await applyFadeAutomation(
|
|
6728
|
+
member.track.handle.id,
|
|
6729
|
+
group.direction,
|
|
6730
|
+
mc.bars,
|
|
6731
|
+
mc.bpm,
|
|
6732
|
+
pos,
|
|
6733
|
+
group.gesture
|
|
6734
|
+
);
|
|
6735
|
+
if (activeSceneId && sceneData) {
|
|
6736
|
+
const meta = asFadeMeta(sceneData[`track:${member.dbId}:fade`]);
|
|
6737
|
+
if (meta) {
|
|
6738
|
+
host.setSceneData(activeSceneId, `track:${member.dbId}:fade`, {
|
|
6739
|
+
...meta,
|
|
6740
|
+
sliderPos: pos
|
|
6741
|
+
}).catch(() => {
|
|
6742
|
+
});
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
}
|
|
6746
|
+
})();
|
|
6747
|
+
}, 200);
|
|
6748
|
+
},
|
|
6749
|
+
[host, activeSceneId, applyFadeAutomation, setFadesMeta]
|
|
6750
|
+
);
|
|
6751
|
+
const handleGroupFadeDelete = (0, import_react29.useCallback)(
|
|
6752
|
+
async (group) => {
|
|
6753
|
+
const suffixes = ["fade", ...adapter.transitionGroup?.cleanupKeySuffixes ?? []];
|
|
6754
|
+
try {
|
|
6755
|
+
for (const member of group.members) {
|
|
6756
|
+
await host.deleteTrack(member.track.handle.id);
|
|
6757
|
+
if (activeSceneId) {
|
|
6758
|
+
for (const suffix of suffixes) {
|
|
6759
|
+
await host.deleteSceneData(activeSceneId, `track:${member.dbId}:${suffix}`).catch(() => {
|
|
6760
|
+
});
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
setFadesMeta((prev) => prev.filter((f) => f.meta.groupId !== group.groupId));
|
|
6765
|
+
const gone = new Set(group.members.map((m) => m.track.handle.id));
|
|
6766
|
+
setTracks((prev) => prev.filter((t) => !gone.has(t.handle.id)));
|
|
6767
|
+
host.showToast("success", "Fade removed");
|
|
6768
|
+
} catch (err) {
|
|
6769
|
+
host.showToast(
|
|
6770
|
+
"error",
|
|
6771
|
+
"Failed to delete fade",
|
|
6772
|
+
err instanceof Error ? err.message : String(err)
|
|
6773
|
+
);
|
|
6774
|
+
}
|
|
6775
|
+
},
|
|
6776
|
+
[host, adapter, activeSceneId, setFadesMeta, setTracks]
|
|
6777
|
+
);
|
|
6778
|
+
const lastResyncKeyRef = (0, import_react29.useRef)("");
|
|
6779
|
+
(0, import_react29.useEffect)(() => {
|
|
6179
6780
|
if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
|
|
6180
6781
|
return;
|
|
6181
6782
|
}
|
|
@@ -6216,7 +6817,7 @@ function useTransitionOps({
|
|
|
6216
6817
|
cancelled = true;
|
|
6217
6818
|
};
|
|
6218
6819
|
}, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
|
|
6219
|
-
(0,
|
|
6820
|
+
(0, import_react29.useEffect)(() => {
|
|
6220
6821
|
if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
|
|
6221
6822
|
void (async () => {
|
|
6222
6823
|
const mc = await host.getMusicalContext();
|
|
@@ -6245,12 +6846,16 @@ function useTransitionOps({
|
|
|
6245
6846
|
handleCrossfadeDelete,
|
|
6246
6847
|
handleCrossfadeSlider,
|
|
6247
6848
|
handleFadeDelete,
|
|
6248
|
-
handleFadeSlider
|
|
6849
|
+
handleFadeSlider,
|
|
6850
|
+
isCreatingGroupFade,
|
|
6851
|
+
handleCreateVerbatimGroupFade,
|
|
6852
|
+
handleGroupFadeSlider,
|
|
6853
|
+
handleGroupFadeDelete
|
|
6249
6854
|
};
|
|
6250
6855
|
}
|
|
6251
6856
|
|
|
6252
6857
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
6253
|
-
var
|
|
6858
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
6254
6859
|
var EMPTY_PLACEHOLDERS = [];
|
|
6255
6860
|
function useGeneratorPanelCore({
|
|
6256
6861
|
ui,
|
|
@@ -6270,8 +6875,8 @@ function useGeneratorPanelCore({
|
|
|
6270
6875
|
} = ui;
|
|
6271
6876
|
const { identity, features } = adapter;
|
|
6272
6877
|
const logTag = identity.logTag;
|
|
6273
|
-
const adapterRef = (0,
|
|
6274
|
-
(0,
|
|
6878
|
+
const adapterRef = (0, import_react30.useRef)(adapter);
|
|
6879
|
+
(0, import_react30.useEffect)(() => {
|
|
6275
6880
|
if (adapterRef.current !== adapter) {
|
|
6276
6881
|
adapterRef.current = adapter;
|
|
6277
6882
|
console.warn(
|
|
@@ -6281,27 +6886,27 @@ function useGeneratorPanelCore({
|
|
|
6281
6886
|
}, [adapter, logTag]);
|
|
6282
6887
|
const supportsMeters = typeof host.getTrackLevels === "function";
|
|
6283
6888
|
const trackLevels = useTrackLevels(host, isExpanded);
|
|
6284
|
-
const [tracks, setTracks] = (0,
|
|
6285
|
-
const [isLoadingTracks, setIsLoadingTracks] = (0,
|
|
6286
|
-
const [importOpen, setImportOpen] = (0,
|
|
6287
|
-
const [soundImportTarget, setSoundImportTarget] = (0,
|
|
6288
|
-
const [designerView, setDesignerView] = (0,
|
|
6289
|
-
const [transitionSourceTotal, setTransitionSourceTotal] = (0,
|
|
6290
|
-
const [crossfadePairsMeta, setCrossfadePairsMeta] = (0,
|
|
6291
|
-
const [fadesMeta, setFadesMeta] = (0,
|
|
6292
|
-
const [genericGroupMetas, setGenericGroupMetas] = (0,
|
|
6889
|
+
const [tracks, setTracks] = (0, import_react30.useState)([]);
|
|
6890
|
+
const [isLoadingTracks, setIsLoadingTracks] = (0, import_react30.useState)(false);
|
|
6891
|
+
const [importOpen, setImportOpen] = (0, import_react30.useState)(false);
|
|
6892
|
+
const [soundImportTarget, setSoundImportTarget] = (0, import_react30.useState)(null);
|
|
6893
|
+
const [designerView, setDesignerView] = (0, import_react30.useState)(false);
|
|
6894
|
+
const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react30.useState)(0);
|
|
6895
|
+
const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react30.useState)([]);
|
|
6896
|
+
const [fadesMeta, setFadesMeta] = (0, import_react30.useState)([]);
|
|
6897
|
+
const [genericGroupMetas, setGenericGroupMetas] = (0, import_react30.useState)({});
|
|
6293
6898
|
const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
|
|
6294
6899
|
const [placeholders, , setPlaceholdersForScene] = useSceneState(
|
|
6295
6900
|
activeSceneId,
|
|
6296
6901
|
EMPTY_PLACEHOLDERS
|
|
6297
6902
|
);
|
|
6298
|
-
const saveTimeoutRefs = (0,
|
|
6299
|
-
const editLoadStartedRef = (0,
|
|
6300
|
-
const [availableInstruments, setAvailableInstruments] = (0,
|
|
6301
|
-
const [instrumentsLoading, setInstrumentsLoading] = (0,
|
|
6302
|
-
const engineToDbIdRef = (0,
|
|
6303
|
-
const tracksLoadedForSceneRef = (0,
|
|
6304
|
-
const persistSoundHistory = (0,
|
|
6903
|
+
const saveTimeoutRefs = (0, import_react30.useRef)({});
|
|
6904
|
+
const editLoadStartedRef = (0, import_react30.useRef)(/* @__PURE__ */ new Set());
|
|
6905
|
+
const [availableInstruments, setAvailableInstruments] = (0, import_react30.useState)([]);
|
|
6906
|
+
const [instrumentsLoading, setInstrumentsLoading] = (0, import_react30.useState)(false);
|
|
6907
|
+
const engineToDbIdRef = (0, import_react30.useRef)(/* @__PURE__ */ new Map());
|
|
6908
|
+
const tracksLoadedForSceneRef = (0, import_react30.useRef)(null);
|
|
6909
|
+
const persistSoundHistory = (0, import_react30.useCallback)(
|
|
6305
6910
|
(trackId, state) => {
|
|
6306
6911
|
if (!activeSceneId) return;
|
|
6307
6912
|
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
@@ -6321,7 +6926,7 @@ function useGeneratorPanelCore({
|
|
|
6321
6926
|
setItems: setTracks,
|
|
6322
6927
|
getId: (t) => t.handle.dbId
|
|
6323
6928
|
});
|
|
6324
|
-
const loadTracks = (0,
|
|
6929
|
+
const loadTracks = (0, import_react30.useCallback)(
|
|
6325
6930
|
async (incremental = false) => {
|
|
6326
6931
|
const sceneAtStart = activeSceneId;
|
|
6327
6932
|
if (!sceneAtStart) {
|
|
@@ -6446,18 +7051,18 @@ function useGeneratorPanelCore({
|
|
|
6446
7051
|
},
|
|
6447
7052
|
[host, activeSceneId, soundHistory, adapter, logTag]
|
|
6448
7053
|
);
|
|
6449
|
-
(0,
|
|
7054
|
+
(0, import_react30.useEffect)(() => {
|
|
6450
7055
|
loadTracks();
|
|
6451
7056
|
}, [loadTracks]);
|
|
6452
|
-
(0,
|
|
7057
|
+
(0, import_react30.useEffect)(() => {
|
|
6453
7058
|
const map = /* @__PURE__ */ new Map();
|
|
6454
7059
|
for (const t of tracks) {
|
|
6455
7060
|
map.set(t.handle.id, t.handle.dbId);
|
|
6456
7061
|
}
|
|
6457
7062
|
engineToDbIdRef.current = map;
|
|
6458
7063
|
}, [tracks]);
|
|
6459
|
-
const loadedCompletedIdsRef = (0,
|
|
6460
|
-
(0,
|
|
7064
|
+
const loadedCompletedIdsRef = (0, import_react30.useRef)(/* @__PURE__ */ new Set());
|
|
7065
|
+
(0, import_react30.useEffect)(() => {
|
|
6461
7066
|
if (placeholders.length === 0) {
|
|
6462
7067
|
loadedCompletedIdsRef.current.clear();
|
|
6463
7068
|
return;
|
|
@@ -6476,16 +7081,16 @@ function useGeneratorPanelCore({
|
|
|
6476
7081
|
loadTracks(true);
|
|
6477
7082
|
}
|
|
6478
7083
|
}, [placeholders, loadTracks, logTag]);
|
|
6479
|
-
const adoptAndLoad = (0,
|
|
7084
|
+
const adoptAndLoad = (0, import_react30.useCallback)(() => {
|
|
6480
7085
|
loadTracks(true);
|
|
6481
7086
|
}, [loadTracks]);
|
|
6482
|
-
(0,
|
|
7087
|
+
(0, import_react30.useEffect)(() => {
|
|
6483
7088
|
const unsub = host.onEngineReady(() => {
|
|
6484
7089
|
adoptAndLoad();
|
|
6485
7090
|
});
|
|
6486
7091
|
return unsub;
|
|
6487
7092
|
}, [host, adoptAndLoad]);
|
|
6488
|
-
(0,
|
|
7093
|
+
(0, import_react30.useEffect)(() => {
|
|
6489
7094
|
if (typeof host.onAfterAgentMutation !== "function") return;
|
|
6490
7095
|
let timer = null;
|
|
6491
7096
|
const unsub = host.onAfterAgentMutation(() => {
|
|
@@ -6500,13 +7105,13 @@ function useGeneratorPanelCore({
|
|
|
6500
7105
|
if (timer) clearTimeout(timer);
|
|
6501
7106
|
};
|
|
6502
7107
|
}, [host, loadTracks]);
|
|
6503
|
-
(0,
|
|
7108
|
+
(0, import_react30.useEffect)(() => {
|
|
6504
7109
|
const unsub = host.onTrackStateChange((trackId, state) => {
|
|
6505
7110
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
|
|
6506
7111
|
});
|
|
6507
7112
|
return unsub;
|
|
6508
7113
|
}, [host]);
|
|
6509
|
-
(0,
|
|
7114
|
+
(0, import_react30.useEffect)(() => {
|
|
6510
7115
|
if (!features.bulkComposePlaceholders) return;
|
|
6511
7116
|
console.log(`[${logTag}] Subscribing to composeProgress`);
|
|
6512
7117
|
const unsub = host.onComposeProgress((event) => {
|
|
@@ -6540,7 +7145,7 @@ function useGeneratorPanelCore({
|
|
|
6540
7145
|
});
|
|
6541
7146
|
return unsub;
|
|
6542
7147
|
}, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
|
|
6543
|
-
(0,
|
|
7148
|
+
(0, import_react30.useEffect)(() => {
|
|
6544
7149
|
const refs = saveTimeoutRefs;
|
|
6545
7150
|
return () => {
|
|
6546
7151
|
for (const timeout of Object.values(refs.current)) {
|
|
@@ -6548,9 +7153,9 @@ function useGeneratorPanelCore({
|
|
|
6548
7153
|
}
|
|
6549
7154
|
};
|
|
6550
7155
|
}, []);
|
|
6551
|
-
const isAddingTrackRef = (0,
|
|
6552
|
-
const [isAddingTrack, setIsAddingTrack] = (0,
|
|
6553
|
-
const handleAddTrack = (0,
|
|
7156
|
+
const isAddingTrackRef = (0, import_react30.useRef)(false);
|
|
7157
|
+
const [isAddingTrack, setIsAddingTrack] = (0, import_react30.useState)(false);
|
|
7158
|
+
const handleAddTrack = (0, import_react30.useCallback)(async () => {
|
|
6554
7159
|
if (isAddingTrackRef.current) return;
|
|
6555
7160
|
if (!activeSceneId) {
|
|
6556
7161
|
host.showToast("warning", "Select SCENE");
|
|
@@ -6590,7 +7195,7 @@ function useGeneratorPanelCore({
|
|
|
6590
7195
|
setIsAddingTrack(false);
|
|
6591
7196
|
}
|
|
6592
7197
|
}, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
|
|
6593
|
-
const handlePortTrack = (0,
|
|
7198
|
+
const handlePortTrack = (0, import_react30.useCallback)(
|
|
6594
7199
|
async (sel) => {
|
|
6595
7200
|
if (!activeSceneId) {
|
|
6596
7201
|
host.showToast("warning", "Select SCENE");
|
|
@@ -6647,7 +7252,7 @@ function useGeneratorPanelCore({
|
|
|
6647
7252
|
},
|
|
6648
7253
|
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
6649
7254
|
);
|
|
6650
|
-
const handleSoundImportPick = (0,
|
|
7255
|
+
const handleSoundImportPick = (0, import_react30.useCallback)(
|
|
6651
7256
|
async (sel) => {
|
|
6652
7257
|
const target = soundImportTarget;
|
|
6653
7258
|
if (!target || !host.getTrackSound) {
|
|
@@ -6678,8 +7283,8 @@ function useGeneratorPanelCore({
|
|
|
6678
7283
|
},
|
|
6679
7284
|
[soundImportTarget, host, adapter, identity.familyKey, soundHistory]
|
|
6680
7285
|
);
|
|
6681
|
-
const [isExportingMidi, setIsExportingMidi] = (0,
|
|
6682
|
-
const handleExportMidi = (0,
|
|
7286
|
+
const [isExportingMidi, setIsExportingMidi] = (0, import_react30.useState)(false);
|
|
7287
|
+
const handleExportMidi = (0, import_react30.useCallback)(async () => {
|
|
6683
7288
|
if (isExportingMidi) return;
|
|
6684
7289
|
setIsExportingMidi(true);
|
|
6685
7290
|
try {
|
|
@@ -6710,10 +7315,10 @@ function useGeneratorPanelCore({
|
|
|
6710
7315
|
const xfFromId = sceneContext?.transitionFromSceneId ?? null;
|
|
6711
7316
|
const xfToId = sceneContext?.transitionToSceneId ?? null;
|
|
6712
7317
|
const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
|
|
6713
|
-
(0,
|
|
7318
|
+
(0, import_react30.useEffect)(() => {
|
|
6714
7319
|
if (!canCrossfade) setDesignerView(false);
|
|
6715
7320
|
}, [canCrossfade]);
|
|
6716
|
-
(0,
|
|
7321
|
+
(0, import_react30.useEffect)(() => {
|
|
6717
7322
|
if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
|
|
6718
7323
|
setTransitionSourceTotal(0);
|
|
6719
7324
|
return;
|
|
@@ -6729,12 +7334,12 @@ function useGeneratorPanelCore({
|
|
|
6729
7334
|
};
|
|
6730
7335
|
}, [canCrossfade, xfFromId, xfToId, host]);
|
|
6731
7336
|
const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
|
|
6732
|
-
(0,
|
|
7337
|
+
(0, import_react30.useEffect)(() => {
|
|
6733
7338
|
if (!onHeaderContent) return;
|
|
6734
7339
|
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
6735
7340
|
onHeaderContent(
|
|
6736
|
-
/* @__PURE__ */ (0,
|
|
6737
|
-
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
7341
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
7342
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
6738
7343
|
"button",
|
|
6739
7344
|
{
|
|
6740
7345
|
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
@@ -6748,7 +7353,7 @@ function useGeneratorPanelCore({
|
|
|
6748
7353
|
children: identity.importTrackLabel ?? "Import Track"
|
|
6749
7354
|
}
|
|
6750
7355
|
),
|
|
6751
|
-
(!canCrossfade || !designerView) && /* @__PURE__ */ (0,
|
|
7356
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
6752
7357
|
"button",
|
|
6753
7358
|
{
|
|
6754
7359
|
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
@@ -6764,7 +7369,7 @@ function useGeneratorPanelCore({
|
|
|
6764
7369
|
children: identity.addTrackLabel ?? "Add Track"
|
|
6765
7370
|
}
|
|
6766
7371
|
),
|
|
6767
|
-
canCrossfade && /* @__PURE__ */ (0,
|
|
7372
|
+
canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
|
|
6768
7373
|
"button",
|
|
6769
7374
|
{
|
|
6770
7375
|
"data-testid": `${identity.familyKey}-view-toggle`,
|
|
@@ -6783,7 +7388,7 @@ function useGeneratorPanelCore({
|
|
|
6783
7388
|
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
6784
7389
|
className: "relative overflow-hidden px-2 py-0.5 text-[10px] font-medium rounded-sm border border-sas-accent/40 text-sas-accent transition-colors hover:border-sas-accent disabled:opacity-50",
|
|
6785
7390
|
children: [
|
|
6786
|
-
transitionSourceTotal > 0 && /* @__PURE__ */ (0,
|
|
7391
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
6787
7392
|
"span",
|
|
6788
7393
|
{
|
|
6789
7394
|
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
@@ -6791,7 +7396,7 @@ function useGeneratorPanelCore({
|
|
|
6791
7396
|
"aria-hidden": true
|
|
6792
7397
|
}
|
|
6793
7398
|
),
|
|
6794
|
-
/* @__PURE__ */ (0,
|
|
7399
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "relative", children: [
|
|
6795
7400
|
"\u21C4 ",
|
|
6796
7401
|
designerView ? "Transition" : "Tracks",
|
|
6797
7402
|
transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
|
|
@@ -6822,7 +7427,7 @@ function useGeneratorPanelCore({
|
|
|
6822
7427
|
identity,
|
|
6823
7428
|
features.importTracks
|
|
6824
7429
|
]);
|
|
6825
|
-
(0,
|
|
7430
|
+
(0, import_react30.useEffect)(() => {
|
|
6826
7431
|
if (!onLoading) return;
|
|
6827
7432
|
const anyGenerating = tracks.some((t) => t.isGenerating);
|
|
6828
7433
|
onLoading(isLoadingTracks || anyGenerating || isBulkActive);
|
|
@@ -6830,7 +7435,7 @@ function useGeneratorPanelCore({
|
|
|
6830
7435
|
onLoading(false);
|
|
6831
7436
|
};
|
|
6832
7437
|
}, [onLoading, isLoadingTracks, tracks, isBulkActive]);
|
|
6833
|
-
const handleDeleteTrack = (0,
|
|
7438
|
+
const handleDeleteTrack = (0, import_react30.useCallback)(
|
|
6834
7439
|
async (trackId) => {
|
|
6835
7440
|
try {
|
|
6836
7441
|
await host.deleteTrack(trackId);
|
|
@@ -6846,7 +7451,7 @@ function useGeneratorPanelCore({
|
|
|
6846
7451
|
},
|
|
6847
7452
|
[host, activeSceneId]
|
|
6848
7453
|
);
|
|
6849
|
-
const handlePromptChange = (0,
|
|
7454
|
+
const handlePromptChange = (0, import_react30.useCallback)(
|
|
6850
7455
|
(trackId, prompt) => {
|
|
6851
7456
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
|
|
6852
7457
|
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
@@ -6862,7 +7467,7 @@ function useGeneratorPanelCore({
|
|
|
6862
7467
|
},
|
|
6863
7468
|
[host, activeSceneId]
|
|
6864
7469
|
);
|
|
6865
|
-
const resolvedGenericGroups = (0,
|
|
7470
|
+
const resolvedGenericGroups = (0, import_react30.useMemo)(() => {
|
|
6866
7471
|
const out = {};
|
|
6867
7472
|
for (const ext of adapter.groupExtensions ?? []) {
|
|
6868
7473
|
out[ext.metaKey] = resolveTrackGroups(
|
|
@@ -6876,18 +7481,18 @@ function useGeneratorPanelCore({
|
|
|
6876
7481
|
}
|
|
6877
7482
|
return out;
|
|
6878
7483
|
}, [adapter, genericGroupMetas, tracks]);
|
|
6879
|
-
const genericGroupMemberDbIds = (0,
|
|
7484
|
+
const genericGroupMemberDbIds = (0, import_react30.useMemo)(() => {
|
|
6880
7485
|
const s = /* @__PURE__ */ new Set();
|
|
6881
7486
|
for (const r of Object.values(resolvedGenericGroups)) {
|
|
6882
7487
|
for (const dbId of r.memberDbIds) s.add(dbId);
|
|
6883
7488
|
}
|
|
6884
7489
|
return s;
|
|
6885
7490
|
}, [resolvedGenericGroups]);
|
|
6886
|
-
const engineToDbId = (0,
|
|
7491
|
+
const engineToDbId = (0, import_react30.useCallback)(
|
|
6887
7492
|
(trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
|
|
6888
7493
|
[]
|
|
6889
7494
|
);
|
|
6890
|
-
const updateTrack = (0,
|
|
7495
|
+
const updateTrack = (0, import_react30.useCallback)(
|
|
6891
7496
|
(trackId, patch) => {
|
|
6892
7497
|
setTracks(
|
|
6893
7498
|
(prev) => prev.map(
|
|
@@ -6897,18 +7502,18 @@ function useGeneratorPanelCore({
|
|
|
6897
7502
|
},
|
|
6898
7503
|
[]
|
|
6899
7504
|
);
|
|
6900
|
-
const markEditLoaded = (0,
|
|
7505
|
+
const markEditLoaded = (0, import_react30.useCallback)((trackId) => {
|
|
6901
7506
|
editLoadStartedRef.current.add(trackId);
|
|
6902
7507
|
}, []);
|
|
6903
|
-
const tracksRef = (0,
|
|
6904
|
-
(0,
|
|
7508
|
+
const tracksRef = (0, import_react30.useRef)(tracks);
|
|
7509
|
+
(0, import_react30.useEffect)(() => {
|
|
6905
7510
|
tracksRef.current = tracks;
|
|
6906
7511
|
}, [tracks]);
|
|
6907
|
-
const resolvedGenericGroupsRef = (0,
|
|
6908
|
-
(0,
|
|
7512
|
+
const resolvedGenericGroupsRef = (0, import_react30.useRef)(resolvedGenericGroups);
|
|
7513
|
+
(0, import_react30.useEffect)(() => {
|
|
6909
7514
|
resolvedGenericGroupsRef.current = resolvedGenericGroups;
|
|
6910
7515
|
}, [resolvedGenericGroups]);
|
|
6911
|
-
const makeServices = (0,
|
|
7516
|
+
const makeServices = (0, import_react30.useCallback)(() => {
|
|
6912
7517
|
return {
|
|
6913
7518
|
host,
|
|
6914
7519
|
activeSceneId,
|
|
@@ -6927,7 +7532,7 @@ function useGeneratorPanelCore({
|
|
|
6927
7532
|
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
6928
7533
|
};
|
|
6929
7534
|
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
6930
|
-
const handleGenerate = (0,
|
|
7535
|
+
const handleGenerate = (0, import_react30.useCallback)(
|
|
6931
7536
|
async (trackId) => {
|
|
6932
7537
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6933
7538
|
if (!track || !track.prompt.trim()) return;
|
|
@@ -6954,7 +7559,7 @@ function useGeneratorPanelCore({
|
|
|
6954
7559
|
},
|
|
6955
7560
|
[host, adapter, tracks, isAuthenticated, makeServices]
|
|
6956
7561
|
);
|
|
6957
|
-
const handleMuteToggle = (0,
|
|
7562
|
+
const handleMuteToggle = (0, import_react30.useCallback)(
|
|
6958
7563
|
(trackId) => {
|
|
6959
7564
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6960
7565
|
if (!track) return;
|
|
@@ -6974,7 +7579,7 @@ function useGeneratorPanelCore({
|
|
|
6974
7579
|
},
|
|
6975
7580
|
[host, tracks]
|
|
6976
7581
|
);
|
|
6977
|
-
const handleSoloToggle = (0,
|
|
7582
|
+
const handleSoloToggle = (0, import_react30.useCallback)(
|
|
6978
7583
|
(trackId) => {
|
|
6979
7584
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6980
7585
|
if (!track) return;
|
|
@@ -6994,7 +7599,7 @@ function useGeneratorPanelCore({
|
|
|
6994
7599
|
},
|
|
6995
7600
|
[host, tracks]
|
|
6996
7601
|
);
|
|
6997
|
-
const handleVolumeChange = (0,
|
|
7602
|
+
const handleVolumeChange = (0, import_react30.useCallback)(
|
|
6998
7603
|
(trackId, volume) => {
|
|
6999
7604
|
setTracks(
|
|
7000
7605
|
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
|
|
@@ -7004,7 +7609,7 @@ function useGeneratorPanelCore({
|
|
|
7004
7609
|
},
|
|
7005
7610
|
[host]
|
|
7006
7611
|
);
|
|
7007
|
-
const handlePanChange = (0,
|
|
7612
|
+
const handlePanChange = (0, import_react30.useCallback)(
|
|
7008
7613
|
(trackId, pan) => {
|
|
7009
7614
|
setTracks(
|
|
7010
7615
|
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
|
|
@@ -7014,7 +7619,7 @@ function useGeneratorPanelCore({
|
|
|
7014
7619
|
},
|
|
7015
7620
|
[host]
|
|
7016
7621
|
);
|
|
7017
|
-
const handleShuffle = (0,
|
|
7622
|
+
const handleShuffle = (0, import_react30.useCallback)(
|
|
7018
7623
|
async (trackId) => {
|
|
7019
7624
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
7020
7625
|
if (!track) return;
|
|
@@ -7056,7 +7661,7 @@ function useGeneratorPanelCore({
|
|
|
7056
7661
|
},
|
|
7057
7662
|
[host, adapter, tracks, soundHistory, logTag]
|
|
7058
7663
|
);
|
|
7059
|
-
const handleCopy = (0,
|
|
7664
|
+
const handleCopy = (0, import_react30.useCallback)(
|
|
7060
7665
|
async (trackId) => {
|
|
7061
7666
|
try {
|
|
7062
7667
|
const newHandle = await host.duplicateTrack(trackId);
|
|
@@ -7069,7 +7674,7 @@ function useGeneratorPanelCore({
|
|
|
7069
7674
|
},
|
|
7070
7675
|
[host, loadTracks]
|
|
7071
7676
|
);
|
|
7072
|
-
const handleFxToggle = (0,
|
|
7677
|
+
const handleFxToggle = (0, import_react30.useCallback)(
|
|
7073
7678
|
(trackId, category, enabled) => {
|
|
7074
7679
|
setTracks(
|
|
7075
7680
|
(prev) => prev.map(
|
|
@@ -7092,7 +7697,7 @@ function useGeneratorPanelCore({
|
|
|
7092
7697
|
},
|
|
7093
7698
|
[host]
|
|
7094
7699
|
);
|
|
7095
|
-
const handleFxPresetChange = (0,
|
|
7700
|
+
const handleFxPresetChange = (0, import_react30.useCallback)(
|
|
7096
7701
|
(trackId, category, presetIndex) => {
|
|
7097
7702
|
setTracks(
|
|
7098
7703
|
(prev) => prev.map(
|
|
@@ -7118,7 +7723,7 @@ function useGeneratorPanelCore({
|
|
|
7118
7723
|
},
|
|
7119
7724
|
[host]
|
|
7120
7725
|
);
|
|
7121
|
-
const handleFxDryWetChange = (0,
|
|
7726
|
+
const handleFxDryWetChange = (0, import_react30.useCallback)(
|
|
7122
7727
|
(trackId, category, value) => {
|
|
7123
7728
|
setTracks(
|
|
7124
7729
|
(prev) => prev.map(
|
|
@@ -7130,7 +7735,7 @@ function useGeneratorPanelCore({
|
|
|
7130
7735
|
},
|
|
7131
7736
|
[host]
|
|
7132
7737
|
);
|
|
7133
|
-
const toggleFxDrawer = (0,
|
|
7738
|
+
const toggleFxDrawer = (0, import_react30.useCallback)(
|
|
7134
7739
|
(trackId) => {
|
|
7135
7740
|
setTracks(
|
|
7136
7741
|
(prev) => prev.map((t) => {
|
|
@@ -7152,7 +7757,7 @@ function useGeneratorPanelCore({
|
|
|
7152
7757
|
},
|
|
7153
7758
|
[host, tracks]
|
|
7154
7759
|
);
|
|
7155
|
-
const loadEditNotes = (0,
|
|
7760
|
+
const loadEditNotes = (0, import_react30.useCallback)(
|
|
7156
7761
|
async (trackId) => {
|
|
7157
7762
|
try {
|
|
7158
7763
|
const mc = await host.getMusicalContext();
|
|
@@ -7170,7 +7775,7 @@ function useGeneratorPanelCore({
|
|
|
7170
7775
|
},
|
|
7171
7776
|
[host, logTag]
|
|
7172
7777
|
);
|
|
7173
|
-
const handleNotesChange = (0,
|
|
7778
|
+
const handleNotesChange = (0, import_react30.useCallback)(
|
|
7174
7779
|
(trackId, notes) => {
|
|
7175
7780
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
|
|
7176
7781
|
const key = `edit:${trackId}`;
|
|
@@ -7200,7 +7805,7 @@ function useGeneratorPanelCore({
|
|
|
7200
7805
|
},
|
|
7201
7806
|
[host]
|
|
7202
7807
|
);
|
|
7203
|
-
const handleTabChange = (0,
|
|
7808
|
+
const handleTabChange = (0, import_react30.useCallback)(
|
|
7204
7809
|
(trackId, tab) => {
|
|
7205
7810
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
|
|
7206
7811
|
if (tab === "fx") {
|
|
@@ -7225,10 +7830,10 @@ function useGeneratorPanelCore({
|
|
|
7225
7830
|
},
|
|
7226
7831
|
[host, availableInstruments.length, instrumentsLoading, loadEditNotes]
|
|
7227
7832
|
);
|
|
7228
|
-
const handleProgressChange = (0,
|
|
7833
|
+
const handleProgressChange = (0, import_react30.useCallback)((trackId, pct) => {
|
|
7229
7834
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
|
|
7230
7835
|
}, []);
|
|
7231
|
-
const handleToggleDrawer = (0,
|
|
7836
|
+
const handleToggleDrawer = (0, import_react30.useCallback)((trackId) => {
|
|
7232
7837
|
setTracks(
|
|
7233
7838
|
(prev) => prev.map((t) => {
|
|
7234
7839
|
if (t.handle.id !== trackId) return t;
|
|
@@ -7237,7 +7842,7 @@ function useGeneratorPanelCore({
|
|
|
7237
7842
|
})
|
|
7238
7843
|
);
|
|
7239
7844
|
}, []);
|
|
7240
|
-
const handleInstrumentSelect = (0,
|
|
7845
|
+
const handleInstrumentSelect = (0, import_react30.useCallback)(
|
|
7241
7846
|
async (trackId, pluginId) => {
|
|
7242
7847
|
const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
|
|
7243
7848
|
if (isDefaultInstrument) {
|
|
@@ -7290,7 +7895,7 @@ function useGeneratorPanelCore({
|
|
|
7290
7895
|
},
|
|
7291
7896
|
[host, identity.defaultInstrumentPluginId, logTag]
|
|
7292
7897
|
);
|
|
7293
|
-
const handleShowEditor = (0,
|
|
7898
|
+
const handleShowEditor = (0, import_react30.useCallback)(
|
|
7294
7899
|
async (trackId) => {
|
|
7295
7900
|
try {
|
|
7296
7901
|
await host.showInstrumentEditor(trackId);
|
|
@@ -7301,12 +7906,12 @@ function useGeneratorPanelCore({
|
|
|
7301
7906
|
},
|
|
7302
7907
|
[host]
|
|
7303
7908
|
);
|
|
7304
|
-
const handleBackToInstruments = (0,
|
|
7909
|
+
const handleBackToInstruments = (0, import_react30.useCallback)((trackId) => {
|
|
7305
7910
|
setTracks(
|
|
7306
7911
|
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
|
|
7307
7912
|
);
|
|
7308
7913
|
}, []);
|
|
7309
|
-
const handleRefreshInstruments = (0,
|
|
7914
|
+
const handleRefreshInstruments = (0, import_react30.useCallback)(() => {
|
|
7310
7915
|
setInstrumentsLoading(true);
|
|
7311
7916
|
host.getAvailableInstruments().then((instruments) => {
|
|
7312
7917
|
setAvailableInstruments(instruments);
|
|
@@ -7315,13 +7920,13 @@ function useGeneratorPanelCore({
|
|
|
7315
7920
|
setInstrumentsLoading(false);
|
|
7316
7921
|
});
|
|
7317
7922
|
}, [host]);
|
|
7318
|
-
const onAuditionNote = (0,
|
|
7923
|
+
const onAuditionNote = (0, import_react30.useCallback)(
|
|
7319
7924
|
(trackId, pitch, velocity, ms) => {
|
|
7320
7925
|
void host.auditionNote(trackId, pitch, velocity, ms);
|
|
7321
7926
|
},
|
|
7322
7927
|
[host]
|
|
7323
7928
|
);
|
|
7324
|
-
const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0,
|
|
7929
|
+
const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react30.useMemo)(() => {
|
|
7325
7930
|
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
7326
7931
|
const pairs = [];
|
|
7327
7932
|
const members = /* @__PURE__ */ new Set();
|
|
@@ -7336,7 +7941,7 @@ function useGeneratorPanelCore({
|
|
|
7336
7941
|
}
|
|
7337
7942
|
return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
|
|
7338
7943
|
}, [tracks, crossfadePairsMeta]);
|
|
7339
|
-
const { resolvedFades, fadeMemberDbIds } = (0,
|
|
7944
|
+
const { resolvedFades, fadeMemberDbIds } = (0, import_react30.useMemo)(() => {
|
|
7340
7945
|
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
7341
7946
|
const list = [];
|
|
7342
7947
|
const members = /* @__PURE__ */ new Set();
|
|
@@ -7349,6 +7954,10 @@ function useGeneratorPanelCore({
|
|
|
7349
7954
|
}
|
|
7350
7955
|
return { resolvedFades: list, fadeMemberDbIds: members };
|
|
7351
7956
|
}, [tracks, fadesMeta]);
|
|
7957
|
+
const { singles: resolvedSingleFades, groups: resolvedGroupFades } = (0, import_react30.useMemo)(
|
|
7958
|
+
() => splitFadeEntries(resolvedFades),
|
|
7959
|
+
[resolvedFades]
|
|
7960
|
+
);
|
|
7352
7961
|
const transition = useTransitionOps({
|
|
7353
7962
|
host,
|
|
7354
7963
|
adapter,
|
|
@@ -7364,7 +7973,7 @@ function useGeneratorPanelCore({
|
|
|
7364
7973
|
resolvedCrossfadePairs,
|
|
7365
7974
|
resolvedFades
|
|
7366
7975
|
});
|
|
7367
|
-
const setGroupMute = (0,
|
|
7976
|
+
const setGroupMute = (0, import_react30.useCallback)(
|
|
7368
7977
|
(trackIds, muted) => {
|
|
7369
7978
|
for (const id of trackIds) {
|
|
7370
7979
|
setTracks(
|
|
@@ -7376,7 +7985,7 @@ function useGeneratorPanelCore({
|
|
|
7376
7985
|
},
|
|
7377
7986
|
[host]
|
|
7378
7987
|
);
|
|
7379
|
-
const setGroupSolo = (0,
|
|
7988
|
+
const setGroupSolo = (0, import_react30.useCallback)(
|
|
7380
7989
|
(trackIds, solo) => {
|
|
7381
7990
|
for (const id of trackIds) {
|
|
7382
7991
|
setTracks(
|
|
@@ -7388,7 +7997,7 @@ function useGeneratorPanelCore({
|
|
|
7388
7997
|
},
|
|
7389
7998
|
[host]
|
|
7390
7999
|
);
|
|
7391
|
-
const deleteGroup = (0,
|
|
8000
|
+
const deleteGroup = (0, import_react30.useCallback)(
|
|
7392
8001
|
async (members, cleanupKeySuffixes) => {
|
|
7393
8002
|
for (const member of members) {
|
|
7394
8003
|
try {
|
|
@@ -7408,7 +8017,7 @@ function useGeneratorPanelCore({
|
|
|
7408
8017
|
},
|
|
7409
8018
|
[host, activeSceneId, loadTracks]
|
|
7410
8019
|
);
|
|
7411
|
-
const handlers = (0,
|
|
8020
|
+
const handlers = (0, import_react30.useMemo)(
|
|
7412
8021
|
() => ({
|
|
7413
8022
|
promptChange: handlePromptChange,
|
|
7414
8023
|
generate: (trackId) => {
|
|
@@ -7485,6 +8094,8 @@ function useGeneratorPanelCore({
|
|
|
7485
8094
|
crossfadeMemberDbIds,
|
|
7486
8095
|
resolvedFades,
|
|
7487
8096
|
fadeMemberDbIds,
|
|
8097
|
+
resolvedSingleFades,
|
|
8098
|
+
resolvedGroupFades,
|
|
7488
8099
|
resolvedGenericGroups,
|
|
7489
8100
|
genericGroupMemberDbIds,
|
|
7490
8101
|
availableInstruments,
|
|
@@ -7522,8 +8133,8 @@ function useGeneratorPanelCore({
|
|
|
7522
8133
|
}
|
|
7523
8134
|
|
|
7524
8135
|
// src/panel-core/GeneratorPanelShell.tsx
|
|
7525
|
-
var
|
|
7526
|
-
var
|
|
8136
|
+
var import_react31 = __toESM(require("react"));
|
|
8137
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
7527
8138
|
function GeneratorPanelShell({ core, slots }) {
|
|
7528
8139
|
const {
|
|
7529
8140
|
ui,
|
|
@@ -7552,7 +8163,8 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7552
8163
|
fadesMeta,
|
|
7553
8164
|
resolvedCrossfadePairs,
|
|
7554
8165
|
crossfadeMemberDbIds,
|
|
7555
|
-
|
|
8166
|
+
resolvedSingleFades,
|
|
8167
|
+
resolvedGroupFades,
|
|
7556
8168
|
fadeMemberDbIds,
|
|
7557
8169
|
resolvedGenericGroups,
|
|
7558
8170
|
genericGroupMemberDbIds,
|
|
@@ -7578,7 +8190,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7578
8190
|
const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
|
|
7579
8191
|
const panelBus = usePanelBus(host, activeSceneId);
|
|
7580
8192
|
const { identity, features } = adapter;
|
|
7581
|
-
const buildRowProps = (0,
|
|
8193
|
+
const buildRowProps = (0, import_react31.useCallback)(
|
|
7582
8194
|
(track, drag) => {
|
|
7583
8195
|
const id = track.handle.id;
|
|
7584
8196
|
const pickerProps = features.instrumentPicker ? {
|
|
@@ -7678,12 +8290,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7678
8290
|
]
|
|
7679
8291
|
);
|
|
7680
8292
|
if (!activeSceneId) {
|
|
7681
|
-
return /* @__PURE__ */ (0,
|
|
8293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7682
8294
|
"div",
|
|
7683
8295
|
{
|
|
7684
8296
|
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
7685
8297
|
className: "flex items-center justify-center py-8",
|
|
7686
|
-
children: /* @__PURE__ */ (0,
|
|
8298
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7687
8299
|
"button",
|
|
7688
8300
|
{
|
|
7689
8301
|
onClick: () => onSelectScene?.(),
|
|
@@ -7695,12 +8307,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7695
8307
|
);
|
|
7696
8308
|
}
|
|
7697
8309
|
if (!sceneContext?.hasContract) {
|
|
7698
|
-
return /* @__PURE__ */ (0,
|
|
8310
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7699
8311
|
"div",
|
|
7700
8312
|
{
|
|
7701
8313
|
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
7702
8314
|
className: "flex items-center justify-center py-8",
|
|
7703
|
-
children: /* @__PURE__ */ (0,
|
|
8315
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7704
8316
|
"button",
|
|
7705
8317
|
{
|
|
7706
8318
|
onClick: () => onOpenContract?.(),
|
|
@@ -7712,7 +8324,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7712
8324
|
);
|
|
7713
8325
|
}
|
|
7714
8326
|
if (features.bulkComposePlaceholders && isComposing) {
|
|
7715
|
-
return /* @__PURE__ */ (0,
|
|
8327
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
7716
8328
|
}
|
|
7717
8329
|
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
7718
8330
|
if (activePlaceholders.length > 0) {
|
|
@@ -7723,18 +8335,18 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7723
8335
|
tracksByDbId.set(t.handle.id, t);
|
|
7724
8336
|
}
|
|
7725
8337
|
}
|
|
7726
|
-
return /* @__PURE__ */ (0,
|
|
8338
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
7727
8339
|
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
7728
8340
|
if (loadedTrack) {
|
|
7729
|
-
return /* @__PURE__ */ (0,
|
|
8341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
7730
8342
|
}
|
|
7731
|
-
return /* @__PURE__ */ (0,
|
|
8343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7732
8344
|
"div",
|
|
7733
8345
|
{
|
|
7734
8346
|
"data-testid": "bulk-placeholder-track",
|
|
7735
8347
|
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
7736
8348
|
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
7737
|
-
children: /* @__PURE__ */ (0,
|
|
8349
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
7738
8350
|
},
|
|
7739
8351
|
ph.id
|
|
7740
8352
|
);
|
|
@@ -7746,13 +8358,13 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7746
8358
|
supportsMeters,
|
|
7747
8359
|
levels: supportsMeters ? trackLevels : void 0,
|
|
7748
8360
|
handlers,
|
|
7749
|
-
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0,
|
|
8361
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
7750
8362
|
setGroupMute,
|
|
7751
8363
|
setGroupSolo,
|
|
7752
8364
|
deleteGroup
|
|
7753
8365
|
};
|
|
7754
|
-
return /* @__PURE__ */ (0,
|
|
7755
|
-
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
8366
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
8367
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7756
8368
|
ImportTrackModal,
|
|
7757
8369
|
{
|
|
7758
8370
|
host,
|
|
@@ -7765,7 +8377,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7765
8377
|
testIdPrefix: `${identity.familyKey}-import`
|
|
7766
8378
|
}
|
|
7767
8379
|
),
|
|
7768
|
-
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0,
|
|
8380
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7769
8381
|
ImportTrackModal,
|
|
7770
8382
|
{
|
|
7771
8383
|
host,
|
|
@@ -7780,7 +8392,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7780
8392
|
}
|
|
7781
8393
|
),
|
|
7782
8394
|
slots?.modals,
|
|
7783
|
-
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0,
|
|
8395
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7784
8396
|
TransitionDesigner,
|
|
7785
8397
|
{
|
|
7786
8398
|
host,
|
|
@@ -7791,14 +8403,16 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7791
8403
|
...crossfadePairsMeta.flatMap((p) => [p.originSourceDbId, p.targetSourceDbId]),
|
|
7792
8404
|
...fadesMeta.map((f) => f.meta.sourceTrackDbId)
|
|
7793
8405
|
],
|
|
7794
|
-
onCreateCrossfade: transition.handleCreateCrossfade,
|
|
7795
|
-
onCreateFade: transition.handleCreateFade,
|
|
8406
|
+
onCreateCrossfade: adapter.transitionGroup?.fadeOnly ? void 0 : transition.handleCreateCrossfade,
|
|
8407
|
+
onCreateFade: adapter.transitionGroup ? (sel, dir) => transition.handleCreateVerbatimGroupFade(sel, dir) : transition.handleCreateFade,
|
|
8408
|
+
mapColumnSubjects: adapter.transitionGroup ? (sceneId, tracks2) => adapter.transitionGroup.mapColumnSubjects(sceneId, tracks2) : void 0,
|
|
8409
|
+
fadeEstimateMs: adapter.transitionGroup?.fadeEstimateMs,
|
|
7796
8410
|
familyLabel: identity.familyLabel,
|
|
7797
8411
|
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
7798
8412
|
}
|
|
7799
8413
|
) }),
|
|
7800
|
-
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0,
|
|
7801
|
-
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0,
|
|
8414
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
8415
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7802
8416
|
PanelMasterStrip,
|
|
7803
8417
|
{
|
|
7804
8418
|
bus: panelBus.bus,
|
|
@@ -7819,7 +8433,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7819
8433
|
}
|
|
7820
8434
|
),
|
|
7821
8435
|
slots?.beforeRows,
|
|
7822
|
-
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0,
|
|
8436
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7823
8437
|
CrossfadeTrackRow,
|
|
7824
8438
|
{
|
|
7825
8439
|
accentColor: identity.transitionAccentColor,
|
|
@@ -7856,7 +8470,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7856
8470
|
},
|
|
7857
8471
|
pair.groupId
|
|
7858
8472
|
)),
|
|
7859
|
-
|
|
8473
|
+
resolvedSingleFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7860
8474
|
FadeTrackRow,
|
|
7861
8475
|
{
|
|
7862
8476
|
accentColor: identity.transitionAccentColor,
|
|
@@ -7881,21 +8495,56 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7881
8495
|
},
|
|
7882
8496
|
fade.dbId
|
|
7883
8497
|
)),
|
|
8498
|
+
resolvedGroupFades.map((group) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
8499
|
+
GroupFadeTrackRow,
|
|
8500
|
+
{
|
|
8501
|
+
accentColor: identity.transitionAccentColor,
|
|
8502
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
8503
|
+
direction: group.direction,
|
|
8504
|
+
gesture: group.gesture,
|
|
8505
|
+
sliderPos: group.sliderPos,
|
|
8506
|
+
groupLabel: adapter.transitionGroup?.groupRowLabel?.(group.members.length) ?? `Group (${group.members.length} tracks)`,
|
|
8507
|
+
members: group.members.map((m) => ({
|
|
8508
|
+
trackId: m.track.handle.id,
|
|
8509
|
+
name: m.track.handle.name,
|
|
8510
|
+
role: m.track.role,
|
|
8511
|
+
sourceName: m.meta.sourceName,
|
|
8512
|
+
soundLabel: m.meta.soundLabel,
|
|
8513
|
+
memberLabel: m.meta.memberLabel,
|
|
8514
|
+
runtimeState: m.track.runtimeState
|
|
8515
|
+
})),
|
|
8516
|
+
onMemberMuteToggle: (trackId) => handlers.muteToggle(trackId),
|
|
8517
|
+
onMemberSoloToggle: (trackId) => handlers.soloToggle(trackId),
|
|
8518
|
+
onMemberVolumeChange: (trackId, vol) => handlers.volumeChange(trackId, vol),
|
|
8519
|
+
onMemberPanChange: (trackId, pan) => handlers.panChange(trackId, pan),
|
|
8520
|
+
onMuteAll: () => setGroupMute(
|
|
8521
|
+
group.members.map((m) => m.track.handle.id),
|
|
8522
|
+
!group.members.every((m) => m.track.runtimeState.muted)
|
|
8523
|
+
),
|
|
8524
|
+
onSoloAll: () => setGroupSolo(
|
|
8525
|
+
group.members.map((m) => m.track.handle.id),
|
|
8526
|
+
!group.members.every((m) => m.track.runtimeState.solo)
|
|
8527
|
+
),
|
|
8528
|
+
onSliderChange: (pos) => transition.handleGroupFadeSlider(group, pos),
|
|
8529
|
+
onDelete: () => transition.handleGroupFadeDelete(group)
|
|
8530
|
+
},
|
|
8531
|
+
group.groupId
|
|
8532
|
+
)),
|
|
7884
8533
|
(adapter.groupExtensions ?? []).flatMap(
|
|
7885
|
-
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0,
|
|
8534
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react31.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
|
|
7886
8535
|
),
|
|
7887
8536
|
tracks.map((track, index) => {
|
|
7888
8537
|
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
7889
8538
|
return null;
|
|
7890
8539
|
}
|
|
7891
|
-
return /* @__PURE__ */ (0,
|
|
8540
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
7892
8541
|
}),
|
|
7893
8542
|
slots?.afterRows
|
|
7894
8543
|
] })),
|
|
7895
8544
|
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
7896
8545
|
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
7897
8546
|
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
7898
|
-
return /* @__PURE__ */ (0,
|
|
8547
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7899
8548
|
"button",
|
|
7900
8549
|
{
|
|
7901
8550
|
"data-testid": "export-midi-tracks-button",
|
|
@@ -7962,31 +8611,447 @@ function createSurgeSoundAdapter(host, overrides = {}) {
|
|
|
7962
8611
|
};
|
|
7963
8612
|
}
|
|
7964
8613
|
|
|
8614
|
+
// src/ensemble-core/voice-spec.ts
|
|
8615
|
+
var TOP = {
|
|
8616
|
+
label: "high florid line",
|
|
8617
|
+
role: "lead",
|
|
8618
|
+
registerLow: 72,
|
|
8619
|
+
registerHigh: 96,
|
|
8620
|
+
maxNotesPerBar: 8,
|
|
8621
|
+
rhythmPalette: "8ths and 16ths; melisma and short runs welcome",
|
|
8622
|
+
harmonicDiscipline: "freest voice \u2014 non-chord tones as passing/neighbor tones on weak beats, resolving by step",
|
|
8623
|
+
monoPreference: "high"
|
|
8624
|
+
};
|
|
8625
|
+
var COUNTER = {
|
|
8626
|
+
label: "countermelody",
|
|
8627
|
+
role: "strings",
|
|
8628
|
+
registerLow: 65,
|
|
8629
|
+
registerHigh: 86,
|
|
8630
|
+
maxNotesPerBar: 6,
|
|
8631
|
+
rhythmPalette: "8ths and quarters; move when the top voice rests",
|
|
8632
|
+
harmonicDiscipline: "mostly chord tones; may imitate the top voice's motifs a bar later",
|
|
8633
|
+
monoPreference: "high"
|
|
8634
|
+
};
|
|
8635
|
+
var INNER = {
|
|
8636
|
+
label: "inner voice",
|
|
8637
|
+
role: "strings",
|
|
8638
|
+
registerLow: 55,
|
|
8639
|
+
registerHigh: 76,
|
|
8640
|
+
maxNotesPerBar: 4,
|
|
8641
|
+
rhythmPalette: "quarters and halves",
|
|
8642
|
+
harmonicDiscipline: "chord tones with smooth stepwise motion between them",
|
|
8643
|
+
monoPreference: "high"
|
|
8644
|
+
};
|
|
8645
|
+
var INNER_2 = {
|
|
8646
|
+
label: "second inner voice",
|
|
8647
|
+
role: "strings",
|
|
8648
|
+
registerLow: 60,
|
|
8649
|
+
registerHigh: 81,
|
|
8650
|
+
maxNotesPerBar: 5,
|
|
8651
|
+
rhythmPalette: "quarters with occasional 8th-note motion",
|
|
8652
|
+
harmonicDiscipline: "chord tones; fill gaps the other inner voice leaves",
|
|
8653
|
+
monoPreference: "high"
|
|
8654
|
+
};
|
|
8655
|
+
var TENOR = {
|
|
8656
|
+
label: "low counterline",
|
|
8657
|
+
role: "strings",
|
|
8658
|
+
registerLow: 43,
|
|
8659
|
+
registerHigh: 64,
|
|
8660
|
+
maxNotesPerBar: 3,
|
|
8661
|
+
rhythmPalette: "quarters and halves; brief walking figures at cadences",
|
|
8662
|
+
harmonicDiscipline: "roots and fifths emphasized; passing tones only between chord tones",
|
|
8663
|
+
monoPreference: "low"
|
|
8664
|
+
};
|
|
8665
|
+
var BASS = {
|
|
8666
|
+
label: "bassline",
|
|
8667
|
+
role: "bass",
|
|
8668
|
+
registerLow: 36,
|
|
8669
|
+
registerHigh: 60,
|
|
8670
|
+
maxNotesPerBar: 3,
|
|
8671
|
+
rhythmPalette: "quarters and halves",
|
|
8672
|
+
harmonicDiscipline: "chord roots and fifths; stepwise approaches into chord changes",
|
|
8673
|
+
monoPreference: "low"
|
|
8674
|
+
};
|
|
8675
|
+
var SUB = {
|
|
8676
|
+
label: "sub anchor",
|
|
8677
|
+
role: "808s",
|
|
8678
|
+
registerLow: 24,
|
|
8679
|
+
registerHigh: 43,
|
|
8680
|
+
maxNotesPerBar: 2,
|
|
8681
|
+
rhythmPalette: "halves and whole notes; sustain into the bar",
|
|
8682
|
+
harmonicDiscipline: "ROOT pitch class only \u2014 the harmonic anchor",
|
|
8683
|
+
rootOnly: true,
|
|
8684
|
+
monoPreference: "low"
|
|
8685
|
+
};
|
|
8686
|
+
var ENSEMBLE_MIN_VOICES = 2;
|
|
8687
|
+
var ENSEMBLE_MAX_VOICES = 6;
|
|
8688
|
+
var SPEC_TABLES = {
|
|
8689
|
+
2: [TOP, BASS],
|
|
8690
|
+
3: [TOP, INNER, BASS],
|
|
8691
|
+
4: [TOP, COUNTER, TENOR, BASS],
|
|
8692
|
+
5: [TOP, COUNTER, INNER, TENOR, SUB],
|
|
8693
|
+
6: [TOP, COUNTER, INNER_2, INNER, TENOR, SUB]
|
|
8694
|
+
};
|
|
8695
|
+
function defaultVoiceSpecs(voiceCount) {
|
|
8696
|
+
const n = Math.max(ENSEMBLE_MIN_VOICES, Math.min(ENSEMBLE_MAX_VOICES, Math.round(voiceCount)));
|
|
8697
|
+
return SPEC_TABLES[n].map((spec, voiceIndex) => ({ ...spec, voiceIndex }));
|
|
8698
|
+
}
|
|
8699
|
+
|
|
8700
|
+
// src/ensemble-core/enforce-voice.ts
|
|
8701
|
+
var MIN_NOTE_DURATION_BEATS = 0.0625;
|
|
8702
|
+
var BEATS_PER_BAR = 4;
|
|
8703
|
+
function foldPitchToRegister(pitch, low, high) {
|
|
8704
|
+
let p = pitch;
|
|
8705
|
+
while (p < low) p += 12;
|
|
8706
|
+
while (p > high) p -= 12;
|
|
8707
|
+
if (p < low) p = low;
|
|
8708
|
+
if (p > high) p = high;
|
|
8709
|
+
return p;
|
|
8710
|
+
}
|
|
8711
|
+
function nearestPitchWithPc(reference, pc, low, high) {
|
|
8712
|
+
let best = null;
|
|
8713
|
+
for (let p = low; p <= high; p++) {
|
|
8714
|
+
if ((p % 12 + 12) % 12 !== pc) continue;
|
|
8715
|
+
if (best === null || Math.abs(p - reference) < Math.abs(best - reference)) best = p;
|
|
8716
|
+
}
|
|
8717
|
+
return best ?? foldPitchToRegister(reference, low, high);
|
|
8718
|
+
}
|
|
8719
|
+
function snapToNearestPc(pitch, pcs) {
|
|
8720
|
+
if (pcs.size === 0) return pitch;
|
|
8721
|
+
for (let d = 0; d <= 6; d++) {
|
|
8722
|
+
if (pcs.has(((pitch - d) % 12 + 12) % 12)) return pitch - d;
|
|
8723
|
+
if (pcs.has(((pitch + d) % 12 + 12) % 12)) return pitch + d;
|
|
8724
|
+
}
|
|
8725
|
+
return pitch;
|
|
8726
|
+
}
|
|
8727
|
+
function enforceVoice(rawNotes, spec, opts) {
|
|
8728
|
+
const repairs = [];
|
|
8729
|
+
const clipEnd = opts.bars * BEATS_PER_BAR;
|
|
8730
|
+
let notes = [];
|
|
8731
|
+
for (const n of rawNotes) {
|
|
8732
|
+
if (!Number.isFinite(n.pitch) || !Number.isFinite(n.startBeat) || !Number.isFinite(n.durationBeats)) continue;
|
|
8733
|
+
if (n.startBeat >= clipEnd || n.startBeat < 0) {
|
|
8734
|
+
repairs.push(`voice ${spec.voiceIndex}: dropped note outside the ${opts.bars}-bar clip (start ${n.startBeat})`);
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
const durationBeats = Math.max(
|
|
8738
|
+
MIN_NOTE_DURATION_BEATS,
|
|
8739
|
+
Math.min(n.durationBeats, clipEnd - n.startBeat)
|
|
8740
|
+
);
|
|
8741
|
+
notes.push({ ...n, durationBeats });
|
|
8742
|
+
}
|
|
8743
|
+
notes = notes.map((n) => {
|
|
8744
|
+
const folded = foldPitchToRegister(Math.round(n.pitch), spec.registerLow, spec.registerHigh);
|
|
8745
|
+
if (folded !== n.pitch) {
|
|
8746
|
+
repairs.push(`voice ${spec.voiceIndex}: folded pitch ${n.pitch} into register ${spec.registerLow}-${spec.registerHigh} (${folded})`);
|
|
8747
|
+
}
|
|
8748
|
+
return { ...n, pitch: folded };
|
|
8749
|
+
});
|
|
8750
|
+
if (spec.rootOnly && opts.chordRootPcAtBar) {
|
|
8751
|
+
notes = notes.map((n) => {
|
|
8752
|
+
const bar = Math.floor(n.startBeat / BEATS_PER_BAR);
|
|
8753
|
+
const rootPc = opts.chordRootPcAtBar(bar);
|
|
8754
|
+
if (rootPc === null) return n;
|
|
8755
|
+
const pinned = nearestPitchWithPc(n.pitch, rootPc, spec.registerLow, spec.registerHigh);
|
|
8756
|
+
if (pinned !== n.pitch) {
|
|
8757
|
+
repairs.push(`voice ${spec.voiceIndex}: pinned bar ${bar + 1} note to the chord root (${n.pitch} \u2192 ${pinned})`);
|
|
8758
|
+
}
|
|
8759
|
+
return { ...n, pitch: pinned };
|
|
8760
|
+
});
|
|
8761
|
+
}
|
|
8762
|
+
if (opts.scalePcs && opts.scalePcs.size > 0 && !spec.rootOnly) {
|
|
8763
|
+
notes = notes.map((n) => {
|
|
8764
|
+
const pc = (n.pitch % 12 + 12) % 12;
|
|
8765
|
+
if (opts.scalePcs.has(pc)) return n;
|
|
8766
|
+
const bar = Math.floor(n.startBeat / BEATS_PER_BAR);
|
|
8767
|
+
const chordPcs = opts.chordPcsAtBar?.(bar);
|
|
8768
|
+
if (chordPcs?.has(pc)) return n;
|
|
8769
|
+
const snapped = foldPitchToRegister(
|
|
8770
|
+
snapToNearestPc(n.pitch, opts.scalePcs),
|
|
8771
|
+
spec.registerLow,
|
|
8772
|
+
spec.registerHigh
|
|
8773
|
+
);
|
|
8774
|
+
if (snapped !== n.pitch) {
|
|
8775
|
+
repairs.push(`voice ${spec.voiceIndex}: snapped out-of-key pitch ${n.pitch} \u2192 ${snapped}`);
|
|
8776
|
+
}
|
|
8777
|
+
return { ...n, pitch: snapped };
|
|
8778
|
+
});
|
|
8779
|
+
}
|
|
8780
|
+
notes.sort((a, b) => a.startBeat - b.startBeat || (spec.monoPreference === "high" ? b.pitch - a.pitch : a.pitch - b.pitch));
|
|
8781
|
+
const mono = [];
|
|
8782
|
+
for (const n of notes) {
|
|
8783
|
+
const prev = mono[mono.length - 1];
|
|
8784
|
+
if (prev && Math.abs(prev.startBeat - n.startBeat) < 1e-9) {
|
|
8785
|
+
repairs.push(`voice ${spec.voiceIndex}: dropped simultaneous note ${n.pitch} at beat ${n.startBeat} (voice is one line)`);
|
|
8786
|
+
continue;
|
|
8787
|
+
}
|
|
8788
|
+
if (prev && prev.startBeat + prev.durationBeats > n.startBeat) {
|
|
8789
|
+
prev.durationBeats = Math.max(MIN_NOTE_DURATION_BEATS, n.startBeat - prev.startBeat);
|
|
8790
|
+
}
|
|
8791
|
+
mono.push({ ...n });
|
|
8792
|
+
}
|
|
8793
|
+
const byBar = /* @__PURE__ */ new Map();
|
|
8794
|
+
for (const n of mono) {
|
|
8795
|
+
const bar = Math.floor(n.startBeat / BEATS_PER_BAR);
|
|
8796
|
+
const bucket = byBar.get(bar) ?? [];
|
|
8797
|
+
bucket.push(n);
|
|
8798
|
+
byBar.set(bar, bucket);
|
|
8799
|
+
}
|
|
8800
|
+
const kept = new Set(mono);
|
|
8801
|
+
for (const [bar, bucket] of byBar) {
|
|
8802
|
+
if (bucket.length <= spec.maxNotesPerBar) continue;
|
|
8803
|
+
const strength = (n) => {
|
|
8804
|
+
const beatInBar = n.startBeat - bar * BEATS_PER_BAR;
|
|
8805
|
+
const onDownbeat = Math.abs(beatInBar % 1) < 1e-9 ? 1 : 0;
|
|
8806
|
+
return n.durationBeats * 4 + n.velocity / 127 + onDownbeat * 2;
|
|
8807
|
+
};
|
|
8808
|
+
const ranked = [...bucket].sort((a, b) => strength(a) - strength(b));
|
|
8809
|
+
const excess = bucket.length - spec.maxNotesPerBar;
|
|
8810
|
+
for (let i = 0; i < excess; i++) {
|
|
8811
|
+
kept.delete(ranked[i]);
|
|
8812
|
+
}
|
|
8813
|
+
repairs.push(`voice ${spec.voiceIndex}: thinned bar ${bar + 1} from ${bucket.length} to ${spec.maxNotesPerBar} notes (density cap)`);
|
|
8814
|
+
}
|
|
8815
|
+
return { notes: mono.filter((n) => kept.has(n)), repairs };
|
|
8816
|
+
}
|
|
8817
|
+
|
|
8818
|
+
// src/ensemble-core/analyze-ensemble.ts
|
|
8819
|
+
var EPS = 1e-6;
|
|
8820
|
+
function pitchAt(voice, beat) {
|
|
8821
|
+
for (const n of voice) {
|
|
8822
|
+
if (n.startBeat - EPS <= beat && beat < n.startBeat + n.durationBeats - EPS) return n.pitch;
|
|
8823
|
+
}
|
|
8824
|
+
return null;
|
|
8825
|
+
}
|
|
8826
|
+
function onsetSet(voice) {
|
|
8827
|
+
return [...new Set(voice.map((n) => Math.round(n.startBeat * 96) / 96))].sort((a, b) => a - b);
|
|
8828
|
+
}
|
|
8829
|
+
function analyzeEnsemble(voices) {
|
|
8830
|
+
const pairs = [];
|
|
8831
|
+
for (let i = 0; i + 1 < voices.length; i++) {
|
|
8832
|
+
const upper = voices[i];
|
|
8833
|
+
const lower = voices[i + 1];
|
|
8834
|
+
const upperOnsets = onsetSet(upper);
|
|
8835
|
+
const lowerOnsets = new Set(onsetSet(lower));
|
|
8836
|
+
const shared = upperOnsets.filter((b) => lowerOnsets.has(b));
|
|
8837
|
+
const onsetIndependence = upperOnsets.length === 0 ? 1 : 1 - shared.length / upperOnsets.length;
|
|
8838
|
+
const motion = { contrary: 0, oblique: 0, similar: 0, parallel: 0 };
|
|
8839
|
+
const parallelPerfects = [];
|
|
8840
|
+
for (let s = 0; s + 1 < shared.length; s++) {
|
|
8841
|
+
const [b0, b1] = [shared[s], shared[s + 1]];
|
|
8842
|
+
const u0 = pitchAt(upper, b0);
|
|
8843
|
+
const u1 = pitchAt(upper, b1);
|
|
8844
|
+
const l0 = pitchAt(lower, b0);
|
|
8845
|
+
const l1 = pitchAt(lower, b1);
|
|
8846
|
+
if (u0 === null || u1 === null || l0 === null || l1 === null) continue;
|
|
8847
|
+
const du = Math.sign(u1 - u0);
|
|
8848
|
+
const dl = Math.sign(l1 - l0);
|
|
8849
|
+
let kind;
|
|
8850
|
+
if (du === 0 || dl === 0) kind = "oblique";
|
|
8851
|
+
else if (du !== dl) kind = "contrary";
|
|
8852
|
+
else {
|
|
8853
|
+
kind = u1 - l1 === u0 - l0 ? "parallel" : "similar";
|
|
8854
|
+
}
|
|
8855
|
+
motion[kind] += 1;
|
|
8856
|
+
if (kind === "parallel") {
|
|
8857
|
+
const intervalPc = ((u1 - l1) % 12 + 12) % 12;
|
|
8858
|
+
if (intervalPc === 0 || intervalPc === 7) {
|
|
8859
|
+
parallelPerfects.push({ atBeat: b1, intervalPc });
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
const crossings = [];
|
|
8864
|
+
for (const b of [...upperOnsets, ...onsetSet(lower)]) {
|
|
8865
|
+
const u = pitchAt(upper, b);
|
|
8866
|
+
const l = pitchAt(lower, b);
|
|
8867
|
+
if (u !== null && l !== null && u < l) crossings.push(b);
|
|
8868
|
+
}
|
|
8869
|
+
pairs.push({
|
|
8870
|
+
upperVoice: i,
|
|
8871
|
+
onsetIndependence,
|
|
8872
|
+
motion,
|
|
8873
|
+
parallelPerfects,
|
|
8874
|
+
crossings: [...new Set(crossings)].sort((a, b) => a - b)
|
|
8875
|
+
});
|
|
8876
|
+
}
|
|
8877
|
+
const allOnsets = voices.map(onsetSet);
|
|
8878
|
+
const union = new Set(allOnsets.flat());
|
|
8879
|
+
let sharedByAll = 0;
|
|
8880
|
+
for (const b of union) {
|
|
8881
|
+
if (allOnsets.every((o) => o.length === 0 || o.includes(b))) sharedByAll += 1;
|
|
8882
|
+
}
|
|
8883
|
+
const homorhythmScore = union.size === 0 ? 0 : sharedByAll / union.size;
|
|
8884
|
+
return { pairs, homorhythmScore };
|
|
8885
|
+
}
|
|
8886
|
+
function describeViolations(analysis, rules) {
|
|
8887
|
+
const out = [];
|
|
8888
|
+
for (const pair of analysis.pairs) {
|
|
8889
|
+
const label = `between voice ${pair.upperVoice + 1} and voice ${pair.upperVoice + 2}`;
|
|
8890
|
+
if (rules.forbidParallelPerfects) {
|
|
8891
|
+
for (const p of pair.parallelPerfects) {
|
|
8892
|
+
out.push(`Parallel ${p.intervalPc === 0 ? "octaves" : "fifths"} ${label} at beat ${p.atBeat} \u2014 approach perfect intervals by contrary or oblique motion.`);
|
|
8893
|
+
}
|
|
8894
|
+
}
|
|
8895
|
+
if (rules.forbidVoiceCrossing && pair.crossings.length > 0) {
|
|
8896
|
+
out.push(`Voice crossing ${label} at beat${pair.crossings.length > 1 ? "s" : ""} ${pair.crossings.join(", ")} \u2014 keep each voice inside its register lane.`);
|
|
8897
|
+
}
|
|
8898
|
+
if (pair.onsetIndependence < rules.minOnsetIndependence) {
|
|
8899
|
+
out.push(`Voices ${pair.upperVoice + 1} and ${pair.upperVoice + 2} attack together too often (independence ${pair.onsetIndependence.toFixed(2)} < ${rules.minOnsetIndependence}) \u2014 stagger entrances and let one voice move while the other holds.`);
|
|
8900
|
+
}
|
|
8901
|
+
}
|
|
8902
|
+
return out;
|
|
8903
|
+
}
|
|
8904
|
+
|
|
8905
|
+
// src/ensemble-core/styles.ts
|
|
8906
|
+
var ENSEMBLE_STYLES = ["counterpoint", "chorale", "interlock"];
|
|
8907
|
+
var STYLE_RULES = {
|
|
8908
|
+
counterpoint: {
|
|
8909
|
+
forbidParallelPerfects: true,
|
|
8910
|
+
forbidVoiceCrossing: true,
|
|
8911
|
+
minOnsetIndependence: 0.35,
|
|
8912
|
+
promptParagraph: "STYLE \u2014 COUNTERPOINT (modern, not strict species): independent singable lines. Favor contrary and oblique motion between neighbors; approach perfect intervals by contrary motion; imitate motifs between voices a bar apart; stagger entrances so voices converse instead of speaking at once."
|
|
8913
|
+
},
|
|
8914
|
+
chorale: {
|
|
8915
|
+
forbidParallelPerfects: true,
|
|
8916
|
+
forbidVoiceCrossing: true,
|
|
8917
|
+
minOnsetIndependence: 0,
|
|
8918
|
+
promptParagraph: "STYLE \u2014 CHORALE: homorhythmic block harmony. Voices move together on the same rhythm with smooth voice-leading \u2014 nearest chord tone, common tones held, no leaps larger than a fifth in inner voices."
|
|
8919
|
+
},
|
|
8920
|
+
interlock: {
|
|
8921
|
+
forbidParallelPerfects: false,
|
|
8922
|
+
forbidVoiceCrossing: false,
|
|
8923
|
+
minOnsetIndependence: 0.6,
|
|
8924
|
+
promptParagraph: "STYLE \u2014 INTERLOCK (minimal / systems music): short repeating cells that mesh like gears. Each voice keeps its own ostinato; onsets rarely coincide with the neighboring voice; parallel motion and doubling are welcome when the composite rhythm stays busy and even."
|
|
8925
|
+
}
|
|
8926
|
+
};
|
|
8927
|
+
|
|
8928
|
+
// src/ensemble-core/ensemble-schema.ts
|
|
8929
|
+
var SUBMIT_ENSEMBLE_TOOL_NAME = "submit_ensemble";
|
|
8930
|
+
function buildSubmitEnsembleParameters(voiceCount) {
|
|
8931
|
+
return {
|
|
8932
|
+
type: "object",
|
|
8933
|
+
properties: {
|
|
8934
|
+
voices: {
|
|
8935
|
+
type: "array",
|
|
8936
|
+
description: `Exactly ${voiceCount} voices, voiceIndex 0 (top) through ${voiceCount - 1} (bottom).`,
|
|
8937
|
+
items: {
|
|
8938
|
+
type: "object",
|
|
8939
|
+
properties: {
|
|
8940
|
+
voiceIndex: { type: "integer", description: "0 = top voice" },
|
|
8941
|
+
notes: {
|
|
8942
|
+
type: "array",
|
|
8943
|
+
items: {
|
|
8944
|
+
type: "object",
|
|
8945
|
+
properties: {
|
|
8946
|
+
pitch: { type: "integer", description: "MIDI note number 0-127" },
|
|
8947
|
+
startBeat: { type: "number", description: "quarter-note beats from clip start" },
|
|
8948
|
+
durationBeats: { type: "number", description: "duration in quarter-note beats" },
|
|
8949
|
+
velocity: { type: "integer", description: "1-127" }
|
|
8950
|
+
},
|
|
8951
|
+
required: ["pitch", "startBeat", "durationBeats", "velocity"]
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8954
|
+
},
|
|
8955
|
+
required: ["voiceIndex", "notes"]
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
},
|
|
8959
|
+
required: ["voices"]
|
|
8960
|
+
};
|
|
8961
|
+
}
|
|
8962
|
+
function parseEnsembleArgs(args, voiceCount) {
|
|
8963
|
+
if (typeof args !== "object" || args === null) return null;
|
|
8964
|
+
const voicesRaw = args.voices;
|
|
8965
|
+
if (!Array.isArray(voicesRaw)) return null;
|
|
8966
|
+
const warnings = [];
|
|
8967
|
+
const voiceNotes = Array.from({ length: voiceCount }, () => []);
|
|
8968
|
+
for (const v of voicesRaw) {
|
|
8969
|
+
if (typeof v !== "object" || v === null) continue;
|
|
8970
|
+
const idxRaw = v.voiceIndex;
|
|
8971
|
+
const idx = typeof idxRaw === "number" ? Math.round(idxRaw) : NaN;
|
|
8972
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= voiceCount) {
|
|
8973
|
+
warnings.push(`ignored voice with out-of-range voiceIndex ${String(idxRaw)}`);
|
|
8974
|
+
continue;
|
|
8975
|
+
}
|
|
8976
|
+
const notesRaw = v.notes;
|
|
8977
|
+
if (!Array.isArray(notesRaw)) continue;
|
|
8978
|
+
const notes = [];
|
|
8979
|
+
let dropped = 0;
|
|
8980
|
+
for (const n of notesRaw) {
|
|
8981
|
+
const note = n;
|
|
8982
|
+
if (typeof note?.pitch === "number" && typeof note?.startBeat === "number" && typeof note?.durationBeats === "number" && typeof note?.velocity === "number" && note.durationBeats > 0 && note.startBeat >= 0) {
|
|
8983
|
+
notes.push({
|
|
8984
|
+
pitch: Math.max(0, Math.min(127, Math.round(note.pitch))),
|
|
8985
|
+
startBeat: note.startBeat,
|
|
8986
|
+
durationBeats: note.durationBeats,
|
|
8987
|
+
velocity: Math.max(1, Math.min(127, Math.round(note.velocity)))
|
|
8988
|
+
});
|
|
8989
|
+
} else {
|
|
8990
|
+
dropped += 1;
|
|
8991
|
+
}
|
|
8992
|
+
}
|
|
8993
|
+
if (dropped > 0) warnings.push(`voice ${idx}: dropped ${dropped} malformed note(s)`);
|
|
8994
|
+
voiceNotes[idx] = notes;
|
|
8995
|
+
}
|
|
8996
|
+
const returned = voicesRaw.length;
|
|
8997
|
+
if (returned !== voiceCount) {
|
|
8998
|
+
warnings.push(`model returned ${returned} voices for a ${voiceCount}-voice ensemble`);
|
|
8999
|
+
}
|
|
9000
|
+
const anyNotes = voiceNotes.some((v) => v.length > 0);
|
|
9001
|
+
return anyNotes ? { voiceNotes, warnings } : null;
|
|
9002
|
+
}
|
|
9003
|
+
|
|
9004
|
+
// src/ensemble-core/ensemble-prompt.ts
|
|
9005
|
+
function voiceContractLine(spec) {
|
|
9006
|
+
const root = spec.rootOnly ? " ROOT PITCH CLASS ONLY (each bar's chord root)." : "";
|
|
9007
|
+
return `- Voice ${spec.voiceIndex + 1} (${spec.label}): MIDI ${spec.registerLow}-${spec.registerHigh}, max ${spec.maxNotesPerBar} notes/bar, rhythm: ${spec.rhythmPalette}. ${spec.harmonicDiscipline}.${root}`;
|
|
9008
|
+
}
|
|
9009
|
+
function buildEnsembleSystemPrompt(specs, style) {
|
|
9010
|
+
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9011
|
+
return `You are an ensemble composer. Compose ${specs.length} voices as ONE piece of music \u2014 not ${specs.length} independent parts.
|
|
9012
|
+
|
|
9013
|
+
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9014
|
+
|
|
9015
|
+
THE ENSEMBLE RULES (most important):
|
|
9016
|
+
1. The voices are composed TOGETHER. They must relate: imitate and answer each other's motifs, move in contrary or oblique motion against neighbors, and stagger entrances so lines converse.
|
|
9017
|
+
2. Each voice is a SINGLE monophonic line \u2014 within one voice, no two notes overlap in time and no chords. Voices MAY and SHOULD overlap EACH OTHER; that overlap is the music.
|
|
9018
|
+
3. Complexity decreases downward: the top voice is the most active and ornamented, the bottom voice the sparsest anchor. Respect each voice's register window and notes-per-bar cap exactly.
|
|
9019
|
+
4. Follow the chord progression in the musical context exactly. Non-chord tones only as passing or neighbor tones on weak beats, resolving by step.
|
|
9020
|
+
5. Not every voice plays all the time \u2014 rests are structural. Avoid all voices attacking the same beat except at phrase boundaries.
|
|
9021
|
+
6. startBeat/durationBeats are in quarter-note beats from the start of the clip; fill the stated bar length, and make bar 1 land immediately (no long silent intro).
|
|
9022
|
+
|
|
9023
|
+
${styleParagraph}
|
|
9024
|
+
|
|
9025
|
+
PER-VOICE CONTRACTS:
|
|
9026
|
+
${specs.map(voiceContractLine).join("\n")}`;
|
|
9027
|
+
}
|
|
9028
|
+
function buildViolationRetrySuffix(violations) {
|
|
9029
|
+
if (violations.length === 0) return "";
|
|
9030
|
+
return `
|
|
9031
|
+
|
|
9032
|
+
Your previous ensemble had these problems \u2014 fix them while keeping everything that worked:
|
|
9033
|
+
` + violations.map((v) => `- ${v}`).join("\n");
|
|
9034
|
+
}
|
|
9035
|
+
|
|
7965
9036
|
// src/constants/sdk-version.ts
|
|
7966
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
9037
|
+
var PLUGIN_SDK_VERSION = "2.42.0";
|
|
7967
9038
|
|
|
7968
9039
|
// src/utils/format-concurrent-tracks.ts
|
|
7969
9040
|
function formatConcurrentTracks(ctx) {
|
|
7970
9041
|
const tracks = ctx.concurrentTracks;
|
|
7971
9042
|
if (!tracks || tracks.length === 0) return "";
|
|
7972
|
-
const
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
if (track.truncated && typeof track.originalNoteCount === "number") {
|
|
7985
|
-
const dropped = track.originalNoteCount - sumKeptNotes(track.notesByChord);
|
|
7986
|
-
if (dropped > 0) {
|
|
7987
|
-
lines.push(` \u2026 (${dropped} more notes truncated)`);
|
|
7988
|
-
}
|
|
7989
|
-
}
|
|
9043
|
+
const pinned = tracks.filter((t) => t.pinned);
|
|
9044
|
+
const ambient = tracks.filter((t) => !t.pinned);
|
|
9045
|
+
const lines = [];
|
|
9046
|
+
if (pinned.length > 0) {
|
|
9047
|
+
lines.push(
|
|
9048
|
+
"REFERENCE TRACKS (write in counterpoint against these \u2014 interlock with their rhythm, avoid doubling their onsets, favor contrary or oblique motion):"
|
|
9049
|
+
);
|
|
9050
|
+
for (const track of pinned) pushTrackLines(lines, track);
|
|
9051
|
+
}
|
|
9052
|
+
if (ambient.length > 0) {
|
|
9053
|
+
lines.push(`Concurrent tracks in scene (already generated):`);
|
|
9054
|
+
for (const track of ambient) pushTrackLines(lines, track);
|
|
7990
9055
|
}
|
|
7991
9056
|
if (ctx.truncatedTrackCount && ctx.truncatedTrackCount > 0) {
|
|
7992
9057
|
lines.push(
|
|
@@ -7995,6 +9060,25 @@ function formatConcurrentTracks(ctx) {
|
|
|
7995
9060
|
}
|
|
7996
9061
|
return lines.join("\n");
|
|
7997
9062
|
}
|
|
9063
|
+
function pushTrackLines(lines, track) {
|
|
9064
|
+
const nameStr = track.name ? ` name="${escapeQuotes(track.name)}"` : "";
|
|
9065
|
+
const promptStr = track.prompt ? ` prompt="${escapeQuotes(track.prompt)}"` : "";
|
|
9066
|
+
lines.push(` - role=${track.role ?? "unknown"}${nameStr}${promptStr}`);
|
|
9067
|
+
if (track.notesByChord.length === 0) {
|
|
9068
|
+
lines.push(` (no notes)`);
|
|
9069
|
+
} else {
|
|
9070
|
+
for (const segment of track.notesByChord) {
|
|
9071
|
+
if (segment.notes.length === 0) continue;
|
|
9072
|
+
lines.push(` ${formatChordSegment(segment)}`);
|
|
9073
|
+
}
|
|
9074
|
+
}
|
|
9075
|
+
if (track.truncated && typeof track.originalNoteCount === "number") {
|
|
9076
|
+
const dropped = track.originalNoteCount - sumKeptNotes(track.notesByChord);
|
|
9077
|
+
if (dropped > 0) {
|
|
9078
|
+
lines.push(` \u2026 (${dropped} more notes truncated)`);
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
7998
9082
|
function formatChordSegment(segment) {
|
|
7999
9083
|
const [start, end] = segment.chordRangeQn;
|
|
8000
9084
|
const notesJson = JSON.stringify(segment.notes.map(compactNote2));
|
|
@@ -8120,6 +9204,9 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8120
9204
|
DownloadPackButton,
|
|
8121
9205
|
EMPTY_FX_DETAIL_STATE,
|
|
8122
9206
|
EMPTY_FX_STATE,
|
|
9207
|
+
ENSEMBLE_MAX_VOICES,
|
|
9208
|
+
ENSEMBLE_MIN_VOICES,
|
|
9209
|
+
ENSEMBLE_STYLES,
|
|
8123
9210
|
EQUAL_POWER_GAIN,
|
|
8124
9211
|
FX_CATEGORIES,
|
|
8125
9212
|
FX_CHAIN_ORDER,
|
|
@@ -8131,9 +9218,11 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8131
9218
|
FxToggleBar,
|
|
8132
9219
|
GUTTER_W,
|
|
8133
9220
|
GeneratorPanelShell,
|
|
9221
|
+
GroupFadeTrackRow,
|
|
8134
9222
|
ImportTrackModal,
|
|
8135
9223
|
InstrumentDrawer,
|
|
8136
9224
|
LevelMeter,
|
|
9225
|
+
MIN_NOTE_DURATION_BEATS,
|
|
8137
9226
|
Modal,
|
|
8138
9227
|
OffsetScrubber,
|
|
8139
9228
|
PLUGIN_SDK_VERSION,
|
|
@@ -8145,6 +9234,8 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8145
9234
|
RESIZE_HANDLE_PX,
|
|
8146
9235
|
ROW_HEIGHT,
|
|
8147
9236
|
SLIDER_UNITY,
|
|
9237
|
+
STYLE_RULES,
|
|
9238
|
+
SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
8148
9239
|
SamplePackCTACard,
|
|
8149
9240
|
ScrollingWaveform,
|
|
8150
9241
|
SorceryProgressBar,
|
|
@@ -8157,6 +9248,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8157
9248
|
TransitionDesigner,
|
|
8158
9249
|
VolumeSlider,
|
|
8159
9250
|
WaveformView,
|
|
9251
|
+
analyzeEnsemble,
|
|
8160
9252
|
analyzeWavPeak,
|
|
8161
9253
|
asAudioEffect,
|
|
8162
9254
|
asCrossfadeMeta,
|
|
@@ -8164,8 +9256,11 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8164
9256
|
asTransitionDesignerDraft,
|
|
8165
9257
|
buildCrossfadeInpaintPrompt,
|
|
8166
9258
|
buildCrossfadeVolumeCurves,
|
|
9259
|
+
buildEnsembleSystemPrompt,
|
|
8167
9260
|
buildFadeVolumeCurve,
|
|
8168
9261
|
buildRowSlots,
|
|
9262
|
+
buildSubmitEnsembleParameters,
|
|
9263
|
+
buildViolationRetrySuffix,
|
|
8169
9264
|
calculateTimeBasedTarget,
|
|
8170
9265
|
cellToPx,
|
|
8171
9266
|
centerScrollTop,
|
|
@@ -8174,15 +9269,21 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8174
9269
|
dbIdsFromKeys,
|
|
8175
9270
|
dbToSlider,
|
|
8176
9271
|
defaultFadeGesture,
|
|
9272
|
+
defaultVoiceSpecs,
|
|
9273
|
+
describeViolations,
|
|
8177
9274
|
drawWaveform,
|
|
9275
|
+
enforceVoice,
|
|
9276
|
+
foldPitchToRegister,
|
|
8178
9277
|
formatConcurrentTracks,
|
|
8179
9278
|
hashString,
|
|
8180
9279
|
moveItem,
|
|
9280
|
+
nearestPitchWithPc,
|
|
8181
9281
|
newTrackState,
|
|
8182
9282
|
normalizeSlots,
|
|
8183
9283
|
padPair,
|
|
8184
9284
|
padSlots,
|
|
8185
9285
|
parseCrossfadePairs,
|
|
9286
|
+
parseEnsembleArgs,
|
|
8186
9287
|
parseFades,
|
|
8187
9288
|
parseLLMNoteResponse,
|
|
8188
9289
|
parseTrackGroups,
|
|
@@ -8199,6 +9300,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8199
9300
|
sliderToDb,
|
|
8200
9301
|
slotsEqual,
|
|
8201
9302
|
soundIdentity,
|
|
9303
|
+
splitFadeEntries,
|
|
8202
9304
|
synthesizeCuePoints,
|
|
8203
9305
|
tokenizePrompt,
|
|
8204
9306
|
trackDataKey,
|