@signalsandsorcery/plugin-sdk 2.34.1 → 2.35.2
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 +831 -74
- package/dist/index.d.ts +831 -74
- package/dist/index.js +2713 -81
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2702 -82
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
FadeTrackRow: () => FadeTrackRow,
|
|
54
54
|
FxToggleBar: () => FxToggleBar,
|
|
55
55
|
GUTTER_W: () => GUTTER_W,
|
|
56
|
+
GeneratorPanelShell: () => GeneratorPanelShell,
|
|
56
57
|
ImportTrackModal: () => ImportTrackModal,
|
|
57
58
|
InstrumentDrawer: () => TrackDrawer,
|
|
58
59
|
LevelMeter: () => LevelMeter,
|
|
@@ -61,6 +62,7 @@ __export(index_exports, {
|
|
|
61
62
|
PLUGIN_SDK_VERSION: () => PLUGIN_SDK_VERSION,
|
|
62
63
|
PX_PER_BEAT: () => PX_PER_BEAT,
|
|
63
64
|
PanSlider: () => PanSlider,
|
|
65
|
+
PanelMasterStrip: () => PanelMasterStrip,
|
|
64
66
|
PianoRollEditor: () => PianoRollEditor,
|
|
65
67
|
PluginError: () => PluginError,
|
|
66
68
|
RESIZE_HANDLE_PX: () => RESIZE_HANDLE_PX,
|
|
@@ -90,6 +92,7 @@ __export(index_exports, {
|
|
|
90
92
|
cellToPx: () => cellToPx,
|
|
91
93
|
centerScrollTop: () => centerScrollTop,
|
|
92
94
|
computePeaks: () => computePeaks,
|
|
95
|
+
createSurgeSoundAdapter: () => createSurgeSoundAdapter,
|
|
93
96
|
dbIdsFromKeys: () => dbIdsFromKeys,
|
|
94
97
|
dbToSlider: () => dbToSlider,
|
|
95
98
|
defaultFadeGesture: () => defaultFadeGesture,
|
|
@@ -97,16 +100,21 @@ __export(index_exports, {
|
|
|
97
100
|
formatConcurrentTracks: () => formatConcurrentTracks,
|
|
98
101
|
hashString: () => hashString,
|
|
99
102
|
moveItem: () => moveItem,
|
|
103
|
+
newTrackState: () => newTrackState,
|
|
100
104
|
normalizeSlots: () => normalizeSlots,
|
|
101
105
|
padPair: () => padPair,
|
|
102
106
|
padSlots: () => padSlots,
|
|
103
107
|
parseCrossfadePairs: () => parseCrossfadePairs,
|
|
104
108
|
parseFades: () => parseFades,
|
|
109
|
+
parseLLMNoteResponse: () => parseLLMNoteResponse,
|
|
110
|
+
parseTrackGroups: () => parseTrackGroups,
|
|
105
111
|
pickTopKWeighted: () => pickTopKWeighted,
|
|
106
112
|
pitchToName: () => pitchToName,
|
|
113
|
+
pluginFxToToggleFx: () => pluginFxToToggleFx,
|
|
107
114
|
pxToCell: () => pxToCell,
|
|
108
115
|
reconcileSlots: () => reconcileSlots,
|
|
109
116
|
resizeNoteDuration: () => resizeNoteDuration,
|
|
117
|
+
resolveTrackGroups: () => resolveTrackGroups,
|
|
110
118
|
rowKey: () => rowKey,
|
|
111
119
|
rowType: () => rowType,
|
|
112
120
|
scorePromptMatch: () => scorePromptMatch,
|
|
@@ -115,14 +123,18 @@ __export(index_exports, {
|
|
|
115
123
|
soundIdentity: () => soundIdentity,
|
|
116
124
|
synthesizeCuePoints: () => synthesizeCuePoints,
|
|
117
125
|
tokenizePrompt: () => tokenizePrompt,
|
|
126
|
+
trackDataKey: () => trackDataKey,
|
|
118
127
|
transposeNotes: () => transposeNotes,
|
|
119
128
|
useAnySolo: () => useAnySolo,
|
|
129
|
+
useGeneratorPanelCore: () => useGeneratorPanelCore,
|
|
130
|
+
usePanelBus: () => usePanelBus,
|
|
120
131
|
useSceneState: () => useSceneState,
|
|
121
132
|
useSoundHistory: () => useSoundHistory,
|
|
122
133
|
useTrackLevel: () => useTrackLevel,
|
|
123
134
|
useTrackLevels: () => useTrackLevels,
|
|
124
135
|
useTrackMeter: () => useTrackMeter,
|
|
125
136
|
useTrackReorder: () => useTrackReorder,
|
|
137
|
+
useTransitionOps: () => useTransitionOps,
|
|
126
138
|
useTransportPlaying: () => useTransportPlaying
|
|
127
139
|
});
|
|
128
140
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -4255,9 +4267,276 @@ function TransitionDesigner({
|
|
|
4255
4267
|
] });
|
|
4256
4268
|
}
|
|
4257
4269
|
|
|
4258
|
-
// src/components/
|
|
4270
|
+
// src/components/PanelMasterStrip.tsx
|
|
4259
4271
|
var import_react17 = require("react");
|
|
4260
4272
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
4273
|
+
function PanelMasterStrip({
|
|
4274
|
+
bus,
|
|
4275
|
+
availableFx = [],
|
|
4276
|
+
fxLoading = false,
|
|
4277
|
+
soloedOut = false,
|
|
4278
|
+
disabled = false,
|
|
4279
|
+
fxPickerOpen,
|
|
4280
|
+
onToggleFxPicker,
|
|
4281
|
+
onRefreshFx,
|
|
4282
|
+
onVolumeChange,
|
|
4283
|
+
onMuteToggle,
|
|
4284
|
+
onSoloToggle,
|
|
4285
|
+
onAddFx,
|
|
4286
|
+
onRemoveFx,
|
|
4287
|
+
onToggleFxEnabled,
|
|
4288
|
+
onShowFxEditor
|
|
4289
|
+
}) {
|
|
4290
|
+
const [search, setSearch] = (0, import_react17.useState)("");
|
|
4291
|
+
const filtered = (0, import_react17.useMemo)(() => {
|
|
4292
|
+
const q = search.trim().toLowerCase();
|
|
4293
|
+
if (!q) return availableFx;
|
|
4294
|
+
return availableFx.filter(
|
|
4295
|
+
(fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
|
|
4296
|
+
);
|
|
4297
|
+
}, [availableFx, search]);
|
|
4298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
4299
|
+
"div",
|
|
4300
|
+
{
|
|
4301
|
+
"data-testid": "panel-master-strip",
|
|
4302
|
+
className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt/50 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
|
|
4303
|
+
children: [
|
|
4304
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4305
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4306
|
+
"span",
|
|
4307
|
+
{
|
|
4308
|
+
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
4309
|
+
title: "Panel mix bus \u2014 volume, mute/solo and FX applied to this panel's summed output",
|
|
4310
|
+
children: "BUS"
|
|
4311
|
+
}
|
|
4312
|
+
),
|
|
4313
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "w-24", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4314
|
+
VolumeSlider,
|
|
4315
|
+
{
|
|
4316
|
+
value: dbToSlider(bus.volume),
|
|
4317
|
+
onChange: (sliderValue) => onVolumeChange(sliderToDb(sliderValue)),
|
|
4318
|
+
disabled
|
|
4319
|
+
}
|
|
4320
|
+
) }),
|
|
4321
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4322
|
+
"button",
|
|
4323
|
+
{
|
|
4324
|
+
"data-testid": "bus-mute-button",
|
|
4325
|
+
onClick: onMuteToggle,
|
|
4326
|
+
disabled,
|
|
4327
|
+
className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${bus.muted ? "bg-red-600 text-white" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"} disabled:opacity-50`,
|
|
4328
|
+
title: bus.muted ? "Unmute panel bus" : "Mute panel bus (silences the whole panel)",
|
|
4329
|
+
children: "M"
|
|
4330
|
+
}
|
|
4331
|
+
),
|
|
4332
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4333
|
+
"button",
|
|
4334
|
+
{
|
|
4335
|
+
"data-testid": "bus-solo-button",
|
|
4336
|
+
onClick: onSoloToggle,
|
|
4337
|
+
disabled,
|
|
4338
|
+
className: `px-1.5 py-0.5 text-xs font-bold rounded transition-colors ${bus.soloed ? "bg-amber-500 text-black" : "bg-sas-panel-alt text-sas-muted hover:bg-sas-border"} disabled:opacity-50`,
|
|
4339
|
+
title: bus.soloed ? "Unsolo panel bus" : "Solo this panel (silences other panels/tracks in scope)",
|
|
4340
|
+
children: "S"
|
|
4341
|
+
}
|
|
4342
|
+
),
|
|
4343
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
4344
|
+
"span",
|
|
4345
|
+
{
|
|
4346
|
+
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
4347
|
+
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"}`,
|
|
4348
|
+
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
4349
|
+
children: [
|
|
4350
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4351
|
+
"button",
|
|
4352
|
+
{
|
|
4353
|
+
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
4354
|
+
onClick: () => onToggleFxEnabled(fx.index, !fx.enabled),
|
|
4355
|
+
disabled,
|
|
4356
|
+
className: "hover:opacity-70 disabled:opacity-50",
|
|
4357
|
+
title: fx.enabled ? `Bypass ${fx.name}` : `Enable ${fx.name}`,
|
|
4358
|
+
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
4359
|
+
}
|
|
4360
|
+
),
|
|
4361
|
+
onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4362
|
+
"button",
|
|
4363
|
+
{
|
|
4364
|
+
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
4365
|
+
onClick: () => onShowFxEditor(fx.index),
|
|
4366
|
+
disabled,
|
|
4367
|
+
className: "max-w-[80px] truncate hover:underline disabled:opacity-50",
|
|
4368
|
+
title: `Open ${fx.name} editor`,
|
|
4369
|
+
children: fx.name
|
|
4370
|
+
}
|
|
4371
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
4372
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4373
|
+
"button",
|
|
4374
|
+
{
|
|
4375
|
+
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
4376
|
+
onClick: () => onRemoveFx(fx.index),
|
|
4377
|
+
disabled,
|
|
4378
|
+
className: "text-sas-muted/60 hover:text-sas-danger disabled:opacity-50",
|
|
4379
|
+
title: `Remove ${fx.name} from the bus`,
|
|
4380
|
+
children: "\u2715"
|
|
4381
|
+
}
|
|
4382
|
+
)
|
|
4383
|
+
]
|
|
4384
|
+
},
|
|
4385
|
+
`${fx.index}:${fx.pluginId}`
|
|
4386
|
+
)) }),
|
|
4387
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4388
|
+
"button",
|
|
4389
|
+
{
|
|
4390
|
+
"data-testid": "bus-fx-add-button",
|
|
4391
|
+
onClick: () => onToggleFxPicker(!fxPickerOpen),
|
|
4392
|
+
disabled,
|
|
4393
|
+
className: `px-1.5 py-0.5 rounded-sm border text-xs whitespace-nowrap transition-colors ${fxPickerOpen ? "border-sas-accent text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"} disabled:opacity-50`,
|
|
4394
|
+
title: "Add an FX plugin to the panel bus",
|
|
4395
|
+
children: "FX +"
|
|
4396
|
+
}
|
|
4397
|
+
)
|
|
4398
|
+
] }),
|
|
4399
|
+
fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
4400
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4401
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4402
|
+
"input",
|
|
4403
|
+
{
|
|
4404
|
+
type: "text",
|
|
4405
|
+
value: search,
|
|
4406
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4407
|
+
placeholder: "Search FX...",
|
|
4408
|
+
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
4409
|
+
}
|
|
4410
|
+
),
|
|
4411
|
+
onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4412
|
+
"button",
|
|
4413
|
+
{
|
|
4414
|
+
onClick: () => onRefreshFx(),
|
|
4415
|
+
disabled: fxLoading,
|
|
4416
|
+
className: "px-2 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors disabled:opacity-50",
|
|
4417
|
+
title: "Re-scan plugins",
|
|
4418
|
+
children: fxLoading ? "..." : "Refresh"
|
|
4419
|
+
}
|
|
4420
|
+
)
|
|
4421
|
+
] }),
|
|
4422
|
+
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
|
|
4423
|
+
filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
4424
|
+
"button",
|
|
4425
|
+
{
|
|
4426
|
+
"data-testid": `bus-fx-pick-${fx.pluginId}`,
|
|
4427
|
+
onClick: () => onAddFx(fx.pluginId),
|
|
4428
|
+
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",
|
|
4429
|
+
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
4430
|
+
children: [
|
|
4431
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
4432
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
4433
|
+
]
|
|
4434
|
+
},
|
|
4435
|
+
fx.pluginId
|
|
4436
|
+
)),
|
|
4437
|
+
filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime18.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" })
|
|
4438
|
+
] })
|
|
4439
|
+
] })
|
|
4440
|
+
]
|
|
4441
|
+
}
|
|
4442
|
+
);
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
// src/hooks/usePanelBus.ts
|
|
4446
|
+
var import_react18 = require("react");
|
|
4447
|
+
function usePanelBus(host, activeSceneId) {
|
|
4448
|
+
const supported = typeof host.getPanelBusState === "function";
|
|
4449
|
+
const [bus, setBus] = (0, import_react18.useState)(null);
|
|
4450
|
+
const [availableFx, setAvailableFx] = (0, import_react18.useState)([]);
|
|
4451
|
+
const [fxLoading, setFxLoading] = (0, import_react18.useState)(false);
|
|
4452
|
+
const [fxPickerOpen, setFxPickerOpen] = (0, import_react18.useState)(false);
|
|
4453
|
+
const fxLoadedRef = (0, import_react18.useRef)(false);
|
|
4454
|
+
const loadSeqRef = (0, import_react18.useRef)(0);
|
|
4455
|
+
const reload = (0, import_react18.useCallback)(async () => {
|
|
4456
|
+
if (!supported || !activeSceneId || !host.getPanelBusState) {
|
|
4457
|
+
setBus(null);
|
|
4458
|
+
return;
|
|
4459
|
+
}
|
|
4460
|
+
const seq = ++loadSeqRef.current;
|
|
4461
|
+
try {
|
|
4462
|
+
const state = await host.getPanelBusState(activeSceneId);
|
|
4463
|
+
if (loadSeqRef.current === seq) setBus(state);
|
|
4464
|
+
} catch {
|
|
4465
|
+
}
|
|
4466
|
+
}, [host, activeSceneId, supported]);
|
|
4467
|
+
(0, import_react18.useEffect)(() => {
|
|
4468
|
+
setBus(null);
|
|
4469
|
+
setFxPickerOpen(false);
|
|
4470
|
+
void reload();
|
|
4471
|
+
}, [reload]);
|
|
4472
|
+
const loadFxList = (0, import_react18.useCallback)(
|
|
4473
|
+
async (force) => {
|
|
4474
|
+
if (!supported || !host.getAvailableFx) return;
|
|
4475
|
+
if (fxLoadedRef.current && !force) return;
|
|
4476
|
+
setFxLoading(true);
|
|
4477
|
+
try {
|
|
4478
|
+
const list = await host.getAvailableFx();
|
|
4479
|
+
setAvailableFx(list);
|
|
4480
|
+
fxLoadedRef.current = true;
|
|
4481
|
+
} catch {
|
|
4482
|
+
} finally {
|
|
4483
|
+
setFxLoading(false);
|
|
4484
|
+
}
|
|
4485
|
+
},
|
|
4486
|
+
[host, supported]
|
|
4487
|
+
);
|
|
4488
|
+
const openPicker = (0, import_react18.useCallback)(
|
|
4489
|
+
(open) => {
|
|
4490
|
+
setFxPickerOpen(open);
|
|
4491
|
+
if (open) void loadFxList(false);
|
|
4492
|
+
},
|
|
4493
|
+
[loadFxList]
|
|
4494
|
+
);
|
|
4495
|
+
const mutate = (0, import_react18.useCallback)(
|
|
4496
|
+
(fn) => {
|
|
4497
|
+
if (!fn || !activeSceneId) return;
|
|
4498
|
+
void (async () => {
|
|
4499
|
+
try {
|
|
4500
|
+
await fn();
|
|
4501
|
+
} catch {
|
|
4502
|
+
}
|
|
4503
|
+
await reload();
|
|
4504
|
+
})();
|
|
4505
|
+
},
|
|
4506
|
+
[activeSceneId, reload]
|
|
4507
|
+
);
|
|
4508
|
+
return {
|
|
4509
|
+
supported,
|
|
4510
|
+
bus,
|
|
4511
|
+
availableFx,
|
|
4512
|
+
fxLoading,
|
|
4513
|
+
fxPickerOpen,
|
|
4514
|
+
setFxPickerOpen: openPicker,
|
|
4515
|
+
refreshFx: () => void loadFxList(true),
|
|
4516
|
+
reload,
|
|
4517
|
+
onVolumeChange: (volumeDb) => mutate(host.setPanelBusVolume && (() => host.setPanelBusVolume(activeSceneId, volumeDb))),
|
|
4518
|
+
onMuteToggle: () => mutate(
|
|
4519
|
+
host.setPanelBusMute && (() => host.setPanelBusMute(activeSceneId, !(bus?.muted ?? false)))
|
|
4520
|
+
),
|
|
4521
|
+
onSoloToggle: () => mutate(
|
|
4522
|
+
host.setPanelBusSolo && (() => host.setPanelBusSolo(activeSceneId, !(bus?.soloed ?? false)))
|
|
4523
|
+
),
|
|
4524
|
+
onAddFx: (pluginId) => mutate(host.loadPanelBusFx && (async () => {
|
|
4525
|
+
await host.loadPanelBusFx(activeSceneId, pluginId);
|
|
4526
|
+
})),
|
|
4527
|
+
onRemoveFx: (fxIndex) => mutate(host.removePanelBusFx && (() => host.removePanelBusFx(activeSceneId, fxIndex))),
|
|
4528
|
+
onToggleFxEnabled: (fxIndex, enabled) => mutate(
|
|
4529
|
+
host.setPanelBusFxEnabled && (() => host.setPanelBusFxEnabled(activeSceneId, fxIndex, enabled))
|
|
4530
|
+
),
|
|
4531
|
+
onShowFxEditor: (fxIndex) => mutate(
|
|
4532
|
+
host.showPanelBusFxEditor && (() => host.showPanelBusFxEditor(activeSceneId, fxIndex))
|
|
4533
|
+
)
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
// src/components/DownloadPackButton.tsx
|
|
4538
|
+
var import_react19 = require("react");
|
|
4539
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
4261
4540
|
function formatSize(bytes) {
|
|
4262
4541
|
if (!bytes || bytes <= 0) return "";
|
|
4263
4542
|
const gb = bytes / 1024 ** 3;
|
|
@@ -4273,10 +4552,10 @@ var DownloadPackButton = ({
|
|
|
4273
4552
|
variant = "compact",
|
|
4274
4553
|
onDownloadComplete
|
|
4275
4554
|
}) => {
|
|
4276
|
-
const [status, setStatus] = (0,
|
|
4277
|
-
const [progress, setProgress] = (0,
|
|
4278
|
-
const [errorMessage, setErrorMessage] = (0,
|
|
4279
|
-
(0,
|
|
4555
|
+
const [status, setStatus] = (0, import_react19.useState)("idle");
|
|
4556
|
+
const [progress, setProgress] = (0, import_react19.useState)(0);
|
|
4557
|
+
const [errorMessage, setErrorMessage] = (0, import_react19.useState)(null);
|
|
4558
|
+
(0, import_react19.useEffect)(() => {
|
|
4280
4559
|
const unsub = host.onSamplePackProgress(packId, (p) => {
|
|
4281
4560
|
setStatus(p.status);
|
|
4282
4561
|
setProgress(p.progress);
|
|
@@ -4291,7 +4570,7 @@ var DownloadPackButton = ({
|
|
|
4291
4570
|
});
|
|
4292
4571
|
return unsub;
|
|
4293
4572
|
}, [host, packId, onDownloadComplete]);
|
|
4294
|
-
const handleClick = (0,
|
|
4573
|
+
const handleClick = (0, import_react19.useCallback)(async () => {
|
|
4295
4574
|
if (status !== "idle" && status !== "error") return;
|
|
4296
4575
|
try {
|
|
4297
4576
|
setStatus("downloading");
|
|
@@ -4345,8 +4624,8 @@ var DownloadPackButton = ({
|
|
|
4345
4624
|
} else {
|
|
4346
4625
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
4347
4626
|
}
|
|
4348
|
-
return /* @__PURE__ */ (0,
|
|
4349
|
-
/* @__PURE__ */ (0,
|
|
4627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
|
|
4628
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4350
4629
|
"button",
|
|
4351
4630
|
{
|
|
4352
4631
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -4357,12 +4636,12 @@ var DownloadPackButton = ({
|
|
|
4357
4636
|
children: buttonLabel
|
|
4358
4637
|
}
|
|
4359
4638
|
),
|
|
4360
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0,
|
|
4639
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
4361
4640
|
] });
|
|
4362
4641
|
};
|
|
4363
4642
|
|
|
4364
4643
|
// src/components/SamplePackCTACard.tsx
|
|
4365
|
-
var
|
|
4644
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4366
4645
|
var SamplePackCTACard = ({
|
|
4367
4646
|
host,
|
|
4368
4647
|
pack,
|
|
@@ -4370,7 +4649,7 @@ var SamplePackCTACard = ({
|
|
|
4370
4649
|
onDownloadComplete
|
|
4371
4650
|
}) => {
|
|
4372
4651
|
if (status === "checking") {
|
|
4373
|
-
return /* @__PURE__ */ (0,
|
|
4652
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4374
4653
|
"div",
|
|
4375
4654
|
{
|
|
4376
4655
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -4381,16 +4660,16 @@ var SamplePackCTACard = ({
|
|
|
4381
4660
|
}
|
|
4382
4661
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
4383
4662
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
4384
|
-
return /* @__PURE__ */ (0,
|
|
4663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4385
4664
|
"div",
|
|
4386
4665
|
{
|
|
4387
4666
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
4388
4667
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
4389
4668
|
children: [
|
|
4390
|
-
/* @__PURE__ */ (0,
|
|
4391
|
-
/* @__PURE__ */ (0,
|
|
4392
|
-
/* @__PURE__ */ (0,
|
|
4393
|
-
/* @__PURE__ */ (0,
|
|
4669
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
4670
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
4671
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
4672
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4394
4673
|
DownloadPackButton,
|
|
4395
4674
|
{
|
|
4396
4675
|
host,
|
|
@@ -4407,7 +4686,7 @@ var SamplePackCTACard = ({
|
|
|
4407
4686
|
};
|
|
4408
4687
|
|
|
4409
4688
|
// src/components/WaveformView.tsx
|
|
4410
|
-
var
|
|
4689
|
+
var import_react20 = require("react");
|
|
4411
4690
|
|
|
4412
4691
|
// src/components/waveform.ts
|
|
4413
4692
|
function computePeaks(audioBuffer, bins, targetSamples) {
|
|
@@ -4470,7 +4749,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
4470
4749
|
}
|
|
4471
4750
|
|
|
4472
4751
|
// src/components/WaveformView.tsx
|
|
4473
|
-
var
|
|
4752
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
4474
4753
|
var WaveformView = ({
|
|
4475
4754
|
host,
|
|
4476
4755
|
filePath,
|
|
@@ -4479,9 +4758,9 @@ var WaveformView = ({
|
|
|
4479
4758
|
fillStyle,
|
|
4480
4759
|
targetSamples
|
|
4481
4760
|
}) => {
|
|
4482
|
-
const canvasRef = (0,
|
|
4483
|
-
const [peaks, setPeaks] = (0,
|
|
4484
|
-
(0,
|
|
4761
|
+
const canvasRef = (0, import_react20.useRef)(null);
|
|
4762
|
+
const [peaks, setPeaks] = (0, import_react20.useState)(null);
|
|
4763
|
+
(0, import_react20.useEffect)(() => {
|
|
4485
4764
|
let cancelled = false;
|
|
4486
4765
|
let audioContext = null;
|
|
4487
4766
|
(async () => {
|
|
@@ -4507,7 +4786,7 @@ var WaveformView = ({
|
|
|
4507
4786
|
cancelled = true;
|
|
4508
4787
|
};
|
|
4509
4788
|
}, [host, filePath, bins, targetSamples]);
|
|
4510
|
-
(0,
|
|
4789
|
+
(0, import_react20.useEffect)(() => {
|
|
4511
4790
|
if (!peaks) return;
|
|
4512
4791
|
const canvas = canvasRef.current;
|
|
4513
4792
|
if (!canvas) return;
|
|
@@ -4518,7 +4797,7 @@ var WaveformView = ({
|
|
|
4518
4797
|
observer.observe(canvas);
|
|
4519
4798
|
return () => observer.disconnect();
|
|
4520
4799
|
}, [peaks, fillStyle]);
|
|
4521
|
-
return /* @__PURE__ */ (0,
|
|
4800
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4522
4801
|
"canvas",
|
|
4523
4802
|
{
|
|
4524
4803
|
ref: canvasRef,
|
|
@@ -4529,8 +4808,8 @@ var WaveformView = ({
|
|
|
4529
4808
|
};
|
|
4530
4809
|
|
|
4531
4810
|
// src/components/ScrollingWaveform.tsx
|
|
4532
|
-
var
|
|
4533
|
-
var
|
|
4811
|
+
var import_react21 = require("react");
|
|
4812
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
4534
4813
|
var ScrollingWaveform = ({
|
|
4535
4814
|
getPeakDb,
|
|
4536
4815
|
active,
|
|
@@ -4538,11 +4817,11 @@ var ScrollingWaveform = ({
|
|
|
4538
4817
|
className,
|
|
4539
4818
|
fillStyle
|
|
4540
4819
|
}) => {
|
|
4541
|
-
const canvasRef = (0,
|
|
4542
|
-
const ringRef = (0,
|
|
4543
|
-
const writeIdxRef = (0,
|
|
4544
|
-
const rafRef = (0,
|
|
4545
|
-
(0,
|
|
4820
|
+
const canvasRef = (0, import_react21.useRef)(null);
|
|
4821
|
+
const ringRef = (0, import_react21.useRef)(new Float32Array(columns));
|
|
4822
|
+
const writeIdxRef = (0, import_react21.useRef)(0);
|
|
4823
|
+
const rafRef = (0, import_react21.useRef)(null);
|
|
4824
|
+
(0, import_react21.useEffect)(() => {
|
|
4546
4825
|
if (ringRef.current.length !== columns) {
|
|
4547
4826
|
const next = new Float32Array(columns);
|
|
4548
4827
|
const prev = ringRef.current;
|
|
@@ -4554,7 +4833,7 @@ var ScrollingWaveform = ({
|
|
|
4554
4833
|
writeIdxRef.current = writeIdxRef.current % columns;
|
|
4555
4834
|
}
|
|
4556
4835
|
}, [columns]);
|
|
4557
|
-
(0,
|
|
4836
|
+
(0, import_react21.useEffect)(() => {
|
|
4558
4837
|
if (!active) {
|
|
4559
4838
|
if (rafRef.current !== null) {
|
|
4560
4839
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -4606,7 +4885,7 @@ var ScrollingWaveform = ({
|
|
|
4606
4885
|
}
|
|
4607
4886
|
};
|
|
4608
4887
|
}, [active, getPeakDb, fillStyle]);
|
|
4609
|
-
return /* @__PURE__ */ (0,
|
|
4888
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4610
4889
|
"canvas",
|
|
4611
4890
|
{
|
|
4612
4891
|
ref: canvasRef,
|
|
@@ -4617,8 +4896,8 @@ var ScrollingWaveform = ({
|
|
|
4617
4896
|
};
|
|
4618
4897
|
|
|
4619
4898
|
// src/components/OffsetScrubber.tsx
|
|
4620
|
-
var
|
|
4621
|
-
var
|
|
4899
|
+
var import_react22 = require("react");
|
|
4900
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
4622
4901
|
var SLIDER_HEIGHT_PX = 28;
|
|
4623
4902
|
var TICK_HEIGHT_PX = 14;
|
|
4624
4903
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -4631,40 +4910,40 @@ function OffsetScrubber({
|
|
|
4631
4910
|
onChange,
|
|
4632
4911
|
disabled = false
|
|
4633
4912
|
}) {
|
|
4634
|
-
const trackRef = (0,
|
|
4635
|
-
const [draftOffset, setDraftOffset] = (0,
|
|
4636
|
-
const [isDragging, setIsDragging] = (0,
|
|
4637
|
-
(0,
|
|
4913
|
+
const trackRef = (0, import_react22.useRef)(null);
|
|
4914
|
+
const [draftOffset, setDraftOffset] = (0, import_react22.useState)(offsetSamples);
|
|
4915
|
+
const [isDragging, setIsDragging] = (0, import_react22.useState)(false);
|
|
4916
|
+
(0, import_react22.useEffect)(() => {
|
|
4638
4917
|
if (!isDragging) setDraftOffset(offsetSamples);
|
|
4639
4918
|
}, [offsetSamples, isDragging]);
|
|
4640
4919
|
const sampleRate = cuePoints?.sample_rate ?? 44100;
|
|
4641
4920
|
const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
|
|
4642
|
-
const beatsForRange = (0,
|
|
4921
|
+
const beatsForRange = (0, import_react22.useMemo)(() => {
|
|
4643
4922
|
return Math.round(60 / projectBpm * sampleRate);
|
|
4644
4923
|
}, [projectBpm, sampleRate]);
|
|
4645
4924
|
const rangeSamples = beatsForRange * meter;
|
|
4646
|
-
const sampleToFraction = (0,
|
|
4925
|
+
const sampleToFraction = (0, import_react22.useCallback)(
|
|
4647
4926
|
(sample) => {
|
|
4648
4927
|
const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
|
|
4649
4928
|
return (clamped + rangeSamples) / (2 * rangeSamples);
|
|
4650
4929
|
},
|
|
4651
4930
|
[rangeSamples]
|
|
4652
4931
|
);
|
|
4653
|
-
const fractionToSample = (0,
|
|
4932
|
+
const fractionToSample = (0, import_react22.useCallback)(
|
|
4654
4933
|
(fraction) => {
|
|
4655
4934
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
4656
4935
|
return Math.round(clamped * 2 * rangeSamples - rangeSamples);
|
|
4657
4936
|
},
|
|
4658
4937
|
[rangeSamples]
|
|
4659
4938
|
);
|
|
4660
|
-
const snapTargets = (0,
|
|
4939
|
+
const snapTargets = (0, import_react22.useMemo)(() => {
|
|
4661
4940
|
if (!cuePoints || cuePoints.beats.length === 0) return [];
|
|
4662
4941
|
const downbeat = cuePoints.beats[0];
|
|
4663
4942
|
const positives = cuePoints.beats.map((b) => b - downbeat);
|
|
4664
4943
|
const negatives = positives.slice(1).map((p) => -p);
|
|
4665
4944
|
return [...negatives, ...positives].sort((a, b) => a - b);
|
|
4666
4945
|
}, [cuePoints]);
|
|
4667
|
-
const snapToBeat = (0,
|
|
4946
|
+
const snapToBeat = (0, import_react22.useCallback)(
|
|
4668
4947
|
(sample) => {
|
|
4669
4948
|
if (snapTargets.length === 0) return sample;
|
|
4670
4949
|
let best = snapTargets[0];
|
|
@@ -4680,7 +4959,7 @@ function OffsetScrubber({
|
|
|
4680
4959
|
},
|
|
4681
4960
|
[snapTargets]
|
|
4682
4961
|
);
|
|
4683
|
-
const handlePointerDown = (0,
|
|
4962
|
+
const handlePointerDown = (0, import_react22.useCallback)(
|
|
4684
4963
|
(e) => {
|
|
4685
4964
|
if (disabled || !cuePoints) return;
|
|
4686
4965
|
e.preventDefault();
|
|
@@ -4714,7 +4993,7 @@ function OffsetScrubber({
|
|
|
4714
4993
|
},
|
|
4715
4994
|
[disabled, cuePoints, fractionToSample, onChange, snapToBeat]
|
|
4716
4995
|
);
|
|
4717
|
-
const handleResetToZero = (0,
|
|
4996
|
+
const handleResetToZero = (0, import_react22.useCallback)(() => {
|
|
4718
4997
|
if (disabled) return;
|
|
4719
4998
|
setDraftOffset(0);
|
|
4720
4999
|
onChange(0);
|
|
@@ -4722,7 +5001,7 @@ function OffsetScrubber({
|
|
|
4722
5001
|
const thumbFraction = sampleToFraction(draftOffset);
|
|
4723
5002
|
const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
|
|
4724
5003
|
const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
|
|
4725
|
-
const ticks = (0,
|
|
5004
|
+
const ticks = (0, import_react22.useMemo)(() => {
|
|
4726
5005
|
if (!cuePoints) return [];
|
|
4727
5006
|
const downbeat = cuePoints.beats[0] ?? 0;
|
|
4728
5007
|
return cuePoints.beats.map((b, i) => {
|
|
@@ -4733,9 +5012,9 @@ function OffsetScrubber({
|
|
|
4733
5012
|
});
|
|
4734
5013
|
}, [cuePoints, sampleToFraction]);
|
|
4735
5014
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
4736
|
-
return /* @__PURE__ */ (0,
|
|
4737
|
-
/* @__PURE__ */ (0,
|
|
4738
|
-
/* @__PURE__ */ (0,
|
|
5015
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
5016
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
5017
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
|
|
4739
5018
|
"div",
|
|
4740
5019
|
{
|
|
4741
5020
|
ref: trackRef,
|
|
@@ -4751,7 +5030,7 @@ function OffsetScrubber({
|
|
|
4751
5030
|
"aria-valuenow": draftOffset,
|
|
4752
5031
|
"aria-disabled": isDisabled,
|
|
4753
5032
|
children: [
|
|
4754
|
-
/* @__PURE__ */ (0,
|
|
5033
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
4755
5034
|
"div",
|
|
4756
5035
|
{
|
|
4757
5036
|
"aria-hidden": "true",
|
|
@@ -4759,7 +5038,7 @@ function OffsetScrubber({
|
|
|
4759
5038
|
style: { left: "50%" }
|
|
4760
5039
|
}
|
|
4761
5040
|
),
|
|
4762
|
-
ticks.map((t) => /* @__PURE__ */ (0,
|
|
5041
|
+
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
4763
5042
|
"div",
|
|
4764
5043
|
{
|
|
4765
5044
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -4774,7 +5053,7 @@ function OffsetScrubber({
|
|
|
4774
5053
|
},
|
|
4775
5054
|
t.i
|
|
4776
5055
|
)),
|
|
4777
|
-
/* @__PURE__ */ (0,
|
|
5056
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
4778
5057
|
"div",
|
|
4779
5058
|
{
|
|
4780
5059
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -4791,7 +5070,7 @@ function OffsetScrubber({
|
|
|
4791
5070
|
]
|
|
4792
5071
|
}
|
|
4793
5072
|
),
|
|
4794
|
-
/* @__PURE__ */ (0,
|
|
5073
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
4795
5074
|
"span",
|
|
4796
5075
|
{
|
|
4797
5076
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -4799,7 +5078,7 @@ function OffsetScrubber({
|
|
|
4799
5078
|
children: formatOffset(draftOffset, sampleRate)
|
|
4800
5079
|
}
|
|
4801
5080
|
),
|
|
4802
|
-
/* @__PURE__ */ (0,
|
|
5081
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
4803
5082
|
"button",
|
|
4804
5083
|
{
|
|
4805
5084
|
type: "button",
|
|
@@ -4811,7 +5090,7 @@ function OffsetScrubber({
|
|
|
4811
5090
|
children: "\u2316"
|
|
4812
5091
|
}
|
|
4813
5092
|
),
|
|
4814
|
-
bpmMismatch && /* @__PURE__ */ (0,
|
|
5093
|
+
bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
4815
5094
|
"span",
|
|
4816
5095
|
{
|
|
4817
5096
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -4882,14 +5161,17 @@ function synthesizeCuePoints({
|
|
|
4882
5161
|
};
|
|
4883
5162
|
}
|
|
4884
5163
|
|
|
5164
|
+
// src/panel-core/useGeneratorPanelCore.tsx
|
|
5165
|
+
var import_react27 = require("react");
|
|
5166
|
+
|
|
4885
5167
|
// src/hooks/useSceneState.ts
|
|
4886
|
-
var
|
|
5168
|
+
var import_react23 = require("react");
|
|
4887
5169
|
function useSceneState(activeSceneId, initialValue) {
|
|
4888
|
-
const [stateMap, setStateMap] = (0,
|
|
4889
|
-
const activeSceneIdRef = (0,
|
|
5170
|
+
const [stateMap, setStateMap] = (0, import_react23.useState)(() => /* @__PURE__ */ new Map());
|
|
5171
|
+
const activeSceneIdRef = (0, import_react23.useRef)(activeSceneId);
|
|
4890
5172
|
activeSceneIdRef.current = activeSceneId;
|
|
4891
5173
|
const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
|
|
4892
|
-
const setForCurrentScene = (0,
|
|
5174
|
+
const setForCurrentScene = (0, import_react23.useCallback)((value) => {
|
|
4893
5175
|
const sid = activeSceneIdRef.current;
|
|
4894
5176
|
if (sid === null) return;
|
|
4895
5177
|
setStateMap((prev) => {
|
|
@@ -4900,7 +5182,7 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
4900
5182
|
return newMap;
|
|
4901
5183
|
});
|
|
4902
5184
|
}, [initialValue]);
|
|
4903
|
-
const setForScene = (0,
|
|
5185
|
+
const setForScene = (0, import_react23.useCallback)((sceneId, value) => {
|
|
4904
5186
|
setStateMap((prev) => {
|
|
4905
5187
|
const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
|
|
4906
5188
|
const next = typeof value === "function" ? value(current) : value;
|
|
@@ -4913,10 +5195,10 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
4913
5195
|
}
|
|
4914
5196
|
|
|
4915
5197
|
// src/hooks/useAnySolo.ts
|
|
4916
|
-
var
|
|
5198
|
+
var import_react24 = require("react");
|
|
4917
5199
|
function useAnySolo(host) {
|
|
4918
|
-
const [anySolo, setAnySolo] = (0,
|
|
4919
|
-
(0,
|
|
5200
|
+
const [anySolo, setAnySolo] = (0, import_react24.useState)(false);
|
|
5201
|
+
(0, import_react24.useEffect)(() => {
|
|
4920
5202
|
let active = true;
|
|
4921
5203
|
const refresh = () => {
|
|
4922
5204
|
host.isAnySoloActive().then((v) => {
|
|
@@ -4935,7 +5217,7 @@ function useAnySolo(host) {
|
|
|
4935
5217
|
}
|
|
4936
5218
|
|
|
4937
5219
|
// src/hooks/useSoundHistory.ts
|
|
4938
|
-
var
|
|
5220
|
+
var import_react25 = require("react");
|
|
4939
5221
|
var EMPTY = { entries: [], cursor: -1 };
|
|
4940
5222
|
function sameDescriptor(a, b) {
|
|
4941
5223
|
if (a === b) return true;
|
|
@@ -4947,14 +5229,14 @@ function sameDescriptor(a, b) {
|
|
|
4947
5229
|
}
|
|
4948
5230
|
function useSoundHistory(applySound, opts = {}) {
|
|
4949
5231
|
const max = Math.max(2, opts.max ?? 24);
|
|
4950
|
-
const applyRef = (0,
|
|
5232
|
+
const applyRef = (0, import_react25.useRef)(applySound);
|
|
4951
5233
|
applyRef.current = applySound;
|
|
4952
|
-
const onChangeRef = (0,
|
|
5234
|
+
const onChangeRef = (0, import_react25.useRef)(opts.onChange);
|
|
4953
5235
|
onChangeRef.current = opts.onChange;
|
|
4954
|
-
const dataRef = (0,
|
|
4955
|
-
const [, setVersion] = (0,
|
|
4956
|
-
const bump = (0,
|
|
4957
|
-
const commit = (0,
|
|
5236
|
+
const dataRef = (0, import_react25.useRef)({});
|
|
5237
|
+
const [, setVersion] = (0, import_react25.useState)(0);
|
|
5238
|
+
const bump = (0, import_react25.useCallback)(() => setVersion((v) => v + 1), []);
|
|
5239
|
+
const commit = (0, import_react25.useCallback)(
|
|
4958
5240
|
(trackId, next, notify) => {
|
|
4959
5241
|
dataRef.current = { ...dataRef.current, [trackId]: next };
|
|
4960
5242
|
bump();
|
|
@@ -4962,7 +5244,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4962
5244
|
},
|
|
4963
5245
|
[bump]
|
|
4964
5246
|
);
|
|
4965
|
-
const record = (0,
|
|
5247
|
+
const record = (0, import_react25.useCallback)(
|
|
4966
5248
|
(trackId, descriptor, label) => {
|
|
4967
5249
|
const h = dataRef.current[trackId];
|
|
4968
5250
|
const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
|
|
@@ -4977,7 +5259,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4977
5259
|
},
|
|
4978
5260
|
[max, commit]
|
|
4979
5261
|
);
|
|
4980
|
-
const restoreTo = (0,
|
|
5262
|
+
const restoreTo = (0, import_react25.useCallback)(
|
|
4981
5263
|
async (trackId, index) => {
|
|
4982
5264
|
const h = dataRef.current[trackId];
|
|
4983
5265
|
if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
|
|
@@ -4987,7 +5269,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4987
5269
|
},
|
|
4988
5270
|
[commit]
|
|
4989
5271
|
);
|
|
4990
|
-
const undo = (0,
|
|
5272
|
+
const undo = (0, import_react25.useCallback)(
|
|
4991
5273
|
(trackId) => {
|
|
4992
5274
|
const h = dataRef.current[trackId];
|
|
4993
5275
|
if (!h || h.cursor <= 0) return Promise.resolve(false);
|
|
@@ -4995,7 +5277,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
4995
5277
|
},
|
|
4996
5278
|
[restoreTo]
|
|
4997
5279
|
);
|
|
4998
|
-
const toggleFavorite = (0,
|
|
5280
|
+
const toggleFavorite = (0, import_react25.useCallback)(
|
|
4999
5281
|
(trackId, index) => {
|
|
5000
5282
|
const h = dataRef.current[trackId];
|
|
5001
5283
|
if (!h || index < 0 || index >= h.entries.length) return;
|
|
@@ -5004,7 +5286,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5004
5286
|
},
|
|
5005
5287
|
[commit]
|
|
5006
5288
|
);
|
|
5007
|
-
const restore = (0,
|
|
5289
|
+
const restore = (0, import_react25.useCallback)(
|
|
5008
5290
|
(trackId, state) => {
|
|
5009
5291
|
const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
|
|
5010
5292
|
const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
|
|
@@ -5013,15 +5295,15 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5013
5295
|
},
|
|
5014
5296
|
[commit]
|
|
5015
5297
|
);
|
|
5016
|
-
const list = (0,
|
|
5298
|
+
const list = (0, import_react25.useCallback)(
|
|
5017
5299
|
(trackId) => dataRef.current[trackId] ?? EMPTY,
|
|
5018
5300
|
[]
|
|
5019
5301
|
);
|
|
5020
|
-
const canUndo = (0,
|
|
5302
|
+
const canUndo = (0, import_react25.useCallback)((trackId) => {
|
|
5021
5303
|
const h = dataRef.current[trackId];
|
|
5022
5304
|
return !!h && h.cursor > 0;
|
|
5023
5305
|
}, []);
|
|
5024
|
-
const clear = (0,
|
|
5306
|
+
const clear = (0, import_react25.useCallback)(
|
|
5025
5307
|
(trackId) => {
|
|
5026
5308
|
if (dataRef.current[trackId]) {
|
|
5027
5309
|
const next = { ...dataRef.current };
|
|
@@ -5033,18 +5315,2356 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5033
5315
|
},
|
|
5034
5316
|
[bump]
|
|
5035
5317
|
);
|
|
5036
|
-
const reset = (0,
|
|
5318
|
+
const reset = (0, import_react25.useCallback)(() => {
|
|
5037
5319
|
dataRef.current = {};
|
|
5038
5320
|
bump();
|
|
5039
5321
|
}, [bump]);
|
|
5040
|
-
return (0,
|
|
5322
|
+
return (0, import_react25.useMemo)(
|
|
5041
5323
|
() => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
|
|
5042
5324
|
[record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
|
|
5043
5325
|
);
|
|
5044
5326
|
}
|
|
5045
5327
|
|
|
5328
|
+
// src/panel-core/track-state.ts
|
|
5329
|
+
function newTrackState(handle, overrides = {}) {
|
|
5330
|
+
return {
|
|
5331
|
+
handle,
|
|
5332
|
+
prompt: "",
|
|
5333
|
+
role: "",
|
|
5334
|
+
runtimeState: { id: handle.id, muted: false, solo: false, volume: 0.75, pan: 0 },
|
|
5335
|
+
fxDetailState: { ...EMPTY_FX_DETAIL_STATE },
|
|
5336
|
+
drawerOpen: false,
|
|
5337
|
+
drawerTab: "fx",
|
|
5338
|
+
editorStage: false,
|
|
5339
|
+
isGenerating: false,
|
|
5340
|
+
error: null,
|
|
5341
|
+
hasMidi: false,
|
|
5342
|
+
generationProgress: 0,
|
|
5343
|
+
editNotes: [],
|
|
5344
|
+
editBars: 4,
|
|
5345
|
+
editBpm: 120,
|
|
5346
|
+
instrumentPluginId: handle.instrumentPluginId ?? null,
|
|
5347
|
+
instrumentName: handle.instrumentName ?? null,
|
|
5348
|
+
instrumentMissing: false,
|
|
5349
|
+
shuffleHistory: /* @__PURE__ */ new Set(),
|
|
5350
|
+
...overrides
|
|
5351
|
+
};
|
|
5352
|
+
}
|
|
5353
|
+
|
|
5354
|
+
// src/panel-core/panel-helpers.ts
|
|
5355
|
+
function trackDataKey(dbId, suffix) {
|
|
5356
|
+
return `track:${dbId}:${suffix}`;
|
|
5357
|
+
}
|
|
5358
|
+
function pluginFxToToggleFx(sdkState) {
|
|
5359
|
+
const result = { ...EMPTY_FX_DETAIL_STATE };
|
|
5360
|
+
for (const category of ["eq", "compressor", "chorus", "phaser", "delay", "reverb"]) {
|
|
5361
|
+
const sdkCat = sdkState[category];
|
|
5362
|
+
if (sdkCat) {
|
|
5363
|
+
result[category] = {
|
|
5364
|
+
enabled: sdkCat.enabled,
|
|
5365
|
+
presetIndex: sdkCat.presetIndex,
|
|
5366
|
+
dryWet: sdkCat.dryWet
|
|
5367
|
+
};
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
return result;
|
|
5371
|
+
}
|
|
5372
|
+
function parseLLMNoteResponse(content) {
|
|
5373
|
+
try {
|
|
5374
|
+
let jsonStr = content.trim();
|
|
5375
|
+
const fenceMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
5376
|
+
if (fenceMatch) {
|
|
5377
|
+
jsonStr = fenceMatch[1].trim();
|
|
5378
|
+
}
|
|
5379
|
+
const parsed = JSON.parse(jsonStr);
|
|
5380
|
+
if (typeof parsed !== "object" || parsed === null || !("notes" in parsed)) {
|
|
5381
|
+
return null;
|
|
5382
|
+
}
|
|
5383
|
+
const obj = parsed;
|
|
5384
|
+
if (!Array.isArray(obj.notes)) {
|
|
5385
|
+
return null;
|
|
5386
|
+
}
|
|
5387
|
+
const validNotes = [];
|
|
5388
|
+
for (const raw of obj.notes) {
|
|
5389
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
5390
|
+
const note = raw;
|
|
5391
|
+
const pitch = typeof note.pitch === "number" ? note.pitch : NaN;
|
|
5392
|
+
const startBeat = typeof note.startBeat === "number" ? note.startBeat : NaN;
|
|
5393
|
+
const durationBeats = typeof note.durationBeats === "number" ? note.durationBeats : NaN;
|
|
5394
|
+
const velocity = typeof note.velocity === "number" ? note.velocity : NaN;
|
|
5395
|
+
if (!isNaN(pitch) && pitch >= 0 && pitch <= 127 && !isNaN(startBeat) && startBeat >= 0 && !isNaN(durationBeats) && durationBeats > 0 && !isNaN(velocity) && velocity >= 1 && velocity <= 127) {
|
|
5396
|
+
validNotes.push({
|
|
5397
|
+
pitch: Math.round(pitch),
|
|
5398
|
+
startBeat,
|
|
5399
|
+
durationBeats,
|
|
5400
|
+
velocity: Math.round(velocity)
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
const role = typeof obj.role === "string" ? obj.role : void 0;
|
|
5405
|
+
return { notes: validNotes, role };
|
|
5406
|
+
} catch {
|
|
5407
|
+
return null;
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5411
|
+
// src/panel-core/group-meta.ts
|
|
5412
|
+
function parseTrackGroups(sceneData, spec) {
|
|
5413
|
+
const pattern = new RegExp(`^track:(.+):${spec.metaKey}$`);
|
|
5414
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5415
|
+
for (const [key, val] of Object.entries(sceneData)) {
|
|
5416
|
+
const match = pattern.exec(key);
|
|
5417
|
+
if (!match) continue;
|
|
5418
|
+
const meta = spec.asMeta(val);
|
|
5419
|
+
if (!meta) continue;
|
|
5420
|
+
const groupId = spec.groupIdOf(meta);
|
|
5421
|
+
const list = groups.get(groupId) ?? [];
|
|
5422
|
+
list.push({ dbId: match[1], meta });
|
|
5423
|
+
groups.set(groupId, list);
|
|
5424
|
+
}
|
|
5425
|
+
const out = [];
|
|
5426
|
+
for (const [groupId, members] of groups) {
|
|
5427
|
+
if (spec.sortMembers) members.sort(spec.sortMembers);
|
|
5428
|
+
out.push({ groupId, members });
|
|
5429
|
+
}
|
|
5430
|
+
return out;
|
|
5431
|
+
}
|
|
5432
|
+
function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
|
|
5433
|
+
const byDbId = /* @__PURE__ */ new Map();
|
|
5434
|
+
for (const t of tracks) byDbId.set(getDbId(t), t);
|
|
5435
|
+
const resolved = [];
|
|
5436
|
+
const memberDbIds = /* @__PURE__ */ new Set();
|
|
5437
|
+
const staleMemberDbIds = [];
|
|
5438
|
+
for (const parsed of parsedGroups) {
|
|
5439
|
+
const live = { groupId: parsed.groupId, members: [] };
|
|
5440
|
+
for (const member of parsed.members) {
|
|
5441
|
+
const track = byDbId.get(member.dbId);
|
|
5442
|
+
if (track) live.members.push({ dbId: member.dbId, meta: member.meta, track });
|
|
5443
|
+
else staleMemberDbIds.push(member.dbId);
|
|
5444
|
+
}
|
|
5445
|
+
if (live.members.length === 0) continue;
|
|
5446
|
+
const complete = opts.isComplete ? opts.isComplete(live, parsed) : live.members.length === parsed.members.length;
|
|
5447
|
+
if (!complete) continue;
|
|
5448
|
+
resolved.push(live);
|
|
5449
|
+
for (const m of live.members) memberDbIds.add(m.dbId);
|
|
5450
|
+
}
|
|
5451
|
+
return { resolved, memberDbIds, staleMemberDbIds };
|
|
5452
|
+
}
|
|
5453
|
+
|
|
5454
|
+
// src/panel-core/useTransitionOps.ts
|
|
5455
|
+
var import_react26 = require("react");
|
|
5456
|
+
function useTransitionOps({
|
|
5457
|
+
host,
|
|
5458
|
+
adapter,
|
|
5459
|
+
activeSceneId,
|
|
5460
|
+
isConnected,
|
|
5461
|
+
isAuthenticated,
|
|
5462
|
+
sceneContext,
|
|
5463
|
+
tracks,
|
|
5464
|
+
setTracks,
|
|
5465
|
+
loadTracks,
|
|
5466
|
+
setCrossfadePairsMeta,
|
|
5467
|
+
setFadesMeta,
|
|
5468
|
+
resolvedCrossfadePairs,
|
|
5469
|
+
resolvedFades
|
|
5470
|
+
}) {
|
|
5471
|
+
const { identity } = adapter;
|
|
5472
|
+
const appliedFadeAutomationRef = (0, import_react26.useRef)(/* @__PURE__ */ new Set());
|
|
5473
|
+
const applyCrossfadeAutomation = (0, import_react26.useCallback)(
|
|
5474
|
+
async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
|
|
5475
|
+
if (host.setTrackVolumeAutomation) {
|
|
5476
|
+
const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
|
|
5477
|
+
await host.setTrackVolumeAutomation(originTrackId, curves.origin).catch(() => {
|
|
5478
|
+
});
|
|
5479
|
+
await host.setTrackVolumeAutomation(targetTrackId, curves.target).catch(() => {
|
|
5480
|
+
});
|
|
5481
|
+
} else {
|
|
5482
|
+
await host.setTrackVolume(originTrackId, EQUAL_POWER_GAIN).catch(() => {
|
|
5483
|
+
});
|
|
5484
|
+
await host.setTrackVolume(targetTrackId, EQUAL_POWER_GAIN).catch(() => {
|
|
5485
|
+
});
|
|
5486
|
+
}
|
|
5487
|
+
},
|
|
5488
|
+
[host]
|
|
5489
|
+
);
|
|
5490
|
+
const applyFadeAutomation = (0, import_react26.useCallback)(
|
|
5491
|
+
async (trackId, direction, bars, bpm, sliderPos, gesture) => {
|
|
5492
|
+
if (!host.setTrackVolumeAutomation) return;
|
|
5493
|
+
const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
|
|
5494
|
+
await host.setTrackVolumeAutomation(trackId, points).catch(() => {
|
|
5495
|
+
});
|
|
5496
|
+
},
|
|
5497
|
+
[host]
|
|
5498
|
+
);
|
|
5499
|
+
const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react26.useState)(false);
|
|
5500
|
+
const handleCreateCrossfade = (0, import_react26.useCallback)(
|
|
5501
|
+
async (origin, target) => {
|
|
5502
|
+
const scene = activeSceneId;
|
|
5503
|
+
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
5504
|
+
const toSceneId = sceneContext?.transitionToSceneId ?? "";
|
|
5505
|
+
if (!scene) throw new Error("No active scene.");
|
|
5506
|
+
if (!isConnected) throw new Error("Systems not connected.");
|
|
5507
|
+
if (!isAuthenticated) throw new Error("Please sign in to generate the bridge.");
|
|
5508
|
+
if (tracks.length + 2 > identity.maxTracks) {
|
|
5509
|
+
throw new Error("Not enough track slots for a crossfade.");
|
|
5510
|
+
}
|
|
5511
|
+
setIsCreatingCrossfade(true);
|
|
5512
|
+
const created = [];
|
|
5513
|
+
try {
|
|
5514
|
+
const role = target.role ?? origin.role ?? "";
|
|
5515
|
+
const mc = await host.getMusicalContext();
|
|
5516
|
+
const [originMidi, targetMidi, originKey, targetKey] = await Promise.all([
|
|
5517
|
+
host.readImportableTrackMidi ? host.readImportableTrackMidi(origin.dbId) : Promise.resolve({ clips: [] }),
|
|
5518
|
+
host.readImportableTrackMidi ? host.readImportableTrackMidi(target.dbId) : Promise.resolve({ clips: [] }),
|
|
5519
|
+
host.getSceneKey ? host.getSceneKey(fromSceneId) : Promise.resolve(null),
|
|
5520
|
+
host.getSceneKey ? host.getSceneKey(toSceneId) : Promise.resolve(null)
|
|
5521
|
+
]);
|
|
5522
|
+
const userPrompt = buildCrossfadeInpaintPrompt({
|
|
5523
|
+
role,
|
|
5524
|
+
bars: mc.bars,
|
|
5525
|
+
originName: origin.name,
|
|
5526
|
+
targetName: target.name,
|
|
5527
|
+
originKey: originKey ? `${originKey.key} ${originKey.mode}` : null,
|
|
5528
|
+
targetKey: targetKey ? `${targetKey.key} ${targetKey.mode}` : null,
|
|
5529
|
+
originNotes: originMidi.clips[0]?.notes ?? [],
|
|
5530
|
+
targetNotes: targetMidi.clips[0]?.notes ?? []
|
|
5531
|
+
});
|
|
5532
|
+
const llm = await host.generateWithLLM({
|
|
5533
|
+
system: adapter.buildSystemPrompt(host.getValidRoles()),
|
|
5534
|
+
user: userPrompt,
|
|
5535
|
+
responseFormat: "json"
|
|
5536
|
+
});
|
|
5537
|
+
const parsed = adapter.parseNotesResponse(llm.content);
|
|
5538
|
+
if (!parsed || parsed.notes.length === 0) {
|
|
5539
|
+
throw new Error("The bridge generator returned no notes.");
|
|
5540
|
+
}
|
|
5541
|
+
const notes = await host.postProcessMidi(parsed.notes, {
|
|
5542
|
+
quantize: true,
|
|
5543
|
+
removeOverlaps: true
|
|
5544
|
+
});
|
|
5545
|
+
const clip = {
|
|
5546
|
+
startTime: 0,
|
|
5547
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
5548
|
+
tempo: mc.bpm,
|
|
5549
|
+
notes
|
|
5550
|
+
};
|
|
5551
|
+
const top = await host.createTrack({
|
|
5552
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-xf-o`,
|
|
5553
|
+
...adapter.createTrackOptions()
|
|
5554
|
+
});
|
|
5555
|
+
created.push(top);
|
|
5556
|
+
const bottom = await host.createTrack({
|
|
5557
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-xf-t`,
|
|
5558
|
+
...adapter.createTrackOptions()
|
|
5559
|
+
});
|
|
5560
|
+
created.push(bottom);
|
|
5561
|
+
if (role) {
|
|
5562
|
+
await host.setTrackRole(top.id, role).catch(() => {
|
|
5563
|
+
});
|
|
5564
|
+
await host.setTrackRole(bottom.id, role).catch(() => {
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
await host.writeMidiClip(top.id, clip);
|
|
5568
|
+
await host.writeMidiClip(bottom.id, clip);
|
|
5569
|
+
const copySound = async (newTrackId, sourceDbId) => {
|
|
5570
|
+
if (!host.getTrackSound) return "default";
|
|
5571
|
+
const snap = await host.getTrackSound(sourceDbId);
|
|
5572
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) return "default";
|
|
5573
|
+
return adapter.sound.copySnapshot(newTrackId, snap);
|
|
5574
|
+
};
|
|
5575
|
+
const originLabel = await copySound(top.id, origin.dbId);
|
|
5576
|
+
const targetLabel = await copySound(bottom.id, target.dbId);
|
|
5577
|
+
await applyCrossfadeAutomation(top.id, bottom.id, mc.bars, mc.bpm, 0.5);
|
|
5578
|
+
const groupId = top.dbId;
|
|
5579
|
+
const originMeta = {
|
|
5580
|
+
groupId,
|
|
5581
|
+
slot: "origin",
|
|
5582
|
+
partnerDbId: bottom.dbId,
|
|
5583
|
+
sourceTrackDbId: origin.dbId,
|
|
5584
|
+
sourceSceneId: fromSceneId,
|
|
5585
|
+
sourceName: origin.name,
|
|
5586
|
+
soundLabel: originLabel,
|
|
5587
|
+
sliderPos: 0.5
|
|
5588
|
+
};
|
|
5589
|
+
const targetMeta = {
|
|
5590
|
+
groupId,
|
|
5591
|
+
slot: "target",
|
|
5592
|
+
partnerDbId: top.dbId,
|
|
5593
|
+
sourceTrackDbId: target.dbId,
|
|
5594
|
+
sourceSceneId: toSceneId,
|
|
5595
|
+
sourceName: target.name,
|
|
5596
|
+
soundLabel: targetLabel,
|
|
5597
|
+
sliderPos: 0.5
|
|
5598
|
+
};
|
|
5599
|
+
await host.setSceneData(scene, `track:${top.dbId}:crossfade`, originMeta);
|
|
5600
|
+
await host.setSceneData(scene, `track:${bottom.dbId}:crossfade`, targetMeta);
|
|
5601
|
+
await loadTracks(true);
|
|
5602
|
+
host.showToast("success", "Crossfade created", `${origin.name} \u2192 ${target.name}`);
|
|
5603
|
+
} catch (err) {
|
|
5604
|
+
for (const h of [...created].reverse()) {
|
|
5605
|
+
try {
|
|
5606
|
+
await host.deleteTrack(h.id);
|
|
5607
|
+
} catch {
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
5611
|
+
} finally {
|
|
5612
|
+
setIsCreatingCrossfade(false);
|
|
5613
|
+
}
|
|
5614
|
+
},
|
|
5615
|
+
[
|
|
5616
|
+
host,
|
|
5617
|
+
adapter,
|
|
5618
|
+
identity,
|
|
5619
|
+
activeSceneId,
|
|
5620
|
+
isConnected,
|
|
5621
|
+
isAuthenticated,
|
|
5622
|
+
tracks.length,
|
|
5623
|
+
sceneContext,
|
|
5624
|
+
applyCrossfadeAutomation,
|
|
5625
|
+
loadTracks
|
|
5626
|
+
]
|
|
5627
|
+
);
|
|
5628
|
+
const [isCreatingFade, setIsCreatingFade] = (0, import_react26.useState)(false);
|
|
5629
|
+
const handleCreateFade = (0, import_react26.useCallback)(
|
|
5630
|
+
async (selection, direction, gesture) => {
|
|
5631
|
+
const scene = activeSceneId;
|
|
5632
|
+
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
5633
|
+
const toSceneId = sceneContext?.transitionToSceneId ?? "";
|
|
5634
|
+
if (!scene) throw new Error("No active scene.");
|
|
5635
|
+
if (!isConnected) throw new Error("Systems not connected.");
|
|
5636
|
+
if (!isAuthenticated) throw new Error("Please sign in to generate the fade.");
|
|
5637
|
+
if (tracks.length + 1 > identity.maxTracks) {
|
|
5638
|
+
throw new Error("Not enough track slots for a fade.");
|
|
5639
|
+
}
|
|
5640
|
+
setIsCreatingFade(true);
|
|
5641
|
+
const created = [];
|
|
5642
|
+
try {
|
|
5643
|
+
const role = selection.role ?? "";
|
|
5644
|
+
const sourceSceneId = direction === "out" ? fromSceneId : toSceneId;
|
|
5645
|
+
const mc = await host.getMusicalContext();
|
|
5646
|
+
const [srcMidi, srcKey] = await Promise.all([
|
|
5647
|
+
host.readImportableTrackMidi ? host.readImportableTrackMidi(selection.dbId) : Promise.resolve({ clips: [] }),
|
|
5648
|
+
host.getSceneKey ? host.getSceneKey(sourceSceneId) : Promise.resolve(null)
|
|
5649
|
+
]);
|
|
5650
|
+
const srcNotes = srcMidi.clips[0]?.notes ?? [];
|
|
5651
|
+
const keyStr = srcKey ? `${srcKey.key} ${srcKey.mode}` : null;
|
|
5652
|
+
const userPrompt = buildCrossfadeInpaintPrompt({
|
|
5653
|
+
role,
|
|
5654
|
+
bars: mc.bars,
|
|
5655
|
+
originName: direction === "out" ? selection.name : "silence",
|
|
5656
|
+
targetName: direction === "in" ? selection.name : "silence",
|
|
5657
|
+
originKey: direction === "out" ? keyStr : null,
|
|
5658
|
+
targetKey: direction === "in" ? keyStr : null,
|
|
5659
|
+
originNotes: direction === "out" ? srcNotes : [],
|
|
5660
|
+
targetNotes: direction === "in" ? srcNotes : []
|
|
5661
|
+
});
|
|
5662
|
+
const llm = await host.generateWithLLM({
|
|
5663
|
+
system: adapter.buildSystemPrompt(host.getValidRoles()),
|
|
5664
|
+
user: userPrompt,
|
|
5665
|
+
responseFormat: "json"
|
|
5666
|
+
});
|
|
5667
|
+
const parsed = adapter.parseNotesResponse(llm.content);
|
|
5668
|
+
if (!parsed || parsed.notes.length === 0) {
|
|
5669
|
+
throw new Error("The fade generator returned no notes.");
|
|
5670
|
+
}
|
|
5671
|
+
const notes = await host.postProcessMidi(parsed.notes, {
|
|
5672
|
+
quantize: true,
|
|
5673
|
+
removeOverlaps: true
|
|
5674
|
+
});
|
|
5675
|
+
const clip = {
|
|
5676
|
+
startTime: 0,
|
|
5677
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
5678
|
+
tempo: mc.bpm,
|
|
5679
|
+
notes
|
|
5680
|
+
};
|
|
5681
|
+
const track = await host.createTrack({
|
|
5682
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-fade-${direction}`,
|
|
5683
|
+
...adapter.createTrackOptions()
|
|
5684
|
+
});
|
|
5685
|
+
created.push(track);
|
|
5686
|
+
if (role) await host.setTrackRole(track.id, role).catch(() => {
|
|
5687
|
+
});
|
|
5688
|
+
await host.writeMidiClip(track.id, clip);
|
|
5689
|
+
let soundLabel = "default";
|
|
5690
|
+
if (host.getTrackSound) {
|
|
5691
|
+
const snap = await host.getTrackSound(selection.dbId);
|
|
5692
|
+
if (snap && snap.kind === adapter.sound.acceptedSnapshotKind) {
|
|
5693
|
+
soundLabel = await adapter.sound.copySnapshot(track.id, snap);
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
await applyFadeAutomation(track.id, direction, mc.bars, mc.bpm, 0.5, gesture);
|
|
5697
|
+
appliedFadeAutomationRef.current.add(track.id);
|
|
5698
|
+
const meta = {
|
|
5699
|
+
direction,
|
|
5700
|
+
gesture,
|
|
5701
|
+
sourceTrackDbId: selection.dbId,
|
|
5702
|
+
sourceSceneId,
|
|
5703
|
+
sourceName: selection.name,
|
|
5704
|
+
soundLabel,
|
|
5705
|
+
sliderPos: 0.5
|
|
5706
|
+
};
|
|
5707
|
+
await host.setSceneData(scene, `track:${track.dbId}:fade`, meta);
|
|
5708
|
+
await loadTracks(true);
|
|
5709
|
+
host.showToast(
|
|
5710
|
+
"success",
|
|
5711
|
+
direction === "in" ? "Fade in created" : "Fade out created",
|
|
5712
|
+
selection.name
|
|
5713
|
+
);
|
|
5714
|
+
} catch (err) {
|
|
5715
|
+
for (const h of [...created].reverse()) {
|
|
5716
|
+
try {
|
|
5717
|
+
await host.deleteTrack(h.id);
|
|
5718
|
+
} catch {
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
5722
|
+
} finally {
|
|
5723
|
+
setIsCreatingFade(false);
|
|
5724
|
+
}
|
|
5725
|
+
},
|
|
5726
|
+
[
|
|
5727
|
+
host,
|
|
5728
|
+
adapter,
|
|
5729
|
+
identity,
|
|
5730
|
+
activeSceneId,
|
|
5731
|
+
isConnected,
|
|
5732
|
+
isAuthenticated,
|
|
5733
|
+
tracks.length,
|
|
5734
|
+
sceneContext,
|
|
5735
|
+
applyFadeAutomation,
|
|
5736
|
+
loadTracks
|
|
5737
|
+
]
|
|
5738
|
+
);
|
|
5739
|
+
const handleCrossfadeMute = (0, import_react26.useCallback)(
|
|
5740
|
+
(pair) => {
|
|
5741
|
+
const newMuted = !pair.origin.runtimeState.muted;
|
|
5742
|
+
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
5743
|
+
setTracks(
|
|
5744
|
+
(prev) => prev.map(
|
|
5745
|
+
(t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, muted: newMuted } } : t
|
|
5746
|
+
)
|
|
5747
|
+
);
|
|
5748
|
+
host.setTrackMute(id, newMuted).catch(() => {
|
|
5749
|
+
});
|
|
5750
|
+
}
|
|
5751
|
+
},
|
|
5752
|
+
[host, setTracks]
|
|
5753
|
+
);
|
|
5754
|
+
const handleCrossfadeSolo = (0, import_react26.useCallback)(
|
|
5755
|
+
(pair) => {
|
|
5756
|
+
const newSolo = !pair.origin.runtimeState.solo;
|
|
5757
|
+
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
5758
|
+
setTracks(
|
|
5759
|
+
(prev) => prev.map(
|
|
5760
|
+
(t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, solo: newSolo } } : t
|
|
5761
|
+
)
|
|
5762
|
+
);
|
|
5763
|
+
host.setTrackSolo(id, newSolo).catch(() => {
|
|
5764
|
+
});
|
|
5765
|
+
}
|
|
5766
|
+
},
|
|
5767
|
+
[host, setTracks]
|
|
5768
|
+
);
|
|
5769
|
+
const handleCrossfadeDelete = (0, import_react26.useCallback)(
|
|
5770
|
+
async (pair) => {
|
|
5771
|
+
try {
|
|
5772
|
+
for (const member of [pair.origin, pair.target]) {
|
|
5773
|
+
await host.deleteTrack(member.handle.id);
|
|
5774
|
+
if (activeSceneId) {
|
|
5775
|
+
await host.deleteSceneData(activeSceneId, `track:${member.handle.dbId}:crossfade`);
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
setCrossfadePairsMeta((prev) => prev.filter((p) => p.groupId !== pair.groupId));
|
|
5779
|
+
setTracks(
|
|
5780
|
+
(prev) => prev.filter(
|
|
5781
|
+
(t) => t.handle.id !== pair.origin.handle.id && t.handle.id !== pair.target.handle.id
|
|
5782
|
+
)
|
|
5783
|
+
);
|
|
5784
|
+
host.showToast("success", "Crossfade removed");
|
|
5785
|
+
} catch (err) {
|
|
5786
|
+
host.showToast(
|
|
5787
|
+
"error",
|
|
5788
|
+
"Failed to delete crossfade",
|
|
5789
|
+
err instanceof Error ? err.message : String(err)
|
|
5790
|
+
);
|
|
5791
|
+
}
|
|
5792
|
+
},
|
|
5793
|
+
[host, activeSceneId, setCrossfadePairsMeta, setTracks]
|
|
5794
|
+
);
|
|
5795
|
+
const crossfadeSliderTimers = (0, import_react26.useRef)({});
|
|
5796
|
+
const handleCrossfadeSlider = (0, import_react26.useCallback)(
|
|
5797
|
+
(pair, pos) => {
|
|
5798
|
+
setCrossfadePairsMeta(
|
|
5799
|
+
(prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
|
|
5800
|
+
);
|
|
5801
|
+
if (crossfadeSliderTimers.current[pair.groupId]) {
|
|
5802
|
+
clearTimeout(crossfadeSliderTimers.current[pair.groupId]);
|
|
5803
|
+
}
|
|
5804
|
+
crossfadeSliderTimers.current[pair.groupId] = setTimeout(() => {
|
|
5805
|
+
void (async () => {
|
|
5806
|
+
const mc = await host.getMusicalContext();
|
|
5807
|
+
await applyCrossfadeAutomation(
|
|
5808
|
+
pair.origin.handle.id,
|
|
5809
|
+
pair.target.handle.id,
|
|
5810
|
+
mc.bars,
|
|
5811
|
+
mc.bpm,
|
|
5812
|
+
pos
|
|
5813
|
+
);
|
|
5814
|
+
if (activeSceneId) {
|
|
5815
|
+
const sceneData = await host.getAllSceneData(activeSceneId);
|
|
5816
|
+
for (const dbId of [pair.originDbId, pair.targetDbId]) {
|
|
5817
|
+
const meta = asCrossfadeMeta(sceneData[`track:${dbId}:crossfade`]);
|
|
5818
|
+
if (meta) {
|
|
5819
|
+
host.setSceneData(activeSceneId, `track:${dbId}:crossfade`, { ...meta, sliderPos: pos }).catch(() => {
|
|
5820
|
+
});
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
})();
|
|
5825
|
+
}, 200);
|
|
5826
|
+
},
|
|
5827
|
+
[host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
|
|
5828
|
+
);
|
|
5829
|
+
const handleFadeDelete = (0, import_react26.useCallback)(
|
|
5830
|
+
async (fade) => {
|
|
5831
|
+
try {
|
|
5832
|
+
await host.deleteTrack(fade.track.handle.id);
|
|
5833
|
+
if (activeSceneId) {
|
|
5834
|
+
await host.deleteSceneData(activeSceneId, `track:${fade.dbId}:fade`);
|
|
5835
|
+
}
|
|
5836
|
+
setFadesMeta((prev) => prev.filter((f) => f.dbId !== fade.dbId));
|
|
5837
|
+
setTracks((prev) => prev.filter((t) => t.handle.id !== fade.track.handle.id));
|
|
5838
|
+
host.showToast("success", "Fade removed");
|
|
5839
|
+
} catch (err) {
|
|
5840
|
+
host.showToast(
|
|
5841
|
+
"error",
|
|
5842
|
+
"Failed to delete fade",
|
|
5843
|
+
err instanceof Error ? err.message : String(err)
|
|
5844
|
+
);
|
|
5845
|
+
}
|
|
5846
|
+
},
|
|
5847
|
+
[host, activeSceneId, setFadesMeta, setTracks]
|
|
5848
|
+
);
|
|
5849
|
+
const fadeSliderTimers = (0, import_react26.useRef)({});
|
|
5850
|
+
const handleFadeSlider = (0, import_react26.useCallback)(
|
|
5851
|
+
(fade, pos) => {
|
|
5852
|
+
setFadesMeta(
|
|
5853
|
+
(prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
|
|
5854
|
+
);
|
|
5855
|
+
if (fadeSliderTimers.current[fade.dbId]) clearTimeout(fadeSliderTimers.current[fade.dbId]);
|
|
5856
|
+
fadeSliderTimers.current[fade.dbId] = setTimeout(() => {
|
|
5857
|
+
void (async () => {
|
|
5858
|
+
const mc = await host.getMusicalContext();
|
|
5859
|
+
await applyFadeAutomation(
|
|
5860
|
+
fade.track.handle.id,
|
|
5861
|
+
fade.meta.direction,
|
|
5862
|
+
mc.bars,
|
|
5863
|
+
mc.bpm,
|
|
5864
|
+
pos,
|
|
5865
|
+
fade.meta.gesture
|
|
5866
|
+
);
|
|
5867
|
+
if (activeSceneId) {
|
|
5868
|
+
const sceneData = await host.getAllSceneData(activeSceneId);
|
|
5869
|
+
const meta = asFadeMeta(sceneData[`track:${fade.dbId}:fade`]);
|
|
5870
|
+
if (meta) {
|
|
5871
|
+
host.setSceneData(activeSceneId, `track:${fade.dbId}:fade`, { ...meta, sliderPos: pos }).catch(() => {
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
})();
|
|
5876
|
+
}, 200);
|
|
5877
|
+
},
|
|
5878
|
+
[host, activeSceneId, applyFadeAutomation, setFadesMeta]
|
|
5879
|
+
);
|
|
5880
|
+
const lastResyncKeyRef = (0, import_react26.useRef)("");
|
|
5881
|
+
(0, import_react26.useEffect)(() => {
|
|
5882
|
+
if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
|
|
5883
|
+
return;
|
|
5884
|
+
}
|
|
5885
|
+
const resyncKey = [
|
|
5886
|
+
...resolvedCrossfadePairs.map(
|
|
5887
|
+
(p) => `${p.origin.handle.dbId}<${p.originSourceDbId}|${p.target.handle.dbId}<${p.targetSourceDbId}`
|
|
5888
|
+
),
|
|
5889
|
+
...resolvedFades.map((f) => `${f.track.handle.dbId}<${f.meta.sourceTrackDbId}`)
|
|
5890
|
+
].join(",");
|
|
5891
|
+
if (resyncKey === lastResyncKeyRef.current) return;
|
|
5892
|
+
lastResyncKeyRef.current = resyncKey;
|
|
5893
|
+
let cancelled = false;
|
|
5894
|
+
const reapplyIfDrifted = async (layerTrackId, layerDbId, sourceDbId) => {
|
|
5895
|
+
if (!host.getTrackSound || cancelled) return;
|
|
5896
|
+
const [sourceSnap, layerSnap] = await Promise.all([
|
|
5897
|
+
host.getTrackSound(sourceDbId),
|
|
5898
|
+
host.getTrackSound(layerDbId)
|
|
5899
|
+
]);
|
|
5900
|
+
if (cancelled || !sourceSnap || sourceSnap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
5901
|
+
return;
|
|
5902
|
+
}
|
|
5903
|
+
if (soundIdentity(sourceSnap) === soundIdentity(layerSnap)) return;
|
|
5904
|
+
try {
|
|
5905
|
+
await adapter.sound.copySnapshot(layerTrackId, sourceSnap);
|
|
5906
|
+
} catch {
|
|
5907
|
+
}
|
|
5908
|
+
};
|
|
5909
|
+
void (async () => {
|
|
5910
|
+
for (const pair of resolvedCrossfadePairs) {
|
|
5911
|
+
await reapplyIfDrifted(pair.origin.handle.id, pair.origin.handle.dbId, pair.originSourceDbId);
|
|
5912
|
+
await reapplyIfDrifted(pair.target.handle.id, pair.target.handle.dbId, pair.targetSourceDbId);
|
|
5913
|
+
}
|
|
5914
|
+
for (const fade of resolvedFades) {
|
|
5915
|
+
await reapplyIfDrifted(fade.track.handle.id, fade.track.handle.dbId, fade.meta.sourceTrackDbId);
|
|
5916
|
+
}
|
|
5917
|
+
})();
|
|
5918
|
+
return () => {
|
|
5919
|
+
cancelled = true;
|
|
5920
|
+
};
|
|
5921
|
+
}, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
|
|
5922
|
+
(0, import_react26.useEffect)(() => {
|
|
5923
|
+
if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
|
|
5924
|
+
void (async () => {
|
|
5925
|
+
const mc = await host.getMusicalContext();
|
|
5926
|
+
for (const fade of resolvedFades) {
|
|
5927
|
+
const id = fade.track.handle.id;
|
|
5928
|
+
if (appliedFadeAutomationRef.current.has(id)) continue;
|
|
5929
|
+
appliedFadeAutomationRef.current.add(id);
|
|
5930
|
+
await applyFadeAutomation(
|
|
5931
|
+
id,
|
|
5932
|
+
fade.meta.direction,
|
|
5933
|
+
mc.bars,
|
|
5934
|
+
mc.bpm,
|
|
5935
|
+
fade.meta.sliderPos,
|
|
5936
|
+
fade.meta.gesture
|
|
5937
|
+
);
|
|
5938
|
+
}
|
|
5939
|
+
})();
|
|
5940
|
+
}, [resolvedFades, host, applyFadeAutomation]);
|
|
5941
|
+
return {
|
|
5942
|
+
isCreatingCrossfade,
|
|
5943
|
+
isCreatingFade,
|
|
5944
|
+
handleCreateCrossfade,
|
|
5945
|
+
handleCreateFade,
|
|
5946
|
+
handleCrossfadeMute,
|
|
5947
|
+
handleCrossfadeSolo,
|
|
5948
|
+
handleCrossfadeDelete,
|
|
5949
|
+
handleCrossfadeSlider,
|
|
5950
|
+
handleFadeDelete,
|
|
5951
|
+
handleFadeSlider
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5954
|
+
|
|
5955
|
+
// src/panel-core/useGeneratorPanelCore.tsx
|
|
5956
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
5957
|
+
var EMPTY_PLACEHOLDERS = [];
|
|
5958
|
+
function useGeneratorPanelCore({
|
|
5959
|
+
ui,
|
|
5960
|
+
adapter
|
|
5961
|
+
}) {
|
|
5962
|
+
const {
|
|
5963
|
+
host,
|
|
5964
|
+
activeSceneId,
|
|
5965
|
+
isAuthenticated,
|
|
5966
|
+
isConnected,
|
|
5967
|
+
onHeaderContent,
|
|
5968
|
+
onLoading,
|
|
5969
|
+
sceneContext,
|
|
5970
|
+
onOpenContract,
|
|
5971
|
+
onExpandSelf,
|
|
5972
|
+
isExpanded
|
|
5973
|
+
} = ui;
|
|
5974
|
+
const { identity, features } = adapter;
|
|
5975
|
+
const logTag = identity.logTag;
|
|
5976
|
+
const adapterRef = (0, import_react27.useRef)(adapter);
|
|
5977
|
+
(0, import_react27.useEffect)(() => {
|
|
5978
|
+
if (adapterRef.current !== adapter) {
|
|
5979
|
+
adapterRef.current = adapter;
|
|
5980
|
+
console.warn(
|
|
5981
|
+
`[${logTag}] GeneratorPanelAdapter identity changed between renders \u2014 wrap it in useMemo(() => createAdapter(host), [host]) to avoid load loops.`
|
|
5982
|
+
);
|
|
5983
|
+
}
|
|
5984
|
+
}, [adapter, logTag]);
|
|
5985
|
+
const supportsMeters = typeof host.getTrackLevels === "function";
|
|
5986
|
+
const trackLevels = useTrackLevels(host, isExpanded);
|
|
5987
|
+
const [tracks, setTracks] = (0, import_react27.useState)([]);
|
|
5988
|
+
const [isLoadingTracks, setIsLoadingTracks] = (0, import_react27.useState)(false);
|
|
5989
|
+
const [importOpen, setImportOpen] = (0, import_react27.useState)(false);
|
|
5990
|
+
const [soundImportTarget, setSoundImportTarget] = (0, import_react27.useState)(null);
|
|
5991
|
+
const [designerView, setDesignerView] = (0, import_react27.useState)(false);
|
|
5992
|
+
const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react27.useState)(0);
|
|
5993
|
+
const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react27.useState)([]);
|
|
5994
|
+
const [fadesMeta, setFadesMeta] = (0, import_react27.useState)([]);
|
|
5995
|
+
const [genericGroupMetas, setGenericGroupMetas] = (0, import_react27.useState)({});
|
|
5996
|
+
const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
|
|
5997
|
+
const [placeholders, , setPlaceholdersForScene] = useSceneState(
|
|
5998
|
+
activeSceneId,
|
|
5999
|
+
EMPTY_PLACEHOLDERS
|
|
6000
|
+
);
|
|
6001
|
+
const saveTimeoutRefs = (0, import_react27.useRef)({});
|
|
6002
|
+
const editLoadStartedRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
|
|
6003
|
+
const [availableInstruments, setAvailableInstruments] = (0, import_react27.useState)([]);
|
|
6004
|
+
const [instrumentsLoading, setInstrumentsLoading] = (0, import_react27.useState)(false);
|
|
6005
|
+
const engineToDbIdRef = (0, import_react27.useRef)(/* @__PURE__ */ new Map());
|
|
6006
|
+
const tracksLoadedForSceneRef = (0, import_react27.useRef)(null);
|
|
6007
|
+
const persistSoundHistory = (0, import_react27.useCallback)(
|
|
6008
|
+
(trackId, state) => {
|
|
6009
|
+
if (!activeSceneId) return;
|
|
6010
|
+
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
6011
|
+
host.setSceneData(activeSceneId, trackDataKey(dbId, "soundHistory"), state).catch(() => {
|
|
6012
|
+
});
|
|
6013
|
+
},
|
|
6014
|
+
[host, activeSceneId]
|
|
6015
|
+
);
|
|
6016
|
+
const soundHistory = useSoundHistory(adapter.sound.applySound, {
|
|
6017
|
+
max: adapter.sound.historyMax,
|
|
6018
|
+
onChange: persistSoundHistory
|
|
6019
|
+
});
|
|
6020
|
+
const anySolo = useAnySolo(host);
|
|
6021
|
+
const reorder = useTrackReorder({
|
|
6022
|
+
host,
|
|
6023
|
+
items: tracks,
|
|
6024
|
+
setItems: setTracks,
|
|
6025
|
+
getId: (t) => t.handle.dbId
|
|
6026
|
+
});
|
|
6027
|
+
const loadTracks = (0, import_react27.useCallback)(
|
|
6028
|
+
async (incremental = false) => {
|
|
6029
|
+
const sceneAtStart = activeSceneId;
|
|
6030
|
+
if (!sceneAtStart) {
|
|
6031
|
+
setTracks([]);
|
|
6032
|
+
setCrossfadePairsMeta([]);
|
|
6033
|
+
setFadesMeta([]);
|
|
6034
|
+
setGenericGroupMetas({});
|
|
6035
|
+
tracksLoadedForSceneRef.current = null;
|
|
6036
|
+
setIsLoadingTracks(false);
|
|
6037
|
+
return;
|
|
6038
|
+
}
|
|
6039
|
+
if (!incremental && tracksLoadedForSceneRef.current !== sceneAtStart) {
|
|
6040
|
+
setTracks([]);
|
|
6041
|
+
}
|
|
6042
|
+
tracksLoadedForSceneRef.current = sceneAtStart;
|
|
6043
|
+
if (!incremental) soundHistory.reset();
|
|
6044
|
+
const isStale = () => tracksLoadedForSceneRef.current !== sceneAtStart;
|
|
6045
|
+
if (!incremental) setIsLoadingTracks(true);
|
|
6046
|
+
try {
|
|
6047
|
+
await host.adoptSceneTracks();
|
|
6048
|
+
if (isStale()) return;
|
|
6049
|
+
const handles = await host.getPluginTracks();
|
|
6050
|
+
if (isStale()) return;
|
|
6051
|
+
const sceneData = await host.getAllSceneData(sceneAtStart);
|
|
6052
|
+
if (isStale()) return;
|
|
6053
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
6054
|
+
for (const h of handles) {
|
|
6055
|
+
idMap.set(h.id, h.dbId);
|
|
6056
|
+
}
|
|
6057
|
+
engineToDbIdRef.current = idMap;
|
|
6058
|
+
const trackStates = [];
|
|
6059
|
+
for (const handle of handles) {
|
|
6060
|
+
let runtimeState = {
|
|
6061
|
+
id: handle.id,
|
|
6062
|
+
muted: false,
|
|
6063
|
+
solo: false,
|
|
6064
|
+
volume: 0.75,
|
|
6065
|
+
pan: 0
|
|
6066
|
+
};
|
|
6067
|
+
let hasMidi = false;
|
|
6068
|
+
try {
|
|
6069
|
+
const info = await host.getTrackInfo(handle.id);
|
|
6070
|
+
runtimeState = {
|
|
6071
|
+
id: handle.id,
|
|
6072
|
+
muted: info.muted,
|
|
6073
|
+
solo: info.soloed,
|
|
6074
|
+
volume: info.volume,
|
|
6075
|
+
pan: info.pan
|
|
6076
|
+
};
|
|
6077
|
+
hasMidi = info.hasMidi;
|
|
6078
|
+
} catch {
|
|
6079
|
+
}
|
|
6080
|
+
let fxDetailState = newTrackState(handle).fxDetailState;
|
|
6081
|
+
try {
|
|
6082
|
+
const fxState = await host.getTrackFxState(handle.id);
|
|
6083
|
+
fxDetailState = pluginFxToToggleFx(fxState);
|
|
6084
|
+
} catch {
|
|
6085
|
+
}
|
|
6086
|
+
const promptKey = trackDataKey(handle.dbId, "prompt");
|
|
6087
|
+
let prompt = typeof sceneData[promptKey] === "string" ? sceneData[promptKey] : "";
|
|
6088
|
+
if (!prompt && handle.prompt) {
|
|
6089
|
+
prompt = handle.prompt;
|
|
6090
|
+
host.setSceneData(sceneAtStart, promptKey, prompt).catch(() => {
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
6093
|
+
if (!hasMidi && handle.role) {
|
|
6094
|
+
hasMidi = true;
|
|
6095
|
+
}
|
|
6096
|
+
let instrumentMissing = false;
|
|
6097
|
+
if (handle.instrumentPluginId) {
|
|
6098
|
+
try {
|
|
6099
|
+
const instrDescriptor = await host.getTrackInstrument(handle.id);
|
|
6100
|
+
if (instrDescriptor?.missing) {
|
|
6101
|
+
instrumentMissing = true;
|
|
6102
|
+
}
|
|
6103
|
+
} catch {
|
|
6104
|
+
}
|
|
6105
|
+
}
|
|
6106
|
+
trackStates.push(
|
|
6107
|
+
newTrackState(handle, {
|
|
6108
|
+
prompt,
|
|
6109
|
+
role: handle.role ?? "",
|
|
6110
|
+
runtimeState,
|
|
6111
|
+
fxDetailState,
|
|
6112
|
+
hasMidi,
|
|
6113
|
+
instrumentMissing
|
|
6114
|
+
})
|
|
6115
|
+
);
|
|
6116
|
+
}
|
|
6117
|
+
if (isStale()) return;
|
|
6118
|
+
setTracks((prev) => {
|
|
6119
|
+
const prevByDbId = new Map(prev.map((p) => [p.handle.dbId, p]));
|
|
6120
|
+
return trackStates.map((ts) => {
|
|
6121
|
+
const carry = prevByDbId.get(ts.handle.dbId);
|
|
6122
|
+
return carry ? { ...ts, editNotes: carry.editNotes, editBars: carry.editBars, editBpm: carry.editBpm } : ts;
|
|
6123
|
+
});
|
|
6124
|
+
});
|
|
6125
|
+
for (const ts of trackStates) {
|
|
6126
|
+
const persisted = sceneData[trackDataKey(ts.handle.dbId, "soundHistory")];
|
|
6127
|
+
if (persisted && typeof persisted === "object") {
|
|
6128
|
+
soundHistory.restore(ts.handle.id, persisted);
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
if (!isStale()) {
|
|
6132
|
+
setCrossfadePairsMeta(parseCrossfadePairs(sceneData));
|
|
6133
|
+
setFadesMeta(parseFades(sceneData));
|
|
6134
|
+
if (adapter.groupExtensions && adapter.groupExtensions.length > 0) {
|
|
6135
|
+
const map = {};
|
|
6136
|
+
for (const ext of adapter.groupExtensions) {
|
|
6137
|
+
map[ext.metaKey] = parseTrackGroups(sceneData, ext);
|
|
6138
|
+
}
|
|
6139
|
+
setGenericGroupMetas(map);
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
} catch (error) {
|
|
6143
|
+
console.error(`[${logTag}] Failed to load tracks:`, error);
|
|
6144
|
+
} finally {
|
|
6145
|
+
if (tracksLoadedForSceneRef.current === sceneAtStart) {
|
|
6146
|
+
setIsLoadingTracks(false);
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
6149
|
+
},
|
|
6150
|
+
[host, activeSceneId, soundHistory, adapter, logTag]
|
|
6151
|
+
);
|
|
6152
|
+
(0, import_react27.useEffect)(() => {
|
|
6153
|
+
loadTracks();
|
|
6154
|
+
}, [loadTracks]);
|
|
6155
|
+
(0, import_react27.useEffect)(() => {
|
|
6156
|
+
const map = /* @__PURE__ */ new Map();
|
|
6157
|
+
for (const t of tracks) {
|
|
6158
|
+
map.set(t.handle.id, t.handle.dbId);
|
|
6159
|
+
}
|
|
6160
|
+
engineToDbIdRef.current = map;
|
|
6161
|
+
}, [tracks]);
|
|
6162
|
+
const loadedCompletedIdsRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
|
|
6163
|
+
(0, import_react27.useEffect)(() => {
|
|
6164
|
+
if (placeholders.length === 0) {
|
|
6165
|
+
loadedCompletedIdsRef.current.clear();
|
|
6166
|
+
return;
|
|
6167
|
+
}
|
|
6168
|
+
const newCompleted = placeholders.filter(
|
|
6169
|
+
(ph) => ph.status === "completed" && !loadedCompletedIdsRef.current.has(ph.id)
|
|
6170
|
+
);
|
|
6171
|
+
if (newCompleted.length > 0) {
|
|
6172
|
+
for (const ph of newCompleted) {
|
|
6173
|
+
loadedCompletedIdsRef.current.add(ph.id);
|
|
6174
|
+
}
|
|
6175
|
+
console.log(
|
|
6176
|
+
`[${logTag}] ${newCompleted.length} track(s) completed, reloading. IDs:`,
|
|
6177
|
+
newCompleted.map((ph) => ph.id)
|
|
6178
|
+
);
|
|
6179
|
+
loadTracks(true);
|
|
6180
|
+
}
|
|
6181
|
+
}, [placeholders, loadTracks, logTag]);
|
|
6182
|
+
const adoptAndLoad = (0, import_react27.useCallback)(() => {
|
|
6183
|
+
loadTracks(true);
|
|
6184
|
+
}, [loadTracks]);
|
|
6185
|
+
(0, import_react27.useEffect)(() => {
|
|
6186
|
+
const unsub = host.onEngineReady(() => {
|
|
6187
|
+
adoptAndLoad();
|
|
6188
|
+
});
|
|
6189
|
+
return unsub;
|
|
6190
|
+
}, [host, adoptAndLoad]);
|
|
6191
|
+
(0, import_react27.useEffect)(() => {
|
|
6192
|
+
if (typeof host.onAfterAgentMutation !== "function") return;
|
|
6193
|
+
let timer = null;
|
|
6194
|
+
const unsub = host.onAfterAgentMutation(() => {
|
|
6195
|
+
if (timer) clearTimeout(timer);
|
|
6196
|
+
timer = setTimeout(() => {
|
|
6197
|
+
timer = null;
|
|
6198
|
+
loadTracks(true);
|
|
6199
|
+
}, 500);
|
|
6200
|
+
});
|
|
6201
|
+
return () => {
|
|
6202
|
+
unsub?.();
|
|
6203
|
+
if (timer) clearTimeout(timer);
|
|
6204
|
+
};
|
|
6205
|
+
}, [host, loadTracks]);
|
|
6206
|
+
(0, import_react27.useEffect)(() => {
|
|
6207
|
+
const unsub = host.onTrackStateChange((trackId, state) => {
|
|
6208
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
|
|
6209
|
+
});
|
|
6210
|
+
return unsub;
|
|
6211
|
+
}, [host]);
|
|
6212
|
+
(0, import_react27.useEffect)(() => {
|
|
6213
|
+
if (!features.bulkComposePlaceholders) return;
|
|
6214
|
+
console.log(`[${logTag}] Subscribing to composeProgress`);
|
|
6215
|
+
const unsub = host.onComposeProgress((event) => {
|
|
6216
|
+
const targetScene = event.sceneId;
|
|
6217
|
+
if (!targetScene) return;
|
|
6218
|
+
console.log(
|
|
6219
|
+
`[${logTag}] composeProgress event:`,
|
|
6220
|
+
event.phase,
|
|
6221
|
+
"sceneId:",
|
|
6222
|
+
targetScene,
|
|
6223
|
+
"placeholders:",
|
|
6224
|
+
event.placeholders?.length ?? "none"
|
|
6225
|
+
);
|
|
6226
|
+
switch (event.phase) {
|
|
6227
|
+
case "planning":
|
|
6228
|
+
setIsComposingForScene(targetScene, true);
|
|
6229
|
+
setPlaceholdersForScene(targetScene, []);
|
|
6230
|
+
break;
|
|
6231
|
+
case "generating":
|
|
6232
|
+
setIsComposingForScene(targetScene, false);
|
|
6233
|
+
if (event.placeholders) {
|
|
6234
|
+
setPlaceholdersForScene(targetScene, event.placeholders);
|
|
6235
|
+
}
|
|
6236
|
+
break;
|
|
6237
|
+
case "complete":
|
|
6238
|
+
case "error":
|
|
6239
|
+
setIsComposingForScene(targetScene, false);
|
|
6240
|
+
setPlaceholdersForScene(targetScene, EMPTY_PLACEHOLDERS);
|
|
6241
|
+
break;
|
|
6242
|
+
}
|
|
6243
|
+
});
|
|
6244
|
+
return unsub;
|
|
6245
|
+
}, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
|
|
6246
|
+
(0, import_react27.useEffect)(() => {
|
|
6247
|
+
const refs = saveTimeoutRefs;
|
|
6248
|
+
return () => {
|
|
6249
|
+
for (const timeout of Object.values(refs.current)) {
|
|
6250
|
+
clearTimeout(timeout);
|
|
6251
|
+
}
|
|
6252
|
+
};
|
|
6253
|
+
}, []);
|
|
6254
|
+
const isAddingTrackRef = (0, import_react27.useRef)(false);
|
|
6255
|
+
const [isAddingTrack, setIsAddingTrack] = (0, import_react27.useState)(false);
|
|
6256
|
+
const handleAddTrack = (0, import_react27.useCallback)(async () => {
|
|
6257
|
+
if (isAddingTrackRef.current) return;
|
|
6258
|
+
if (!activeSceneId) {
|
|
6259
|
+
host.showToast("warning", "Select SCENE");
|
|
6260
|
+
return;
|
|
6261
|
+
}
|
|
6262
|
+
if (!isConnected) {
|
|
6263
|
+
host.showToast("warning", "Systems not connected");
|
|
6264
|
+
return;
|
|
6265
|
+
}
|
|
6266
|
+
if (!isAuthenticated) {
|
|
6267
|
+
host.showToast("warning", "Sign In Required", "Please sign in to add tracks");
|
|
6268
|
+
return;
|
|
6269
|
+
}
|
|
6270
|
+
if (tracks.length >= identity.maxTracks) return;
|
|
6271
|
+
isAddingTrackRef.current = true;
|
|
6272
|
+
setIsAddingTrack(true);
|
|
6273
|
+
try {
|
|
6274
|
+
const handle = await host.createTrack({
|
|
6275
|
+
name: `${identity.trackNamePrefix}-${Date.now()}`,
|
|
6276
|
+
...adapter.createTrackOptions()
|
|
6277
|
+
});
|
|
6278
|
+
setTracks((prev) => [...prev, newTrackState(handle)]);
|
|
6279
|
+
onExpandSelf?.();
|
|
6280
|
+
setTimeout(() => {
|
|
6281
|
+
const inputs = document.querySelectorAll(
|
|
6282
|
+
`[data-testid="${identity.familyKey}-section"] [data-testid="sdk-prompt-input"]`
|
|
6283
|
+
);
|
|
6284
|
+
if (inputs.length > 0) {
|
|
6285
|
+
inputs[inputs.length - 1].focus();
|
|
6286
|
+
}
|
|
6287
|
+
}, 350);
|
|
6288
|
+
} catch (error) {
|
|
6289
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
6290
|
+
host.showToast("error", "Failed to create track", msg);
|
|
6291
|
+
} finally {
|
|
6292
|
+
isAddingTrackRef.current = false;
|
|
6293
|
+
setIsAddingTrack(false);
|
|
6294
|
+
}
|
|
6295
|
+
}, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
|
|
6296
|
+
const handlePortTrack = (0, import_react27.useCallback)(
|
|
6297
|
+
async (sel) => {
|
|
6298
|
+
if (!activeSceneId) {
|
|
6299
|
+
host.showToast("warning", "Select SCENE");
|
|
6300
|
+
return;
|
|
6301
|
+
}
|
|
6302
|
+
if (!isConnected) {
|
|
6303
|
+
host.showToast("warning", "Systems not connected");
|
|
6304
|
+
return;
|
|
6305
|
+
}
|
|
6306
|
+
if (tracks.length >= identity.maxTracks) {
|
|
6307
|
+
host.showToast("warning", "Track limit reached");
|
|
6308
|
+
return;
|
|
6309
|
+
}
|
|
6310
|
+
if (!host.readImportableTrackMidi) return;
|
|
6311
|
+
let handle = null;
|
|
6312
|
+
try {
|
|
6313
|
+
handle = await host.createTrack({
|
|
6314
|
+
name: `${identity.trackNamePrefix}-${Date.now()}`,
|
|
6315
|
+
...adapter.createTrackOptions()
|
|
6316
|
+
});
|
|
6317
|
+
if (sel.role) {
|
|
6318
|
+
try {
|
|
6319
|
+
await host.setTrackRole(handle.id, sel.role);
|
|
6320
|
+
} catch {
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
const midi = await host.readImportableTrackMidi(sel.sourceTrackDbId);
|
|
6324
|
+
const notes = midi.clips[0]?.notes ?? [];
|
|
6325
|
+
if (notes.length > 0) {
|
|
6326
|
+
const mc = await host.getMusicalContext();
|
|
6327
|
+
await host.writeMidiClip(handle.id, {
|
|
6328
|
+
startTime: 0,
|
|
6329
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
6330
|
+
tempo: mc.bpm,
|
|
6331
|
+
notes
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
6334
|
+
await adapter.applyPortedTrackSound(handle, sel.role);
|
|
6335
|
+
host.showToast(
|
|
6336
|
+
"success",
|
|
6337
|
+
`Imported to ${identity.familyKey}`,
|
|
6338
|
+
notes.length ? `${sel.trackName} \u2192 ${identity.familyKey}` : `${sel.trackName} (no MIDI yet)`
|
|
6339
|
+
);
|
|
6340
|
+
await loadTracks(true);
|
|
6341
|
+
} catch (err) {
|
|
6342
|
+
if (handle) {
|
|
6343
|
+
try {
|
|
6344
|
+
await host.deleteTrack(handle.id);
|
|
6345
|
+
} catch {
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
6349
|
+
}
|
|
6350
|
+
},
|
|
6351
|
+
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
6352
|
+
);
|
|
6353
|
+
const handleSoundImportPick = (0, import_react27.useCallback)(
|
|
6354
|
+
async (sel) => {
|
|
6355
|
+
const target = soundImportTarget;
|
|
6356
|
+
if (!target || !host.getTrackSound) {
|
|
6357
|
+
setSoundImportTarget(null);
|
|
6358
|
+
return;
|
|
6359
|
+
}
|
|
6360
|
+
const noun = adapter.sound.importNoun;
|
|
6361
|
+
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
6362
|
+
try {
|
|
6363
|
+
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
6364
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
6365
|
+
host.showToast(
|
|
6366
|
+
"error",
|
|
6367
|
+
`No ${noun} to import`,
|
|
6368
|
+
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
6369
|
+
);
|
|
6370
|
+
return;
|
|
6371
|
+
}
|
|
6372
|
+
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
6373
|
+
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
6374
|
+
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
6375
|
+
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
6376
|
+
} catch (err) {
|
|
6377
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
6378
|
+
} finally {
|
|
6379
|
+
setSoundImportTarget(null);
|
|
6380
|
+
}
|
|
6381
|
+
},
|
|
6382
|
+
[soundImportTarget, host, adapter, identity.familyKey, soundHistory]
|
|
6383
|
+
);
|
|
6384
|
+
const [isExportingMidi, setIsExportingMidi] = (0, import_react27.useState)(false);
|
|
6385
|
+
const handleExportMidi = (0, import_react27.useCallback)(async () => {
|
|
6386
|
+
if (isExportingMidi) return;
|
|
6387
|
+
setIsExportingMidi(true);
|
|
6388
|
+
try {
|
|
6389
|
+
const result = await host.exportTracksAsMidiBundle({
|
|
6390
|
+
defaultName: identity.exportDefaultName ?? "midi-tracks"
|
|
6391
|
+
});
|
|
6392
|
+
if (result.success) {
|
|
6393
|
+
const filename = result.filePath.split("/").pop() || result.filePath;
|
|
6394
|
+
const skippedNote = result.skippedCount > 0 ? ` (${result.skippedCount} empty track${result.skippedCount === 1 ? "" : "s"} skipped)` : "";
|
|
6395
|
+
host.showToast(
|
|
6396
|
+
"success",
|
|
6397
|
+
"MIDI exported",
|
|
6398
|
+
`${result.trackCount} track${result.trackCount === 1 ? "" : "s"} \u2192 ${filename}${skippedNote}`
|
|
6399
|
+
);
|
|
6400
|
+
} else if (!("canceled" in result && result.canceled)) {
|
|
6401
|
+
const errMsg = "error" in result ? result.error : "Unknown error";
|
|
6402
|
+
host.showToast("error", "Export failed", errMsg);
|
|
6403
|
+
}
|
|
6404
|
+
} catch (error) {
|
|
6405
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
6406
|
+
host.showToast("error", "Export failed", msg);
|
|
6407
|
+
} finally {
|
|
6408
|
+
setIsExportingMidi(false);
|
|
6409
|
+
}
|
|
6410
|
+
}, [host, identity.exportDefaultName, isExportingMidi]);
|
|
6411
|
+
const isBulkActive = !!(isComposing || placeholders.length > 0);
|
|
6412
|
+
const needsContract = !sceneContext?.hasContract;
|
|
6413
|
+
const xfFromId = sceneContext?.transitionFromSceneId ?? null;
|
|
6414
|
+
const xfToId = sceneContext?.transitionToSceneId ?? null;
|
|
6415
|
+
const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
|
|
6416
|
+
(0, import_react27.useEffect)(() => {
|
|
6417
|
+
if (!canCrossfade) setDesignerView(false);
|
|
6418
|
+
}, [canCrossfade]);
|
|
6419
|
+
(0, import_react27.useEffect)(() => {
|
|
6420
|
+
if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
|
|
6421
|
+
setTransitionSourceTotal(0);
|
|
6422
|
+
return;
|
|
6423
|
+
}
|
|
6424
|
+
let cancelled = false;
|
|
6425
|
+
void Promise.all([host.listSceneFamilyTracks(xfFromId), host.listSceneFamilyTracks(xfToId)]).then(([a, b]) => {
|
|
6426
|
+
if (!cancelled) setTransitionSourceTotal(a.length + b.length);
|
|
6427
|
+
}).catch(() => {
|
|
6428
|
+
if (!cancelled) setTransitionSourceTotal(0);
|
|
6429
|
+
});
|
|
6430
|
+
return () => {
|
|
6431
|
+
cancelled = true;
|
|
6432
|
+
};
|
|
6433
|
+
}, [canCrossfade, xfFromId, xfToId, host]);
|
|
6434
|
+
const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
|
|
6435
|
+
(0, import_react27.useEffect)(() => {
|
|
6436
|
+
if (!onHeaderContent) return;
|
|
6437
|
+
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
6438
|
+
onHeaderContent(
|
|
6439
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
6440
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
6441
|
+
"button",
|
|
6442
|
+
{
|
|
6443
|
+
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
6444
|
+
onClick: (e) => {
|
|
6445
|
+
e.stopPropagation();
|
|
6446
|
+
onExpandSelf?.();
|
|
6447
|
+
setImportOpen(true);
|
|
6448
|
+
},
|
|
6449
|
+
disabled: !activeSceneId || needsContract,
|
|
6450
|
+
className: `px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${!activeSceneId || needsContract ? "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
|
|
6451
|
+
children: identity.importTrackLabel ?? "Import Track"
|
|
6452
|
+
}
|
|
6453
|
+
),
|
|
6454
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
6455
|
+
"button",
|
|
6456
|
+
{
|
|
6457
|
+
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
6458
|
+
onClick: (e) => {
|
|
6459
|
+
e.stopPropagation();
|
|
6460
|
+
if (needsContract) {
|
|
6461
|
+
onOpenContract?.();
|
|
6462
|
+
return;
|
|
6463
|
+
}
|
|
6464
|
+
handleAddTrack();
|
|
6465
|
+
},
|
|
6466
|
+
className: `px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors ${addDisabled ? "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed" : "bg-sas-accent/10 border-sas-accent/30 text-sas-accent hover:bg-sas-accent/20"}`,
|
|
6467
|
+
children: identity.addTrackLabel ?? "Add Track"
|
|
6468
|
+
}
|
|
6469
|
+
),
|
|
6470
|
+
canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
6471
|
+
"button",
|
|
6472
|
+
{
|
|
6473
|
+
"data-testid": `${identity.familyKey}-view-toggle`,
|
|
6474
|
+
onClick: (e) => {
|
|
6475
|
+
e.stopPropagation();
|
|
6476
|
+
if (!designerView) {
|
|
6477
|
+
if (needsContract) {
|
|
6478
|
+
onOpenContract?.();
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6481
|
+
onExpandSelf?.();
|
|
6482
|
+
}
|
|
6483
|
+
setDesignerView((v) => !v);
|
|
6484
|
+
},
|
|
6485
|
+
disabled: !designerView && needsContract,
|
|
6486
|
+
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
6487
|
+
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",
|
|
6488
|
+
children: [
|
|
6489
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
6490
|
+
"span",
|
|
6491
|
+
{
|
|
6492
|
+
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
6493
|
+
style: { width: `${Math.min(100, transitionDone / transitionSourceTotal * 100)}%` },
|
|
6494
|
+
"aria-hidden": true
|
|
6495
|
+
}
|
|
6496
|
+
),
|
|
6497
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "relative", children: [
|
|
6498
|
+
"\u21C4 ",
|
|
6499
|
+
designerView ? "Transition" : "Tracks",
|
|
6500
|
+
transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
|
|
6501
|
+
] })
|
|
6502
|
+
]
|
|
6503
|
+
}
|
|
6504
|
+
)
|
|
6505
|
+
] })
|
|
6506
|
+
);
|
|
6507
|
+
return () => {
|
|
6508
|
+
onHeaderContent(null);
|
|
6509
|
+
};
|
|
6510
|
+
}, [
|
|
6511
|
+
onHeaderContent,
|
|
6512
|
+
needsContract,
|
|
6513
|
+
isConnected,
|
|
6514
|
+
activeSceneId,
|
|
6515
|
+
tracks.length,
|
|
6516
|
+
isAddingTrack,
|
|
6517
|
+
handleAddTrack,
|
|
6518
|
+
onOpenContract,
|
|
6519
|
+
host,
|
|
6520
|
+
canCrossfade,
|
|
6521
|
+
designerView,
|
|
6522
|
+
transitionDone,
|
|
6523
|
+
transitionSourceTotal,
|
|
6524
|
+
onExpandSelf,
|
|
6525
|
+
identity,
|
|
6526
|
+
features.importTracks
|
|
6527
|
+
]);
|
|
6528
|
+
(0, import_react27.useEffect)(() => {
|
|
6529
|
+
if (!onLoading) return;
|
|
6530
|
+
const anyGenerating = tracks.some((t) => t.isGenerating);
|
|
6531
|
+
onLoading(isLoadingTracks || anyGenerating || isBulkActive);
|
|
6532
|
+
return () => {
|
|
6533
|
+
onLoading(false);
|
|
6534
|
+
};
|
|
6535
|
+
}, [onLoading, isLoadingTracks, tracks, isBulkActive]);
|
|
6536
|
+
const handleDeleteTrack = (0, import_react27.useCallback)(
|
|
6537
|
+
async (trackId) => {
|
|
6538
|
+
try {
|
|
6539
|
+
await host.deleteTrack(trackId);
|
|
6540
|
+
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
6541
|
+
if (activeSceneId) {
|
|
6542
|
+
await host.deleteSceneData(activeSceneId, trackDataKey(dbId, "prompt"));
|
|
6543
|
+
}
|
|
6544
|
+
setTracks((prev) => prev.filter((t) => t.handle.id !== trackId));
|
|
6545
|
+
} catch (error) {
|
|
6546
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
6547
|
+
host.showToast("error", "Failed to delete track", msg);
|
|
6548
|
+
}
|
|
6549
|
+
},
|
|
6550
|
+
[host, activeSceneId]
|
|
6551
|
+
);
|
|
6552
|
+
const handlePromptChange = (0, import_react27.useCallback)(
|
|
6553
|
+
(trackId, prompt) => {
|
|
6554
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
|
|
6555
|
+
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
6556
|
+
if (saveTimeoutRefs.current[trackId]) {
|
|
6557
|
+
clearTimeout(saveTimeoutRefs.current[trackId]);
|
|
6558
|
+
}
|
|
6559
|
+
saveTimeoutRefs.current[trackId] = setTimeout(() => {
|
|
6560
|
+
if (activeSceneId) {
|
|
6561
|
+
host.setSceneData(activeSceneId, trackDataKey(dbId, "prompt"), prompt).catch(() => {
|
|
6562
|
+
});
|
|
6563
|
+
}
|
|
6564
|
+
}, 500);
|
|
6565
|
+
},
|
|
6566
|
+
[host, activeSceneId]
|
|
6567
|
+
);
|
|
6568
|
+
const resolvedGenericGroups = (0, import_react27.useMemo)(() => {
|
|
6569
|
+
const out = {};
|
|
6570
|
+
for (const ext of adapter.groupExtensions ?? []) {
|
|
6571
|
+
out[ext.metaKey] = resolveTrackGroups(
|
|
6572
|
+
genericGroupMetas[ext.metaKey] ?? [],
|
|
6573
|
+
tracks,
|
|
6574
|
+
(t) => t.handle.dbId,
|
|
6575
|
+
{
|
|
6576
|
+
isComplete: ext.isComplete
|
|
6577
|
+
}
|
|
6578
|
+
);
|
|
6579
|
+
}
|
|
6580
|
+
return out;
|
|
6581
|
+
}, [adapter, genericGroupMetas, tracks]);
|
|
6582
|
+
const genericGroupMemberDbIds = (0, import_react27.useMemo)(() => {
|
|
6583
|
+
const s = /* @__PURE__ */ new Set();
|
|
6584
|
+
for (const r of Object.values(resolvedGenericGroups)) {
|
|
6585
|
+
for (const dbId of r.memberDbIds) s.add(dbId);
|
|
6586
|
+
}
|
|
6587
|
+
return s;
|
|
6588
|
+
}, [resolvedGenericGroups]);
|
|
6589
|
+
const engineToDbId = (0, import_react27.useCallback)(
|
|
6590
|
+
(trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
|
|
6591
|
+
[]
|
|
6592
|
+
);
|
|
6593
|
+
const updateTrack = (0, import_react27.useCallback)(
|
|
6594
|
+
(trackId, patch) => {
|
|
6595
|
+
setTracks(
|
|
6596
|
+
(prev) => prev.map(
|
|
6597
|
+
(t) => t.handle.id === trackId ? typeof patch === "function" ? patch(t) : { ...t, ...patch } : t
|
|
6598
|
+
)
|
|
6599
|
+
);
|
|
6600
|
+
},
|
|
6601
|
+
[]
|
|
6602
|
+
);
|
|
6603
|
+
const markEditLoaded = (0, import_react27.useCallback)((trackId) => {
|
|
6604
|
+
editLoadStartedRef.current.add(trackId);
|
|
6605
|
+
}, []);
|
|
6606
|
+
const tracksRef = (0, import_react27.useRef)(tracks);
|
|
6607
|
+
(0, import_react27.useEffect)(() => {
|
|
6608
|
+
tracksRef.current = tracks;
|
|
6609
|
+
}, [tracks]);
|
|
6610
|
+
const resolvedGenericGroupsRef = (0, import_react27.useRef)(resolvedGenericGroups);
|
|
6611
|
+
(0, import_react27.useEffect)(() => {
|
|
6612
|
+
resolvedGenericGroupsRef.current = resolvedGenericGroups;
|
|
6613
|
+
}, [resolvedGenericGroups]);
|
|
6614
|
+
const makeServices = (0, import_react27.useCallback)(() => {
|
|
6615
|
+
return {
|
|
6616
|
+
host,
|
|
6617
|
+
activeSceneId,
|
|
6618
|
+
tracks: tracksRef.current,
|
|
6619
|
+
updateTrack,
|
|
6620
|
+
setTracks,
|
|
6621
|
+
reloadTracks: loadTracks,
|
|
6622
|
+
soundHistory,
|
|
6623
|
+
engineToDbId,
|
|
6624
|
+
trackDataKey,
|
|
6625
|
+
markEditLoaded,
|
|
6626
|
+
createFamilyTrack: (nameSuffix = "") => host.createTrack({
|
|
6627
|
+
name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,
|
|
6628
|
+
...adapter.createTrackOptions()
|
|
6629
|
+
}),
|
|
6630
|
+
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
6631
|
+
};
|
|
6632
|
+
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
6633
|
+
const handleGenerate = (0, import_react27.useCallback)(
|
|
6634
|
+
async (trackId) => {
|
|
6635
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6636
|
+
if (!track || !track.prompt.trim()) return;
|
|
6637
|
+
if (!isAuthenticated) {
|
|
6638
|
+
host.showToast("warning", "Sign In Required", "Please sign in to generate MIDI");
|
|
6639
|
+
return;
|
|
6640
|
+
}
|
|
6641
|
+
setTracks(
|
|
6642
|
+
(prev) => prev.map(
|
|
6643
|
+
(t) => t.handle.id === trackId ? { ...t, isGenerating: true, error: null, generationProgress: 0 } : t
|
|
6644
|
+
)
|
|
6645
|
+
);
|
|
6646
|
+
try {
|
|
6647
|
+
await adapter.generation.generate(track, makeServices());
|
|
6648
|
+
} catch (error) {
|
|
6649
|
+
const msg = error instanceof Error ? error.message : "Generation failed";
|
|
6650
|
+
setTracks(
|
|
6651
|
+
(prev) => prev.map(
|
|
6652
|
+
(t) => t.handle.id === trackId ? { ...t, isGenerating: false, error: msg, generationProgress: 0 } : t
|
|
6653
|
+
)
|
|
6654
|
+
);
|
|
6655
|
+
host.showToast("error", "Generation failed", msg);
|
|
6656
|
+
}
|
|
6657
|
+
},
|
|
6658
|
+
[host, adapter, tracks, isAuthenticated, makeServices]
|
|
6659
|
+
);
|
|
6660
|
+
const handleMuteToggle = (0, import_react27.useCallback)(
|
|
6661
|
+
(trackId) => {
|
|
6662
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6663
|
+
if (!track) return;
|
|
6664
|
+
const newMuted = !track.runtimeState.muted;
|
|
6665
|
+
setTracks(
|
|
6666
|
+
(prev) => prev.map(
|
|
6667
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, muted: newMuted } } : t
|
|
6668
|
+
)
|
|
6669
|
+
);
|
|
6670
|
+
host.setTrackMute(trackId, newMuted).catch(() => {
|
|
6671
|
+
setTracks(
|
|
6672
|
+
(prev) => prev.map(
|
|
6673
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, muted: !newMuted } } : t
|
|
6674
|
+
)
|
|
6675
|
+
);
|
|
6676
|
+
});
|
|
6677
|
+
},
|
|
6678
|
+
[host, tracks]
|
|
6679
|
+
);
|
|
6680
|
+
const handleSoloToggle = (0, import_react27.useCallback)(
|
|
6681
|
+
(trackId) => {
|
|
6682
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6683
|
+
if (!track) return;
|
|
6684
|
+
const newSolo = !track.runtimeState.solo;
|
|
6685
|
+
setTracks(
|
|
6686
|
+
(prev) => prev.map(
|
|
6687
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, solo: newSolo } } : t
|
|
6688
|
+
)
|
|
6689
|
+
);
|
|
6690
|
+
host.setTrackSolo(trackId, newSolo).catch(() => {
|
|
6691
|
+
setTracks(
|
|
6692
|
+
(prev) => prev.map(
|
|
6693
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, solo: !newSolo } } : t
|
|
6694
|
+
)
|
|
6695
|
+
);
|
|
6696
|
+
});
|
|
6697
|
+
},
|
|
6698
|
+
[host, tracks]
|
|
6699
|
+
);
|
|
6700
|
+
const handleVolumeChange = (0, import_react27.useCallback)(
|
|
6701
|
+
(trackId, volume) => {
|
|
6702
|
+
setTracks(
|
|
6703
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
|
|
6704
|
+
);
|
|
6705
|
+
host.setTrackVolume(trackId, volume).catch(() => {
|
|
6706
|
+
});
|
|
6707
|
+
},
|
|
6708
|
+
[host]
|
|
6709
|
+
);
|
|
6710
|
+
const handlePanChange = (0, import_react27.useCallback)(
|
|
6711
|
+
(trackId, pan) => {
|
|
6712
|
+
setTracks(
|
|
6713
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
|
|
6714
|
+
);
|
|
6715
|
+
host.setTrackPan(trackId, pan).catch(() => {
|
|
6716
|
+
});
|
|
6717
|
+
},
|
|
6718
|
+
[host]
|
|
6719
|
+
);
|
|
6720
|
+
const handleShuffle = (0, import_react27.useCallback)(
|
|
6721
|
+
async (trackId) => {
|
|
6722
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6723
|
+
if (!track) return;
|
|
6724
|
+
if (soundHistory.list(trackId).entries.length === 0) {
|
|
6725
|
+
try {
|
|
6726
|
+
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
6727
|
+
if (cap) soundHistory.record(trackId, cap.descriptor, adapter.sound.previousSoundLabel);
|
|
6728
|
+
} catch {
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
try {
|
|
6732
|
+
let result;
|
|
6733
|
+
let nextHistory;
|
|
6734
|
+
try {
|
|
6735
|
+
result = await adapter.shuffle.shuffle(track, Array.from(track.shuffleHistory));
|
|
6736
|
+
nextHistory = new Set(track.shuffleHistory);
|
|
6737
|
+
} catch (firstErr) {
|
|
6738
|
+
if (adapter.shuffle.isExhaustedError(firstErr)) {
|
|
6739
|
+
nextHistory = /* @__PURE__ */ new Set();
|
|
6740
|
+
result = await adapter.shuffle.shuffle(track, []);
|
|
6741
|
+
} else {
|
|
6742
|
+
throw firstErr;
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
nextHistory.add(result.appliedName);
|
|
6746
|
+
setTracks(
|
|
6747
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, shuffleHistory: nextHistory } : t)
|
|
6748
|
+
);
|
|
6749
|
+
try {
|
|
6750
|
+
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
6751
|
+
if (cap) soundHistory.record(trackId, cap.descriptor, result.appliedName);
|
|
6752
|
+
} catch {
|
|
6753
|
+
}
|
|
6754
|
+
console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);
|
|
6755
|
+
} catch (error) {
|
|
6756
|
+
const msg = error instanceof Error ? error.message : "Shuffle failed";
|
|
6757
|
+
host.showToast("error", "Shuffle failed", msg);
|
|
6758
|
+
}
|
|
6759
|
+
},
|
|
6760
|
+
[host, adapter, tracks, soundHistory, logTag]
|
|
6761
|
+
);
|
|
6762
|
+
const handleCopy = (0, import_react27.useCallback)(
|
|
6763
|
+
async (trackId) => {
|
|
6764
|
+
try {
|
|
6765
|
+
const newHandle = await host.duplicateTrack(trackId);
|
|
6766
|
+
await loadTracks();
|
|
6767
|
+
host.showToast("success", "Track duplicated", newHandle.name);
|
|
6768
|
+
} catch (error) {
|
|
6769
|
+
const msg = error instanceof Error ? error.message : "Copy failed";
|
|
6770
|
+
host.showToast("error", "Copy failed", msg);
|
|
6771
|
+
}
|
|
6772
|
+
},
|
|
6773
|
+
[host, loadTracks]
|
|
6774
|
+
);
|
|
6775
|
+
const handleFxToggle = (0, import_react27.useCallback)(
|
|
6776
|
+
(trackId, category, enabled) => {
|
|
6777
|
+
setTracks(
|
|
6778
|
+
(prev) => prev.map(
|
|
6779
|
+
(t) => t.handle.id === trackId ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], enabled } } } : t
|
|
6780
|
+
)
|
|
6781
|
+
);
|
|
6782
|
+
host.toggleTrackFx(trackId, category, enabled).catch(() => {
|
|
6783
|
+
setTracks(
|
|
6784
|
+
(prev) => prev.map(
|
|
6785
|
+
(t) => t.handle.id === trackId ? {
|
|
6786
|
+
...t,
|
|
6787
|
+
fxDetailState: {
|
|
6788
|
+
...t.fxDetailState,
|
|
6789
|
+
[category]: { ...t.fxDetailState[category], enabled: !enabled }
|
|
6790
|
+
}
|
|
6791
|
+
} : t
|
|
6792
|
+
)
|
|
6793
|
+
);
|
|
6794
|
+
});
|
|
6795
|
+
},
|
|
6796
|
+
[host]
|
|
6797
|
+
);
|
|
6798
|
+
const handleFxPresetChange = (0, import_react27.useCallback)(
|
|
6799
|
+
(trackId, category, presetIndex) => {
|
|
6800
|
+
setTracks(
|
|
6801
|
+
(prev) => prev.map(
|
|
6802
|
+
(t) => t.handle.id === trackId ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], presetIndex } } } : t
|
|
6803
|
+
)
|
|
6804
|
+
);
|
|
6805
|
+
host.setTrackFxPreset(trackId, category, presetIndex).then((result) => {
|
|
6806
|
+
if (result.dryWet !== void 0) {
|
|
6807
|
+
setTracks(
|
|
6808
|
+
(prev) => prev.map(
|
|
6809
|
+
(t) => t.handle.id === trackId ? {
|
|
6810
|
+
...t,
|
|
6811
|
+
fxDetailState: {
|
|
6812
|
+
...t.fxDetailState,
|
|
6813
|
+
[category]: { ...t.fxDetailState[category], dryWet: result.dryWet }
|
|
6814
|
+
}
|
|
6815
|
+
} : t
|
|
6816
|
+
)
|
|
6817
|
+
);
|
|
6818
|
+
}
|
|
6819
|
+
}).catch(() => {
|
|
6820
|
+
});
|
|
6821
|
+
},
|
|
6822
|
+
[host]
|
|
6823
|
+
);
|
|
6824
|
+
const handleFxDryWetChange = (0, import_react27.useCallback)(
|
|
6825
|
+
(trackId, category, value) => {
|
|
6826
|
+
setTracks(
|
|
6827
|
+
(prev) => prev.map(
|
|
6828
|
+
(t) => t.handle.id === trackId ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], dryWet: value } } } : t
|
|
6829
|
+
)
|
|
6830
|
+
);
|
|
6831
|
+
host.setTrackFxDryWet(trackId, category, value).catch(() => {
|
|
6832
|
+
});
|
|
6833
|
+
},
|
|
6834
|
+
[host]
|
|
6835
|
+
);
|
|
6836
|
+
const toggleFxDrawer = (0, import_react27.useCallback)(
|
|
6837
|
+
(trackId) => {
|
|
6838
|
+
setTracks(
|
|
6839
|
+
(prev) => prev.map((t) => {
|
|
6840
|
+
if (t.handle.id !== trackId) return t;
|
|
6841
|
+
const onFx = t.drawerOpen && t.drawerTab === "fx";
|
|
6842
|
+
return { ...t, drawerOpen: !onFx, drawerTab: "fx", editorStage: false };
|
|
6843
|
+
})
|
|
6844
|
+
);
|
|
6845
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6846
|
+
const wasOnFx = !!track && track.drawerOpen && track.drawerTab === "fx";
|
|
6847
|
+
if (track && !wasOnFx) {
|
|
6848
|
+
host.getTrackFxState(trackId).then((fxState) => {
|
|
6849
|
+
setTracks(
|
|
6850
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, fxDetailState: pluginFxToToggleFx(fxState) } : t)
|
|
6851
|
+
);
|
|
6852
|
+
}).catch(() => {
|
|
6853
|
+
});
|
|
6854
|
+
}
|
|
6855
|
+
},
|
|
6856
|
+
[host, tracks]
|
|
6857
|
+
);
|
|
6858
|
+
const loadEditNotes = (0, import_react27.useCallback)(
|
|
6859
|
+
async (trackId) => {
|
|
6860
|
+
try {
|
|
6861
|
+
const mc = await host.getMusicalContext();
|
|
6862
|
+
let notes = [];
|
|
6863
|
+
if (typeof host.readMidiNotes === "function") {
|
|
6864
|
+
const result = await host.readMidiNotes(trackId);
|
|
6865
|
+
notes = result.clips[0]?.notes ?? [];
|
|
6866
|
+
}
|
|
6867
|
+
setTracks(
|
|
6868
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes, editBars: mc.bars, editBpm: mc.bpm } : t)
|
|
6869
|
+
);
|
|
6870
|
+
} catch (err) {
|
|
6871
|
+
console.warn(`[${logTag}] Failed to load MIDI for editing:`, err);
|
|
6872
|
+
}
|
|
6873
|
+
},
|
|
6874
|
+
[host, logTag]
|
|
6875
|
+
);
|
|
6876
|
+
const handleNotesChange = (0, import_react27.useCallback)(
|
|
6877
|
+
(trackId, notes) => {
|
|
6878
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
|
|
6879
|
+
const key = `edit:${trackId}`;
|
|
6880
|
+
if (saveTimeoutRefs.current[key]) {
|
|
6881
|
+
clearTimeout(saveTimeoutRefs.current[key]);
|
|
6882
|
+
}
|
|
6883
|
+
saveTimeoutRefs.current[key] = setTimeout(() => {
|
|
6884
|
+
void (async () => {
|
|
6885
|
+
try {
|
|
6886
|
+
if (notes.length === 0) {
|
|
6887
|
+
await host.clearMidi(trackId);
|
|
6888
|
+
} else {
|
|
6889
|
+
const mc = await host.getMusicalContext();
|
|
6890
|
+
await host.writeMidiClip(trackId, {
|
|
6891
|
+
startTime: 0,
|
|
6892
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
6893
|
+
tempo: mc.bpm,
|
|
6894
|
+
notes
|
|
6895
|
+
});
|
|
6896
|
+
}
|
|
6897
|
+
} catch (err) {
|
|
6898
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6899
|
+
host.showToast("error", "Failed to save edit", msg);
|
|
6900
|
+
}
|
|
6901
|
+
})();
|
|
6902
|
+
}, 300);
|
|
6903
|
+
},
|
|
6904
|
+
[host]
|
|
6905
|
+
);
|
|
6906
|
+
const handleTabChange = (0, import_react27.useCallback)(
|
|
6907
|
+
(trackId, tab) => {
|
|
6908
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
|
|
6909
|
+
if (tab === "fx") {
|
|
6910
|
+
host.getTrackFxState(trackId).then((fxState) => {
|
|
6911
|
+
setTracks(
|
|
6912
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, fxDetailState: pluginFxToToggleFx(fxState) } : t)
|
|
6913
|
+
);
|
|
6914
|
+
}).catch(() => {
|
|
6915
|
+
});
|
|
6916
|
+
} else if (tab === "pick" && availableInstruments.length === 0 && !instrumentsLoading) {
|
|
6917
|
+
setInstrumentsLoading(true);
|
|
6918
|
+
host.getAvailableInstruments().then((instruments) => {
|
|
6919
|
+
setAvailableInstruments(instruments);
|
|
6920
|
+
}).catch(() => {
|
|
6921
|
+
}).finally(() => {
|
|
6922
|
+
setInstrumentsLoading(false);
|
|
6923
|
+
});
|
|
6924
|
+
} else if (tab === "edit" && !editLoadStartedRef.current.has(trackId)) {
|
|
6925
|
+
editLoadStartedRef.current.add(trackId);
|
|
6926
|
+
void loadEditNotes(trackId);
|
|
6927
|
+
}
|
|
6928
|
+
},
|
|
6929
|
+
[host, availableInstruments.length, instrumentsLoading, loadEditNotes]
|
|
6930
|
+
);
|
|
6931
|
+
const handleProgressChange = (0, import_react27.useCallback)((trackId, pct) => {
|
|
6932
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
|
|
6933
|
+
}, []);
|
|
6934
|
+
const handleToggleDrawer = (0, import_react27.useCallback)((trackId) => {
|
|
6935
|
+
setTracks(
|
|
6936
|
+
(prev) => prev.map((t) => {
|
|
6937
|
+
if (t.handle.id !== trackId) return t;
|
|
6938
|
+
const onSound = t.drawerOpen && t.drawerTab !== "fx";
|
|
6939
|
+
return { ...t, drawerOpen: !onSound, drawerTab: "history", editorStage: false };
|
|
6940
|
+
})
|
|
6941
|
+
);
|
|
6942
|
+
}, []);
|
|
6943
|
+
const handleInstrumentSelect = (0, import_react27.useCallback)(
|
|
6944
|
+
async (trackId, pluginId) => {
|
|
6945
|
+
const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
|
|
6946
|
+
if (isDefaultInstrument) {
|
|
6947
|
+
setTracks(
|
|
6948
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: false, editorStage: false } : t)
|
|
6949
|
+
);
|
|
6950
|
+
try {
|
|
6951
|
+
await host.setTrackInstrument(trackId, pluginId);
|
|
6952
|
+
const descriptor = await host.getTrackInstrument(trackId);
|
|
6953
|
+
setTracks(
|
|
6954
|
+
(prev) => prev.map(
|
|
6955
|
+
(t) => t.handle.id === trackId ? {
|
|
6956
|
+
...t,
|
|
6957
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
6958
|
+
instrumentName: descriptor?.name ?? null,
|
|
6959
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
6960
|
+
} : t
|
|
6961
|
+
)
|
|
6962
|
+
);
|
|
6963
|
+
} catch (err) {
|
|
6964
|
+
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
6965
|
+
host.showToast("error", "Instrument load failed", msg);
|
|
6966
|
+
}
|
|
6967
|
+
return;
|
|
6968
|
+
}
|
|
6969
|
+
setTracks(
|
|
6970
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerTab: "pick", editorStage: true } : t)
|
|
6971
|
+
);
|
|
6972
|
+
try {
|
|
6973
|
+
await host.setTrackInstrument(trackId, pluginId);
|
|
6974
|
+
const descriptor = await host.getTrackInstrument(trackId);
|
|
6975
|
+
setTracks(
|
|
6976
|
+
(prev) => prev.map(
|
|
6977
|
+
(t) => t.handle.id === trackId ? {
|
|
6978
|
+
...t,
|
|
6979
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
6980
|
+
instrumentName: descriptor?.name ?? null,
|
|
6981
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
6982
|
+
} : t
|
|
6983
|
+
)
|
|
6984
|
+
);
|
|
6985
|
+
} catch (err) {
|
|
6986
|
+
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
6987
|
+
console.error(`[${logTag}] Failed to set instrument:`, err);
|
|
6988
|
+
host.showToast("error", "Instrument load failed", msg);
|
|
6989
|
+
setTracks(
|
|
6990
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
|
|
6991
|
+
);
|
|
6992
|
+
}
|
|
6993
|
+
},
|
|
6994
|
+
[host, identity.defaultInstrumentPluginId, logTag]
|
|
6995
|
+
);
|
|
6996
|
+
const handleShowEditor = (0, import_react27.useCallback)(
|
|
6997
|
+
async (trackId) => {
|
|
6998
|
+
try {
|
|
6999
|
+
await host.showInstrumentEditor(trackId);
|
|
7000
|
+
} catch (err) {
|
|
7001
|
+
const msg = err instanceof Error ? err.message : "Failed to open editor";
|
|
7002
|
+
host.showToast("error", "Editor failed", msg);
|
|
7003
|
+
}
|
|
7004
|
+
},
|
|
7005
|
+
[host]
|
|
7006
|
+
);
|
|
7007
|
+
const handleBackToInstruments = (0, import_react27.useCallback)((trackId) => {
|
|
7008
|
+
setTracks(
|
|
7009
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
|
|
7010
|
+
);
|
|
7011
|
+
}, []);
|
|
7012
|
+
const handleRefreshInstruments = (0, import_react27.useCallback)(() => {
|
|
7013
|
+
setInstrumentsLoading(true);
|
|
7014
|
+
host.getAvailableInstruments().then((instruments) => {
|
|
7015
|
+
setAvailableInstruments(instruments);
|
|
7016
|
+
}).catch(() => {
|
|
7017
|
+
}).finally(() => {
|
|
7018
|
+
setInstrumentsLoading(false);
|
|
7019
|
+
});
|
|
7020
|
+
}, [host]);
|
|
7021
|
+
const onAuditionNote = (0, import_react27.useCallback)(
|
|
7022
|
+
(trackId, pitch, velocity, ms) => {
|
|
7023
|
+
void host.auditionNote(trackId, pitch, velocity, ms);
|
|
7024
|
+
},
|
|
7025
|
+
[host]
|
|
7026
|
+
);
|
|
7027
|
+
const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react27.useMemo)(() => {
|
|
7028
|
+
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
7029
|
+
const pairs = [];
|
|
7030
|
+
const members = /* @__PURE__ */ new Set();
|
|
7031
|
+
for (const p of crossfadePairsMeta) {
|
|
7032
|
+
const origin = byDbId.get(p.originDbId);
|
|
7033
|
+
const target = byDbId.get(p.targetDbId);
|
|
7034
|
+
if (origin && target) {
|
|
7035
|
+
pairs.push({ ...p, origin, target });
|
|
7036
|
+
members.add(p.originDbId);
|
|
7037
|
+
members.add(p.targetDbId);
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
|
|
7041
|
+
}, [tracks, crossfadePairsMeta]);
|
|
7042
|
+
const { resolvedFades, fadeMemberDbIds } = (0, import_react27.useMemo)(() => {
|
|
7043
|
+
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
7044
|
+
const list = [];
|
|
7045
|
+
const members = /* @__PURE__ */ new Set();
|
|
7046
|
+
for (const f of fadesMeta) {
|
|
7047
|
+
const track = byDbId.get(f.dbId);
|
|
7048
|
+
if (track) {
|
|
7049
|
+
list.push({ ...f, track });
|
|
7050
|
+
members.add(f.dbId);
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
return { resolvedFades: list, fadeMemberDbIds: members };
|
|
7054
|
+
}, [tracks, fadesMeta]);
|
|
7055
|
+
const transition = useTransitionOps({
|
|
7056
|
+
host,
|
|
7057
|
+
adapter,
|
|
7058
|
+
activeSceneId,
|
|
7059
|
+
isConnected,
|
|
7060
|
+
isAuthenticated,
|
|
7061
|
+
sceneContext,
|
|
7062
|
+
tracks,
|
|
7063
|
+
setTracks,
|
|
7064
|
+
loadTracks,
|
|
7065
|
+
setCrossfadePairsMeta,
|
|
7066
|
+
setFadesMeta,
|
|
7067
|
+
resolvedCrossfadePairs,
|
|
7068
|
+
resolvedFades
|
|
7069
|
+
});
|
|
7070
|
+
const setGroupMute = (0, import_react27.useCallback)(
|
|
7071
|
+
(trackIds, muted) => {
|
|
7072
|
+
for (const id of trackIds) {
|
|
7073
|
+
setTracks(
|
|
7074
|
+
(prev) => prev.map((t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, muted } } : t)
|
|
7075
|
+
);
|
|
7076
|
+
host.setTrackMute(id, muted).catch(() => {
|
|
7077
|
+
});
|
|
7078
|
+
}
|
|
7079
|
+
},
|
|
7080
|
+
[host]
|
|
7081
|
+
);
|
|
7082
|
+
const setGroupSolo = (0, import_react27.useCallback)(
|
|
7083
|
+
(trackIds, solo) => {
|
|
7084
|
+
for (const id of trackIds) {
|
|
7085
|
+
setTracks(
|
|
7086
|
+
(prev) => prev.map((t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, solo } } : t)
|
|
7087
|
+
);
|
|
7088
|
+
host.setTrackSolo(id, solo).catch(() => {
|
|
7089
|
+
});
|
|
7090
|
+
}
|
|
7091
|
+
},
|
|
7092
|
+
[host]
|
|
7093
|
+
);
|
|
7094
|
+
const deleteGroup = (0, import_react27.useCallback)(
|
|
7095
|
+
async (members, cleanupKeySuffixes) => {
|
|
7096
|
+
for (const member of members) {
|
|
7097
|
+
try {
|
|
7098
|
+
await host.deleteTrack(member.engineId);
|
|
7099
|
+
} catch {
|
|
7100
|
+
}
|
|
7101
|
+
if (activeSceneId) {
|
|
7102
|
+
for (const suffix of cleanupKeySuffixes) {
|
|
7103
|
+
await host.deleteSceneData(activeSceneId, trackDataKey(member.dbId, suffix)).catch(() => {
|
|
7104
|
+
});
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
const gone = new Set(members.map((m) => m.engineId));
|
|
7109
|
+
setTracks((prev) => prev.filter((t) => !gone.has(t.handle.id)));
|
|
7110
|
+
await loadTracks(true);
|
|
7111
|
+
},
|
|
7112
|
+
[host, activeSceneId, loadTracks]
|
|
7113
|
+
);
|
|
7114
|
+
const handlers = (0, import_react27.useMemo)(
|
|
7115
|
+
() => ({
|
|
7116
|
+
promptChange: handlePromptChange,
|
|
7117
|
+
generate: (trackId) => {
|
|
7118
|
+
void handleGenerate(trackId);
|
|
7119
|
+
},
|
|
7120
|
+
shuffle: (trackId) => {
|
|
7121
|
+
void handleShuffle(trackId);
|
|
7122
|
+
},
|
|
7123
|
+
copy: (trackId) => {
|
|
7124
|
+
void handleCopy(trackId);
|
|
7125
|
+
},
|
|
7126
|
+
delete: (trackId) => {
|
|
7127
|
+
void handleDeleteTrack(trackId);
|
|
7128
|
+
},
|
|
7129
|
+
muteToggle: handleMuteToggle,
|
|
7130
|
+
soloToggle: handleSoloToggle,
|
|
7131
|
+
volumeChange: handleVolumeChange,
|
|
7132
|
+
panChange: handlePanChange,
|
|
7133
|
+
tabChange: handleTabChange,
|
|
7134
|
+
toggleDrawer: handleToggleDrawer,
|
|
7135
|
+
toggleFxDrawer,
|
|
7136
|
+
notesChange: handleNotesChange,
|
|
7137
|
+
progressChange: handleProgressChange
|
|
7138
|
+
}),
|
|
7139
|
+
[
|
|
7140
|
+
handlePromptChange,
|
|
7141
|
+
handleGenerate,
|
|
7142
|
+
handleShuffle,
|
|
7143
|
+
handleCopy,
|
|
7144
|
+
handleDeleteTrack,
|
|
7145
|
+
handleMuteToggle,
|
|
7146
|
+
handleSoloToggle,
|
|
7147
|
+
handleVolumeChange,
|
|
7148
|
+
handlePanChange,
|
|
7149
|
+
handleTabChange,
|
|
7150
|
+
handleToggleDrawer,
|
|
7151
|
+
toggleFxDrawer,
|
|
7152
|
+
handleNotesChange,
|
|
7153
|
+
handleProgressChange
|
|
7154
|
+
]
|
|
7155
|
+
);
|
|
7156
|
+
return {
|
|
7157
|
+
ui,
|
|
7158
|
+
adapter,
|
|
7159
|
+
tracks,
|
|
7160
|
+
setTracks,
|
|
7161
|
+
isLoadingTracks,
|
|
7162
|
+
loadTracks,
|
|
7163
|
+
engineToDbId,
|
|
7164
|
+
supportsMeters,
|
|
7165
|
+
trackLevels,
|
|
7166
|
+
anySolo,
|
|
7167
|
+
reorder,
|
|
7168
|
+
soundHistory,
|
|
7169
|
+
isComposing,
|
|
7170
|
+
placeholders,
|
|
7171
|
+
isAddingTrack,
|
|
7172
|
+
isExportingMidi,
|
|
7173
|
+
designerView,
|
|
7174
|
+
canCrossfade,
|
|
7175
|
+
needsContract,
|
|
7176
|
+
xfFromId,
|
|
7177
|
+
xfToId,
|
|
7178
|
+
importOpen,
|
|
7179
|
+
setImportOpen,
|
|
7180
|
+
soundImportTarget,
|
|
7181
|
+
setSoundImportTarget,
|
|
7182
|
+
handleSoundImportPick,
|
|
7183
|
+
handlePortTrack,
|
|
7184
|
+
transition,
|
|
7185
|
+
crossfadePairsMeta,
|
|
7186
|
+
fadesMeta,
|
|
7187
|
+
resolvedCrossfadePairs,
|
|
7188
|
+
crossfadeMemberDbIds,
|
|
7189
|
+
resolvedFades,
|
|
7190
|
+
fadeMemberDbIds,
|
|
7191
|
+
resolvedGenericGroups,
|
|
7192
|
+
genericGroupMemberDbIds,
|
|
7193
|
+
availableInstruments,
|
|
7194
|
+
instrumentsLoading,
|
|
7195
|
+
handlers,
|
|
7196
|
+
handleGenerate,
|
|
7197
|
+
handleShuffle,
|
|
7198
|
+
handleAddTrack,
|
|
7199
|
+
handleDeleteTrack,
|
|
7200
|
+
handleExportMidi,
|
|
7201
|
+
handlePromptChange,
|
|
7202
|
+
handleMuteToggle,
|
|
7203
|
+
handleSoloToggle,
|
|
7204
|
+
handleVolumeChange,
|
|
7205
|
+
handlePanChange,
|
|
7206
|
+
handleTabChange,
|
|
7207
|
+
handleToggleDrawer,
|
|
7208
|
+
toggleFxDrawer,
|
|
7209
|
+
handleNotesChange,
|
|
7210
|
+
handleProgressChange,
|
|
7211
|
+
handleCopy,
|
|
7212
|
+
handleFxToggle,
|
|
7213
|
+
handleFxPresetChange,
|
|
7214
|
+
handleFxDryWetChange,
|
|
7215
|
+
handleInstrumentSelect,
|
|
7216
|
+
handleShowEditor,
|
|
7217
|
+
handleBackToInstruments,
|
|
7218
|
+
handleRefreshInstruments,
|
|
7219
|
+
onAuditionNote,
|
|
7220
|
+
makeServices,
|
|
7221
|
+
setGroupMute,
|
|
7222
|
+
setGroupSolo,
|
|
7223
|
+
deleteGroup
|
|
7224
|
+
};
|
|
7225
|
+
}
|
|
7226
|
+
|
|
7227
|
+
// src/panel-core/GeneratorPanelShell.tsx
|
|
7228
|
+
var import_react28 = __toESM(require("react"));
|
|
7229
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
7230
|
+
function GeneratorPanelShell({ core, slots }) {
|
|
7231
|
+
const {
|
|
7232
|
+
ui,
|
|
7233
|
+
adapter,
|
|
7234
|
+
tracks,
|
|
7235
|
+
isLoadingTracks,
|
|
7236
|
+
supportsMeters,
|
|
7237
|
+
trackLevels,
|
|
7238
|
+
anySolo,
|
|
7239
|
+
reorder,
|
|
7240
|
+
soundHistory,
|
|
7241
|
+
isComposing,
|
|
7242
|
+
placeholders,
|
|
7243
|
+
designerView,
|
|
7244
|
+
canCrossfade,
|
|
7245
|
+
xfFromId,
|
|
7246
|
+
xfToId,
|
|
7247
|
+
importOpen,
|
|
7248
|
+
setImportOpen,
|
|
7249
|
+
soundImportTarget,
|
|
7250
|
+
setSoundImportTarget,
|
|
7251
|
+
handleSoundImportPick,
|
|
7252
|
+
handlePortTrack,
|
|
7253
|
+
transition,
|
|
7254
|
+
crossfadePairsMeta,
|
|
7255
|
+
fadesMeta,
|
|
7256
|
+
resolvedCrossfadePairs,
|
|
7257
|
+
crossfadeMemberDbIds,
|
|
7258
|
+
resolvedFades,
|
|
7259
|
+
fadeMemberDbIds,
|
|
7260
|
+
resolvedGenericGroups,
|
|
7261
|
+
genericGroupMemberDbIds,
|
|
7262
|
+
availableInstruments,
|
|
7263
|
+
instrumentsLoading,
|
|
7264
|
+
handlers,
|
|
7265
|
+
isExportingMidi,
|
|
7266
|
+
handleExportMidi,
|
|
7267
|
+
handleFxToggle,
|
|
7268
|
+
handleFxPresetChange,
|
|
7269
|
+
handleFxDryWetChange,
|
|
7270
|
+
handleInstrumentSelect,
|
|
7271
|
+
handleShowEditor,
|
|
7272
|
+
handleBackToInstruments,
|
|
7273
|
+
handleRefreshInstruments,
|
|
7274
|
+
onAuditionNote,
|
|
7275
|
+
loadTracks,
|
|
7276
|
+
makeServices,
|
|
7277
|
+
setGroupMute,
|
|
7278
|
+
setGroupSolo,
|
|
7279
|
+
deleteGroup
|
|
7280
|
+
} = core;
|
|
7281
|
+
const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
|
|
7282
|
+
const panelBus = usePanelBus(host, activeSceneId);
|
|
7283
|
+
const { identity, features } = adapter;
|
|
7284
|
+
const buildRowProps = (0, import_react28.useCallback)(
|
|
7285
|
+
(track, drag) => {
|
|
7286
|
+
const id = track.handle.id;
|
|
7287
|
+
const pickerProps = features.instrumentPicker ? {
|
|
7288
|
+
instrumentName: track.instrumentName,
|
|
7289
|
+
instrumentMissing: track.instrumentMissing,
|
|
7290
|
+
onToggleDrawer: () => handlers.toggleDrawer(id),
|
|
7291
|
+
availableInstruments,
|
|
7292
|
+
currentInstrumentPluginId: track.instrumentPluginId,
|
|
7293
|
+
onInstrumentSelect: (pluginId) => handleInstrumentSelect(id, pluginId),
|
|
7294
|
+
instrumentsLoading,
|
|
7295
|
+
onRefreshInstruments: handleRefreshInstruments,
|
|
7296
|
+
editorStage: track.editorStage,
|
|
7297
|
+
onShowEditor: () => handleShowEditor(id),
|
|
7298
|
+
onBackToInstruments: () => handleBackToInstruments(id)
|
|
7299
|
+
} : {};
|
|
7300
|
+
const importSoundProps = features.importTracks ? {
|
|
7301
|
+
onImportSound: () => setSoundImportTarget(track),
|
|
7302
|
+
importSoundLabel: adapter.sound.importSoundLabel
|
|
7303
|
+
} : {};
|
|
7304
|
+
const props = {
|
|
7305
|
+
...drag ? { drag } : {},
|
|
7306
|
+
track: { id, name: track.handle.name, role: track.role },
|
|
7307
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7308
|
+
prompt: track.prompt,
|
|
7309
|
+
runtimeState: {
|
|
7310
|
+
muted: track.runtimeState.muted,
|
|
7311
|
+
solo: track.runtimeState.solo,
|
|
7312
|
+
volume: track.runtimeState.volume,
|
|
7313
|
+
pan: track.runtimeState.pan
|
|
7314
|
+
},
|
|
7315
|
+
soloedOut: anySolo && !track.runtimeState.solo,
|
|
7316
|
+
fxDetailState: track.fxDetailState,
|
|
7317
|
+
drawerOpen: track.drawerOpen,
|
|
7318
|
+
drawerTab: track.drawerTab,
|
|
7319
|
+
onTabChange: (tab) => handlers.tabChange(id, tab),
|
|
7320
|
+
isGenerating: track.isGenerating,
|
|
7321
|
+
isAuthenticated,
|
|
7322
|
+
error: track.error,
|
|
7323
|
+
hasMidi: track.hasMidi,
|
|
7324
|
+
generationProgress: track.generationProgress,
|
|
7325
|
+
estimatedGenerationMs: identity.estimatedGenerationMs,
|
|
7326
|
+
onPromptChange: (prompt) => handlers.promptChange(id, prompt),
|
|
7327
|
+
onGenerate: () => handlers.generate(id),
|
|
7328
|
+
onShuffle: () => handlers.shuffle(id),
|
|
7329
|
+
onCopy: () => handlers.copy(id),
|
|
7330
|
+
onDelete: () => handlers.delete(id),
|
|
7331
|
+
onMuteToggle: () => handlers.muteToggle(id),
|
|
7332
|
+
onSoloToggle: () => handlers.soloToggle(id),
|
|
7333
|
+
onVolumeChange: (vol) => handlers.volumeChange(id, vol),
|
|
7334
|
+
onPanChange: (pan) => handlers.panChange(id, pan),
|
|
7335
|
+
onFxToggle: (cat, enabled) => handleFxToggle(id, cat, enabled),
|
|
7336
|
+
onFxPresetChange: (cat, idx) => handleFxPresetChange(id, cat, idx),
|
|
7337
|
+
onFxDryWetChange: (cat, val) => handleFxDryWetChange(id, cat, val),
|
|
7338
|
+
onToggleFxDrawer: () => handlers.toggleFxDrawer(id),
|
|
7339
|
+
onProgressChange: (pct) => handlers.progressChange(id, pct),
|
|
7340
|
+
accentColor: identity.accentColor,
|
|
7341
|
+
...pickerProps,
|
|
7342
|
+
soundHistory: soundHistory.list(id).entries,
|
|
7343
|
+
soundHistoryCursor: soundHistory.list(id).cursor,
|
|
7344
|
+
onRestoreSound: (i) => {
|
|
7345
|
+
void soundHistory.restoreTo(id, i);
|
|
7346
|
+
},
|
|
7347
|
+
onToggleFavorite: (i) => soundHistory.toggleFavorite(id, i),
|
|
7348
|
+
...importSoundProps,
|
|
7349
|
+
editNotes: track.editNotes,
|
|
7350
|
+
onNotesChange: (notes) => handlers.notesChange(id, notes),
|
|
7351
|
+
editBars: track.editBars,
|
|
7352
|
+
editBpm: track.editBpm,
|
|
7353
|
+
editSnap: 0.25,
|
|
7354
|
+
onAuditionNote: (pitch, vel, ms) => onAuditionNote(id, pitch, vel, ms)
|
|
7355
|
+
};
|
|
7356
|
+
return adapter.mapTrackRowProps ? adapter.mapTrackRowProps(track, props) : props;
|
|
7357
|
+
},
|
|
7358
|
+
[
|
|
7359
|
+
features.instrumentPicker,
|
|
7360
|
+
features.importTracks,
|
|
7361
|
+
adapter,
|
|
7362
|
+
supportsMeters,
|
|
7363
|
+
trackLevels,
|
|
7364
|
+
anySolo,
|
|
7365
|
+
isAuthenticated,
|
|
7366
|
+
identity,
|
|
7367
|
+
handlers,
|
|
7368
|
+
availableInstruments,
|
|
7369
|
+
instrumentsLoading,
|
|
7370
|
+
handleInstrumentSelect,
|
|
7371
|
+
handleRefreshInstruments,
|
|
7372
|
+
handleShowEditor,
|
|
7373
|
+
handleBackToInstruments,
|
|
7374
|
+
setSoundImportTarget,
|
|
7375
|
+
soundHistory,
|
|
7376
|
+
handleFxToggle,
|
|
7377
|
+
handleFxPresetChange,
|
|
7378
|
+
handleFxDryWetChange,
|
|
7379
|
+
onAuditionNote
|
|
7380
|
+
]
|
|
7381
|
+
);
|
|
7382
|
+
if (!activeSceneId) {
|
|
7383
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7384
|
+
"div",
|
|
7385
|
+
{
|
|
7386
|
+
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
7387
|
+
className: "flex items-center justify-center py-8",
|
|
7388
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7389
|
+
"button",
|
|
7390
|
+
{
|
|
7391
|
+
onClick: () => onSelectScene?.(),
|
|
7392
|
+
className: "text-sas-muted text-xs hover:text-sas-accent transition-colors underline underline-offset-2",
|
|
7393
|
+
children: "Select a Scene"
|
|
7394
|
+
}
|
|
7395
|
+
)
|
|
7396
|
+
}
|
|
7397
|
+
);
|
|
7398
|
+
}
|
|
7399
|
+
if (!sceneContext?.hasContract) {
|
|
7400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7401
|
+
"div",
|
|
7402
|
+
{
|
|
7403
|
+
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
7404
|
+
className: "flex items-center justify-center py-8",
|
|
7405
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7406
|
+
"button",
|
|
7407
|
+
{
|
|
7408
|
+
onClick: () => onOpenContract?.(),
|
|
7409
|
+
className: "text-sas-muted text-xs hover:text-sas-accent transition-colors underline underline-offset-2",
|
|
7410
|
+
children: "Generate a Contract"
|
|
7411
|
+
}
|
|
7412
|
+
)
|
|
7413
|
+
}
|
|
7414
|
+
);
|
|
7415
|
+
}
|
|
7416
|
+
if (features.bulkComposePlaceholders && isComposing) {
|
|
7417
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
7418
|
+
}
|
|
7419
|
+
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
7420
|
+
if (activePlaceholders.length > 0) {
|
|
7421
|
+
const tracksByDbId = /* @__PURE__ */ new Map();
|
|
7422
|
+
for (const t of tracks) {
|
|
7423
|
+
tracksByDbId.set(t.handle.dbId, t);
|
|
7424
|
+
if (t.handle.id !== t.handle.dbId) {
|
|
7425
|
+
tracksByDbId.set(t.handle.id, t);
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
7429
|
+
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
7430
|
+
if (loadedTrack) {
|
|
7431
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
7432
|
+
}
|
|
7433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7434
|
+
"div",
|
|
7435
|
+
{
|
|
7436
|
+
"data-testid": "bulk-placeholder-track",
|
|
7437
|
+
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
7438
|
+
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
7439
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
7440
|
+
},
|
|
7441
|
+
ph.id
|
|
7442
|
+
);
|
|
7443
|
+
}) });
|
|
7444
|
+
}
|
|
7445
|
+
const groupCtx = {
|
|
7446
|
+
services: makeServices(),
|
|
7447
|
+
anySolo,
|
|
7448
|
+
supportsMeters,
|
|
7449
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7450
|
+
handlers,
|
|
7451
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
7452
|
+
setGroupMute,
|
|
7453
|
+
setGroupSolo,
|
|
7454
|
+
deleteGroup
|
|
7455
|
+
};
|
|
7456
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
7457
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7458
|
+
ImportTrackModal,
|
|
7459
|
+
{
|
|
7460
|
+
host,
|
|
7461
|
+
open: importOpen,
|
|
7462
|
+
onClose: () => setImportOpen(false),
|
|
7463
|
+
onImported: () => {
|
|
7464
|
+
void loadTracks(true);
|
|
7465
|
+
},
|
|
7466
|
+
onPortTrack: host.readImportableTrackMidi ? handlePortTrack : void 0,
|
|
7467
|
+
testIdPrefix: `${identity.familyKey}-import`
|
|
7468
|
+
}
|
|
7469
|
+
),
|
|
7470
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7471
|
+
ImportTrackModal,
|
|
7472
|
+
{
|
|
7473
|
+
host,
|
|
7474
|
+
mode: "sound",
|
|
7475
|
+
open: !!soundImportTarget,
|
|
7476
|
+
title: adapter.sound.importSoundLabel,
|
|
7477
|
+
onClose: () => setSoundImportTarget(null),
|
|
7478
|
+
onImported: () => {
|
|
7479
|
+
},
|
|
7480
|
+
onPick: handleSoundImportPick,
|
|
7481
|
+
testIdPrefix: `${identity.familyKey}-sound-import`
|
|
7482
|
+
}
|
|
7483
|
+
),
|
|
7484
|
+
slots?.modals,
|
|
7485
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7486
|
+
TransitionDesigner,
|
|
7487
|
+
{
|
|
7488
|
+
host,
|
|
7489
|
+
fromSceneId: xfFromId,
|
|
7490
|
+
toSceneId: xfToId,
|
|
7491
|
+
transitionSceneId: activeSceneId ?? "",
|
|
7492
|
+
excludeSourceDbIds: [
|
|
7493
|
+
...crossfadePairsMeta.flatMap((p) => [p.originSourceDbId, p.targetSourceDbId]),
|
|
7494
|
+
...fadesMeta.map((f) => f.meta.sourceTrackDbId)
|
|
7495
|
+
],
|
|
7496
|
+
onCreateCrossfade: transition.handleCreateCrossfade,
|
|
7497
|
+
onCreateFade: transition.handleCreateFade,
|
|
7498
|
+
familyLabel: identity.familyLabel,
|
|
7499
|
+
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
7500
|
+
}
|
|
7501
|
+
) }),
|
|
7502
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
7503
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7504
|
+
PanelMasterStrip,
|
|
7505
|
+
{
|
|
7506
|
+
bus: panelBus.bus,
|
|
7507
|
+
availableFx: panelBus.availableFx,
|
|
7508
|
+
fxLoading: panelBus.fxLoading,
|
|
7509
|
+
soloedOut: anySolo && !panelBus.bus.soloed,
|
|
7510
|
+
fxPickerOpen: panelBus.fxPickerOpen,
|
|
7511
|
+
onToggleFxPicker: panelBus.setFxPickerOpen,
|
|
7512
|
+
onRefreshFx: panelBus.refreshFx,
|
|
7513
|
+
onVolumeChange: panelBus.onVolumeChange,
|
|
7514
|
+
onMuteToggle: panelBus.onMuteToggle,
|
|
7515
|
+
onSoloToggle: panelBus.onSoloToggle,
|
|
7516
|
+
onAddFx: panelBus.onAddFx,
|
|
7517
|
+
onRemoveFx: panelBus.onRemoveFx,
|
|
7518
|
+
onToggleFxEnabled: panelBus.onToggleFxEnabled,
|
|
7519
|
+
onShowFxEditor: panelBus.onShowFxEditor
|
|
7520
|
+
}
|
|
7521
|
+
),
|
|
7522
|
+
slots?.beforeRows,
|
|
7523
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7524
|
+
CrossfadeTrackRow,
|
|
7525
|
+
{
|
|
7526
|
+
accentColor: identity.transitionAccentColor,
|
|
7527
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7528
|
+
sliderPos: pair.sliderPos,
|
|
7529
|
+
origin: {
|
|
7530
|
+
trackId: pair.origin.handle.id,
|
|
7531
|
+
name: pair.origin.handle.name,
|
|
7532
|
+
role: pair.origin.role,
|
|
7533
|
+
sourceName: pair.originSourceName,
|
|
7534
|
+
soundLabel: pair.originSoundLabel,
|
|
7535
|
+
runtimeState: pair.origin.runtimeState
|
|
7536
|
+
},
|
|
7537
|
+
target: {
|
|
7538
|
+
trackId: pair.target.handle.id,
|
|
7539
|
+
name: pair.target.handle.name,
|
|
7540
|
+
role: pair.target.role,
|
|
7541
|
+
sourceName: pair.targetSourceName,
|
|
7542
|
+
soundLabel: pair.targetSoundLabel,
|
|
7543
|
+
runtimeState: pair.target.runtimeState
|
|
7544
|
+
},
|
|
7545
|
+
onMuteToggle: () => transition.handleCrossfadeMute(pair),
|
|
7546
|
+
onSoloToggle: () => transition.handleCrossfadeSolo(pair),
|
|
7547
|
+
onVolumeChange: (slot, vol) => handlers.volumeChange(
|
|
7548
|
+
slot === "origin" ? pair.origin.handle.id : pair.target.handle.id,
|
|
7549
|
+
vol
|
|
7550
|
+
),
|
|
7551
|
+
onPanChange: (slot, pan) => handlers.panChange(
|
|
7552
|
+
slot === "origin" ? pair.origin.handle.id : pair.target.handle.id,
|
|
7553
|
+
pan
|
|
7554
|
+
),
|
|
7555
|
+
onSliderChange: (pos) => transition.handleCrossfadeSlider(pair, pos),
|
|
7556
|
+
onDelete: () => transition.handleCrossfadeDelete(pair)
|
|
7557
|
+
},
|
|
7558
|
+
pair.groupId
|
|
7559
|
+
)),
|
|
7560
|
+
resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7561
|
+
FadeTrackRow,
|
|
7562
|
+
{
|
|
7563
|
+
accentColor: identity.transitionAccentColor,
|
|
7564
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7565
|
+
direction: fade.meta.direction,
|
|
7566
|
+
gesture: fade.meta.gesture,
|
|
7567
|
+
sliderPos: fade.meta.sliderPos,
|
|
7568
|
+
layer: {
|
|
7569
|
+
trackId: fade.track.handle.id,
|
|
7570
|
+
name: fade.track.handle.name,
|
|
7571
|
+
role: fade.track.role,
|
|
7572
|
+
sourceName: fade.meta.sourceName,
|
|
7573
|
+
soundLabel: fade.meta.soundLabel,
|
|
7574
|
+
runtimeState: fade.track.runtimeState
|
|
7575
|
+
},
|
|
7576
|
+
onMuteToggle: () => handlers.muteToggle(fade.track.handle.id),
|
|
7577
|
+
onSoloToggle: () => handlers.soloToggle(fade.track.handle.id),
|
|
7578
|
+
onVolumeChange: (vol) => handlers.volumeChange(fade.track.handle.id, vol),
|
|
7579
|
+
onPanChange: (pan) => handlers.panChange(fade.track.handle.id, pan),
|
|
7580
|
+
onSliderChange: (pos) => transition.handleFadeSlider(fade, pos),
|
|
7581
|
+
onDelete: () => transition.handleFadeDelete(fade)
|
|
7582
|
+
},
|
|
7583
|
+
fade.dbId
|
|
7584
|
+
)),
|
|
7585
|
+
(adapter.groupExtensions ?? []).flatMap(
|
|
7586
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react28.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
|
|
7587
|
+
),
|
|
7588
|
+
tracks.map((track, index) => {
|
|
7589
|
+
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
7590
|
+
return null;
|
|
7591
|
+
}
|
|
7592
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
7593
|
+
}),
|
|
7594
|
+
slots?.afterRows
|
|
7595
|
+
] })),
|
|
7596
|
+
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
7597
|
+
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
7598
|
+
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
7599
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
7600
|
+
"button",
|
|
7601
|
+
{
|
|
7602
|
+
"data-testid": "export-midi-tracks-button",
|
|
7603
|
+
onClick: handleExportMidi,
|
|
7604
|
+
disabled: exportDisabled,
|
|
7605
|
+
title: isExportingMidi ? "Exporting..." : !hasAnyMidi ? "Generate MIDI on at least one track first" : "Export all tracks as a ZIP of .mid files",
|
|
7606
|
+
className: `w-full px-2 py-1.5 text-[10px] uppercase tracking-wide rounded-sm border transition-colors ${exportDisabled ? "text-sas-muted/40 border-transparent hover:border-sas-accent cursor-not-allowed" : "text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent"}`,
|
|
7607
|
+
children: isExportingMidi ? "Exporting..." : "Export Tracks"
|
|
7608
|
+
}
|
|
7609
|
+
) });
|
|
7610
|
+
})()
|
|
7611
|
+
] });
|
|
7612
|
+
}
|
|
7613
|
+
|
|
7614
|
+
// src/panel-core/surge-sound-adapter.ts
|
|
7615
|
+
async function getInstrument(host, trackId) {
|
|
7616
|
+
try {
|
|
7617
|
+
const plugins = await host.getTrackPlugins(trackId);
|
|
7618
|
+
const instrument = plugins.find(
|
|
7619
|
+
(p) => !p.name.includes("Volume") && !p.name.includes("Pan") && !p.name.includes("Level")
|
|
7620
|
+
);
|
|
7621
|
+
if (!instrument) return null;
|
|
7622
|
+
return { index: instrument.index, isRaw: !instrument.name.includes("Surge") };
|
|
7623
|
+
} catch {
|
|
7624
|
+
return null;
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
function createSurgeSoundAdapter(host, overrides = {}) {
|
|
7628
|
+
const applySound = async (trackId, descriptor) => {
|
|
7629
|
+
const { state, stateType } = descriptor;
|
|
7630
|
+
const inst = await getInstrument(host, trackId);
|
|
7631
|
+
if (!inst) return;
|
|
7632
|
+
if (stateType === "raw") await host.setRawPluginState(trackId, inst.index, state);
|
|
7633
|
+
else await host.setPluginState(trackId, inst.index, state);
|
|
7634
|
+
};
|
|
7635
|
+
return {
|
|
7636
|
+
applySound,
|
|
7637
|
+
captureSoundDescriptor: async (trackId) => {
|
|
7638
|
+
const inst = await getInstrument(host, trackId);
|
|
7639
|
+
if (!inst) return null;
|
|
7640
|
+
const state = inst.isRaw ? await host.getRawPluginState(trackId, inst.index) : await host.getPluginState(trackId, inst.index);
|
|
7641
|
+
return { descriptor: { state, stateType: inst.isRaw ? "raw" : "valuetree" } };
|
|
7642
|
+
},
|
|
7643
|
+
copySnapshot: async (trackId, snap) => {
|
|
7644
|
+
if (snap.kind !== "preset") return "default";
|
|
7645
|
+
await applySound(trackId, { state: snap.state, stateType: snap.stateType });
|
|
7646
|
+
await host.persistTrackPresetState?.(trackId, {
|
|
7647
|
+
state: snap.state,
|
|
7648
|
+
stateType: snap.stateType ?? "valuetree",
|
|
7649
|
+
name: snap.label
|
|
7650
|
+
}).catch(() => {
|
|
7651
|
+
});
|
|
7652
|
+
return snap.label;
|
|
7653
|
+
},
|
|
7654
|
+
descriptorFromSnapshot: (snap) => {
|
|
7655
|
+
const preset = snap;
|
|
7656
|
+
return { state: preset.state, stateType: preset.stateType };
|
|
7657
|
+
},
|
|
7658
|
+
acceptedSnapshotKind: "preset",
|
|
7659
|
+
historyMax: overrides.historyMax ?? 12,
|
|
7660
|
+
importSoundLabel: overrides.importSoundLabel ?? "Import Preset",
|
|
7661
|
+
importNoun: "preset",
|
|
7662
|
+
previousSoundLabel: "Previous preset"
|
|
7663
|
+
};
|
|
7664
|
+
}
|
|
7665
|
+
|
|
5046
7666
|
// src/constants/sdk-version.ts
|
|
5047
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
7667
|
+
var PLUGIN_SDK_VERSION = "2.37.0";
|
|
5048
7668
|
|
|
5049
7669
|
// src/utils/format-concurrent-tracks.ts
|
|
5050
7670
|
function formatConcurrentTracks(ctx) {
|
|
@@ -5211,6 +7831,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5211
7831
|
FadeTrackRow,
|
|
5212
7832
|
FxToggleBar,
|
|
5213
7833
|
GUTTER_W,
|
|
7834
|
+
GeneratorPanelShell,
|
|
5214
7835
|
ImportTrackModal,
|
|
5215
7836
|
InstrumentDrawer,
|
|
5216
7837
|
LevelMeter,
|
|
@@ -5219,6 +7840,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5219
7840
|
PLUGIN_SDK_VERSION,
|
|
5220
7841
|
PX_PER_BEAT,
|
|
5221
7842
|
PanSlider,
|
|
7843
|
+
PanelMasterStrip,
|
|
5222
7844
|
PianoRollEditor,
|
|
5223
7845
|
PluginError,
|
|
5224
7846
|
RESIZE_HANDLE_PX,
|
|
@@ -5248,6 +7870,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5248
7870
|
cellToPx,
|
|
5249
7871
|
centerScrollTop,
|
|
5250
7872
|
computePeaks,
|
|
7873
|
+
createSurgeSoundAdapter,
|
|
5251
7874
|
dbIdsFromKeys,
|
|
5252
7875
|
dbToSlider,
|
|
5253
7876
|
defaultFadeGesture,
|
|
@@ -5255,16 +7878,21 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5255
7878
|
formatConcurrentTracks,
|
|
5256
7879
|
hashString,
|
|
5257
7880
|
moveItem,
|
|
7881
|
+
newTrackState,
|
|
5258
7882
|
normalizeSlots,
|
|
5259
7883
|
padPair,
|
|
5260
7884
|
padSlots,
|
|
5261
7885
|
parseCrossfadePairs,
|
|
5262
7886
|
parseFades,
|
|
7887
|
+
parseLLMNoteResponse,
|
|
7888
|
+
parseTrackGroups,
|
|
5263
7889
|
pickTopKWeighted,
|
|
5264
7890
|
pitchToName,
|
|
7891
|
+
pluginFxToToggleFx,
|
|
5265
7892
|
pxToCell,
|
|
5266
7893
|
reconcileSlots,
|
|
5267
7894
|
resizeNoteDuration,
|
|
7895
|
+
resolveTrackGroups,
|
|
5268
7896
|
rowKey,
|
|
5269
7897
|
rowType,
|
|
5270
7898
|
scorePromptMatch,
|
|
@@ -5273,14 +7901,18 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5273
7901
|
soundIdentity,
|
|
5274
7902
|
synthesizeCuePoints,
|
|
5275
7903
|
tokenizePrompt,
|
|
7904
|
+
trackDataKey,
|
|
5276
7905
|
transposeNotes,
|
|
5277
7906
|
useAnySolo,
|
|
7907
|
+
useGeneratorPanelCore,
|
|
7908
|
+
usePanelBus,
|
|
5278
7909
|
useSceneState,
|
|
5279
7910
|
useSoundHistory,
|
|
5280
7911
|
useTrackLevel,
|
|
5281
7912
|
useTrackLevels,
|
|
5282
7913
|
useTrackMeter,
|
|
5283
7914
|
useTrackReorder,
|
|
7915
|
+
useTransitionOps,
|
|
5284
7916
|
useTransportPlaying
|
|
5285
7917
|
});
|
|
5286
7918
|
//# sourceMappingURL=index.js.map
|