@signalsandsorcery/plugin-sdk 2.34.1 → 2.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +679 -74
- package/dist/index.d.ts +679 -74
- package/dist/index.js +2342 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2332 -1
- 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,
|
|
@@ -90,6 +91,7 @@ __export(index_exports, {
|
|
|
90
91
|
cellToPx: () => cellToPx,
|
|
91
92
|
centerScrollTop: () => centerScrollTop,
|
|
92
93
|
computePeaks: () => computePeaks,
|
|
94
|
+
createSurgeSoundAdapter: () => createSurgeSoundAdapter,
|
|
93
95
|
dbIdsFromKeys: () => dbIdsFromKeys,
|
|
94
96
|
dbToSlider: () => dbToSlider,
|
|
95
97
|
defaultFadeGesture: () => defaultFadeGesture,
|
|
@@ -97,16 +99,21 @@ __export(index_exports, {
|
|
|
97
99
|
formatConcurrentTracks: () => formatConcurrentTracks,
|
|
98
100
|
hashString: () => hashString,
|
|
99
101
|
moveItem: () => moveItem,
|
|
102
|
+
newTrackState: () => newTrackState,
|
|
100
103
|
normalizeSlots: () => normalizeSlots,
|
|
101
104
|
padPair: () => padPair,
|
|
102
105
|
padSlots: () => padSlots,
|
|
103
106
|
parseCrossfadePairs: () => parseCrossfadePairs,
|
|
104
107
|
parseFades: () => parseFades,
|
|
108
|
+
parseLLMNoteResponse: () => parseLLMNoteResponse,
|
|
109
|
+
parseTrackGroups: () => parseTrackGroups,
|
|
105
110
|
pickTopKWeighted: () => pickTopKWeighted,
|
|
106
111
|
pitchToName: () => pitchToName,
|
|
112
|
+
pluginFxToToggleFx: () => pluginFxToToggleFx,
|
|
107
113
|
pxToCell: () => pxToCell,
|
|
108
114
|
reconcileSlots: () => reconcileSlots,
|
|
109
115
|
resizeNoteDuration: () => resizeNoteDuration,
|
|
116
|
+
resolveTrackGroups: () => resolveTrackGroups,
|
|
110
117
|
rowKey: () => rowKey,
|
|
111
118
|
rowType: () => rowType,
|
|
112
119
|
scorePromptMatch: () => scorePromptMatch,
|
|
@@ -115,14 +122,17 @@ __export(index_exports, {
|
|
|
115
122
|
soundIdentity: () => soundIdentity,
|
|
116
123
|
synthesizeCuePoints: () => synthesizeCuePoints,
|
|
117
124
|
tokenizePrompt: () => tokenizePrompt,
|
|
125
|
+
trackDataKey: () => trackDataKey,
|
|
118
126
|
transposeNotes: () => transposeNotes,
|
|
119
127
|
useAnySolo: () => useAnySolo,
|
|
128
|
+
useGeneratorPanelCore: () => useGeneratorPanelCore,
|
|
120
129
|
useSceneState: () => useSceneState,
|
|
121
130
|
useSoundHistory: () => useSoundHistory,
|
|
122
131
|
useTrackLevel: () => useTrackLevel,
|
|
123
132
|
useTrackLevels: () => useTrackLevels,
|
|
124
133
|
useTrackMeter: () => useTrackMeter,
|
|
125
134
|
useTrackReorder: () => useTrackReorder,
|
|
135
|
+
useTransitionOps: () => useTransitionOps,
|
|
126
136
|
useTransportPlaying: () => useTransportPlaying
|
|
127
137
|
});
|
|
128
138
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -4882,6 +4892,9 @@ function synthesizeCuePoints({
|
|
|
4882
4892
|
};
|
|
4883
4893
|
}
|
|
4884
4894
|
|
|
4895
|
+
// src/panel-core/useGeneratorPanelCore.tsx
|
|
4896
|
+
var import_react25 = require("react");
|
|
4897
|
+
|
|
4885
4898
|
// src/hooks/useSceneState.ts
|
|
4886
4899
|
var import_react21 = require("react");
|
|
4887
4900
|
function useSceneState(activeSceneId, initialValue) {
|
|
@@ -5043,8 +5056,2326 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5043
5056
|
);
|
|
5044
5057
|
}
|
|
5045
5058
|
|
|
5059
|
+
// src/panel-core/track-state.ts
|
|
5060
|
+
function newTrackState(handle, overrides = {}) {
|
|
5061
|
+
return {
|
|
5062
|
+
handle,
|
|
5063
|
+
prompt: "",
|
|
5064
|
+
role: "",
|
|
5065
|
+
runtimeState: { id: handle.id, muted: false, solo: false, volume: 0.75, pan: 0 },
|
|
5066
|
+
fxDetailState: { ...EMPTY_FX_DETAIL_STATE },
|
|
5067
|
+
drawerOpen: false,
|
|
5068
|
+
drawerTab: "fx",
|
|
5069
|
+
editorStage: false,
|
|
5070
|
+
isGenerating: false,
|
|
5071
|
+
error: null,
|
|
5072
|
+
hasMidi: false,
|
|
5073
|
+
generationProgress: 0,
|
|
5074
|
+
editNotes: [],
|
|
5075
|
+
editBars: 4,
|
|
5076
|
+
editBpm: 120,
|
|
5077
|
+
instrumentPluginId: handle.instrumentPluginId ?? null,
|
|
5078
|
+
instrumentName: handle.instrumentName ?? null,
|
|
5079
|
+
instrumentMissing: false,
|
|
5080
|
+
shuffleHistory: /* @__PURE__ */ new Set(),
|
|
5081
|
+
...overrides
|
|
5082
|
+
};
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
// src/panel-core/panel-helpers.ts
|
|
5086
|
+
function trackDataKey(dbId, suffix) {
|
|
5087
|
+
return `track:${dbId}:${suffix}`;
|
|
5088
|
+
}
|
|
5089
|
+
function pluginFxToToggleFx(sdkState) {
|
|
5090
|
+
const result = { ...EMPTY_FX_DETAIL_STATE };
|
|
5091
|
+
for (const category of ["eq", "compressor", "chorus", "phaser", "delay", "reverb"]) {
|
|
5092
|
+
const sdkCat = sdkState[category];
|
|
5093
|
+
if (sdkCat) {
|
|
5094
|
+
result[category] = {
|
|
5095
|
+
enabled: sdkCat.enabled,
|
|
5096
|
+
presetIndex: sdkCat.presetIndex,
|
|
5097
|
+
dryWet: sdkCat.dryWet
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
return result;
|
|
5102
|
+
}
|
|
5103
|
+
function parseLLMNoteResponse(content) {
|
|
5104
|
+
try {
|
|
5105
|
+
let jsonStr = content.trim();
|
|
5106
|
+
const fenceMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
5107
|
+
if (fenceMatch) {
|
|
5108
|
+
jsonStr = fenceMatch[1].trim();
|
|
5109
|
+
}
|
|
5110
|
+
const parsed = JSON.parse(jsonStr);
|
|
5111
|
+
if (typeof parsed !== "object" || parsed === null || !("notes" in parsed)) {
|
|
5112
|
+
return null;
|
|
5113
|
+
}
|
|
5114
|
+
const obj = parsed;
|
|
5115
|
+
if (!Array.isArray(obj.notes)) {
|
|
5116
|
+
return null;
|
|
5117
|
+
}
|
|
5118
|
+
const validNotes = [];
|
|
5119
|
+
for (const raw of obj.notes) {
|
|
5120
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
5121
|
+
const note = raw;
|
|
5122
|
+
const pitch = typeof note.pitch === "number" ? note.pitch : NaN;
|
|
5123
|
+
const startBeat = typeof note.startBeat === "number" ? note.startBeat : NaN;
|
|
5124
|
+
const durationBeats = typeof note.durationBeats === "number" ? note.durationBeats : NaN;
|
|
5125
|
+
const velocity = typeof note.velocity === "number" ? note.velocity : NaN;
|
|
5126
|
+
if (!isNaN(pitch) && pitch >= 0 && pitch <= 127 && !isNaN(startBeat) && startBeat >= 0 && !isNaN(durationBeats) && durationBeats > 0 && !isNaN(velocity) && velocity >= 1 && velocity <= 127) {
|
|
5127
|
+
validNotes.push({
|
|
5128
|
+
pitch: Math.round(pitch),
|
|
5129
|
+
startBeat,
|
|
5130
|
+
durationBeats,
|
|
5131
|
+
velocity: Math.round(velocity)
|
|
5132
|
+
});
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
const role = typeof obj.role === "string" ? obj.role : void 0;
|
|
5136
|
+
return { notes: validNotes, role };
|
|
5137
|
+
} catch {
|
|
5138
|
+
return null;
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
// src/panel-core/group-meta.ts
|
|
5143
|
+
function parseTrackGroups(sceneData, spec) {
|
|
5144
|
+
const pattern = new RegExp(`^track:(.+):${spec.metaKey}$`);
|
|
5145
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5146
|
+
for (const [key, val] of Object.entries(sceneData)) {
|
|
5147
|
+
const match = pattern.exec(key);
|
|
5148
|
+
if (!match) continue;
|
|
5149
|
+
const meta = spec.asMeta(val);
|
|
5150
|
+
if (!meta) continue;
|
|
5151
|
+
const groupId = spec.groupIdOf(meta);
|
|
5152
|
+
const list = groups.get(groupId) ?? [];
|
|
5153
|
+
list.push({ dbId: match[1], meta });
|
|
5154
|
+
groups.set(groupId, list);
|
|
5155
|
+
}
|
|
5156
|
+
const out = [];
|
|
5157
|
+
for (const [groupId, members] of groups) {
|
|
5158
|
+
if (spec.sortMembers) members.sort(spec.sortMembers);
|
|
5159
|
+
out.push({ groupId, members });
|
|
5160
|
+
}
|
|
5161
|
+
return out;
|
|
5162
|
+
}
|
|
5163
|
+
function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
|
|
5164
|
+
const byDbId = /* @__PURE__ */ new Map();
|
|
5165
|
+
for (const t of tracks) byDbId.set(getDbId(t), t);
|
|
5166
|
+
const resolved = [];
|
|
5167
|
+
const memberDbIds = /* @__PURE__ */ new Set();
|
|
5168
|
+
const staleMemberDbIds = [];
|
|
5169
|
+
for (const parsed of parsedGroups) {
|
|
5170
|
+
const live = { groupId: parsed.groupId, members: [] };
|
|
5171
|
+
for (const member of parsed.members) {
|
|
5172
|
+
const track = byDbId.get(member.dbId);
|
|
5173
|
+
if (track) live.members.push({ dbId: member.dbId, meta: member.meta, track });
|
|
5174
|
+
else staleMemberDbIds.push(member.dbId);
|
|
5175
|
+
}
|
|
5176
|
+
if (live.members.length === 0) continue;
|
|
5177
|
+
const complete = opts.isComplete ? opts.isComplete(live, parsed) : live.members.length === parsed.members.length;
|
|
5178
|
+
if (!complete) continue;
|
|
5179
|
+
resolved.push(live);
|
|
5180
|
+
for (const m of live.members) memberDbIds.add(m.dbId);
|
|
5181
|
+
}
|
|
5182
|
+
return { resolved, memberDbIds, staleMemberDbIds };
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
// src/panel-core/useTransitionOps.ts
|
|
5186
|
+
var import_react24 = require("react");
|
|
5187
|
+
function useTransitionOps({
|
|
5188
|
+
host,
|
|
5189
|
+
adapter,
|
|
5190
|
+
activeSceneId,
|
|
5191
|
+
isConnected,
|
|
5192
|
+
isAuthenticated,
|
|
5193
|
+
sceneContext,
|
|
5194
|
+
tracks,
|
|
5195
|
+
setTracks,
|
|
5196
|
+
loadTracks,
|
|
5197
|
+
setCrossfadePairsMeta,
|
|
5198
|
+
setFadesMeta,
|
|
5199
|
+
resolvedCrossfadePairs,
|
|
5200
|
+
resolvedFades
|
|
5201
|
+
}) {
|
|
5202
|
+
const { identity } = adapter;
|
|
5203
|
+
const appliedFadeAutomationRef = (0, import_react24.useRef)(/* @__PURE__ */ new Set());
|
|
5204
|
+
const applyCrossfadeAutomation = (0, import_react24.useCallback)(
|
|
5205
|
+
async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
|
|
5206
|
+
if (host.setTrackVolumeAutomation) {
|
|
5207
|
+
const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
|
|
5208
|
+
await host.setTrackVolumeAutomation(originTrackId, curves.origin).catch(() => {
|
|
5209
|
+
});
|
|
5210
|
+
await host.setTrackVolumeAutomation(targetTrackId, curves.target).catch(() => {
|
|
5211
|
+
});
|
|
5212
|
+
} else {
|
|
5213
|
+
await host.setTrackVolume(originTrackId, EQUAL_POWER_GAIN).catch(() => {
|
|
5214
|
+
});
|
|
5215
|
+
await host.setTrackVolume(targetTrackId, EQUAL_POWER_GAIN).catch(() => {
|
|
5216
|
+
});
|
|
5217
|
+
}
|
|
5218
|
+
},
|
|
5219
|
+
[host]
|
|
5220
|
+
);
|
|
5221
|
+
const applyFadeAutomation = (0, import_react24.useCallback)(
|
|
5222
|
+
async (trackId, direction, bars, bpm, sliderPos, gesture) => {
|
|
5223
|
+
if (!host.setTrackVolumeAutomation) return;
|
|
5224
|
+
const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
|
|
5225
|
+
await host.setTrackVolumeAutomation(trackId, points).catch(() => {
|
|
5226
|
+
});
|
|
5227
|
+
},
|
|
5228
|
+
[host]
|
|
5229
|
+
);
|
|
5230
|
+
const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react24.useState)(false);
|
|
5231
|
+
const handleCreateCrossfade = (0, import_react24.useCallback)(
|
|
5232
|
+
async (origin, target) => {
|
|
5233
|
+
const scene = activeSceneId;
|
|
5234
|
+
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
5235
|
+
const toSceneId = sceneContext?.transitionToSceneId ?? "";
|
|
5236
|
+
if (!scene) throw new Error("No active scene.");
|
|
5237
|
+
if (!isConnected) throw new Error("Systems not connected.");
|
|
5238
|
+
if (!isAuthenticated) throw new Error("Please sign in to generate the bridge.");
|
|
5239
|
+
if (tracks.length + 2 > identity.maxTracks) {
|
|
5240
|
+
throw new Error("Not enough track slots for a crossfade.");
|
|
5241
|
+
}
|
|
5242
|
+
setIsCreatingCrossfade(true);
|
|
5243
|
+
const created = [];
|
|
5244
|
+
try {
|
|
5245
|
+
const role = target.role ?? origin.role ?? "";
|
|
5246
|
+
const mc = await host.getMusicalContext();
|
|
5247
|
+
const [originMidi, targetMidi, originKey, targetKey] = await Promise.all([
|
|
5248
|
+
host.readImportableTrackMidi ? host.readImportableTrackMidi(origin.dbId) : Promise.resolve({ clips: [] }),
|
|
5249
|
+
host.readImportableTrackMidi ? host.readImportableTrackMidi(target.dbId) : Promise.resolve({ clips: [] }),
|
|
5250
|
+
host.getSceneKey ? host.getSceneKey(fromSceneId) : Promise.resolve(null),
|
|
5251
|
+
host.getSceneKey ? host.getSceneKey(toSceneId) : Promise.resolve(null)
|
|
5252
|
+
]);
|
|
5253
|
+
const userPrompt = buildCrossfadeInpaintPrompt({
|
|
5254
|
+
role,
|
|
5255
|
+
bars: mc.bars,
|
|
5256
|
+
originName: origin.name,
|
|
5257
|
+
targetName: target.name,
|
|
5258
|
+
originKey: originKey ? `${originKey.key} ${originKey.mode}` : null,
|
|
5259
|
+
targetKey: targetKey ? `${targetKey.key} ${targetKey.mode}` : null,
|
|
5260
|
+
originNotes: originMidi.clips[0]?.notes ?? [],
|
|
5261
|
+
targetNotes: targetMidi.clips[0]?.notes ?? []
|
|
5262
|
+
});
|
|
5263
|
+
const llm = await host.generateWithLLM({
|
|
5264
|
+
system: adapter.buildSystemPrompt(host.getValidRoles()),
|
|
5265
|
+
user: userPrompt,
|
|
5266
|
+
responseFormat: "json"
|
|
5267
|
+
});
|
|
5268
|
+
const parsed = adapter.parseNotesResponse(llm.content);
|
|
5269
|
+
if (!parsed || parsed.notes.length === 0) {
|
|
5270
|
+
throw new Error("The bridge generator returned no notes.");
|
|
5271
|
+
}
|
|
5272
|
+
const notes = await host.postProcessMidi(parsed.notes, {
|
|
5273
|
+
quantize: true,
|
|
5274
|
+
removeOverlaps: true
|
|
5275
|
+
});
|
|
5276
|
+
const clip = {
|
|
5277
|
+
startTime: 0,
|
|
5278
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
5279
|
+
tempo: mc.bpm,
|
|
5280
|
+
notes
|
|
5281
|
+
};
|
|
5282
|
+
const top = await host.createTrack({
|
|
5283
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-xf-o`,
|
|
5284
|
+
...adapter.createTrackOptions()
|
|
5285
|
+
});
|
|
5286
|
+
created.push(top);
|
|
5287
|
+
const bottom = await host.createTrack({
|
|
5288
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-xf-t`,
|
|
5289
|
+
...adapter.createTrackOptions()
|
|
5290
|
+
});
|
|
5291
|
+
created.push(bottom);
|
|
5292
|
+
if (role) {
|
|
5293
|
+
await host.setTrackRole(top.id, role).catch(() => {
|
|
5294
|
+
});
|
|
5295
|
+
await host.setTrackRole(bottom.id, role).catch(() => {
|
|
5296
|
+
});
|
|
5297
|
+
}
|
|
5298
|
+
await host.writeMidiClip(top.id, clip);
|
|
5299
|
+
await host.writeMidiClip(bottom.id, clip);
|
|
5300
|
+
const copySound = async (newTrackId, sourceDbId) => {
|
|
5301
|
+
if (!host.getTrackSound) return "default";
|
|
5302
|
+
const snap = await host.getTrackSound(sourceDbId);
|
|
5303
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) return "default";
|
|
5304
|
+
return adapter.sound.copySnapshot(newTrackId, snap);
|
|
5305
|
+
};
|
|
5306
|
+
const originLabel = await copySound(top.id, origin.dbId);
|
|
5307
|
+
const targetLabel = await copySound(bottom.id, target.dbId);
|
|
5308
|
+
await applyCrossfadeAutomation(top.id, bottom.id, mc.bars, mc.bpm, 0.5);
|
|
5309
|
+
const groupId = top.dbId;
|
|
5310
|
+
const originMeta = {
|
|
5311
|
+
groupId,
|
|
5312
|
+
slot: "origin",
|
|
5313
|
+
partnerDbId: bottom.dbId,
|
|
5314
|
+
sourceTrackDbId: origin.dbId,
|
|
5315
|
+
sourceSceneId: fromSceneId,
|
|
5316
|
+
sourceName: origin.name,
|
|
5317
|
+
soundLabel: originLabel,
|
|
5318
|
+
sliderPos: 0.5
|
|
5319
|
+
};
|
|
5320
|
+
const targetMeta = {
|
|
5321
|
+
groupId,
|
|
5322
|
+
slot: "target",
|
|
5323
|
+
partnerDbId: top.dbId,
|
|
5324
|
+
sourceTrackDbId: target.dbId,
|
|
5325
|
+
sourceSceneId: toSceneId,
|
|
5326
|
+
sourceName: target.name,
|
|
5327
|
+
soundLabel: targetLabel,
|
|
5328
|
+
sliderPos: 0.5
|
|
5329
|
+
};
|
|
5330
|
+
await host.setSceneData(scene, `track:${top.dbId}:crossfade`, originMeta);
|
|
5331
|
+
await host.setSceneData(scene, `track:${bottom.dbId}:crossfade`, targetMeta);
|
|
5332
|
+
await loadTracks(true);
|
|
5333
|
+
host.showToast("success", "Crossfade created", `${origin.name} \u2192 ${target.name}`);
|
|
5334
|
+
} catch (err) {
|
|
5335
|
+
for (const h of [...created].reverse()) {
|
|
5336
|
+
try {
|
|
5337
|
+
await host.deleteTrack(h.id);
|
|
5338
|
+
} catch {
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
5342
|
+
} finally {
|
|
5343
|
+
setIsCreatingCrossfade(false);
|
|
5344
|
+
}
|
|
5345
|
+
},
|
|
5346
|
+
[
|
|
5347
|
+
host,
|
|
5348
|
+
adapter,
|
|
5349
|
+
identity,
|
|
5350
|
+
activeSceneId,
|
|
5351
|
+
isConnected,
|
|
5352
|
+
isAuthenticated,
|
|
5353
|
+
tracks.length,
|
|
5354
|
+
sceneContext,
|
|
5355
|
+
applyCrossfadeAutomation,
|
|
5356
|
+
loadTracks
|
|
5357
|
+
]
|
|
5358
|
+
);
|
|
5359
|
+
const [isCreatingFade, setIsCreatingFade] = (0, import_react24.useState)(false);
|
|
5360
|
+
const handleCreateFade = (0, import_react24.useCallback)(
|
|
5361
|
+
async (selection, direction, gesture) => {
|
|
5362
|
+
const scene = activeSceneId;
|
|
5363
|
+
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
5364
|
+
const toSceneId = sceneContext?.transitionToSceneId ?? "";
|
|
5365
|
+
if (!scene) throw new Error("No active scene.");
|
|
5366
|
+
if (!isConnected) throw new Error("Systems not connected.");
|
|
5367
|
+
if (!isAuthenticated) throw new Error("Please sign in to generate the fade.");
|
|
5368
|
+
if (tracks.length + 1 > identity.maxTracks) {
|
|
5369
|
+
throw new Error("Not enough track slots for a fade.");
|
|
5370
|
+
}
|
|
5371
|
+
setIsCreatingFade(true);
|
|
5372
|
+
const created = [];
|
|
5373
|
+
try {
|
|
5374
|
+
const role = selection.role ?? "";
|
|
5375
|
+
const sourceSceneId = direction === "out" ? fromSceneId : toSceneId;
|
|
5376
|
+
const mc = await host.getMusicalContext();
|
|
5377
|
+
const [srcMidi, srcKey] = await Promise.all([
|
|
5378
|
+
host.readImportableTrackMidi ? host.readImportableTrackMidi(selection.dbId) : Promise.resolve({ clips: [] }),
|
|
5379
|
+
host.getSceneKey ? host.getSceneKey(sourceSceneId) : Promise.resolve(null)
|
|
5380
|
+
]);
|
|
5381
|
+
const srcNotes = srcMidi.clips[0]?.notes ?? [];
|
|
5382
|
+
const keyStr = srcKey ? `${srcKey.key} ${srcKey.mode}` : null;
|
|
5383
|
+
const userPrompt = buildCrossfadeInpaintPrompt({
|
|
5384
|
+
role,
|
|
5385
|
+
bars: mc.bars,
|
|
5386
|
+
originName: direction === "out" ? selection.name : "silence",
|
|
5387
|
+
targetName: direction === "in" ? selection.name : "silence",
|
|
5388
|
+
originKey: direction === "out" ? keyStr : null,
|
|
5389
|
+
targetKey: direction === "in" ? keyStr : null,
|
|
5390
|
+
originNotes: direction === "out" ? srcNotes : [],
|
|
5391
|
+
targetNotes: direction === "in" ? srcNotes : []
|
|
5392
|
+
});
|
|
5393
|
+
const llm = await host.generateWithLLM({
|
|
5394
|
+
system: adapter.buildSystemPrompt(host.getValidRoles()),
|
|
5395
|
+
user: userPrompt,
|
|
5396
|
+
responseFormat: "json"
|
|
5397
|
+
});
|
|
5398
|
+
const parsed = adapter.parseNotesResponse(llm.content);
|
|
5399
|
+
if (!parsed || parsed.notes.length === 0) {
|
|
5400
|
+
throw new Error("The fade generator returned no notes.");
|
|
5401
|
+
}
|
|
5402
|
+
const notes = await host.postProcessMidi(parsed.notes, {
|
|
5403
|
+
quantize: true,
|
|
5404
|
+
removeOverlaps: true
|
|
5405
|
+
});
|
|
5406
|
+
const clip = {
|
|
5407
|
+
startTime: 0,
|
|
5408
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
5409
|
+
tempo: mc.bpm,
|
|
5410
|
+
notes
|
|
5411
|
+
};
|
|
5412
|
+
const track = await host.createTrack({
|
|
5413
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-fade-${direction}`,
|
|
5414
|
+
...adapter.createTrackOptions()
|
|
5415
|
+
});
|
|
5416
|
+
created.push(track);
|
|
5417
|
+
if (role) await host.setTrackRole(track.id, role).catch(() => {
|
|
5418
|
+
});
|
|
5419
|
+
await host.writeMidiClip(track.id, clip);
|
|
5420
|
+
let soundLabel = "default";
|
|
5421
|
+
if (host.getTrackSound) {
|
|
5422
|
+
const snap = await host.getTrackSound(selection.dbId);
|
|
5423
|
+
if (snap && snap.kind === adapter.sound.acceptedSnapshotKind) {
|
|
5424
|
+
soundLabel = await adapter.sound.copySnapshot(track.id, snap);
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
await applyFadeAutomation(track.id, direction, mc.bars, mc.bpm, 0.5, gesture);
|
|
5428
|
+
appliedFadeAutomationRef.current.add(track.id);
|
|
5429
|
+
const meta = {
|
|
5430
|
+
direction,
|
|
5431
|
+
gesture,
|
|
5432
|
+
sourceTrackDbId: selection.dbId,
|
|
5433
|
+
sourceSceneId,
|
|
5434
|
+
sourceName: selection.name,
|
|
5435
|
+
soundLabel,
|
|
5436
|
+
sliderPos: 0.5
|
|
5437
|
+
};
|
|
5438
|
+
await host.setSceneData(scene, `track:${track.dbId}:fade`, meta);
|
|
5439
|
+
await loadTracks(true);
|
|
5440
|
+
host.showToast(
|
|
5441
|
+
"success",
|
|
5442
|
+
direction === "in" ? "Fade in created" : "Fade out created",
|
|
5443
|
+
selection.name
|
|
5444
|
+
);
|
|
5445
|
+
} catch (err) {
|
|
5446
|
+
for (const h of [...created].reverse()) {
|
|
5447
|
+
try {
|
|
5448
|
+
await host.deleteTrack(h.id);
|
|
5449
|
+
} catch {
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5452
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
5453
|
+
} finally {
|
|
5454
|
+
setIsCreatingFade(false);
|
|
5455
|
+
}
|
|
5456
|
+
},
|
|
5457
|
+
[
|
|
5458
|
+
host,
|
|
5459
|
+
adapter,
|
|
5460
|
+
identity,
|
|
5461
|
+
activeSceneId,
|
|
5462
|
+
isConnected,
|
|
5463
|
+
isAuthenticated,
|
|
5464
|
+
tracks.length,
|
|
5465
|
+
sceneContext,
|
|
5466
|
+
applyFadeAutomation,
|
|
5467
|
+
loadTracks
|
|
5468
|
+
]
|
|
5469
|
+
);
|
|
5470
|
+
const handleCrossfadeMute = (0, import_react24.useCallback)(
|
|
5471
|
+
(pair) => {
|
|
5472
|
+
const newMuted = !pair.origin.runtimeState.muted;
|
|
5473
|
+
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
5474
|
+
setTracks(
|
|
5475
|
+
(prev) => prev.map(
|
|
5476
|
+
(t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, muted: newMuted } } : t
|
|
5477
|
+
)
|
|
5478
|
+
);
|
|
5479
|
+
host.setTrackMute(id, newMuted).catch(() => {
|
|
5480
|
+
});
|
|
5481
|
+
}
|
|
5482
|
+
},
|
|
5483
|
+
[host, setTracks]
|
|
5484
|
+
);
|
|
5485
|
+
const handleCrossfadeSolo = (0, import_react24.useCallback)(
|
|
5486
|
+
(pair) => {
|
|
5487
|
+
const newSolo = !pair.origin.runtimeState.solo;
|
|
5488
|
+
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
5489
|
+
setTracks(
|
|
5490
|
+
(prev) => prev.map(
|
|
5491
|
+
(t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, solo: newSolo } } : t
|
|
5492
|
+
)
|
|
5493
|
+
);
|
|
5494
|
+
host.setTrackSolo(id, newSolo).catch(() => {
|
|
5495
|
+
});
|
|
5496
|
+
}
|
|
5497
|
+
},
|
|
5498
|
+
[host, setTracks]
|
|
5499
|
+
);
|
|
5500
|
+
const handleCrossfadeDelete = (0, import_react24.useCallback)(
|
|
5501
|
+
async (pair) => {
|
|
5502
|
+
try {
|
|
5503
|
+
for (const member of [pair.origin, pair.target]) {
|
|
5504
|
+
await host.deleteTrack(member.handle.id);
|
|
5505
|
+
if (activeSceneId) {
|
|
5506
|
+
await host.deleteSceneData(activeSceneId, `track:${member.handle.dbId}:crossfade`);
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
setCrossfadePairsMeta((prev) => prev.filter((p) => p.groupId !== pair.groupId));
|
|
5510
|
+
setTracks(
|
|
5511
|
+
(prev) => prev.filter(
|
|
5512
|
+
(t) => t.handle.id !== pair.origin.handle.id && t.handle.id !== pair.target.handle.id
|
|
5513
|
+
)
|
|
5514
|
+
);
|
|
5515
|
+
host.showToast("success", "Crossfade removed");
|
|
5516
|
+
} catch (err) {
|
|
5517
|
+
host.showToast(
|
|
5518
|
+
"error",
|
|
5519
|
+
"Failed to delete crossfade",
|
|
5520
|
+
err instanceof Error ? err.message : String(err)
|
|
5521
|
+
);
|
|
5522
|
+
}
|
|
5523
|
+
},
|
|
5524
|
+
[host, activeSceneId, setCrossfadePairsMeta, setTracks]
|
|
5525
|
+
);
|
|
5526
|
+
const crossfadeSliderTimers = (0, import_react24.useRef)({});
|
|
5527
|
+
const handleCrossfadeSlider = (0, import_react24.useCallback)(
|
|
5528
|
+
(pair, pos) => {
|
|
5529
|
+
setCrossfadePairsMeta(
|
|
5530
|
+
(prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
|
|
5531
|
+
);
|
|
5532
|
+
if (crossfadeSliderTimers.current[pair.groupId]) {
|
|
5533
|
+
clearTimeout(crossfadeSliderTimers.current[pair.groupId]);
|
|
5534
|
+
}
|
|
5535
|
+
crossfadeSliderTimers.current[pair.groupId] = setTimeout(() => {
|
|
5536
|
+
void (async () => {
|
|
5537
|
+
const mc = await host.getMusicalContext();
|
|
5538
|
+
await applyCrossfadeAutomation(
|
|
5539
|
+
pair.origin.handle.id,
|
|
5540
|
+
pair.target.handle.id,
|
|
5541
|
+
mc.bars,
|
|
5542
|
+
mc.bpm,
|
|
5543
|
+
pos
|
|
5544
|
+
);
|
|
5545
|
+
if (activeSceneId) {
|
|
5546
|
+
const sceneData = await host.getAllSceneData(activeSceneId);
|
|
5547
|
+
for (const dbId of [pair.originDbId, pair.targetDbId]) {
|
|
5548
|
+
const meta = asCrossfadeMeta(sceneData[`track:${dbId}:crossfade`]);
|
|
5549
|
+
if (meta) {
|
|
5550
|
+
host.setSceneData(activeSceneId, `track:${dbId}:crossfade`, { ...meta, sliderPos: pos }).catch(() => {
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
})();
|
|
5556
|
+
}, 200);
|
|
5557
|
+
},
|
|
5558
|
+
[host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
|
|
5559
|
+
);
|
|
5560
|
+
const handleFadeDelete = (0, import_react24.useCallback)(
|
|
5561
|
+
async (fade) => {
|
|
5562
|
+
try {
|
|
5563
|
+
await host.deleteTrack(fade.track.handle.id);
|
|
5564
|
+
if (activeSceneId) {
|
|
5565
|
+
await host.deleteSceneData(activeSceneId, `track:${fade.dbId}:fade`);
|
|
5566
|
+
}
|
|
5567
|
+
setFadesMeta((prev) => prev.filter((f) => f.dbId !== fade.dbId));
|
|
5568
|
+
setTracks((prev) => prev.filter((t) => t.handle.id !== fade.track.handle.id));
|
|
5569
|
+
host.showToast("success", "Fade removed");
|
|
5570
|
+
} catch (err) {
|
|
5571
|
+
host.showToast(
|
|
5572
|
+
"error",
|
|
5573
|
+
"Failed to delete fade",
|
|
5574
|
+
err instanceof Error ? err.message : String(err)
|
|
5575
|
+
);
|
|
5576
|
+
}
|
|
5577
|
+
},
|
|
5578
|
+
[host, activeSceneId, setFadesMeta, setTracks]
|
|
5579
|
+
);
|
|
5580
|
+
const fadeSliderTimers = (0, import_react24.useRef)({});
|
|
5581
|
+
const handleFadeSlider = (0, import_react24.useCallback)(
|
|
5582
|
+
(fade, pos) => {
|
|
5583
|
+
setFadesMeta(
|
|
5584
|
+
(prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
|
|
5585
|
+
);
|
|
5586
|
+
if (fadeSliderTimers.current[fade.dbId]) clearTimeout(fadeSliderTimers.current[fade.dbId]);
|
|
5587
|
+
fadeSliderTimers.current[fade.dbId] = setTimeout(() => {
|
|
5588
|
+
void (async () => {
|
|
5589
|
+
const mc = await host.getMusicalContext();
|
|
5590
|
+
await applyFadeAutomation(
|
|
5591
|
+
fade.track.handle.id,
|
|
5592
|
+
fade.meta.direction,
|
|
5593
|
+
mc.bars,
|
|
5594
|
+
mc.bpm,
|
|
5595
|
+
pos,
|
|
5596
|
+
fade.meta.gesture
|
|
5597
|
+
);
|
|
5598
|
+
if (activeSceneId) {
|
|
5599
|
+
const sceneData = await host.getAllSceneData(activeSceneId);
|
|
5600
|
+
const meta = asFadeMeta(sceneData[`track:${fade.dbId}:fade`]);
|
|
5601
|
+
if (meta) {
|
|
5602
|
+
host.setSceneData(activeSceneId, `track:${fade.dbId}:fade`, { ...meta, sliderPos: pos }).catch(() => {
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
})();
|
|
5607
|
+
}, 200);
|
|
5608
|
+
},
|
|
5609
|
+
[host, activeSceneId, applyFadeAutomation, setFadesMeta]
|
|
5610
|
+
);
|
|
5611
|
+
const lastResyncKeyRef = (0, import_react24.useRef)("");
|
|
5612
|
+
(0, import_react24.useEffect)(() => {
|
|
5613
|
+
if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
|
|
5614
|
+
return;
|
|
5615
|
+
}
|
|
5616
|
+
const resyncKey = [
|
|
5617
|
+
...resolvedCrossfadePairs.map(
|
|
5618
|
+
(p) => `${p.origin.handle.dbId}<${p.originSourceDbId}|${p.target.handle.dbId}<${p.targetSourceDbId}`
|
|
5619
|
+
),
|
|
5620
|
+
...resolvedFades.map((f) => `${f.track.handle.dbId}<${f.meta.sourceTrackDbId}`)
|
|
5621
|
+
].join(",");
|
|
5622
|
+
if (resyncKey === lastResyncKeyRef.current) return;
|
|
5623
|
+
lastResyncKeyRef.current = resyncKey;
|
|
5624
|
+
let cancelled = false;
|
|
5625
|
+
const reapplyIfDrifted = async (layerTrackId, layerDbId, sourceDbId) => {
|
|
5626
|
+
if (!host.getTrackSound || cancelled) return;
|
|
5627
|
+
const [sourceSnap, layerSnap] = await Promise.all([
|
|
5628
|
+
host.getTrackSound(sourceDbId),
|
|
5629
|
+
host.getTrackSound(layerDbId)
|
|
5630
|
+
]);
|
|
5631
|
+
if (cancelled || !sourceSnap || sourceSnap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
5632
|
+
return;
|
|
5633
|
+
}
|
|
5634
|
+
if (soundIdentity(sourceSnap) === soundIdentity(layerSnap)) return;
|
|
5635
|
+
try {
|
|
5636
|
+
await adapter.sound.copySnapshot(layerTrackId, sourceSnap);
|
|
5637
|
+
} catch {
|
|
5638
|
+
}
|
|
5639
|
+
};
|
|
5640
|
+
void (async () => {
|
|
5641
|
+
for (const pair of resolvedCrossfadePairs) {
|
|
5642
|
+
await reapplyIfDrifted(pair.origin.handle.id, pair.origin.handle.dbId, pair.originSourceDbId);
|
|
5643
|
+
await reapplyIfDrifted(pair.target.handle.id, pair.target.handle.dbId, pair.targetSourceDbId);
|
|
5644
|
+
}
|
|
5645
|
+
for (const fade of resolvedFades) {
|
|
5646
|
+
await reapplyIfDrifted(fade.track.handle.id, fade.track.handle.dbId, fade.meta.sourceTrackDbId);
|
|
5647
|
+
}
|
|
5648
|
+
})();
|
|
5649
|
+
return () => {
|
|
5650
|
+
cancelled = true;
|
|
5651
|
+
};
|
|
5652
|
+
}, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
|
|
5653
|
+
(0, import_react24.useEffect)(() => {
|
|
5654
|
+
if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
|
|
5655
|
+
void (async () => {
|
|
5656
|
+
const mc = await host.getMusicalContext();
|
|
5657
|
+
for (const fade of resolvedFades) {
|
|
5658
|
+
const id = fade.track.handle.id;
|
|
5659
|
+
if (appliedFadeAutomationRef.current.has(id)) continue;
|
|
5660
|
+
appliedFadeAutomationRef.current.add(id);
|
|
5661
|
+
await applyFadeAutomation(
|
|
5662
|
+
id,
|
|
5663
|
+
fade.meta.direction,
|
|
5664
|
+
mc.bars,
|
|
5665
|
+
mc.bpm,
|
|
5666
|
+
fade.meta.sliderPos,
|
|
5667
|
+
fade.meta.gesture
|
|
5668
|
+
);
|
|
5669
|
+
}
|
|
5670
|
+
})();
|
|
5671
|
+
}, [resolvedFades, host, applyFadeAutomation]);
|
|
5672
|
+
return {
|
|
5673
|
+
isCreatingCrossfade,
|
|
5674
|
+
isCreatingFade,
|
|
5675
|
+
handleCreateCrossfade,
|
|
5676
|
+
handleCreateFade,
|
|
5677
|
+
handleCrossfadeMute,
|
|
5678
|
+
handleCrossfadeSolo,
|
|
5679
|
+
handleCrossfadeDelete,
|
|
5680
|
+
handleCrossfadeSlider,
|
|
5681
|
+
handleFadeDelete,
|
|
5682
|
+
handleFadeSlider
|
|
5683
|
+
};
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
// src/panel-core/useGeneratorPanelCore.tsx
|
|
5687
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
5688
|
+
var EMPTY_PLACEHOLDERS = [];
|
|
5689
|
+
function useGeneratorPanelCore({
|
|
5690
|
+
ui,
|
|
5691
|
+
adapter
|
|
5692
|
+
}) {
|
|
5693
|
+
const {
|
|
5694
|
+
host,
|
|
5695
|
+
activeSceneId,
|
|
5696
|
+
isAuthenticated,
|
|
5697
|
+
isConnected,
|
|
5698
|
+
onHeaderContent,
|
|
5699
|
+
onLoading,
|
|
5700
|
+
sceneContext,
|
|
5701
|
+
onOpenContract,
|
|
5702
|
+
onExpandSelf,
|
|
5703
|
+
isExpanded
|
|
5704
|
+
} = ui;
|
|
5705
|
+
const { identity, features } = adapter;
|
|
5706
|
+
const logTag = identity.logTag;
|
|
5707
|
+
const adapterRef = (0, import_react25.useRef)(adapter);
|
|
5708
|
+
(0, import_react25.useEffect)(() => {
|
|
5709
|
+
if (adapterRef.current !== adapter) {
|
|
5710
|
+
adapterRef.current = adapter;
|
|
5711
|
+
console.warn(
|
|
5712
|
+
`[${logTag}] GeneratorPanelAdapter identity changed between renders \u2014 wrap it in useMemo(() => createAdapter(host), [host]) to avoid load loops.`
|
|
5713
|
+
);
|
|
5714
|
+
}
|
|
5715
|
+
}, [adapter, logTag]);
|
|
5716
|
+
const supportsMeters = typeof host.getTrackLevels === "function";
|
|
5717
|
+
const trackLevels = useTrackLevels(host, isExpanded);
|
|
5718
|
+
const [tracks, setTracks] = (0, import_react25.useState)([]);
|
|
5719
|
+
const [isLoadingTracks, setIsLoadingTracks] = (0, import_react25.useState)(false);
|
|
5720
|
+
const [importOpen, setImportOpen] = (0, import_react25.useState)(false);
|
|
5721
|
+
const [soundImportTarget, setSoundImportTarget] = (0, import_react25.useState)(null);
|
|
5722
|
+
const [designerView, setDesignerView] = (0, import_react25.useState)(false);
|
|
5723
|
+
const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react25.useState)(0);
|
|
5724
|
+
const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react25.useState)([]);
|
|
5725
|
+
const [fadesMeta, setFadesMeta] = (0, import_react25.useState)([]);
|
|
5726
|
+
const [genericGroupMetas, setGenericGroupMetas] = (0, import_react25.useState)({});
|
|
5727
|
+
const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
|
|
5728
|
+
const [placeholders, , setPlaceholdersForScene] = useSceneState(
|
|
5729
|
+
activeSceneId,
|
|
5730
|
+
EMPTY_PLACEHOLDERS
|
|
5731
|
+
);
|
|
5732
|
+
const saveTimeoutRefs = (0, import_react25.useRef)({});
|
|
5733
|
+
const editLoadStartedRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
|
|
5734
|
+
const [availableInstruments, setAvailableInstruments] = (0, import_react25.useState)([]);
|
|
5735
|
+
const [instrumentsLoading, setInstrumentsLoading] = (0, import_react25.useState)(false);
|
|
5736
|
+
const engineToDbIdRef = (0, import_react25.useRef)(/* @__PURE__ */ new Map());
|
|
5737
|
+
const tracksLoadedForSceneRef = (0, import_react25.useRef)(null);
|
|
5738
|
+
const persistSoundHistory = (0, import_react25.useCallback)(
|
|
5739
|
+
(trackId, state) => {
|
|
5740
|
+
if (!activeSceneId) return;
|
|
5741
|
+
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
5742
|
+
host.setSceneData(activeSceneId, trackDataKey(dbId, "soundHistory"), state).catch(() => {
|
|
5743
|
+
});
|
|
5744
|
+
},
|
|
5745
|
+
[host, activeSceneId]
|
|
5746
|
+
);
|
|
5747
|
+
const soundHistory = useSoundHistory(adapter.sound.applySound, {
|
|
5748
|
+
max: adapter.sound.historyMax,
|
|
5749
|
+
onChange: persistSoundHistory
|
|
5750
|
+
});
|
|
5751
|
+
const anySolo = useAnySolo(host);
|
|
5752
|
+
const reorder = useTrackReorder({
|
|
5753
|
+
host,
|
|
5754
|
+
items: tracks,
|
|
5755
|
+
setItems: setTracks,
|
|
5756
|
+
getId: (t) => t.handle.dbId
|
|
5757
|
+
});
|
|
5758
|
+
const loadTracks = (0, import_react25.useCallback)(
|
|
5759
|
+
async (incremental = false) => {
|
|
5760
|
+
const sceneAtStart = activeSceneId;
|
|
5761
|
+
if (!sceneAtStart) {
|
|
5762
|
+
setTracks([]);
|
|
5763
|
+
setCrossfadePairsMeta([]);
|
|
5764
|
+
setFadesMeta([]);
|
|
5765
|
+
setGenericGroupMetas({});
|
|
5766
|
+
tracksLoadedForSceneRef.current = null;
|
|
5767
|
+
setIsLoadingTracks(false);
|
|
5768
|
+
return;
|
|
5769
|
+
}
|
|
5770
|
+
if (!incremental && tracksLoadedForSceneRef.current !== sceneAtStart) {
|
|
5771
|
+
setTracks([]);
|
|
5772
|
+
}
|
|
5773
|
+
tracksLoadedForSceneRef.current = sceneAtStart;
|
|
5774
|
+
if (!incremental) soundHistory.reset();
|
|
5775
|
+
const isStale = () => tracksLoadedForSceneRef.current !== sceneAtStart;
|
|
5776
|
+
if (!incremental) setIsLoadingTracks(true);
|
|
5777
|
+
try {
|
|
5778
|
+
await host.adoptSceneTracks();
|
|
5779
|
+
if (isStale()) return;
|
|
5780
|
+
const handles = await host.getPluginTracks();
|
|
5781
|
+
if (isStale()) return;
|
|
5782
|
+
const sceneData = await host.getAllSceneData(sceneAtStart);
|
|
5783
|
+
if (isStale()) return;
|
|
5784
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
5785
|
+
for (const h of handles) {
|
|
5786
|
+
idMap.set(h.id, h.dbId);
|
|
5787
|
+
}
|
|
5788
|
+
engineToDbIdRef.current = idMap;
|
|
5789
|
+
const trackStates = [];
|
|
5790
|
+
for (const handle of handles) {
|
|
5791
|
+
let runtimeState = {
|
|
5792
|
+
id: handle.id,
|
|
5793
|
+
muted: false,
|
|
5794
|
+
solo: false,
|
|
5795
|
+
volume: 0.75,
|
|
5796
|
+
pan: 0
|
|
5797
|
+
};
|
|
5798
|
+
let hasMidi = false;
|
|
5799
|
+
try {
|
|
5800
|
+
const info = await host.getTrackInfo(handle.id);
|
|
5801
|
+
runtimeState = {
|
|
5802
|
+
id: handle.id,
|
|
5803
|
+
muted: info.muted,
|
|
5804
|
+
solo: info.soloed,
|
|
5805
|
+
volume: info.volume,
|
|
5806
|
+
pan: info.pan
|
|
5807
|
+
};
|
|
5808
|
+
hasMidi = info.hasMidi;
|
|
5809
|
+
} catch {
|
|
5810
|
+
}
|
|
5811
|
+
let fxDetailState = newTrackState(handle).fxDetailState;
|
|
5812
|
+
try {
|
|
5813
|
+
const fxState = await host.getTrackFxState(handle.id);
|
|
5814
|
+
fxDetailState = pluginFxToToggleFx(fxState);
|
|
5815
|
+
} catch {
|
|
5816
|
+
}
|
|
5817
|
+
const promptKey = trackDataKey(handle.dbId, "prompt");
|
|
5818
|
+
let prompt = typeof sceneData[promptKey] === "string" ? sceneData[promptKey] : "";
|
|
5819
|
+
if (!prompt && handle.prompt) {
|
|
5820
|
+
prompt = handle.prompt;
|
|
5821
|
+
host.setSceneData(sceneAtStart, promptKey, prompt).catch(() => {
|
|
5822
|
+
});
|
|
5823
|
+
}
|
|
5824
|
+
if (!hasMidi && handle.role) {
|
|
5825
|
+
hasMidi = true;
|
|
5826
|
+
}
|
|
5827
|
+
let instrumentMissing = false;
|
|
5828
|
+
if (handle.instrumentPluginId) {
|
|
5829
|
+
try {
|
|
5830
|
+
const instrDescriptor = await host.getTrackInstrument(handle.id);
|
|
5831
|
+
if (instrDescriptor?.missing) {
|
|
5832
|
+
instrumentMissing = true;
|
|
5833
|
+
}
|
|
5834
|
+
} catch {
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
trackStates.push(
|
|
5838
|
+
newTrackState(handle, {
|
|
5839
|
+
prompt,
|
|
5840
|
+
role: handle.role ?? "",
|
|
5841
|
+
runtimeState,
|
|
5842
|
+
fxDetailState,
|
|
5843
|
+
hasMidi,
|
|
5844
|
+
instrumentMissing
|
|
5845
|
+
})
|
|
5846
|
+
);
|
|
5847
|
+
}
|
|
5848
|
+
if (isStale()) return;
|
|
5849
|
+
setTracks((prev) => {
|
|
5850
|
+
const prevByDbId = new Map(prev.map((p) => [p.handle.dbId, p]));
|
|
5851
|
+
return trackStates.map((ts) => {
|
|
5852
|
+
const carry = prevByDbId.get(ts.handle.dbId);
|
|
5853
|
+
return carry ? { ...ts, editNotes: carry.editNotes, editBars: carry.editBars, editBpm: carry.editBpm } : ts;
|
|
5854
|
+
});
|
|
5855
|
+
});
|
|
5856
|
+
for (const ts of trackStates) {
|
|
5857
|
+
const persisted = sceneData[trackDataKey(ts.handle.dbId, "soundHistory")];
|
|
5858
|
+
if (persisted && typeof persisted === "object") {
|
|
5859
|
+
soundHistory.restore(ts.handle.id, persisted);
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
if (!isStale()) {
|
|
5863
|
+
setCrossfadePairsMeta(parseCrossfadePairs(sceneData));
|
|
5864
|
+
setFadesMeta(parseFades(sceneData));
|
|
5865
|
+
if (adapter.groupExtensions && adapter.groupExtensions.length > 0) {
|
|
5866
|
+
const map = {};
|
|
5867
|
+
for (const ext of adapter.groupExtensions) {
|
|
5868
|
+
map[ext.metaKey] = parseTrackGroups(sceneData, ext);
|
|
5869
|
+
}
|
|
5870
|
+
setGenericGroupMetas(map);
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
} catch (error) {
|
|
5874
|
+
console.error(`[${logTag}] Failed to load tracks:`, error);
|
|
5875
|
+
} finally {
|
|
5876
|
+
if (tracksLoadedForSceneRef.current === sceneAtStart) {
|
|
5877
|
+
setIsLoadingTracks(false);
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
},
|
|
5881
|
+
[host, activeSceneId, soundHistory, adapter, logTag]
|
|
5882
|
+
);
|
|
5883
|
+
(0, import_react25.useEffect)(() => {
|
|
5884
|
+
loadTracks();
|
|
5885
|
+
}, [loadTracks]);
|
|
5886
|
+
(0, import_react25.useEffect)(() => {
|
|
5887
|
+
const map = /* @__PURE__ */ new Map();
|
|
5888
|
+
for (const t of tracks) {
|
|
5889
|
+
map.set(t.handle.id, t.handle.dbId);
|
|
5890
|
+
}
|
|
5891
|
+
engineToDbIdRef.current = map;
|
|
5892
|
+
}, [tracks]);
|
|
5893
|
+
const loadedCompletedIdsRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
|
|
5894
|
+
(0, import_react25.useEffect)(() => {
|
|
5895
|
+
if (placeholders.length === 0) {
|
|
5896
|
+
loadedCompletedIdsRef.current.clear();
|
|
5897
|
+
return;
|
|
5898
|
+
}
|
|
5899
|
+
const newCompleted = placeholders.filter(
|
|
5900
|
+
(ph) => ph.status === "completed" && !loadedCompletedIdsRef.current.has(ph.id)
|
|
5901
|
+
);
|
|
5902
|
+
if (newCompleted.length > 0) {
|
|
5903
|
+
for (const ph of newCompleted) {
|
|
5904
|
+
loadedCompletedIdsRef.current.add(ph.id);
|
|
5905
|
+
}
|
|
5906
|
+
console.log(
|
|
5907
|
+
`[${logTag}] ${newCompleted.length} track(s) completed, reloading. IDs:`,
|
|
5908
|
+
newCompleted.map((ph) => ph.id)
|
|
5909
|
+
);
|
|
5910
|
+
loadTracks(true);
|
|
5911
|
+
}
|
|
5912
|
+
}, [placeholders, loadTracks, logTag]);
|
|
5913
|
+
const adoptAndLoad = (0, import_react25.useCallback)(() => {
|
|
5914
|
+
loadTracks(true);
|
|
5915
|
+
}, [loadTracks]);
|
|
5916
|
+
(0, import_react25.useEffect)(() => {
|
|
5917
|
+
const unsub = host.onEngineReady(() => {
|
|
5918
|
+
adoptAndLoad();
|
|
5919
|
+
});
|
|
5920
|
+
return unsub;
|
|
5921
|
+
}, [host, adoptAndLoad]);
|
|
5922
|
+
(0, import_react25.useEffect)(() => {
|
|
5923
|
+
if (typeof host.onAfterAgentMutation !== "function") return;
|
|
5924
|
+
let timer = null;
|
|
5925
|
+
const unsub = host.onAfterAgentMutation(() => {
|
|
5926
|
+
if (timer) clearTimeout(timer);
|
|
5927
|
+
timer = setTimeout(() => {
|
|
5928
|
+
timer = null;
|
|
5929
|
+
loadTracks(true);
|
|
5930
|
+
}, 500);
|
|
5931
|
+
});
|
|
5932
|
+
return () => {
|
|
5933
|
+
unsub?.();
|
|
5934
|
+
if (timer) clearTimeout(timer);
|
|
5935
|
+
};
|
|
5936
|
+
}, [host, loadTracks]);
|
|
5937
|
+
(0, import_react25.useEffect)(() => {
|
|
5938
|
+
const unsub = host.onTrackStateChange((trackId, state) => {
|
|
5939
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
|
|
5940
|
+
});
|
|
5941
|
+
return unsub;
|
|
5942
|
+
}, [host]);
|
|
5943
|
+
(0, import_react25.useEffect)(() => {
|
|
5944
|
+
if (!features.bulkComposePlaceholders) return;
|
|
5945
|
+
console.log(`[${logTag}] Subscribing to composeProgress`);
|
|
5946
|
+
const unsub = host.onComposeProgress((event) => {
|
|
5947
|
+
const targetScene = event.sceneId;
|
|
5948
|
+
if (!targetScene) return;
|
|
5949
|
+
console.log(
|
|
5950
|
+
`[${logTag}] composeProgress event:`,
|
|
5951
|
+
event.phase,
|
|
5952
|
+
"sceneId:",
|
|
5953
|
+
targetScene,
|
|
5954
|
+
"placeholders:",
|
|
5955
|
+
event.placeholders?.length ?? "none"
|
|
5956
|
+
);
|
|
5957
|
+
switch (event.phase) {
|
|
5958
|
+
case "planning":
|
|
5959
|
+
setIsComposingForScene(targetScene, true);
|
|
5960
|
+
setPlaceholdersForScene(targetScene, []);
|
|
5961
|
+
break;
|
|
5962
|
+
case "generating":
|
|
5963
|
+
setIsComposingForScene(targetScene, false);
|
|
5964
|
+
if (event.placeholders) {
|
|
5965
|
+
setPlaceholdersForScene(targetScene, event.placeholders);
|
|
5966
|
+
}
|
|
5967
|
+
break;
|
|
5968
|
+
case "complete":
|
|
5969
|
+
case "error":
|
|
5970
|
+
setIsComposingForScene(targetScene, false);
|
|
5971
|
+
setPlaceholdersForScene(targetScene, EMPTY_PLACEHOLDERS);
|
|
5972
|
+
break;
|
|
5973
|
+
}
|
|
5974
|
+
});
|
|
5975
|
+
return unsub;
|
|
5976
|
+
}, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
|
|
5977
|
+
(0, import_react25.useEffect)(() => {
|
|
5978
|
+
const refs = saveTimeoutRefs;
|
|
5979
|
+
return () => {
|
|
5980
|
+
for (const timeout of Object.values(refs.current)) {
|
|
5981
|
+
clearTimeout(timeout);
|
|
5982
|
+
}
|
|
5983
|
+
};
|
|
5984
|
+
}, []);
|
|
5985
|
+
const isAddingTrackRef = (0, import_react25.useRef)(false);
|
|
5986
|
+
const [isAddingTrack, setIsAddingTrack] = (0, import_react25.useState)(false);
|
|
5987
|
+
const handleAddTrack = (0, import_react25.useCallback)(async () => {
|
|
5988
|
+
if (isAddingTrackRef.current) return;
|
|
5989
|
+
if (!activeSceneId) {
|
|
5990
|
+
host.showToast("warning", "Select SCENE");
|
|
5991
|
+
return;
|
|
5992
|
+
}
|
|
5993
|
+
if (!isConnected) {
|
|
5994
|
+
host.showToast("warning", "Systems not connected");
|
|
5995
|
+
return;
|
|
5996
|
+
}
|
|
5997
|
+
if (!isAuthenticated) {
|
|
5998
|
+
host.showToast("warning", "Sign In Required", "Please sign in to add tracks");
|
|
5999
|
+
return;
|
|
6000
|
+
}
|
|
6001
|
+
if (tracks.length >= identity.maxTracks) return;
|
|
6002
|
+
isAddingTrackRef.current = true;
|
|
6003
|
+
setIsAddingTrack(true);
|
|
6004
|
+
try {
|
|
6005
|
+
const handle = await host.createTrack({
|
|
6006
|
+
name: `${identity.trackNamePrefix}-${Date.now()}`,
|
|
6007
|
+
...adapter.createTrackOptions()
|
|
6008
|
+
});
|
|
6009
|
+
setTracks((prev) => [...prev, newTrackState(handle)]);
|
|
6010
|
+
onExpandSelf?.();
|
|
6011
|
+
setTimeout(() => {
|
|
6012
|
+
const inputs = document.querySelectorAll(
|
|
6013
|
+
`[data-testid="${identity.familyKey}-section"] [data-testid="sdk-prompt-input"]`
|
|
6014
|
+
);
|
|
6015
|
+
if (inputs.length > 0) {
|
|
6016
|
+
inputs[inputs.length - 1].focus();
|
|
6017
|
+
}
|
|
6018
|
+
}, 350);
|
|
6019
|
+
} catch (error) {
|
|
6020
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
6021
|
+
host.showToast("error", "Failed to create track", msg);
|
|
6022
|
+
} finally {
|
|
6023
|
+
isAddingTrackRef.current = false;
|
|
6024
|
+
setIsAddingTrack(false);
|
|
6025
|
+
}
|
|
6026
|
+
}, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
|
|
6027
|
+
const handlePortTrack = (0, import_react25.useCallback)(
|
|
6028
|
+
async (sel) => {
|
|
6029
|
+
if (!activeSceneId) {
|
|
6030
|
+
host.showToast("warning", "Select SCENE");
|
|
6031
|
+
return;
|
|
6032
|
+
}
|
|
6033
|
+
if (!isConnected) {
|
|
6034
|
+
host.showToast("warning", "Systems not connected");
|
|
6035
|
+
return;
|
|
6036
|
+
}
|
|
6037
|
+
if (tracks.length >= identity.maxTracks) {
|
|
6038
|
+
host.showToast("warning", "Track limit reached");
|
|
6039
|
+
return;
|
|
6040
|
+
}
|
|
6041
|
+
if (!host.readImportableTrackMidi) return;
|
|
6042
|
+
let handle = null;
|
|
6043
|
+
try {
|
|
6044
|
+
handle = await host.createTrack({
|
|
6045
|
+
name: `${identity.trackNamePrefix}-${Date.now()}`,
|
|
6046
|
+
...adapter.createTrackOptions()
|
|
6047
|
+
});
|
|
6048
|
+
if (sel.role) {
|
|
6049
|
+
try {
|
|
6050
|
+
await host.setTrackRole(handle.id, sel.role);
|
|
6051
|
+
} catch {
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
6054
|
+
const midi = await host.readImportableTrackMidi(sel.sourceTrackDbId);
|
|
6055
|
+
const notes = midi.clips[0]?.notes ?? [];
|
|
6056
|
+
if (notes.length > 0) {
|
|
6057
|
+
const mc = await host.getMusicalContext();
|
|
6058
|
+
await host.writeMidiClip(handle.id, {
|
|
6059
|
+
startTime: 0,
|
|
6060
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
6061
|
+
tempo: mc.bpm,
|
|
6062
|
+
notes
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
6065
|
+
await adapter.applyPortedTrackSound(handle, sel.role);
|
|
6066
|
+
host.showToast(
|
|
6067
|
+
"success",
|
|
6068
|
+
`Imported to ${identity.familyKey}`,
|
|
6069
|
+
notes.length ? `${sel.trackName} \u2192 ${identity.familyKey}` : `${sel.trackName} (no MIDI yet)`
|
|
6070
|
+
);
|
|
6071
|
+
await loadTracks(true);
|
|
6072
|
+
} catch (err) {
|
|
6073
|
+
if (handle) {
|
|
6074
|
+
try {
|
|
6075
|
+
await host.deleteTrack(handle.id);
|
|
6076
|
+
} catch {
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
6080
|
+
}
|
|
6081
|
+
},
|
|
6082
|
+
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
6083
|
+
);
|
|
6084
|
+
const handleSoundImportPick = (0, import_react25.useCallback)(
|
|
6085
|
+
async (sel) => {
|
|
6086
|
+
const target = soundImportTarget;
|
|
6087
|
+
if (!target || !host.getTrackSound) {
|
|
6088
|
+
setSoundImportTarget(null);
|
|
6089
|
+
return;
|
|
6090
|
+
}
|
|
6091
|
+
const noun = adapter.sound.importNoun;
|
|
6092
|
+
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
6093
|
+
try {
|
|
6094
|
+
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
6095
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
6096
|
+
host.showToast(
|
|
6097
|
+
"error",
|
|
6098
|
+
`No ${noun} to import`,
|
|
6099
|
+
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
6100
|
+
);
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
6104
|
+
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
6105
|
+
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
6106
|
+
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
6107
|
+
} catch (err) {
|
|
6108
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
6109
|
+
} finally {
|
|
6110
|
+
setSoundImportTarget(null);
|
|
6111
|
+
}
|
|
6112
|
+
},
|
|
6113
|
+
[soundImportTarget, host, adapter, identity.familyKey, soundHistory]
|
|
6114
|
+
);
|
|
6115
|
+
const [isExportingMidi, setIsExportingMidi] = (0, import_react25.useState)(false);
|
|
6116
|
+
const handleExportMidi = (0, import_react25.useCallback)(async () => {
|
|
6117
|
+
if (isExportingMidi) return;
|
|
6118
|
+
setIsExportingMidi(true);
|
|
6119
|
+
try {
|
|
6120
|
+
const result = await host.exportTracksAsMidiBundle({
|
|
6121
|
+
defaultName: identity.exportDefaultName ?? "midi-tracks"
|
|
6122
|
+
});
|
|
6123
|
+
if (result.success) {
|
|
6124
|
+
const filename = result.filePath.split("/").pop() || result.filePath;
|
|
6125
|
+
const skippedNote = result.skippedCount > 0 ? ` (${result.skippedCount} empty track${result.skippedCount === 1 ? "" : "s"} skipped)` : "";
|
|
6126
|
+
host.showToast(
|
|
6127
|
+
"success",
|
|
6128
|
+
"MIDI exported",
|
|
6129
|
+
`${result.trackCount} track${result.trackCount === 1 ? "" : "s"} \u2192 ${filename}${skippedNote}`
|
|
6130
|
+
);
|
|
6131
|
+
} else if (!("canceled" in result && result.canceled)) {
|
|
6132
|
+
const errMsg = "error" in result ? result.error : "Unknown error";
|
|
6133
|
+
host.showToast("error", "Export failed", errMsg);
|
|
6134
|
+
}
|
|
6135
|
+
} catch (error) {
|
|
6136
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
6137
|
+
host.showToast("error", "Export failed", msg);
|
|
6138
|
+
} finally {
|
|
6139
|
+
setIsExportingMidi(false);
|
|
6140
|
+
}
|
|
6141
|
+
}, [host, identity.exportDefaultName, isExportingMidi]);
|
|
6142
|
+
const isBulkActive = !!(isComposing || placeholders.length > 0);
|
|
6143
|
+
const needsContract = !sceneContext?.hasContract;
|
|
6144
|
+
const xfFromId = sceneContext?.transitionFromSceneId ?? null;
|
|
6145
|
+
const xfToId = sceneContext?.transitionToSceneId ?? null;
|
|
6146
|
+
const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
|
|
6147
|
+
(0, import_react25.useEffect)(() => {
|
|
6148
|
+
if (!canCrossfade) setDesignerView(false);
|
|
6149
|
+
}, [canCrossfade]);
|
|
6150
|
+
(0, import_react25.useEffect)(() => {
|
|
6151
|
+
if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
|
|
6152
|
+
setTransitionSourceTotal(0);
|
|
6153
|
+
return;
|
|
6154
|
+
}
|
|
6155
|
+
let cancelled = false;
|
|
6156
|
+
void Promise.all([host.listSceneFamilyTracks(xfFromId), host.listSceneFamilyTracks(xfToId)]).then(([a, b]) => {
|
|
6157
|
+
if (!cancelled) setTransitionSourceTotal(a.length + b.length);
|
|
6158
|
+
}).catch(() => {
|
|
6159
|
+
if (!cancelled) setTransitionSourceTotal(0);
|
|
6160
|
+
});
|
|
6161
|
+
return () => {
|
|
6162
|
+
cancelled = true;
|
|
6163
|
+
};
|
|
6164
|
+
}, [canCrossfade, xfFromId, xfToId, host]);
|
|
6165
|
+
const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
|
|
6166
|
+
(0, import_react25.useEffect)(() => {
|
|
6167
|
+
if (!onHeaderContent) return;
|
|
6168
|
+
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
6169
|
+
onHeaderContent(
|
|
6170
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
6171
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
6172
|
+
"button",
|
|
6173
|
+
{
|
|
6174
|
+
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
6175
|
+
onClick: (e) => {
|
|
6176
|
+
e.stopPropagation();
|
|
6177
|
+
onExpandSelf?.();
|
|
6178
|
+
setImportOpen(true);
|
|
6179
|
+
},
|
|
6180
|
+
disabled: !activeSceneId || needsContract,
|
|
6181
|
+
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"}`,
|
|
6182
|
+
children: identity.importTrackLabel ?? "Import Track"
|
|
6183
|
+
}
|
|
6184
|
+
),
|
|
6185
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
6186
|
+
"button",
|
|
6187
|
+
{
|
|
6188
|
+
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
6189
|
+
onClick: (e) => {
|
|
6190
|
+
e.stopPropagation();
|
|
6191
|
+
if (needsContract) {
|
|
6192
|
+
onOpenContract?.();
|
|
6193
|
+
return;
|
|
6194
|
+
}
|
|
6195
|
+
handleAddTrack();
|
|
6196
|
+
},
|
|
6197
|
+
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"}`,
|
|
6198
|
+
children: identity.addTrackLabel ?? "Add Track"
|
|
6199
|
+
}
|
|
6200
|
+
),
|
|
6201
|
+
canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
|
|
6202
|
+
"button",
|
|
6203
|
+
{
|
|
6204
|
+
"data-testid": `${identity.familyKey}-view-toggle`,
|
|
6205
|
+
onClick: (e) => {
|
|
6206
|
+
e.stopPropagation();
|
|
6207
|
+
if (!designerView) {
|
|
6208
|
+
if (needsContract) {
|
|
6209
|
+
onOpenContract?.();
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
onExpandSelf?.();
|
|
6213
|
+
}
|
|
6214
|
+
setDesignerView((v) => !v);
|
|
6215
|
+
},
|
|
6216
|
+
disabled: !designerView && needsContract,
|
|
6217
|
+
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
6218
|
+
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",
|
|
6219
|
+
children: [
|
|
6220
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
6221
|
+
"span",
|
|
6222
|
+
{
|
|
6223
|
+
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
6224
|
+
style: { width: `${Math.min(100, transitionDone / transitionSourceTotal * 100)}%` },
|
|
6225
|
+
"aria-hidden": true
|
|
6226
|
+
}
|
|
6227
|
+
),
|
|
6228
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "relative", children: [
|
|
6229
|
+
"\u21C4 ",
|
|
6230
|
+
designerView ? "Transition" : "Tracks",
|
|
6231
|
+
transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
|
|
6232
|
+
] })
|
|
6233
|
+
]
|
|
6234
|
+
}
|
|
6235
|
+
)
|
|
6236
|
+
] })
|
|
6237
|
+
);
|
|
6238
|
+
return () => {
|
|
6239
|
+
onHeaderContent(null);
|
|
6240
|
+
};
|
|
6241
|
+
}, [
|
|
6242
|
+
onHeaderContent,
|
|
6243
|
+
needsContract,
|
|
6244
|
+
isConnected,
|
|
6245
|
+
activeSceneId,
|
|
6246
|
+
tracks.length,
|
|
6247
|
+
isAddingTrack,
|
|
6248
|
+
handleAddTrack,
|
|
6249
|
+
onOpenContract,
|
|
6250
|
+
host,
|
|
6251
|
+
canCrossfade,
|
|
6252
|
+
designerView,
|
|
6253
|
+
transitionDone,
|
|
6254
|
+
transitionSourceTotal,
|
|
6255
|
+
onExpandSelf,
|
|
6256
|
+
identity,
|
|
6257
|
+
features.importTracks
|
|
6258
|
+
]);
|
|
6259
|
+
(0, import_react25.useEffect)(() => {
|
|
6260
|
+
if (!onLoading) return;
|
|
6261
|
+
const anyGenerating = tracks.some((t) => t.isGenerating);
|
|
6262
|
+
onLoading(isLoadingTracks || anyGenerating || isBulkActive);
|
|
6263
|
+
return () => {
|
|
6264
|
+
onLoading(false);
|
|
6265
|
+
};
|
|
6266
|
+
}, [onLoading, isLoadingTracks, tracks, isBulkActive]);
|
|
6267
|
+
const handleDeleteTrack = (0, import_react25.useCallback)(
|
|
6268
|
+
async (trackId) => {
|
|
6269
|
+
try {
|
|
6270
|
+
await host.deleteTrack(trackId);
|
|
6271
|
+
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
6272
|
+
if (activeSceneId) {
|
|
6273
|
+
await host.deleteSceneData(activeSceneId, trackDataKey(dbId, "prompt"));
|
|
6274
|
+
}
|
|
6275
|
+
setTracks((prev) => prev.filter((t) => t.handle.id !== trackId));
|
|
6276
|
+
} catch (error) {
|
|
6277
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
6278
|
+
host.showToast("error", "Failed to delete track", msg);
|
|
6279
|
+
}
|
|
6280
|
+
},
|
|
6281
|
+
[host, activeSceneId]
|
|
6282
|
+
);
|
|
6283
|
+
const handlePromptChange = (0, import_react25.useCallback)(
|
|
6284
|
+
(trackId, prompt) => {
|
|
6285
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
|
|
6286
|
+
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
6287
|
+
if (saveTimeoutRefs.current[trackId]) {
|
|
6288
|
+
clearTimeout(saveTimeoutRefs.current[trackId]);
|
|
6289
|
+
}
|
|
6290
|
+
saveTimeoutRefs.current[trackId] = setTimeout(() => {
|
|
6291
|
+
if (activeSceneId) {
|
|
6292
|
+
host.setSceneData(activeSceneId, trackDataKey(dbId, "prompt"), prompt).catch(() => {
|
|
6293
|
+
});
|
|
6294
|
+
}
|
|
6295
|
+
}, 500);
|
|
6296
|
+
},
|
|
6297
|
+
[host, activeSceneId]
|
|
6298
|
+
);
|
|
6299
|
+
const resolvedGenericGroups = (0, import_react25.useMemo)(() => {
|
|
6300
|
+
const out = {};
|
|
6301
|
+
for (const ext of adapter.groupExtensions ?? []) {
|
|
6302
|
+
out[ext.metaKey] = resolveTrackGroups(
|
|
6303
|
+
genericGroupMetas[ext.metaKey] ?? [],
|
|
6304
|
+
tracks,
|
|
6305
|
+
(t) => t.handle.dbId,
|
|
6306
|
+
{
|
|
6307
|
+
isComplete: ext.isComplete
|
|
6308
|
+
}
|
|
6309
|
+
);
|
|
6310
|
+
}
|
|
6311
|
+
return out;
|
|
6312
|
+
}, [adapter, genericGroupMetas, tracks]);
|
|
6313
|
+
const genericGroupMemberDbIds = (0, import_react25.useMemo)(() => {
|
|
6314
|
+
const s = /* @__PURE__ */ new Set();
|
|
6315
|
+
for (const r of Object.values(resolvedGenericGroups)) {
|
|
6316
|
+
for (const dbId of r.memberDbIds) s.add(dbId);
|
|
6317
|
+
}
|
|
6318
|
+
return s;
|
|
6319
|
+
}, [resolvedGenericGroups]);
|
|
6320
|
+
const engineToDbId = (0, import_react25.useCallback)(
|
|
6321
|
+
(trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
|
|
6322
|
+
[]
|
|
6323
|
+
);
|
|
6324
|
+
const updateTrack = (0, import_react25.useCallback)(
|
|
6325
|
+
(trackId, patch) => {
|
|
6326
|
+
setTracks(
|
|
6327
|
+
(prev) => prev.map(
|
|
6328
|
+
(t) => t.handle.id === trackId ? typeof patch === "function" ? patch(t) : { ...t, ...patch } : t
|
|
6329
|
+
)
|
|
6330
|
+
);
|
|
6331
|
+
},
|
|
6332
|
+
[]
|
|
6333
|
+
);
|
|
6334
|
+
const markEditLoaded = (0, import_react25.useCallback)((trackId) => {
|
|
6335
|
+
editLoadStartedRef.current.add(trackId);
|
|
6336
|
+
}, []);
|
|
6337
|
+
const tracksRef = (0, import_react25.useRef)(tracks);
|
|
6338
|
+
(0, import_react25.useEffect)(() => {
|
|
6339
|
+
tracksRef.current = tracks;
|
|
6340
|
+
}, [tracks]);
|
|
6341
|
+
const resolvedGenericGroupsRef = (0, import_react25.useRef)(resolvedGenericGroups);
|
|
6342
|
+
(0, import_react25.useEffect)(() => {
|
|
6343
|
+
resolvedGenericGroupsRef.current = resolvedGenericGroups;
|
|
6344
|
+
}, [resolvedGenericGroups]);
|
|
6345
|
+
const makeServices = (0, import_react25.useCallback)(() => {
|
|
6346
|
+
return {
|
|
6347
|
+
host,
|
|
6348
|
+
activeSceneId,
|
|
6349
|
+
tracks: tracksRef.current,
|
|
6350
|
+
updateTrack,
|
|
6351
|
+
setTracks,
|
|
6352
|
+
reloadTracks: loadTracks,
|
|
6353
|
+
soundHistory,
|
|
6354
|
+
engineToDbId,
|
|
6355
|
+
trackDataKey,
|
|
6356
|
+
markEditLoaded,
|
|
6357
|
+
createFamilyTrack: (nameSuffix = "") => host.createTrack({
|
|
6358
|
+
name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,
|
|
6359
|
+
...adapter.createTrackOptions()
|
|
6360
|
+
}),
|
|
6361
|
+
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
6362
|
+
};
|
|
6363
|
+
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
6364
|
+
const handleGenerate = (0, import_react25.useCallback)(
|
|
6365
|
+
async (trackId) => {
|
|
6366
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6367
|
+
if (!track || !track.prompt.trim()) return;
|
|
6368
|
+
if (!isAuthenticated) {
|
|
6369
|
+
host.showToast("warning", "Sign In Required", "Please sign in to generate MIDI");
|
|
6370
|
+
return;
|
|
6371
|
+
}
|
|
6372
|
+
setTracks(
|
|
6373
|
+
(prev) => prev.map(
|
|
6374
|
+
(t) => t.handle.id === trackId ? { ...t, isGenerating: true, error: null, generationProgress: 0 } : t
|
|
6375
|
+
)
|
|
6376
|
+
);
|
|
6377
|
+
try {
|
|
6378
|
+
await adapter.generation.generate(track, makeServices());
|
|
6379
|
+
} catch (error) {
|
|
6380
|
+
const msg = error instanceof Error ? error.message : "Generation failed";
|
|
6381
|
+
setTracks(
|
|
6382
|
+
(prev) => prev.map(
|
|
6383
|
+
(t) => t.handle.id === trackId ? { ...t, isGenerating: false, error: msg, generationProgress: 0 } : t
|
|
6384
|
+
)
|
|
6385
|
+
);
|
|
6386
|
+
host.showToast("error", "Generation failed", msg);
|
|
6387
|
+
}
|
|
6388
|
+
},
|
|
6389
|
+
[host, adapter, tracks, isAuthenticated, makeServices]
|
|
6390
|
+
);
|
|
6391
|
+
const handleMuteToggle = (0, import_react25.useCallback)(
|
|
6392
|
+
(trackId) => {
|
|
6393
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6394
|
+
if (!track) return;
|
|
6395
|
+
const newMuted = !track.runtimeState.muted;
|
|
6396
|
+
setTracks(
|
|
6397
|
+
(prev) => prev.map(
|
|
6398
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, muted: newMuted } } : t
|
|
6399
|
+
)
|
|
6400
|
+
);
|
|
6401
|
+
host.setTrackMute(trackId, newMuted).catch(() => {
|
|
6402
|
+
setTracks(
|
|
6403
|
+
(prev) => prev.map(
|
|
6404
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, muted: !newMuted } } : t
|
|
6405
|
+
)
|
|
6406
|
+
);
|
|
6407
|
+
});
|
|
6408
|
+
},
|
|
6409
|
+
[host, tracks]
|
|
6410
|
+
);
|
|
6411
|
+
const handleSoloToggle = (0, import_react25.useCallback)(
|
|
6412
|
+
(trackId) => {
|
|
6413
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6414
|
+
if (!track) return;
|
|
6415
|
+
const newSolo = !track.runtimeState.solo;
|
|
6416
|
+
setTracks(
|
|
6417
|
+
(prev) => prev.map(
|
|
6418
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, solo: newSolo } } : t
|
|
6419
|
+
)
|
|
6420
|
+
);
|
|
6421
|
+
host.setTrackSolo(trackId, newSolo).catch(() => {
|
|
6422
|
+
setTracks(
|
|
6423
|
+
(prev) => prev.map(
|
|
6424
|
+
(t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, solo: !newSolo } } : t
|
|
6425
|
+
)
|
|
6426
|
+
);
|
|
6427
|
+
});
|
|
6428
|
+
},
|
|
6429
|
+
[host, tracks]
|
|
6430
|
+
);
|
|
6431
|
+
const handleVolumeChange = (0, import_react25.useCallback)(
|
|
6432
|
+
(trackId, volume) => {
|
|
6433
|
+
setTracks(
|
|
6434
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
|
|
6435
|
+
);
|
|
6436
|
+
host.setTrackVolume(trackId, volume).catch(() => {
|
|
6437
|
+
});
|
|
6438
|
+
},
|
|
6439
|
+
[host]
|
|
6440
|
+
);
|
|
6441
|
+
const handlePanChange = (0, import_react25.useCallback)(
|
|
6442
|
+
(trackId, pan) => {
|
|
6443
|
+
setTracks(
|
|
6444
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
|
|
6445
|
+
);
|
|
6446
|
+
host.setTrackPan(trackId, pan).catch(() => {
|
|
6447
|
+
});
|
|
6448
|
+
},
|
|
6449
|
+
[host]
|
|
6450
|
+
);
|
|
6451
|
+
const handleShuffle = (0, import_react25.useCallback)(
|
|
6452
|
+
async (trackId) => {
|
|
6453
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6454
|
+
if (!track) return;
|
|
6455
|
+
if (soundHistory.list(trackId).entries.length === 0) {
|
|
6456
|
+
try {
|
|
6457
|
+
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
6458
|
+
if (cap) soundHistory.record(trackId, cap.descriptor, adapter.sound.previousSoundLabel);
|
|
6459
|
+
} catch {
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
try {
|
|
6463
|
+
let result;
|
|
6464
|
+
let nextHistory;
|
|
6465
|
+
try {
|
|
6466
|
+
result = await adapter.shuffle.shuffle(track, Array.from(track.shuffleHistory));
|
|
6467
|
+
nextHistory = new Set(track.shuffleHistory);
|
|
6468
|
+
} catch (firstErr) {
|
|
6469
|
+
if (adapter.shuffle.isExhaustedError(firstErr)) {
|
|
6470
|
+
nextHistory = /* @__PURE__ */ new Set();
|
|
6471
|
+
result = await adapter.shuffle.shuffle(track, []);
|
|
6472
|
+
} else {
|
|
6473
|
+
throw firstErr;
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
nextHistory.add(result.appliedName);
|
|
6477
|
+
setTracks(
|
|
6478
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, shuffleHistory: nextHistory } : t)
|
|
6479
|
+
);
|
|
6480
|
+
try {
|
|
6481
|
+
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
6482
|
+
if (cap) soundHistory.record(trackId, cap.descriptor, result.appliedName);
|
|
6483
|
+
} catch {
|
|
6484
|
+
}
|
|
6485
|
+
console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);
|
|
6486
|
+
} catch (error) {
|
|
6487
|
+
const msg = error instanceof Error ? error.message : "Shuffle failed";
|
|
6488
|
+
host.showToast("error", "Shuffle failed", msg);
|
|
6489
|
+
}
|
|
6490
|
+
},
|
|
6491
|
+
[host, adapter, tracks, soundHistory, logTag]
|
|
6492
|
+
);
|
|
6493
|
+
const handleCopy = (0, import_react25.useCallback)(
|
|
6494
|
+
async (trackId) => {
|
|
6495
|
+
try {
|
|
6496
|
+
const newHandle = await host.duplicateTrack(trackId);
|
|
6497
|
+
await loadTracks();
|
|
6498
|
+
host.showToast("success", "Track duplicated", newHandle.name);
|
|
6499
|
+
} catch (error) {
|
|
6500
|
+
const msg = error instanceof Error ? error.message : "Copy failed";
|
|
6501
|
+
host.showToast("error", "Copy failed", msg);
|
|
6502
|
+
}
|
|
6503
|
+
},
|
|
6504
|
+
[host, loadTracks]
|
|
6505
|
+
);
|
|
6506
|
+
const handleFxToggle = (0, import_react25.useCallback)(
|
|
6507
|
+
(trackId, category, enabled) => {
|
|
6508
|
+
setTracks(
|
|
6509
|
+
(prev) => prev.map(
|
|
6510
|
+
(t) => t.handle.id === trackId ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], enabled } } } : t
|
|
6511
|
+
)
|
|
6512
|
+
);
|
|
6513
|
+
host.toggleTrackFx(trackId, category, enabled).catch(() => {
|
|
6514
|
+
setTracks(
|
|
6515
|
+
(prev) => prev.map(
|
|
6516
|
+
(t) => t.handle.id === trackId ? {
|
|
6517
|
+
...t,
|
|
6518
|
+
fxDetailState: {
|
|
6519
|
+
...t.fxDetailState,
|
|
6520
|
+
[category]: { ...t.fxDetailState[category], enabled: !enabled }
|
|
6521
|
+
}
|
|
6522
|
+
} : t
|
|
6523
|
+
)
|
|
6524
|
+
);
|
|
6525
|
+
});
|
|
6526
|
+
},
|
|
6527
|
+
[host]
|
|
6528
|
+
);
|
|
6529
|
+
const handleFxPresetChange = (0, import_react25.useCallback)(
|
|
6530
|
+
(trackId, category, presetIndex) => {
|
|
6531
|
+
setTracks(
|
|
6532
|
+
(prev) => prev.map(
|
|
6533
|
+
(t) => t.handle.id === trackId ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], presetIndex } } } : t
|
|
6534
|
+
)
|
|
6535
|
+
);
|
|
6536
|
+
host.setTrackFxPreset(trackId, category, presetIndex).then((result) => {
|
|
6537
|
+
if (result.dryWet !== void 0) {
|
|
6538
|
+
setTracks(
|
|
6539
|
+
(prev) => prev.map(
|
|
6540
|
+
(t) => t.handle.id === trackId ? {
|
|
6541
|
+
...t,
|
|
6542
|
+
fxDetailState: {
|
|
6543
|
+
...t.fxDetailState,
|
|
6544
|
+
[category]: { ...t.fxDetailState[category], dryWet: result.dryWet }
|
|
6545
|
+
}
|
|
6546
|
+
} : t
|
|
6547
|
+
)
|
|
6548
|
+
);
|
|
6549
|
+
}
|
|
6550
|
+
}).catch(() => {
|
|
6551
|
+
});
|
|
6552
|
+
},
|
|
6553
|
+
[host]
|
|
6554
|
+
);
|
|
6555
|
+
const handleFxDryWetChange = (0, import_react25.useCallback)(
|
|
6556
|
+
(trackId, category, value) => {
|
|
6557
|
+
setTracks(
|
|
6558
|
+
(prev) => prev.map(
|
|
6559
|
+
(t) => t.handle.id === trackId ? { ...t, fxDetailState: { ...t.fxDetailState, [category]: { ...t.fxDetailState[category], dryWet: value } } } : t
|
|
6560
|
+
)
|
|
6561
|
+
);
|
|
6562
|
+
host.setTrackFxDryWet(trackId, category, value).catch(() => {
|
|
6563
|
+
});
|
|
6564
|
+
},
|
|
6565
|
+
[host]
|
|
6566
|
+
);
|
|
6567
|
+
const toggleFxDrawer = (0, import_react25.useCallback)(
|
|
6568
|
+
(trackId) => {
|
|
6569
|
+
setTracks(
|
|
6570
|
+
(prev) => prev.map((t) => {
|
|
6571
|
+
if (t.handle.id !== trackId) return t;
|
|
6572
|
+
const onFx = t.drawerOpen && t.drawerTab === "fx";
|
|
6573
|
+
return { ...t, drawerOpen: !onFx, drawerTab: "fx", editorStage: false };
|
|
6574
|
+
})
|
|
6575
|
+
);
|
|
6576
|
+
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6577
|
+
const wasOnFx = !!track && track.drawerOpen && track.drawerTab === "fx";
|
|
6578
|
+
if (track && !wasOnFx) {
|
|
6579
|
+
host.getTrackFxState(trackId).then((fxState) => {
|
|
6580
|
+
setTracks(
|
|
6581
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, fxDetailState: pluginFxToToggleFx(fxState) } : t)
|
|
6582
|
+
);
|
|
6583
|
+
}).catch(() => {
|
|
6584
|
+
});
|
|
6585
|
+
}
|
|
6586
|
+
},
|
|
6587
|
+
[host, tracks]
|
|
6588
|
+
);
|
|
6589
|
+
const loadEditNotes = (0, import_react25.useCallback)(
|
|
6590
|
+
async (trackId) => {
|
|
6591
|
+
try {
|
|
6592
|
+
const mc = await host.getMusicalContext();
|
|
6593
|
+
let notes = [];
|
|
6594
|
+
if (typeof host.readMidiNotes === "function") {
|
|
6595
|
+
const result = await host.readMidiNotes(trackId);
|
|
6596
|
+
notes = result.clips[0]?.notes ?? [];
|
|
6597
|
+
}
|
|
6598
|
+
setTracks(
|
|
6599
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes, editBars: mc.bars, editBpm: mc.bpm } : t)
|
|
6600
|
+
);
|
|
6601
|
+
} catch (err) {
|
|
6602
|
+
console.warn(`[${logTag}] Failed to load MIDI for editing:`, err);
|
|
6603
|
+
}
|
|
6604
|
+
},
|
|
6605
|
+
[host, logTag]
|
|
6606
|
+
);
|
|
6607
|
+
const handleNotesChange = (0, import_react25.useCallback)(
|
|
6608
|
+
(trackId, notes) => {
|
|
6609
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
|
|
6610
|
+
const key = `edit:${trackId}`;
|
|
6611
|
+
if (saveTimeoutRefs.current[key]) {
|
|
6612
|
+
clearTimeout(saveTimeoutRefs.current[key]);
|
|
6613
|
+
}
|
|
6614
|
+
saveTimeoutRefs.current[key] = setTimeout(() => {
|
|
6615
|
+
void (async () => {
|
|
6616
|
+
try {
|
|
6617
|
+
if (notes.length === 0) {
|
|
6618
|
+
await host.clearMidi(trackId);
|
|
6619
|
+
} else {
|
|
6620
|
+
const mc = await host.getMusicalContext();
|
|
6621
|
+
await host.writeMidiClip(trackId, {
|
|
6622
|
+
startTime: 0,
|
|
6623
|
+
endTime: mc.bars * 4 * 60 / mc.bpm,
|
|
6624
|
+
tempo: mc.bpm,
|
|
6625
|
+
notes
|
|
6626
|
+
});
|
|
6627
|
+
}
|
|
6628
|
+
} catch (err) {
|
|
6629
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6630
|
+
host.showToast("error", "Failed to save edit", msg);
|
|
6631
|
+
}
|
|
6632
|
+
})();
|
|
6633
|
+
}, 300);
|
|
6634
|
+
},
|
|
6635
|
+
[host]
|
|
6636
|
+
);
|
|
6637
|
+
const handleTabChange = (0, import_react25.useCallback)(
|
|
6638
|
+
(trackId, tab) => {
|
|
6639
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
|
|
6640
|
+
if (tab === "fx") {
|
|
6641
|
+
host.getTrackFxState(trackId).then((fxState) => {
|
|
6642
|
+
setTracks(
|
|
6643
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, fxDetailState: pluginFxToToggleFx(fxState) } : t)
|
|
6644
|
+
);
|
|
6645
|
+
}).catch(() => {
|
|
6646
|
+
});
|
|
6647
|
+
} else if (tab === "pick" && availableInstruments.length === 0 && !instrumentsLoading) {
|
|
6648
|
+
setInstrumentsLoading(true);
|
|
6649
|
+
host.getAvailableInstruments().then((instruments) => {
|
|
6650
|
+
setAvailableInstruments(instruments);
|
|
6651
|
+
}).catch(() => {
|
|
6652
|
+
}).finally(() => {
|
|
6653
|
+
setInstrumentsLoading(false);
|
|
6654
|
+
});
|
|
6655
|
+
} else if (tab === "edit" && !editLoadStartedRef.current.has(trackId)) {
|
|
6656
|
+
editLoadStartedRef.current.add(trackId);
|
|
6657
|
+
void loadEditNotes(trackId);
|
|
6658
|
+
}
|
|
6659
|
+
},
|
|
6660
|
+
[host, availableInstruments.length, instrumentsLoading, loadEditNotes]
|
|
6661
|
+
);
|
|
6662
|
+
const handleProgressChange = (0, import_react25.useCallback)((trackId, pct) => {
|
|
6663
|
+
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
|
|
6664
|
+
}, []);
|
|
6665
|
+
const handleToggleDrawer = (0, import_react25.useCallback)((trackId) => {
|
|
6666
|
+
setTracks(
|
|
6667
|
+
(prev) => prev.map((t) => {
|
|
6668
|
+
if (t.handle.id !== trackId) return t;
|
|
6669
|
+
const onSound = t.drawerOpen && t.drawerTab !== "fx";
|
|
6670
|
+
return { ...t, drawerOpen: !onSound, drawerTab: "history", editorStage: false };
|
|
6671
|
+
})
|
|
6672
|
+
);
|
|
6673
|
+
}, []);
|
|
6674
|
+
const handleInstrumentSelect = (0, import_react25.useCallback)(
|
|
6675
|
+
async (trackId, pluginId) => {
|
|
6676
|
+
const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
|
|
6677
|
+
if (isDefaultInstrument) {
|
|
6678
|
+
setTracks(
|
|
6679
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: false, editorStage: false } : t)
|
|
6680
|
+
);
|
|
6681
|
+
try {
|
|
6682
|
+
await host.setTrackInstrument(trackId, pluginId);
|
|
6683
|
+
const descriptor = await host.getTrackInstrument(trackId);
|
|
6684
|
+
setTracks(
|
|
6685
|
+
(prev) => prev.map(
|
|
6686
|
+
(t) => t.handle.id === trackId ? {
|
|
6687
|
+
...t,
|
|
6688
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
6689
|
+
instrumentName: descriptor?.name ?? null,
|
|
6690
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
6691
|
+
} : t
|
|
6692
|
+
)
|
|
6693
|
+
);
|
|
6694
|
+
} catch (err) {
|
|
6695
|
+
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
6696
|
+
host.showToast("error", "Instrument load failed", msg);
|
|
6697
|
+
}
|
|
6698
|
+
return;
|
|
6699
|
+
}
|
|
6700
|
+
setTracks(
|
|
6701
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerTab: "pick", editorStage: true } : t)
|
|
6702
|
+
);
|
|
6703
|
+
try {
|
|
6704
|
+
await host.setTrackInstrument(trackId, pluginId);
|
|
6705
|
+
const descriptor = await host.getTrackInstrument(trackId);
|
|
6706
|
+
setTracks(
|
|
6707
|
+
(prev) => prev.map(
|
|
6708
|
+
(t) => t.handle.id === trackId ? {
|
|
6709
|
+
...t,
|
|
6710
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
6711
|
+
instrumentName: descriptor?.name ?? null,
|
|
6712
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
6713
|
+
} : t
|
|
6714
|
+
)
|
|
6715
|
+
);
|
|
6716
|
+
} catch (err) {
|
|
6717
|
+
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
6718
|
+
console.error(`[${logTag}] Failed to set instrument:`, err);
|
|
6719
|
+
host.showToast("error", "Instrument load failed", msg);
|
|
6720
|
+
setTracks(
|
|
6721
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
|
|
6722
|
+
);
|
|
6723
|
+
}
|
|
6724
|
+
},
|
|
6725
|
+
[host, identity.defaultInstrumentPluginId, logTag]
|
|
6726
|
+
);
|
|
6727
|
+
const handleShowEditor = (0, import_react25.useCallback)(
|
|
6728
|
+
async (trackId) => {
|
|
6729
|
+
try {
|
|
6730
|
+
await host.showInstrumentEditor(trackId);
|
|
6731
|
+
} catch (err) {
|
|
6732
|
+
const msg = err instanceof Error ? err.message : "Failed to open editor";
|
|
6733
|
+
host.showToast("error", "Editor failed", msg);
|
|
6734
|
+
}
|
|
6735
|
+
},
|
|
6736
|
+
[host]
|
|
6737
|
+
);
|
|
6738
|
+
const handleBackToInstruments = (0, import_react25.useCallback)((trackId) => {
|
|
6739
|
+
setTracks(
|
|
6740
|
+
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
|
|
6741
|
+
);
|
|
6742
|
+
}, []);
|
|
6743
|
+
const handleRefreshInstruments = (0, import_react25.useCallback)(() => {
|
|
6744
|
+
setInstrumentsLoading(true);
|
|
6745
|
+
host.getAvailableInstruments().then((instruments) => {
|
|
6746
|
+
setAvailableInstruments(instruments);
|
|
6747
|
+
}).catch(() => {
|
|
6748
|
+
}).finally(() => {
|
|
6749
|
+
setInstrumentsLoading(false);
|
|
6750
|
+
});
|
|
6751
|
+
}, [host]);
|
|
6752
|
+
const onAuditionNote = (0, import_react25.useCallback)(
|
|
6753
|
+
(trackId, pitch, velocity, ms) => {
|
|
6754
|
+
void host.auditionNote(trackId, pitch, velocity, ms);
|
|
6755
|
+
},
|
|
6756
|
+
[host]
|
|
6757
|
+
);
|
|
6758
|
+
const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react25.useMemo)(() => {
|
|
6759
|
+
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
6760
|
+
const pairs = [];
|
|
6761
|
+
const members = /* @__PURE__ */ new Set();
|
|
6762
|
+
for (const p of crossfadePairsMeta) {
|
|
6763
|
+
const origin = byDbId.get(p.originDbId);
|
|
6764
|
+
const target = byDbId.get(p.targetDbId);
|
|
6765
|
+
if (origin && target) {
|
|
6766
|
+
pairs.push({ ...p, origin, target });
|
|
6767
|
+
members.add(p.originDbId);
|
|
6768
|
+
members.add(p.targetDbId);
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
|
|
6772
|
+
}, [tracks, crossfadePairsMeta]);
|
|
6773
|
+
const { resolvedFades, fadeMemberDbIds } = (0, import_react25.useMemo)(() => {
|
|
6774
|
+
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
6775
|
+
const list = [];
|
|
6776
|
+
const members = /* @__PURE__ */ new Set();
|
|
6777
|
+
for (const f of fadesMeta) {
|
|
6778
|
+
const track = byDbId.get(f.dbId);
|
|
6779
|
+
if (track) {
|
|
6780
|
+
list.push({ ...f, track });
|
|
6781
|
+
members.add(f.dbId);
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
return { resolvedFades: list, fadeMemberDbIds: members };
|
|
6785
|
+
}, [tracks, fadesMeta]);
|
|
6786
|
+
const transition = useTransitionOps({
|
|
6787
|
+
host,
|
|
6788
|
+
adapter,
|
|
6789
|
+
activeSceneId,
|
|
6790
|
+
isConnected,
|
|
6791
|
+
isAuthenticated,
|
|
6792
|
+
sceneContext,
|
|
6793
|
+
tracks,
|
|
6794
|
+
setTracks,
|
|
6795
|
+
loadTracks,
|
|
6796
|
+
setCrossfadePairsMeta,
|
|
6797
|
+
setFadesMeta,
|
|
6798
|
+
resolvedCrossfadePairs,
|
|
6799
|
+
resolvedFades
|
|
6800
|
+
});
|
|
6801
|
+
const setGroupMute = (0, import_react25.useCallback)(
|
|
6802
|
+
(trackIds, muted) => {
|
|
6803
|
+
for (const id of trackIds) {
|
|
6804
|
+
setTracks(
|
|
6805
|
+
(prev) => prev.map((t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, muted } } : t)
|
|
6806
|
+
);
|
|
6807
|
+
host.setTrackMute(id, muted).catch(() => {
|
|
6808
|
+
});
|
|
6809
|
+
}
|
|
6810
|
+
},
|
|
6811
|
+
[host]
|
|
6812
|
+
);
|
|
6813
|
+
const setGroupSolo = (0, import_react25.useCallback)(
|
|
6814
|
+
(trackIds, solo) => {
|
|
6815
|
+
for (const id of trackIds) {
|
|
6816
|
+
setTracks(
|
|
6817
|
+
(prev) => prev.map((t) => t.handle.id === id ? { ...t, runtimeState: { ...t.runtimeState, solo } } : t)
|
|
6818
|
+
);
|
|
6819
|
+
host.setTrackSolo(id, solo).catch(() => {
|
|
6820
|
+
});
|
|
6821
|
+
}
|
|
6822
|
+
},
|
|
6823
|
+
[host]
|
|
6824
|
+
);
|
|
6825
|
+
const deleteGroup = (0, import_react25.useCallback)(
|
|
6826
|
+
async (members, cleanupKeySuffixes) => {
|
|
6827
|
+
for (const member of members) {
|
|
6828
|
+
try {
|
|
6829
|
+
await host.deleteTrack(member.engineId);
|
|
6830
|
+
} catch {
|
|
6831
|
+
}
|
|
6832
|
+
if (activeSceneId) {
|
|
6833
|
+
for (const suffix of cleanupKeySuffixes) {
|
|
6834
|
+
await host.deleteSceneData(activeSceneId, trackDataKey(member.dbId, suffix)).catch(() => {
|
|
6835
|
+
});
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6839
|
+
const gone = new Set(members.map((m) => m.engineId));
|
|
6840
|
+
setTracks((prev) => prev.filter((t) => !gone.has(t.handle.id)));
|
|
6841
|
+
await loadTracks(true);
|
|
6842
|
+
},
|
|
6843
|
+
[host, activeSceneId, loadTracks]
|
|
6844
|
+
);
|
|
6845
|
+
const handlers = (0, import_react25.useMemo)(
|
|
6846
|
+
() => ({
|
|
6847
|
+
promptChange: handlePromptChange,
|
|
6848
|
+
generate: (trackId) => {
|
|
6849
|
+
void handleGenerate(trackId);
|
|
6850
|
+
},
|
|
6851
|
+
shuffle: (trackId) => {
|
|
6852
|
+
void handleShuffle(trackId);
|
|
6853
|
+
},
|
|
6854
|
+
copy: (trackId) => {
|
|
6855
|
+
void handleCopy(trackId);
|
|
6856
|
+
},
|
|
6857
|
+
delete: (trackId) => {
|
|
6858
|
+
void handleDeleteTrack(trackId);
|
|
6859
|
+
},
|
|
6860
|
+
muteToggle: handleMuteToggle,
|
|
6861
|
+
soloToggle: handleSoloToggle,
|
|
6862
|
+
volumeChange: handleVolumeChange,
|
|
6863
|
+
panChange: handlePanChange,
|
|
6864
|
+
tabChange: handleTabChange,
|
|
6865
|
+
toggleDrawer: handleToggleDrawer,
|
|
6866
|
+
toggleFxDrawer,
|
|
6867
|
+
notesChange: handleNotesChange,
|
|
6868
|
+
progressChange: handleProgressChange
|
|
6869
|
+
}),
|
|
6870
|
+
[
|
|
6871
|
+
handlePromptChange,
|
|
6872
|
+
handleGenerate,
|
|
6873
|
+
handleShuffle,
|
|
6874
|
+
handleCopy,
|
|
6875
|
+
handleDeleteTrack,
|
|
6876
|
+
handleMuteToggle,
|
|
6877
|
+
handleSoloToggle,
|
|
6878
|
+
handleVolumeChange,
|
|
6879
|
+
handlePanChange,
|
|
6880
|
+
handleTabChange,
|
|
6881
|
+
handleToggleDrawer,
|
|
6882
|
+
toggleFxDrawer,
|
|
6883
|
+
handleNotesChange,
|
|
6884
|
+
handleProgressChange
|
|
6885
|
+
]
|
|
6886
|
+
);
|
|
6887
|
+
return {
|
|
6888
|
+
ui,
|
|
6889
|
+
adapter,
|
|
6890
|
+
tracks,
|
|
6891
|
+
setTracks,
|
|
6892
|
+
isLoadingTracks,
|
|
6893
|
+
loadTracks,
|
|
6894
|
+
engineToDbId,
|
|
6895
|
+
supportsMeters,
|
|
6896
|
+
trackLevels,
|
|
6897
|
+
anySolo,
|
|
6898
|
+
reorder,
|
|
6899
|
+
soundHistory,
|
|
6900
|
+
isComposing,
|
|
6901
|
+
placeholders,
|
|
6902
|
+
isAddingTrack,
|
|
6903
|
+
isExportingMidi,
|
|
6904
|
+
designerView,
|
|
6905
|
+
canCrossfade,
|
|
6906
|
+
needsContract,
|
|
6907
|
+
xfFromId,
|
|
6908
|
+
xfToId,
|
|
6909
|
+
importOpen,
|
|
6910
|
+
setImportOpen,
|
|
6911
|
+
soundImportTarget,
|
|
6912
|
+
setSoundImportTarget,
|
|
6913
|
+
handleSoundImportPick,
|
|
6914
|
+
handlePortTrack,
|
|
6915
|
+
transition,
|
|
6916
|
+
crossfadePairsMeta,
|
|
6917
|
+
fadesMeta,
|
|
6918
|
+
resolvedCrossfadePairs,
|
|
6919
|
+
crossfadeMemberDbIds,
|
|
6920
|
+
resolvedFades,
|
|
6921
|
+
fadeMemberDbIds,
|
|
6922
|
+
resolvedGenericGroups,
|
|
6923
|
+
genericGroupMemberDbIds,
|
|
6924
|
+
availableInstruments,
|
|
6925
|
+
instrumentsLoading,
|
|
6926
|
+
handlers,
|
|
6927
|
+
handleGenerate,
|
|
6928
|
+
handleShuffle,
|
|
6929
|
+
handleAddTrack,
|
|
6930
|
+
handleDeleteTrack,
|
|
6931
|
+
handleExportMidi,
|
|
6932
|
+
handlePromptChange,
|
|
6933
|
+
handleMuteToggle,
|
|
6934
|
+
handleSoloToggle,
|
|
6935
|
+
handleVolumeChange,
|
|
6936
|
+
handlePanChange,
|
|
6937
|
+
handleTabChange,
|
|
6938
|
+
handleToggleDrawer,
|
|
6939
|
+
toggleFxDrawer,
|
|
6940
|
+
handleNotesChange,
|
|
6941
|
+
handleProgressChange,
|
|
6942
|
+
handleCopy,
|
|
6943
|
+
handleFxToggle,
|
|
6944
|
+
handleFxPresetChange,
|
|
6945
|
+
handleFxDryWetChange,
|
|
6946
|
+
handleInstrumentSelect,
|
|
6947
|
+
handleShowEditor,
|
|
6948
|
+
handleBackToInstruments,
|
|
6949
|
+
handleRefreshInstruments,
|
|
6950
|
+
onAuditionNote,
|
|
6951
|
+
makeServices,
|
|
6952
|
+
setGroupMute,
|
|
6953
|
+
setGroupSolo,
|
|
6954
|
+
deleteGroup
|
|
6955
|
+
};
|
|
6956
|
+
}
|
|
6957
|
+
|
|
6958
|
+
// src/panel-core/GeneratorPanelShell.tsx
|
|
6959
|
+
var import_react26 = __toESM(require("react"));
|
|
6960
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
6961
|
+
function GeneratorPanelShell({ core, slots }) {
|
|
6962
|
+
const {
|
|
6963
|
+
ui,
|
|
6964
|
+
adapter,
|
|
6965
|
+
tracks,
|
|
6966
|
+
isLoadingTracks,
|
|
6967
|
+
supportsMeters,
|
|
6968
|
+
trackLevels,
|
|
6969
|
+
anySolo,
|
|
6970
|
+
reorder,
|
|
6971
|
+
soundHistory,
|
|
6972
|
+
isComposing,
|
|
6973
|
+
placeholders,
|
|
6974
|
+
designerView,
|
|
6975
|
+
canCrossfade,
|
|
6976
|
+
xfFromId,
|
|
6977
|
+
xfToId,
|
|
6978
|
+
importOpen,
|
|
6979
|
+
setImportOpen,
|
|
6980
|
+
soundImportTarget,
|
|
6981
|
+
setSoundImportTarget,
|
|
6982
|
+
handleSoundImportPick,
|
|
6983
|
+
handlePortTrack,
|
|
6984
|
+
transition,
|
|
6985
|
+
crossfadePairsMeta,
|
|
6986
|
+
fadesMeta,
|
|
6987
|
+
resolvedCrossfadePairs,
|
|
6988
|
+
crossfadeMemberDbIds,
|
|
6989
|
+
resolvedFades,
|
|
6990
|
+
fadeMemberDbIds,
|
|
6991
|
+
resolvedGenericGroups,
|
|
6992
|
+
genericGroupMemberDbIds,
|
|
6993
|
+
availableInstruments,
|
|
6994
|
+
instrumentsLoading,
|
|
6995
|
+
handlers,
|
|
6996
|
+
isExportingMidi,
|
|
6997
|
+
handleExportMidi,
|
|
6998
|
+
handleFxToggle,
|
|
6999
|
+
handleFxPresetChange,
|
|
7000
|
+
handleFxDryWetChange,
|
|
7001
|
+
handleInstrumentSelect,
|
|
7002
|
+
handleShowEditor,
|
|
7003
|
+
handleBackToInstruments,
|
|
7004
|
+
handleRefreshInstruments,
|
|
7005
|
+
onAuditionNote,
|
|
7006
|
+
loadTracks,
|
|
7007
|
+
makeServices,
|
|
7008
|
+
setGroupMute,
|
|
7009
|
+
setGroupSolo,
|
|
7010
|
+
deleteGroup
|
|
7011
|
+
} = core;
|
|
7012
|
+
const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
|
|
7013
|
+
const { identity, features } = adapter;
|
|
7014
|
+
const buildRowProps = (0, import_react26.useCallback)(
|
|
7015
|
+
(track, drag) => {
|
|
7016
|
+
const id = track.handle.id;
|
|
7017
|
+
const pickerProps = features.instrumentPicker ? {
|
|
7018
|
+
instrumentName: track.instrumentName,
|
|
7019
|
+
instrumentMissing: track.instrumentMissing,
|
|
7020
|
+
onToggleDrawer: () => handlers.toggleDrawer(id),
|
|
7021
|
+
availableInstruments,
|
|
7022
|
+
currentInstrumentPluginId: track.instrumentPluginId,
|
|
7023
|
+
onInstrumentSelect: (pluginId) => handleInstrumentSelect(id, pluginId),
|
|
7024
|
+
instrumentsLoading,
|
|
7025
|
+
onRefreshInstruments: handleRefreshInstruments,
|
|
7026
|
+
editorStage: track.editorStage,
|
|
7027
|
+
onShowEditor: () => handleShowEditor(id),
|
|
7028
|
+
onBackToInstruments: () => handleBackToInstruments(id)
|
|
7029
|
+
} : {};
|
|
7030
|
+
const importSoundProps = features.importTracks ? {
|
|
7031
|
+
onImportSound: () => setSoundImportTarget(track),
|
|
7032
|
+
importSoundLabel: adapter.sound.importSoundLabel
|
|
7033
|
+
} : {};
|
|
7034
|
+
const props = {
|
|
7035
|
+
...drag ? { drag } : {},
|
|
7036
|
+
track: { id, name: track.handle.name, role: track.role },
|
|
7037
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7038
|
+
prompt: track.prompt,
|
|
7039
|
+
runtimeState: {
|
|
7040
|
+
muted: track.runtimeState.muted,
|
|
7041
|
+
solo: track.runtimeState.solo,
|
|
7042
|
+
volume: track.runtimeState.volume,
|
|
7043
|
+
pan: track.runtimeState.pan
|
|
7044
|
+
},
|
|
7045
|
+
soloedOut: anySolo && !track.runtimeState.solo,
|
|
7046
|
+
fxDetailState: track.fxDetailState,
|
|
7047
|
+
drawerOpen: track.drawerOpen,
|
|
7048
|
+
drawerTab: track.drawerTab,
|
|
7049
|
+
onTabChange: (tab) => handlers.tabChange(id, tab),
|
|
7050
|
+
isGenerating: track.isGenerating,
|
|
7051
|
+
isAuthenticated,
|
|
7052
|
+
error: track.error,
|
|
7053
|
+
hasMidi: track.hasMidi,
|
|
7054
|
+
generationProgress: track.generationProgress,
|
|
7055
|
+
estimatedGenerationMs: identity.estimatedGenerationMs,
|
|
7056
|
+
onPromptChange: (prompt) => handlers.promptChange(id, prompt),
|
|
7057
|
+
onGenerate: () => handlers.generate(id),
|
|
7058
|
+
onShuffle: () => handlers.shuffle(id),
|
|
7059
|
+
onCopy: () => handlers.copy(id),
|
|
7060
|
+
onDelete: () => handlers.delete(id),
|
|
7061
|
+
onMuteToggle: () => handlers.muteToggle(id),
|
|
7062
|
+
onSoloToggle: () => handlers.soloToggle(id),
|
|
7063
|
+
onVolumeChange: (vol) => handlers.volumeChange(id, vol),
|
|
7064
|
+
onPanChange: (pan) => handlers.panChange(id, pan),
|
|
7065
|
+
onFxToggle: (cat, enabled) => handleFxToggle(id, cat, enabled),
|
|
7066
|
+
onFxPresetChange: (cat, idx) => handleFxPresetChange(id, cat, idx),
|
|
7067
|
+
onFxDryWetChange: (cat, val) => handleFxDryWetChange(id, cat, val),
|
|
7068
|
+
onToggleFxDrawer: () => handlers.toggleFxDrawer(id),
|
|
7069
|
+
onProgressChange: (pct) => handlers.progressChange(id, pct),
|
|
7070
|
+
accentColor: identity.accentColor,
|
|
7071
|
+
...pickerProps,
|
|
7072
|
+
soundHistory: soundHistory.list(id).entries,
|
|
7073
|
+
soundHistoryCursor: soundHistory.list(id).cursor,
|
|
7074
|
+
onRestoreSound: (i) => {
|
|
7075
|
+
void soundHistory.restoreTo(id, i);
|
|
7076
|
+
},
|
|
7077
|
+
onToggleFavorite: (i) => soundHistory.toggleFavorite(id, i),
|
|
7078
|
+
...importSoundProps,
|
|
7079
|
+
editNotes: track.editNotes,
|
|
7080
|
+
onNotesChange: (notes) => handlers.notesChange(id, notes),
|
|
7081
|
+
editBars: track.editBars,
|
|
7082
|
+
editBpm: track.editBpm,
|
|
7083
|
+
editSnap: 0.25,
|
|
7084
|
+
onAuditionNote: (pitch, vel, ms) => onAuditionNote(id, pitch, vel, ms)
|
|
7085
|
+
};
|
|
7086
|
+
return adapter.mapTrackRowProps ? adapter.mapTrackRowProps(track, props) : props;
|
|
7087
|
+
},
|
|
7088
|
+
[
|
|
7089
|
+
features.instrumentPicker,
|
|
7090
|
+
features.importTracks,
|
|
7091
|
+
adapter,
|
|
7092
|
+
supportsMeters,
|
|
7093
|
+
trackLevels,
|
|
7094
|
+
anySolo,
|
|
7095
|
+
isAuthenticated,
|
|
7096
|
+
identity,
|
|
7097
|
+
handlers,
|
|
7098
|
+
availableInstruments,
|
|
7099
|
+
instrumentsLoading,
|
|
7100
|
+
handleInstrumentSelect,
|
|
7101
|
+
handleRefreshInstruments,
|
|
7102
|
+
handleShowEditor,
|
|
7103
|
+
handleBackToInstruments,
|
|
7104
|
+
setSoundImportTarget,
|
|
7105
|
+
soundHistory,
|
|
7106
|
+
handleFxToggle,
|
|
7107
|
+
handleFxPresetChange,
|
|
7108
|
+
handleFxDryWetChange,
|
|
7109
|
+
onAuditionNote
|
|
7110
|
+
]
|
|
7111
|
+
);
|
|
7112
|
+
if (!activeSceneId) {
|
|
7113
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7114
|
+
"div",
|
|
7115
|
+
{
|
|
7116
|
+
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
7117
|
+
className: "flex items-center justify-center py-8",
|
|
7118
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7119
|
+
"button",
|
|
7120
|
+
{
|
|
7121
|
+
onClick: () => onSelectScene?.(),
|
|
7122
|
+
className: "text-sas-muted text-xs hover:text-sas-accent transition-colors underline underline-offset-2",
|
|
7123
|
+
children: "Select a Scene"
|
|
7124
|
+
}
|
|
7125
|
+
)
|
|
7126
|
+
}
|
|
7127
|
+
);
|
|
7128
|
+
}
|
|
7129
|
+
if (!sceneContext?.hasContract) {
|
|
7130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7131
|
+
"div",
|
|
7132
|
+
{
|
|
7133
|
+
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
7134
|
+
className: "flex items-center justify-center py-8",
|
|
7135
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7136
|
+
"button",
|
|
7137
|
+
{
|
|
7138
|
+
onClick: () => onOpenContract?.(),
|
|
7139
|
+
className: "text-sas-muted text-xs hover:text-sas-accent transition-colors underline underline-offset-2",
|
|
7140
|
+
children: "Generate a Contract"
|
|
7141
|
+
}
|
|
7142
|
+
)
|
|
7143
|
+
}
|
|
7144
|
+
);
|
|
7145
|
+
}
|
|
7146
|
+
if (features.bulkComposePlaceholders && isComposing) {
|
|
7147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
7148
|
+
}
|
|
7149
|
+
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
7150
|
+
if (activePlaceholders.length > 0) {
|
|
7151
|
+
const tracksByDbId = /* @__PURE__ */ new Map();
|
|
7152
|
+
for (const t of tracks) {
|
|
7153
|
+
tracksByDbId.set(t.handle.dbId, t);
|
|
7154
|
+
if (t.handle.id !== t.handle.dbId) {
|
|
7155
|
+
tracksByDbId.set(t.handle.id, t);
|
|
7156
|
+
}
|
|
7157
|
+
}
|
|
7158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
7159
|
+
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
7160
|
+
if (loadedTrack) {
|
|
7161
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
7162
|
+
}
|
|
7163
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7164
|
+
"div",
|
|
7165
|
+
{
|
|
7166
|
+
"data-testid": "bulk-placeholder-track",
|
|
7167
|
+
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
7168
|
+
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
7169
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
7170
|
+
},
|
|
7171
|
+
ph.id
|
|
7172
|
+
);
|
|
7173
|
+
}) });
|
|
7174
|
+
}
|
|
7175
|
+
const groupCtx = {
|
|
7176
|
+
services: makeServices(),
|
|
7177
|
+
anySolo,
|
|
7178
|
+
supportsMeters,
|
|
7179
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7180
|
+
handlers,
|
|
7181
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
7182
|
+
setGroupMute,
|
|
7183
|
+
setGroupSolo,
|
|
7184
|
+
deleteGroup
|
|
7185
|
+
};
|
|
7186
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
7187
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7188
|
+
ImportTrackModal,
|
|
7189
|
+
{
|
|
7190
|
+
host,
|
|
7191
|
+
open: importOpen,
|
|
7192
|
+
onClose: () => setImportOpen(false),
|
|
7193
|
+
onImported: () => {
|
|
7194
|
+
void loadTracks(true);
|
|
7195
|
+
},
|
|
7196
|
+
onPortTrack: host.readImportableTrackMidi ? handlePortTrack : void 0,
|
|
7197
|
+
testIdPrefix: `${identity.familyKey}-import`
|
|
7198
|
+
}
|
|
7199
|
+
),
|
|
7200
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7201
|
+
ImportTrackModal,
|
|
7202
|
+
{
|
|
7203
|
+
host,
|
|
7204
|
+
mode: "sound",
|
|
7205
|
+
open: !!soundImportTarget,
|
|
7206
|
+
title: adapter.sound.importSoundLabel,
|
|
7207
|
+
onClose: () => setSoundImportTarget(null),
|
|
7208
|
+
onImported: () => {
|
|
7209
|
+
},
|
|
7210
|
+
onPick: handleSoundImportPick,
|
|
7211
|
+
testIdPrefix: `${identity.familyKey}-sound-import`
|
|
7212
|
+
}
|
|
7213
|
+
),
|
|
7214
|
+
slots?.modals,
|
|
7215
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7216
|
+
TransitionDesigner,
|
|
7217
|
+
{
|
|
7218
|
+
host,
|
|
7219
|
+
fromSceneId: xfFromId,
|
|
7220
|
+
toSceneId: xfToId,
|
|
7221
|
+
transitionSceneId: activeSceneId ?? "",
|
|
7222
|
+
excludeSourceDbIds: [
|
|
7223
|
+
...crossfadePairsMeta.flatMap((p) => [p.originSourceDbId, p.targetSourceDbId]),
|
|
7224
|
+
...fadesMeta.map((f) => f.meta.sourceTrackDbId)
|
|
7225
|
+
],
|
|
7226
|
+
onCreateCrossfade: transition.handleCreateCrossfade,
|
|
7227
|
+
onCreateFade: transition.handleCreateFade,
|
|
7228
|
+
familyLabel: identity.familyLabel,
|
|
7229
|
+
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
7230
|
+
}
|
|
7231
|
+
) }),
|
|
7232
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
|
|
7233
|
+
slots?.beforeRows,
|
|
7234
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7235
|
+
CrossfadeTrackRow,
|
|
7236
|
+
{
|
|
7237
|
+
accentColor: identity.transitionAccentColor,
|
|
7238
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7239
|
+
sliderPos: pair.sliderPos,
|
|
7240
|
+
origin: {
|
|
7241
|
+
trackId: pair.origin.handle.id,
|
|
7242
|
+
name: pair.origin.handle.name,
|
|
7243
|
+
role: pair.origin.role,
|
|
7244
|
+
sourceName: pair.originSourceName,
|
|
7245
|
+
soundLabel: pair.originSoundLabel,
|
|
7246
|
+
runtimeState: pair.origin.runtimeState
|
|
7247
|
+
},
|
|
7248
|
+
target: {
|
|
7249
|
+
trackId: pair.target.handle.id,
|
|
7250
|
+
name: pair.target.handle.name,
|
|
7251
|
+
role: pair.target.role,
|
|
7252
|
+
sourceName: pair.targetSourceName,
|
|
7253
|
+
soundLabel: pair.targetSoundLabel,
|
|
7254
|
+
runtimeState: pair.target.runtimeState
|
|
7255
|
+
},
|
|
7256
|
+
onMuteToggle: () => transition.handleCrossfadeMute(pair),
|
|
7257
|
+
onSoloToggle: () => transition.handleCrossfadeSolo(pair),
|
|
7258
|
+
onVolumeChange: (slot, vol) => handlers.volumeChange(
|
|
7259
|
+
slot === "origin" ? pair.origin.handle.id : pair.target.handle.id,
|
|
7260
|
+
vol
|
|
7261
|
+
),
|
|
7262
|
+
onPanChange: (slot, pan) => handlers.panChange(
|
|
7263
|
+
slot === "origin" ? pair.origin.handle.id : pair.target.handle.id,
|
|
7264
|
+
pan
|
|
7265
|
+
),
|
|
7266
|
+
onSliderChange: (pos) => transition.handleCrossfadeSlider(pair, pos),
|
|
7267
|
+
onDelete: () => transition.handleCrossfadeDelete(pair)
|
|
7268
|
+
},
|
|
7269
|
+
pair.groupId
|
|
7270
|
+
)),
|
|
7271
|
+
resolvedFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7272
|
+
FadeTrackRow,
|
|
7273
|
+
{
|
|
7274
|
+
accentColor: identity.transitionAccentColor,
|
|
7275
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
7276
|
+
direction: fade.meta.direction,
|
|
7277
|
+
gesture: fade.meta.gesture,
|
|
7278
|
+
sliderPos: fade.meta.sliderPos,
|
|
7279
|
+
layer: {
|
|
7280
|
+
trackId: fade.track.handle.id,
|
|
7281
|
+
name: fade.track.handle.name,
|
|
7282
|
+
role: fade.track.role,
|
|
7283
|
+
sourceName: fade.meta.sourceName,
|
|
7284
|
+
soundLabel: fade.meta.soundLabel,
|
|
7285
|
+
runtimeState: fade.track.runtimeState
|
|
7286
|
+
},
|
|
7287
|
+
onMuteToggle: () => handlers.muteToggle(fade.track.handle.id),
|
|
7288
|
+
onSoloToggle: () => handlers.soloToggle(fade.track.handle.id),
|
|
7289
|
+
onVolumeChange: (vol) => handlers.volumeChange(fade.track.handle.id, vol),
|
|
7290
|
+
onPanChange: (pan) => handlers.panChange(fade.track.handle.id, pan),
|
|
7291
|
+
onSliderChange: (pos) => transition.handleFadeSlider(fade, pos),
|
|
7292
|
+
onDelete: () => transition.handleFadeDelete(fade)
|
|
7293
|
+
},
|
|
7294
|
+
fade.dbId
|
|
7295
|
+
)),
|
|
7296
|
+
(adapter.groupExtensions ?? []).flatMap(
|
|
7297
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react26.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
|
|
7298
|
+
),
|
|
7299
|
+
tracks.map((track, index) => {
|
|
7300
|
+
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
7301
|
+
return null;
|
|
7302
|
+
}
|
|
7303
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
7304
|
+
}),
|
|
7305
|
+
slots?.afterRows
|
|
7306
|
+
] })),
|
|
7307
|
+
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
7308
|
+
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
7309
|
+
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
7310
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
7311
|
+
"button",
|
|
7312
|
+
{
|
|
7313
|
+
"data-testid": "export-midi-tracks-button",
|
|
7314
|
+
onClick: handleExportMidi,
|
|
7315
|
+
disabled: exportDisabled,
|
|
7316
|
+
title: isExportingMidi ? "Exporting..." : !hasAnyMidi ? "Generate MIDI on at least one track first" : "Export all tracks as a ZIP of .mid files",
|
|
7317
|
+
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"}`,
|
|
7318
|
+
children: isExportingMidi ? "Exporting..." : "Export Tracks"
|
|
7319
|
+
}
|
|
7320
|
+
) });
|
|
7321
|
+
})()
|
|
7322
|
+
] });
|
|
7323
|
+
}
|
|
7324
|
+
|
|
7325
|
+
// src/panel-core/surge-sound-adapter.ts
|
|
7326
|
+
async function getInstrument(host, trackId) {
|
|
7327
|
+
try {
|
|
7328
|
+
const plugins = await host.getTrackPlugins(trackId);
|
|
7329
|
+
const instrument = plugins.find(
|
|
7330
|
+
(p) => !p.name.includes("Volume") && !p.name.includes("Pan") && !p.name.includes("Level")
|
|
7331
|
+
);
|
|
7332
|
+
if (!instrument) return null;
|
|
7333
|
+
return { index: instrument.index, isRaw: !instrument.name.includes("Surge") };
|
|
7334
|
+
} catch {
|
|
7335
|
+
return null;
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
function createSurgeSoundAdapter(host, overrides = {}) {
|
|
7339
|
+
const applySound = async (trackId, descriptor) => {
|
|
7340
|
+
const { state, stateType } = descriptor;
|
|
7341
|
+
const inst = await getInstrument(host, trackId);
|
|
7342
|
+
if (!inst) return;
|
|
7343
|
+
if (stateType === "raw") await host.setRawPluginState(trackId, inst.index, state);
|
|
7344
|
+
else await host.setPluginState(trackId, inst.index, state);
|
|
7345
|
+
};
|
|
7346
|
+
return {
|
|
7347
|
+
applySound,
|
|
7348
|
+
captureSoundDescriptor: async (trackId) => {
|
|
7349
|
+
const inst = await getInstrument(host, trackId);
|
|
7350
|
+
if (!inst) return null;
|
|
7351
|
+
const state = inst.isRaw ? await host.getRawPluginState(trackId, inst.index) : await host.getPluginState(trackId, inst.index);
|
|
7352
|
+
return { descriptor: { state, stateType: inst.isRaw ? "raw" : "valuetree" } };
|
|
7353
|
+
},
|
|
7354
|
+
copySnapshot: async (trackId, snap) => {
|
|
7355
|
+
if (snap.kind !== "preset") return "default";
|
|
7356
|
+
await applySound(trackId, { state: snap.state, stateType: snap.stateType });
|
|
7357
|
+
await host.persistTrackPresetState?.(trackId, {
|
|
7358
|
+
state: snap.state,
|
|
7359
|
+
stateType: snap.stateType ?? "valuetree",
|
|
7360
|
+
name: snap.label
|
|
7361
|
+
}).catch(() => {
|
|
7362
|
+
});
|
|
7363
|
+
return snap.label;
|
|
7364
|
+
},
|
|
7365
|
+
descriptorFromSnapshot: (snap) => {
|
|
7366
|
+
const preset = snap;
|
|
7367
|
+
return { state: preset.state, stateType: preset.stateType };
|
|
7368
|
+
},
|
|
7369
|
+
acceptedSnapshotKind: "preset",
|
|
7370
|
+
historyMax: overrides.historyMax ?? 12,
|
|
7371
|
+
importSoundLabel: overrides.importSoundLabel ?? "Import Preset",
|
|
7372
|
+
importNoun: "preset",
|
|
7373
|
+
previousSoundLabel: "Previous preset"
|
|
7374
|
+
};
|
|
7375
|
+
}
|
|
7376
|
+
|
|
5046
7377
|
// src/constants/sdk-version.ts
|
|
5047
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
7378
|
+
var PLUGIN_SDK_VERSION = "2.35.0";
|
|
5048
7379
|
|
|
5049
7380
|
// src/utils/format-concurrent-tracks.ts
|
|
5050
7381
|
function formatConcurrentTracks(ctx) {
|
|
@@ -5211,6 +7542,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5211
7542
|
FadeTrackRow,
|
|
5212
7543
|
FxToggleBar,
|
|
5213
7544
|
GUTTER_W,
|
|
7545
|
+
GeneratorPanelShell,
|
|
5214
7546
|
ImportTrackModal,
|
|
5215
7547
|
InstrumentDrawer,
|
|
5216
7548
|
LevelMeter,
|
|
@@ -5248,6 +7580,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5248
7580
|
cellToPx,
|
|
5249
7581
|
centerScrollTop,
|
|
5250
7582
|
computePeaks,
|
|
7583
|
+
createSurgeSoundAdapter,
|
|
5251
7584
|
dbIdsFromKeys,
|
|
5252
7585
|
dbToSlider,
|
|
5253
7586
|
defaultFadeGesture,
|
|
@@ -5255,16 +7588,21 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5255
7588
|
formatConcurrentTracks,
|
|
5256
7589
|
hashString,
|
|
5257
7590
|
moveItem,
|
|
7591
|
+
newTrackState,
|
|
5258
7592
|
normalizeSlots,
|
|
5259
7593
|
padPair,
|
|
5260
7594
|
padSlots,
|
|
5261
7595
|
parseCrossfadePairs,
|
|
5262
7596
|
parseFades,
|
|
7597
|
+
parseLLMNoteResponse,
|
|
7598
|
+
parseTrackGroups,
|
|
5263
7599
|
pickTopKWeighted,
|
|
5264
7600
|
pitchToName,
|
|
7601
|
+
pluginFxToToggleFx,
|
|
5265
7602
|
pxToCell,
|
|
5266
7603
|
reconcileSlots,
|
|
5267
7604
|
resizeNoteDuration,
|
|
7605
|
+
resolveTrackGroups,
|
|
5268
7606
|
rowKey,
|
|
5269
7607
|
rowType,
|
|
5270
7608
|
scorePromptMatch,
|
|
@@ -5273,14 +7611,17 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
5273
7611
|
soundIdentity,
|
|
5274
7612
|
synthesizeCuePoints,
|
|
5275
7613
|
tokenizePrompt,
|
|
7614
|
+
trackDataKey,
|
|
5276
7615
|
transposeNotes,
|
|
5277
7616
|
useAnySolo,
|
|
7617
|
+
useGeneratorPanelCore,
|
|
5278
7618
|
useSceneState,
|
|
5279
7619
|
useSoundHistory,
|
|
5280
7620
|
useTrackLevel,
|
|
5281
7621
|
useTrackLevels,
|
|
5282
7622
|
useTrackMeter,
|
|
5283
7623
|
useTrackReorder,
|
|
7624
|
+
useTransitionOps,
|
|
5284
7625
|
useTransportPlaying
|
|
5285
7626
|
});
|
|
5286
7627
|
//# sourceMappingURL=index.js.map
|