@signalsandsorcery/plugin-sdk 2.38.2 → 2.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +250 -50
- package/dist/index.d.ts +250 -50
- package/dist/index.js +668 -129
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +642 -114
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,10 +39,12 @@ __export(index_exports, {
|
|
|
39
39
|
DB_MIN: () => DB_MIN,
|
|
40
40
|
DEFAULT_FX_CATEGORY_DETAIL: () => DEFAULT_FX_CATEGORY_DETAIL,
|
|
41
41
|
DEFAULT_FX_DRY_WET: () => DEFAULT_FX_DRY_WET,
|
|
42
|
+
DEFAULT_STYLE_FOR_INSTRUMENTATION: () => DEFAULT_STYLE_FOR_INSTRUMENTATION,
|
|
42
43
|
DRAG_DEAD_ZONE: () => DRAG_DEAD_ZONE,
|
|
43
44
|
DownloadPackButton: () => DownloadPackButton,
|
|
44
45
|
EMPTY_FX_DETAIL_STATE: () => EMPTY_FX_DETAIL_STATE,
|
|
45
46
|
EMPTY_FX_STATE: () => EMPTY_FX_STATE,
|
|
47
|
+
ENSEMBLE_INSTRUMENTATIONS: () => ENSEMBLE_INSTRUMENTATIONS,
|
|
46
48
|
ENSEMBLE_MAX_VOICES: () => ENSEMBLE_MAX_VOICES,
|
|
47
49
|
ENSEMBLE_MIN_VOICES: () => ENSEMBLE_MIN_VOICES,
|
|
48
50
|
ENSEMBLE_STYLES: () => ENSEMBLE_STYLES,
|
|
@@ -57,7 +59,9 @@ __export(index_exports, {
|
|
|
57
59
|
FxToggleBar: () => FxToggleBar,
|
|
58
60
|
GUTTER_W: () => GUTTER_W,
|
|
59
61
|
GeneratorPanelShell: () => GeneratorPanelShell,
|
|
62
|
+
GroupCollapseChevron: () => GroupCollapseChevron,
|
|
60
63
|
GroupFadeTrackRow: () => GroupFadeTrackRow,
|
|
64
|
+
HORN_MAX_NOTES_PER_BAR: () => HORN_MAX_NOTES_PER_BAR,
|
|
61
65
|
ImportTrackModal: () => ImportTrackModal,
|
|
62
66
|
InstrumentDrawer: () => TrackDrawer,
|
|
63
67
|
LevelMeter: () => LevelMeter,
|
|
@@ -71,8 +75,12 @@ __export(index_exports, {
|
|
|
71
75
|
PianoRollEditor: () => PianoRollEditor,
|
|
72
76
|
PluginError: () => PluginError,
|
|
73
77
|
RESIZE_HANDLE_PX: () => RESIZE_HANDLE_PX,
|
|
78
|
+
RIFF_MAX_ONSET_INDEPENDENCE: () => RIFF_MAX_ONSET_INDEPENDENCE,
|
|
74
79
|
ROW_HEIGHT: () => ROW_HEIGHT,
|
|
75
80
|
SLIDER_UNITY: () => SLIDER_UNITY,
|
|
81
|
+
STAB_MAX_NOTE_DURATION_BEATS: () => STAB_MAX_NOTE_DURATION_BEATS,
|
|
82
|
+
STAB_MAX_ONSET_INDEPENDENCE: () => STAB_MAX_ONSET_INDEPENDENCE,
|
|
83
|
+
STYLES_FOR_INSTRUMENTATION: () => STYLES_FOR_INSTRUMENTATION,
|
|
76
84
|
STYLE_RULES: () => STYLE_RULES,
|
|
77
85
|
SUBMIT_ENSEMBLE_TOOL_NAME: () => SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
78
86
|
SamplePackCTACard: () => SamplePackCTACard,
|
|
@@ -86,6 +94,7 @@ __export(index_exports, {
|
|
|
86
94
|
TrackMeterStrip: () => TrackMeterStrip,
|
|
87
95
|
TrackRow: () => TrackRow,
|
|
88
96
|
TransitionDesigner: () => TransitionDesigner,
|
|
97
|
+
UNISON_MAX_ONSET_INDEPENDENCE: () => UNISON_MAX_ONSET_INDEPENDENCE,
|
|
89
98
|
VolumeSlider: () => VolumeSlider,
|
|
90
99
|
WaveformView: () => WaveformView,
|
|
91
100
|
analyzeEnsemble: () => analyzeEnsemble,
|
|
@@ -120,6 +129,7 @@ __export(index_exports, {
|
|
|
120
129
|
moveItem: () => moveItem,
|
|
121
130
|
nearestPitchWithPc: () => nearestPitchWithPc,
|
|
122
131
|
newTrackState: () => newTrackState,
|
|
132
|
+
normalizeInstrumentation: () => normalizeInstrumentation,
|
|
123
133
|
normalizeSlots: () => normalizeSlots,
|
|
124
134
|
padPair: () => padPair,
|
|
125
135
|
padSlots: () => padSlots,
|
|
@@ -143,6 +153,7 @@ __export(index_exports, {
|
|
|
143
153
|
slotsEqual: () => slotsEqual,
|
|
144
154
|
soundIdentity: () => soundIdentity,
|
|
145
155
|
splitFadeEntries: () => splitFadeEntries,
|
|
156
|
+
styleForInstrumentation: () => styleForInstrumentation,
|
|
146
157
|
synthesizeCuePoints: () => synthesizeCuePoints,
|
|
147
158
|
tokenizePrompt: () => tokenizePrompt,
|
|
148
159
|
trackDataKey: () => trackDataKey,
|
|
@@ -1369,6 +1380,7 @@ function TrackDrawer({
|
|
|
1369
1380
|
onToggleFavorite,
|
|
1370
1381
|
onImportSound,
|
|
1371
1382
|
importSoundLabel,
|
|
1383
|
+
linkedSoundHint,
|
|
1372
1384
|
editNotes,
|
|
1373
1385
|
onNotesChange,
|
|
1374
1386
|
editBars,
|
|
@@ -1435,7 +1447,7 @@ function TrackDrawer({
|
|
|
1435
1447
|
}
|
|
1436
1448
|
) : null;
|
|
1437
1449
|
const currentSound = soundHistoryCursor >= 0 && soundHistoryCursor < history.length ? history[soundHistoryCursor].label : null;
|
|
1438
|
-
const header = strip || currentSound ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
|
|
1450
|
+
const header = strip || currentSound || linkedSoundHint ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
|
|
1439
1451
|
strip,
|
|
1440
1452
|
currentSound && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1441
1453
|
"span",
|
|
@@ -1444,6 +1456,14 @@ function TrackDrawer({
|
|
|
1444
1456
|
title: currentSound,
|
|
1445
1457
|
children: currentSound
|
|
1446
1458
|
}
|
|
1459
|
+
),
|
|
1460
|
+
linkedSoundHint && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1461
|
+
"span",
|
|
1462
|
+
{
|
|
1463
|
+
"data-testid": "sdk-drawer-linked-hint",
|
|
1464
|
+
className: "text-[10px] text-sas-accent/80 px-0.5",
|
|
1465
|
+
children: linkedSoundHint
|
|
1466
|
+
}
|
|
1447
1467
|
)
|
|
1448
1468
|
] }) : null;
|
|
1449
1469
|
if (effectiveTab === "freeze") {
|
|
@@ -2679,6 +2699,8 @@ function TrackRow({
|
|
|
2679
2699
|
onToggleFavorite,
|
|
2680
2700
|
onImportSound,
|
|
2681
2701
|
importSoundLabel,
|
|
2702
|
+
linkedSoundHint,
|
|
2703
|
+
onSyncSoundToGroup,
|
|
2682
2704
|
editNotes,
|
|
2683
2705
|
onNotesChange,
|
|
2684
2706
|
editBars,
|
|
@@ -2844,15 +2866,37 @@ function TrackRow({
|
|
|
2844
2866
|
)
|
|
2845
2867
|
] }),
|
|
2846
2868
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
2847
|
-
onShuffle && /* @__PURE__ */ (0, import_jsx_runtime13.
|
|
2869
|
+
onShuffle && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2848
2870
|
"button",
|
|
2849
2871
|
{
|
|
2850
2872
|
"data-testid": "sdk-shuffle-button",
|
|
2851
2873
|
onClick: onShuffle,
|
|
2852
2874
|
disabled: !hasMidi || isGenerating || !!currentInstrumentPluginId,
|
|
2853
|
-
className: `w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${!hasMidi || isGenerating || !!currentInstrumentPluginId ? "bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
|
|
2854
|
-
title: currentInstrumentPluginId ? "Shuffle only works with default Surge XT" : hasMidi ? "Re-roll sound (keep MIDI)" : "Generate MIDI first",
|
|
2855
|
-
children:
|
|
2875
|
+
className: `relative w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${!hasMidi || isGenerating || !!currentInstrumentPluginId ? "bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
|
|
2876
|
+
title: currentInstrumentPluginId ? "Shuffle only works with default Surge XT" : hasMidi ? linkedSoundHint ? `Re-roll sound (keep MIDI) \u2014 ${linkedSoundHint}` : "Re-roll sound (keep MIDI)" : "Generate MIDI first",
|
|
2877
|
+
children: [
|
|
2878
|
+
"Shuffle",
|
|
2879
|
+
linkedSoundHint && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2880
|
+
"span",
|
|
2881
|
+
{
|
|
2882
|
+
"aria-hidden": true,
|
|
2883
|
+
"data-testid": "sdk-shuffle-linked-marker",
|
|
2884
|
+
className: "absolute -top-1 -right-1 text-[8px] leading-none",
|
|
2885
|
+
children: "\u{1F517}"
|
|
2886
|
+
}
|
|
2887
|
+
)
|
|
2888
|
+
]
|
|
2889
|
+
}
|
|
2890
|
+
),
|
|
2891
|
+
onSyncSoundToGroup && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2892
|
+
"button",
|
|
2893
|
+
{
|
|
2894
|
+
"data-testid": "sdk-sync-sound-button",
|
|
2895
|
+
onClick: onSyncSoundToGroup,
|
|
2896
|
+
disabled: isGenerating,
|
|
2897
|
+
className: `px-1.5 py-0.5 rounded-sm text-xs font-medium transition-colors border whitespace-nowrap ${isGenerating ? "bg-sas-panel border-sas-border text-sas-muted/30 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-muted hover:border-sas-accent hover:text-sas-accent"}`,
|
|
2898
|
+
title: "Apply THIS part's current sound to all linked parts \u2014 use after picking a patch inside the plugin's own editor",
|
|
2899
|
+
children: "\u2192 All"
|
|
2856
2900
|
}
|
|
2857
2901
|
),
|
|
2858
2902
|
freezeBadge && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
@@ -2936,6 +2980,7 @@ function TrackRow({
|
|
|
2936
2980
|
onToggleFavorite,
|
|
2937
2981
|
onImportSound,
|
|
2938
2982
|
importSoundLabel,
|
|
2983
|
+
linkedSoundHint,
|
|
2939
2984
|
editNotes,
|
|
2940
2985
|
onNotesChange,
|
|
2941
2986
|
editBars,
|
|
@@ -5163,9 +5208,38 @@ function TransitionDesigner({
|
|
|
5163
5208
|
] });
|
|
5164
5209
|
}
|
|
5165
5210
|
|
|
5211
|
+
// src/components/GroupCollapseChevron.tsx
|
|
5212
|
+
var import_lucide_react2 = require("lucide-react");
|
|
5213
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
5214
|
+
function GroupCollapseChevron({
|
|
5215
|
+
collapsed = false,
|
|
5216
|
+
onToggle,
|
|
5217
|
+
what = "group"
|
|
5218
|
+
}) {
|
|
5219
|
+
if (!onToggle) return null;
|
|
5220
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
5221
|
+
"button",
|
|
5222
|
+
{
|
|
5223
|
+
type: "button",
|
|
5224
|
+
"data-testid": "sdk-group-collapse",
|
|
5225
|
+
onClick: onToggle,
|
|
5226
|
+
"aria-expanded": !collapsed,
|
|
5227
|
+
title: collapsed ? `Expand ${what}` : `Collapse ${what}`,
|
|
5228
|
+
className: "shrink-0 px-0.5 py-0.5 rounded-sm text-sas-muted hover:text-sas-accent transition-colors",
|
|
5229
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
5230
|
+
import_lucide_react2.ChevronDown,
|
|
5231
|
+
{
|
|
5232
|
+
className: `w-3 h-3 transition-transform ${collapsed ? "-rotate-90" : ""}`,
|
|
5233
|
+
strokeWidth: 2.5
|
|
5234
|
+
}
|
|
5235
|
+
)
|
|
5236
|
+
}
|
|
5237
|
+
);
|
|
5238
|
+
}
|
|
5239
|
+
|
|
5166
5240
|
// src/components/PanelMasterStrip.tsx
|
|
5167
5241
|
var import_react21 = require("react");
|
|
5168
|
-
var
|
|
5242
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5169
5243
|
function PanelMasterStrip({
|
|
5170
5244
|
bus,
|
|
5171
5245
|
levels = null,
|
|
@@ -5192,14 +5266,14 @@ function PanelMasterStrip({
|
|
|
5192
5266
|
(fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
|
|
5193
5267
|
);
|
|
5194
5268
|
}, [availableFx, search]);
|
|
5195
|
-
return /* @__PURE__ */ (0,
|
|
5269
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5196
5270
|
"div",
|
|
5197
5271
|
{
|
|
5198
5272
|
"data-testid": "panel-master-strip",
|
|
5199
5273
|
className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border border-l-2 border-l-sas-accent/50 bg-sas-accent/5 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
|
|
5200
5274
|
children: [
|
|
5201
|
-
/* @__PURE__ */ (0,
|
|
5202
|
-
/* @__PURE__ */ (0,
|
|
5275
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
5276
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5203
5277
|
"span",
|
|
5204
5278
|
{
|
|
5205
5279
|
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
@@ -5207,8 +5281,8 @@ function PanelMasterStrip({
|
|
|
5207
5281
|
children: "BUS"
|
|
5208
5282
|
}
|
|
5209
5283
|
),
|
|
5210
|
-
/* @__PURE__ */ (0,
|
|
5211
|
-
/* @__PURE__ */ (0,
|
|
5284
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
|
|
5285
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5212
5286
|
VolumeSlider,
|
|
5213
5287
|
{
|
|
5214
5288
|
value: dbToSlider(bus.volume),
|
|
@@ -5216,8 +5290,8 @@ function PanelMasterStrip({
|
|
|
5216
5290
|
disabled
|
|
5217
5291
|
}
|
|
5218
5292
|
),
|
|
5219
|
-
/* @__PURE__ */ (0,
|
|
5220
|
-
/* @__PURE__ */ (0,
|
|
5293
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
|
|
5294
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5221
5295
|
LevelMeter,
|
|
5222
5296
|
{
|
|
5223
5297
|
peakDb: levels?.leftDb ?? -120,
|
|
@@ -5227,7 +5301,7 @@ function PanelMasterStrip({
|
|
|
5227
5301
|
"data-testid": "bus-meter-left"
|
|
5228
5302
|
}
|
|
5229
5303
|
),
|
|
5230
|
-
/* @__PURE__ */ (0,
|
|
5304
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5231
5305
|
LevelMeter,
|
|
5232
5306
|
{
|
|
5233
5307
|
peakDb: levels?.rightDb ?? -120,
|
|
@@ -5238,7 +5312,7 @@ function PanelMasterStrip({
|
|
|
5238
5312
|
)
|
|
5239
5313
|
] })
|
|
5240
5314
|
] }),
|
|
5241
|
-
/* @__PURE__ */ (0,
|
|
5315
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5242
5316
|
"button",
|
|
5243
5317
|
{
|
|
5244
5318
|
"data-testid": "bus-mute-button",
|
|
@@ -5249,7 +5323,7 @@ function PanelMasterStrip({
|
|
|
5249
5323
|
children: "M"
|
|
5250
5324
|
}
|
|
5251
5325
|
),
|
|
5252
|
-
/* @__PURE__ */ (0,
|
|
5326
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5253
5327
|
"button",
|
|
5254
5328
|
{
|
|
5255
5329
|
"data-testid": "bus-solo-button",
|
|
@@ -5260,14 +5334,14 @@ function PanelMasterStrip({
|
|
|
5260
5334
|
children: "S"
|
|
5261
5335
|
}
|
|
5262
5336
|
),
|
|
5263
|
-
/* @__PURE__ */ (0,
|
|
5337
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex items-center gap-1 max-w-[45%] min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5264
5338
|
"span",
|
|
5265
5339
|
{
|
|
5266
5340
|
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
5267
5341
|
className: `flex items-center gap-1 px-1.5 py-0.5 rounded-sm border text-[10px] whitespace-nowrap ${fx.enabled ? "border-sas-accent/60 text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted/50 bg-sas-panel"}`,
|
|
5268
5342
|
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
5269
5343
|
children: [
|
|
5270
|
-
/* @__PURE__ */ (0,
|
|
5344
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5271
5345
|
"button",
|
|
5272
5346
|
{
|
|
5273
5347
|
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
@@ -5278,7 +5352,7 @@ function PanelMasterStrip({
|
|
|
5278
5352
|
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
5279
5353
|
}
|
|
5280
5354
|
),
|
|
5281
|
-
onShowFxEditor ? /* @__PURE__ */ (0,
|
|
5355
|
+
onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5282
5356
|
"button",
|
|
5283
5357
|
{
|
|
5284
5358
|
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
@@ -5288,8 +5362,8 @@ function PanelMasterStrip({
|
|
|
5288
5362
|
title: `Open ${fx.name} editor`,
|
|
5289
5363
|
children: fx.name
|
|
5290
5364
|
}
|
|
5291
|
-
) : /* @__PURE__ */ (0,
|
|
5292
|
-
/* @__PURE__ */ (0,
|
|
5365
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
5366
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5293
5367
|
"button",
|
|
5294
5368
|
{
|
|
5295
5369
|
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
@@ -5304,7 +5378,7 @@ function PanelMasterStrip({
|
|
|
5304
5378
|
},
|
|
5305
5379
|
`${fx.index}:${fx.pluginId}`
|
|
5306
5380
|
)) }),
|
|
5307
|
-
/* @__PURE__ */ (0,
|
|
5381
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5308
5382
|
"button",
|
|
5309
5383
|
{
|
|
5310
5384
|
"data-testid": "bus-fx-add-button",
|
|
@@ -5316,9 +5390,9 @@ function PanelMasterStrip({
|
|
|
5316
5390
|
}
|
|
5317
5391
|
)
|
|
5318
5392
|
] }),
|
|
5319
|
-
fxPickerOpen && /* @__PURE__ */ (0,
|
|
5320
|
-
/* @__PURE__ */ (0,
|
|
5321
|
-
/* @__PURE__ */ (0,
|
|
5393
|
+
fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
5394
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
5395
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5322
5396
|
"input",
|
|
5323
5397
|
{
|
|
5324
5398
|
type: "text",
|
|
@@ -5328,7 +5402,7 @@ function PanelMasterStrip({
|
|
|
5328
5402
|
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
5329
5403
|
}
|
|
5330
5404
|
),
|
|
5331
|
-
onRefreshFx && /* @__PURE__ */ (0,
|
|
5405
|
+
onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5332
5406
|
"button",
|
|
5333
5407
|
{
|
|
5334
5408
|
onClick: () => onRefreshFx(),
|
|
@@ -5339,8 +5413,8 @@ function PanelMasterStrip({
|
|
|
5339
5413
|
}
|
|
5340
5414
|
)
|
|
5341
5415
|
] }),
|
|
5342
|
-
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0,
|
|
5343
|
-
filtered.map((fx) => /* @__PURE__ */ (0,
|
|
5416
|
+
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
|
|
5417
|
+
filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5344
5418
|
"button",
|
|
5345
5419
|
{
|
|
5346
5420
|
"data-testid": `bus-fx-pick-${fx.pluginId}`,
|
|
@@ -5348,13 +5422,13 @@ function PanelMasterStrip({
|
|
|
5348
5422
|
className: "flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent",
|
|
5349
5423
|
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
5350
5424
|
children: [
|
|
5351
|
-
/* @__PURE__ */ (0,
|
|
5352
|
-
/* @__PURE__ */ (0,
|
|
5425
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
5426
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
5353
5427
|
]
|
|
5354
5428
|
},
|
|
5355
5429
|
fx.pluginId
|
|
5356
5430
|
)),
|
|
5357
|
-
filtered.length === 0 && /* @__PURE__ */ (0,
|
|
5431
|
+
filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
|
|
5358
5432
|
] })
|
|
5359
5433
|
] })
|
|
5360
5434
|
]
|
|
@@ -5481,7 +5555,7 @@ function usePanelBus(host, activeSceneId) {
|
|
|
5481
5555
|
|
|
5482
5556
|
// src/components/DownloadPackButton.tsx
|
|
5483
5557
|
var import_react23 = require("react");
|
|
5484
|
-
var
|
|
5558
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
5485
5559
|
function formatSize(bytes) {
|
|
5486
5560
|
if (!bytes || bytes <= 0) return "";
|
|
5487
5561
|
const gb = bytes / 1024 ** 3;
|
|
@@ -5569,8 +5643,8 @@ var DownloadPackButton = ({
|
|
|
5569
5643
|
} else {
|
|
5570
5644
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
5571
5645
|
}
|
|
5572
|
-
return /* @__PURE__ */ (0,
|
|
5573
|
-
/* @__PURE__ */ (0,
|
|
5646
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
|
|
5647
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5574
5648
|
"button",
|
|
5575
5649
|
{
|
|
5576
5650
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -5581,12 +5655,12 @@ var DownloadPackButton = ({
|
|
|
5581
5655
|
children: buttonLabel
|
|
5582
5656
|
}
|
|
5583
5657
|
),
|
|
5584
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0,
|
|
5658
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
5585
5659
|
] });
|
|
5586
5660
|
};
|
|
5587
5661
|
|
|
5588
5662
|
// src/components/SamplePackCTACard.tsx
|
|
5589
|
-
var
|
|
5663
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
5590
5664
|
var SamplePackCTACard = ({
|
|
5591
5665
|
host,
|
|
5592
5666
|
pack,
|
|
@@ -5594,7 +5668,7 @@ var SamplePackCTACard = ({
|
|
|
5594
5668
|
onDownloadComplete
|
|
5595
5669
|
}) => {
|
|
5596
5670
|
if (status === "checking") {
|
|
5597
|
-
return /* @__PURE__ */ (0,
|
|
5671
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5598
5672
|
"div",
|
|
5599
5673
|
{
|
|
5600
5674
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -5605,16 +5679,16 @@ var SamplePackCTACard = ({
|
|
|
5605
5679
|
}
|
|
5606
5680
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
5607
5681
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
5608
|
-
return /* @__PURE__ */ (0,
|
|
5682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
5609
5683
|
"div",
|
|
5610
5684
|
{
|
|
5611
5685
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
5612
5686
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
5613
5687
|
children: [
|
|
5614
|
-
/* @__PURE__ */ (0,
|
|
5615
|
-
/* @__PURE__ */ (0,
|
|
5616
|
-
/* @__PURE__ */ (0,
|
|
5617
|
-
/* @__PURE__ */ (0,
|
|
5688
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
5689
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
5690
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
5691
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5618
5692
|
DownloadPackButton,
|
|
5619
5693
|
{
|
|
5620
5694
|
host,
|
|
@@ -5694,7 +5768,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
5694
5768
|
}
|
|
5695
5769
|
|
|
5696
5770
|
// src/components/WaveformView.tsx
|
|
5697
|
-
var
|
|
5771
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
5698
5772
|
var WaveformView = ({
|
|
5699
5773
|
host,
|
|
5700
5774
|
filePath,
|
|
@@ -5742,7 +5816,7 @@ var WaveformView = ({
|
|
|
5742
5816
|
observer.observe(canvas);
|
|
5743
5817
|
return () => observer.disconnect();
|
|
5744
5818
|
}, [peaks, fillStyle]);
|
|
5745
|
-
return /* @__PURE__ */ (0,
|
|
5819
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5746
5820
|
"canvas",
|
|
5747
5821
|
{
|
|
5748
5822
|
ref: canvasRef,
|
|
@@ -5754,7 +5828,7 @@ var WaveformView = ({
|
|
|
5754
5828
|
|
|
5755
5829
|
// src/components/ScrollingWaveform.tsx
|
|
5756
5830
|
var import_react25 = require("react");
|
|
5757
|
-
var
|
|
5831
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
5758
5832
|
var ScrollingWaveform = ({
|
|
5759
5833
|
getPeakDb,
|
|
5760
5834
|
active,
|
|
@@ -5830,7 +5904,7 @@ var ScrollingWaveform = ({
|
|
|
5830
5904
|
}
|
|
5831
5905
|
};
|
|
5832
5906
|
}, [active, getPeakDb, fillStyle]);
|
|
5833
|
-
return /* @__PURE__ */ (0,
|
|
5907
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
5834
5908
|
"canvas",
|
|
5835
5909
|
{
|
|
5836
5910
|
ref: canvasRef,
|
|
@@ -5842,7 +5916,7 @@ var ScrollingWaveform = ({
|
|
|
5842
5916
|
|
|
5843
5917
|
// src/components/OffsetScrubber.tsx
|
|
5844
5918
|
var import_react26 = require("react");
|
|
5845
|
-
var
|
|
5919
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
5846
5920
|
var SLIDER_HEIGHT_PX = 28;
|
|
5847
5921
|
var TICK_HEIGHT_PX = 14;
|
|
5848
5922
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -5957,9 +6031,9 @@ function OffsetScrubber({
|
|
|
5957
6031
|
});
|
|
5958
6032
|
}, [cuePoints, sampleToFraction]);
|
|
5959
6033
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
5960
|
-
return /* @__PURE__ */ (0,
|
|
5961
|
-
/* @__PURE__ */ (0,
|
|
5962
|
-
/* @__PURE__ */ (0,
|
|
6034
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
6035
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
6036
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
5963
6037
|
"div",
|
|
5964
6038
|
{
|
|
5965
6039
|
ref: trackRef,
|
|
@@ -5975,7 +6049,7 @@ function OffsetScrubber({
|
|
|
5975
6049
|
"aria-valuenow": draftOffset,
|
|
5976
6050
|
"aria-disabled": isDisabled,
|
|
5977
6051
|
children: [
|
|
5978
|
-
/* @__PURE__ */ (0,
|
|
6052
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
5979
6053
|
"div",
|
|
5980
6054
|
{
|
|
5981
6055
|
"aria-hidden": "true",
|
|
@@ -5983,7 +6057,7 @@ function OffsetScrubber({
|
|
|
5983
6057
|
style: { left: "50%" }
|
|
5984
6058
|
}
|
|
5985
6059
|
),
|
|
5986
|
-
ticks.map((t) => /* @__PURE__ */ (0,
|
|
6060
|
+
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
5987
6061
|
"div",
|
|
5988
6062
|
{
|
|
5989
6063
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -5998,7 +6072,7 @@ function OffsetScrubber({
|
|
|
5998
6072
|
},
|
|
5999
6073
|
t.i
|
|
6000
6074
|
)),
|
|
6001
|
-
/* @__PURE__ */ (0,
|
|
6075
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6002
6076
|
"div",
|
|
6003
6077
|
{
|
|
6004
6078
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -6015,7 +6089,7 @@ function OffsetScrubber({
|
|
|
6015
6089
|
]
|
|
6016
6090
|
}
|
|
6017
6091
|
),
|
|
6018
|
-
/* @__PURE__ */ (0,
|
|
6092
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6019
6093
|
"span",
|
|
6020
6094
|
{
|
|
6021
6095
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -6023,7 +6097,7 @@ function OffsetScrubber({
|
|
|
6023
6097
|
children: formatOffset(draftOffset, sampleRate)
|
|
6024
6098
|
}
|
|
6025
6099
|
),
|
|
6026
|
-
/* @__PURE__ */ (0,
|
|
6100
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6027
6101
|
"button",
|
|
6028
6102
|
{
|
|
6029
6103
|
type: "button",
|
|
@@ -6035,7 +6109,7 @@ function OffsetScrubber({
|
|
|
6035
6109
|
children: "\u2316"
|
|
6036
6110
|
}
|
|
6037
6111
|
),
|
|
6038
|
-
bpmMismatch && /* @__PURE__ */ (0,
|
|
6112
|
+
bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6039
6113
|
"span",
|
|
6040
6114
|
{
|
|
6041
6115
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -7048,7 +7122,7 @@ function useTransitionOps({
|
|
|
7048
7122
|
}
|
|
7049
7123
|
|
|
7050
7124
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
7051
|
-
var
|
|
7125
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
7052
7126
|
var EMPTY_PLACEHOLDERS = [];
|
|
7053
7127
|
function useGeneratorPanelCore({
|
|
7054
7128
|
ui,
|
|
@@ -7459,37 +7533,6 @@ function useGeneratorPanelCore({
|
|
|
7459
7533
|
},
|
|
7460
7534
|
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
7461
7535
|
);
|
|
7462
|
-
const handleSoundImportPick = (0, import_react31.useCallback)(
|
|
7463
|
-
async (sel) => {
|
|
7464
|
-
const target = soundImportTarget;
|
|
7465
|
-
if (!target || !host.getTrackSound) {
|
|
7466
|
-
setSoundImportTarget(null);
|
|
7467
|
-
return;
|
|
7468
|
-
}
|
|
7469
|
-
const noun = adapter.sound.importNoun;
|
|
7470
|
-
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
7471
|
-
try {
|
|
7472
|
-
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
7473
|
-
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
7474
|
-
host.showToast(
|
|
7475
|
-
"error",
|
|
7476
|
-
`No ${noun} to import`,
|
|
7477
|
-
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
7478
|
-
);
|
|
7479
|
-
return;
|
|
7480
|
-
}
|
|
7481
|
-
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
7482
|
-
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
7483
|
-
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
7484
|
-
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
7485
|
-
} catch (err) {
|
|
7486
|
-
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
7487
|
-
} finally {
|
|
7488
|
-
setSoundImportTarget(null);
|
|
7489
|
-
}
|
|
7490
|
-
},
|
|
7491
|
-
[soundImportTarget, host, adapter, identity.familyKey, soundHistory]
|
|
7492
|
-
);
|
|
7493
7536
|
const [isExportingMidi, setIsExportingMidi] = (0, import_react31.useState)(false);
|
|
7494
7537
|
const handleExportMidi = (0, import_react31.useCallback)(async () => {
|
|
7495
7538
|
if (isExportingMidi) return;
|
|
@@ -7545,8 +7588,8 @@ function useGeneratorPanelCore({
|
|
|
7545
7588
|
if (!onHeaderContent) return;
|
|
7546
7589
|
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
7547
7590
|
onHeaderContent(
|
|
7548
|
-
/* @__PURE__ */ (0,
|
|
7549
|
-
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
7591
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
7592
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
7550
7593
|
"button",
|
|
7551
7594
|
{
|
|
7552
7595
|
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
@@ -7560,7 +7603,7 @@ function useGeneratorPanelCore({
|
|
|
7560
7603
|
children: identity.importTrackLabel ?? "Import Track"
|
|
7561
7604
|
}
|
|
7562
7605
|
),
|
|
7563
|
-
(!canCrossfade || !designerView) && /* @__PURE__ */ (0,
|
|
7606
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
7564
7607
|
"button",
|
|
7565
7608
|
{
|
|
7566
7609
|
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
@@ -7576,7 +7619,7 @@ function useGeneratorPanelCore({
|
|
|
7576
7619
|
children: identity.addTrackLabel ?? "Add Track"
|
|
7577
7620
|
}
|
|
7578
7621
|
),
|
|
7579
|
-
canCrossfade && /* @__PURE__ */ (0,
|
|
7622
|
+
canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
|
|
7580
7623
|
"button",
|
|
7581
7624
|
{
|
|
7582
7625
|
"data-testid": `${identity.familyKey}-view-toggle`,
|
|
@@ -7595,7 +7638,7 @@ function useGeneratorPanelCore({
|
|
|
7595
7638
|
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
7596
7639
|
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",
|
|
7597
7640
|
children: [
|
|
7598
|
-
transitionSourceTotal > 0 && /* @__PURE__ */ (0,
|
|
7641
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
7599
7642
|
"span",
|
|
7600
7643
|
{
|
|
7601
7644
|
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
@@ -7603,7 +7646,7 @@ function useGeneratorPanelCore({
|
|
|
7603
7646
|
"aria-hidden": true
|
|
7604
7647
|
}
|
|
7605
7648
|
),
|
|
7606
|
-
/* @__PURE__ */ (0,
|
|
7649
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "relative", children: [
|
|
7607
7650
|
"\u21C4 ",
|
|
7608
7651
|
designerView ? "Transition" : "Tracks",
|
|
7609
7652
|
transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
|
|
@@ -7736,9 +7779,183 @@ function useGeneratorPanelCore({
|
|
|
7736
7779
|
name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,
|
|
7737
7780
|
...adapter.createTrackOptions()
|
|
7738
7781
|
}),
|
|
7739
|
-
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
7782
|
+
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? [],
|
|
7783
|
+
sound: adapter.sound
|
|
7740
7784
|
};
|
|
7741
7785
|
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
7786
|
+
const broadcastSoundFromTrack = (0, import_react31.useCallback)(
|
|
7787
|
+
async (sourceTrackId, descriptor, label) => {
|
|
7788
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7789
|
+
if (!targetsOf) return;
|
|
7790
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7791
|
+
if (!source) return;
|
|
7792
|
+
let targets;
|
|
7793
|
+
try {
|
|
7794
|
+
targets = await targetsOf(source, makeServices());
|
|
7795
|
+
} catch (err) {
|
|
7796
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7797
|
+
return;
|
|
7798
|
+
}
|
|
7799
|
+
const sourceInstrument = source.instrumentPluginId ?? null;
|
|
7800
|
+
const others = (targets ?? []).filter((t) => {
|
|
7801
|
+
if (t.engineId === sourceTrackId) return false;
|
|
7802
|
+
const live = tracksRef.current.find((x) => x.handle.id === t.engineId);
|
|
7803
|
+
return (live?.instrumentPluginId ?? null) === sourceInstrument;
|
|
7804
|
+
});
|
|
7805
|
+
if (others.length === 0) return;
|
|
7806
|
+
try {
|
|
7807
|
+
await adapter.sound.persistDescriptor?.(sourceTrackId, descriptor, label);
|
|
7808
|
+
} catch {
|
|
7809
|
+
}
|
|
7810
|
+
const appliedIds = /* @__PURE__ */ new Set();
|
|
7811
|
+
const failed = [];
|
|
7812
|
+
for (const target of others) {
|
|
7813
|
+
try {
|
|
7814
|
+
if (soundHistory.list(target.engineId).entries.length === 0) {
|
|
7815
|
+
try {
|
|
7816
|
+
const cap = await adapter.sound.captureSoundDescriptor(target.engineId);
|
|
7817
|
+
if (cap) {
|
|
7818
|
+
soundHistory.record(target.engineId, cap.descriptor, adapter.sound.previousSoundLabel);
|
|
7819
|
+
}
|
|
7820
|
+
} catch {
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7823
|
+
await adapter.sound.applySound(target.engineId, descriptor);
|
|
7824
|
+
try {
|
|
7825
|
+
await adapter.sound.persistDescriptor?.(target.engineId, descriptor, label);
|
|
7826
|
+
} catch {
|
|
7827
|
+
}
|
|
7828
|
+
soundHistory.record(target.engineId, descriptor, label);
|
|
7829
|
+
appliedIds.add(target.engineId);
|
|
7830
|
+
} catch (err) {
|
|
7831
|
+
failed.push(target.label ?? target.engineId);
|
|
7832
|
+
console.warn(`[${logTag}] Linked sound apply failed for ${target.engineId}:`, err);
|
|
7833
|
+
}
|
|
7834
|
+
}
|
|
7835
|
+
if (appliedIds.size > 0) {
|
|
7836
|
+
setTracks(
|
|
7837
|
+
(prev) => prev.map(
|
|
7838
|
+
(t) => appliedIds.has(t.handle.id) ? { ...t, shuffleHistory: /* @__PURE__ */ new Set([...t.shuffleHistory, label]) } : t
|
|
7839
|
+
)
|
|
7840
|
+
);
|
|
7841
|
+
}
|
|
7842
|
+
if (failed.length > 0) {
|
|
7843
|
+
host.showToast(
|
|
7844
|
+
"warning",
|
|
7845
|
+
"Linked sound applied to some parts only",
|
|
7846
|
+
`Skipped: ${failed.join(", ")}`
|
|
7847
|
+
);
|
|
7848
|
+
}
|
|
7849
|
+
},
|
|
7850
|
+
[adapter, makeServices, soundHistory, host, logTag]
|
|
7851
|
+
);
|
|
7852
|
+
const broadcastInstrumentFromTrack = (0, import_react31.useCallback)(
|
|
7853
|
+
async (sourceTrackId, pluginId) => {
|
|
7854
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7855
|
+
if (!targetsOf) return;
|
|
7856
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7857
|
+
if (!source) return;
|
|
7858
|
+
let targets;
|
|
7859
|
+
try {
|
|
7860
|
+
targets = await targetsOf(source, makeServices());
|
|
7861
|
+
} catch (err) {
|
|
7862
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7863
|
+
return;
|
|
7864
|
+
}
|
|
7865
|
+
const others = (targets ?? []).filter((t) => t.engineId !== sourceTrackId);
|
|
7866
|
+
if (others.length === 0) return;
|
|
7867
|
+
const failed = [];
|
|
7868
|
+
for (const target of others) {
|
|
7869
|
+
try {
|
|
7870
|
+
await host.setTrackInstrument(target.engineId, pluginId);
|
|
7871
|
+
const descriptor = await host.getTrackInstrument(target.engineId);
|
|
7872
|
+
setTracks(
|
|
7873
|
+
(prev) => prev.map(
|
|
7874
|
+
(t) => t.handle.id === target.engineId ? {
|
|
7875
|
+
...t,
|
|
7876
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
7877
|
+
instrumentName: descriptor?.name ?? null,
|
|
7878
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
7879
|
+
} : t
|
|
7880
|
+
)
|
|
7881
|
+
);
|
|
7882
|
+
} catch (err) {
|
|
7883
|
+
failed.push(target.label ?? target.engineId);
|
|
7884
|
+
console.warn(`[${logTag}] Linked instrument apply failed for ${target.engineId}:`, err);
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
if (failed.length > 0) {
|
|
7888
|
+
host.showToast(
|
|
7889
|
+
"warning",
|
|
7890
|
+
"Instrument applied to some parts only",
|
|
7891
|
+
`Skipped: ${failed.join(", ")}`
|
|
7892
|
+
);
|
|
7893
|
+
}
|
|
7894
|
+
},
|
|
7895
|
+
[adapter, makeServices, host, logTag]
|
|
7896
|
+
);
|
|
7897
|
+
const handleSyncSoundToGroup = (0, import_react31.useCallback)(
|
|
7898
|
+
async (trackId) => {
|
|
7899
|
+
const track = tracksRef.current.find((t) => t.handle.id === trackId);
|
|
7900
|
+
if (!track) return;
|
|
7901
|
+
let cap = null;
|
|
7902
|
+
try {
|
|
7903
|
+
cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
7904
|
+
} catch {
|
|
7905
|
+
cap = null;
|
|
7906
|
+
}
|
|
7907
|
+
if (!cap) {
|
|
7908
|
+
host.showToast("warning", "Nothing to sync", "This track has no sound to copy yet.");
|
|
7909
|
+
return;
|
|
7910
|
+
}
|
|
7911
|
+
const hist = soundHistory.list(trackId);
|
|
7912
|
+
const label = (hist.cursor >= 0 ? hist.entries[hist.cursor]?.label : void 0) ?? track.instrumentName ?? "Current sound";
|
|
7913
|
+
await broadcastSoundFromTrack(trackId, cap.descriptor, label);
|
|
7914
|
+
},
|
|
7915
|
+
[adapter, soundHistory, host, broadcastSoundFromTrack]
|
|
7916
|
+
);
|
|
7917
|
+
const handleRestoreSound = (0, import_react31.useCallback)(
|
|
7918
|
+
async (trackId, index) => {
|
|
7919
|
+
const entry = soundHistory.list(trackId).entries[index];
|
|
7920
|
+
const moved = await soundHistory.restoreTo(trackId, index);
|
|
7921
|
+
if (moved && entry) {
|
|
7922
|
+
await broadcastSoundFromTrack(trackId, entry.descriptor, entry.label);
|
|
7923
|
+
}
|
|
7924
|
+
},
|
|
7925
|
+
[soundHistory, broadcastSoundFromTrack]
|
|
7926
|
+
);
|
|
7927
|
+
const handleSoundImportPick = (0, import_react31.useCallback)(
|
|
7928
|
+
async (sel) => {
|
|
7929
|
+
const target = soundImportTarget;
|
|
7930
|
+
if (!target || !host.getTrackSound) {
|
|
7931
|
+
setSoundImportTarget(null);
|
|
7932
|
+
return;
|
|
7933
|
+
}
|
|
7934
|
+
const noun = adapter.sound.importNoun;
|
|
7935
|
+
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
7936
|
+
try {
|
|
7937
|
+
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
7938
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
7939
|
+
host.showToast(
|
|
7940
|
+
"error",
|
|
7941
|
+
`No ${noun} to import`,
|
|
7942
|
+
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
7943
|
+
);
|
|
7944
|
+
return;
|
|
7945
|
+
}
|
|
7946
|
+
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
7947
|
+
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
7948
|
+
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
7949
|
+
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
7950
|
+
await broadcastSoundFromTrack(target.handle.id, descriptor, snap.label);
|
|
7951
|
+
} catch (err) {
|
|
7952
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
7953
|
+
} finally {
|
|
7954
|
+
setSoundImportTarget(null);
|
|
7955
|
+
}
|
|
7956
|
+
},
|
|
7957
|
+
[soundImportTarget, host, adapter, identity.familyKey, soundHistory, broadcastSoundFromTrack]
|
|
7958
|
+
);
|
|
7742
7959
|
const handleGenerate = (0, import_react31.useCallback)(
|
|
7743
7960
|
async (trackId) => {
|
|
7744
7961
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
@@ -7857,7 +8074,10 @@ function useGeneratorPanelCore({
|
|
|
7857
8074
|
);
|
|
7858
8075
|
try {
|
|
7859
8076
|
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
7860
|
-
if (cap)
|
|
8077
|
+
if (cap) {
|
|
8078
|
+
soundHistory.record(trackId, cap.descriptor, result.appliedName);
|
|
8079
|
+
await broadcastSoundFromTrack(trackId, cap.descriptor, result.appliedName);
|
|
8080
|
+
}
|
|
7861
8081
|
} catch {
|
|
7862
8082
|
}
|
|
7863
8083
|
console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);
|
|
@@ -7866,7 +8086,7 @@ function useGeneratorPanelCore({
|
|
|
7866
8086
|
host.showToast("error", "Shuffle failed", msg);
|
|
7867
8087
|
}
|
|
7868
8088
|
},
|
|
7869
|
-
[host, adapter, tracks, soundHistory, logTag]
|
|
8089
|
+
[host, adapter, tracks, soundHistory, logTag, broadcastSoundFromTrack]
|
|
7870
8090
|
);
|
|
7871
8091
|
const handleCopy = (0, import_react31.useCallback)(
|
|
7872
8092
|
async (trackId) => {
|
|
@@ -8069,6 +8289,7 @@ function useGeneratorPanelCore({
|
|
|
8069
8289
|
} : t
|
|
8070
8290
|
)
|
|
8071
8291
|
);
|
|
8292
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
8072
8293
|
} catch (err) {
|
|
8073
8294
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
8074
8295
|
host.showToast("error", "Instrument load failed", msg);
|
|
@@ -8091,6 +8312,7 @@ function useGeneratorPanelCore({
|
|
|
8091
8312
|
} : t
|
|
8092
8313
|
)
|
|
8093
8314
|
);
|
|
8315
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
8094
8316
|
} catch (err) {
|
|
8095
8317
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
8096
8318
|
console.error(`[${logTag}] Failed to set instrument:`, err);
|
|
@@ -8100,7 +8322,7 @@ function useGeneratorPanelCore({
|
|
|
8100
8322
|
);
|
|
8101
8323
|
}
|
|
8102
8324
|
},
|
|
8103
|
-
[host, identity.defaultInstrumentPluginId, logTag]
|
|
8325
|
+
[host, identity.defaultInstrumentPluginId, logTag, broadcastInstrumentFromTrack]
|
|
8104
8326
|
);
|
|
8105
8327
|
const handleShowEditor = (0, import_react31.useCallback)(
|
|
8106
8328
|
async (trackId) => {
|
|
@@ -8233,6 +8455,9 @@ function useGeneratorPanelCore({
|
|
|
8233
8455
|
shuffle: (trackId) => {
|
|
8234
8456
|
void handleShuffle(trackId);
|
|
8235
8457
|
},
|
|
8458
|
+
syncSoundToGroup: (trackId) => {
|
|
8459
|
+
void handleSyncSoundToGroup(trackId);
|
|
8460
|
+
},
|
|
8236
8461
|
copy: (trackId) => {
|
|
8237
8462
|
void handleCopy(trackId);
|
|
8238
8463
|
},
|
|
@@ -8253,6 +8478,7 @@ function useGeneratorPanelCore({
|
|
|
8253
8478
|
handlePromptChange,
|
|
8254
8479
|
handleGenerate,
|
|
8255
8480
|
handleShuffle,
|
|
8481
|
+
handleSyncSoundToGroup,
|
|
8256
8482
|
handleCopy,
|
|
8257
8483
|
handleDeleteTrack,
|
|
8258
8484
|
handleMuteToggle,
|
|
@@ -8310,6 +8536,8 @@ function useGeneratorPanelCore({
|
|
|
8310
8536
|
handlers,
|
|
8311
8537
|
handleGenerate,
|
|
8312
8538
|
handleShuffle,
|
|
8539
|
+
handleRestoreSound,
|
|
8540
|
+
handleSyncSoundToGroup,
|
|
8313
8541
|
handleAddTrack,
|
|
8314
8542
|
handleDeleteTrack,
|
|
8315
8543
|
handleExportMidi,
|
|
@@ -8340,8 +8568,41 @@ function useGeneratorPanelCore({
|
|
|
8340
8568
|
}
|
|
8341
8569
|
|
|
8342
8570
|
// src/panel-core/GeneratorPanelShell.tsx
|
|
8343
|
-
var import_react32 =
|
|
8344
|
-
var
|
|
8571
|
+
var import_react32 = require("react");
|
|
8572
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
8573
|
+
var GROUP_UI_KEY = "groupUi";
|
|
8574
|
+
function CollapsibleGroup({
|
|
8575
|
+
ext,
|
|
8576
|
+
group,
|
|
8577
|
+
groupCtx
|
|
8578
|
+
}) {
|
|
8579
|
+
const { host, activeSceneId, trackDataKey: trackDataKey2 } = groupCtx.services;
|
|
8580
|
+
const uiKey = trackDataKey2(group.groupId, GROUP_UI_KEY);
|
|
8581
|
+
const [collapsed, setCollapsed] = (0, import_react32.useState)(false);
|
|
8582
|
+
(0, import_react32.useEffect)(() => {
|
|
8583
|
+
let cancelled = false;
|
|
8584
|
+
if (!activeSceneId) return void 0;
|
|
8585
|
+
void host.getSceneData(activeSceneId, uiKey).then((raw) => {
|
|
8586
|
+
if (cancelled || !raw || typeof raw !== "object") return;
|
|
8587
|
+
setCollapsed(raw.collapsed === true);
|
|
8588
|
+
}).catch(() => {
|
|
8589
|
+
});
|
|
8590
|
+
return () => {
|
|
8591
|
+
cancelled = true;
|
|
8592
|
+
};
|
|
8593
|
+
}, [host, activeSceneId, uiKey]);
|
|
8594
|
+
const onToggleCollapse = (0, import_react32.useCallback)(() => {
|
|
8595
|
+
setCollapsed((prev) => {
|
|
8596
|
+
const next = !prev;
|
|
8597
|
+
if (activeSceneId) {
|
|
8598
|
+
void host.setSceneData(activeSceneId, uiKey, { collapsed: next }).catch(() => {
|
|
8599
|
+
});
|
|
8600
|
+
}
|
|
8601
|
+
return next;
|
|
8602
|
+
});
|
|
8603
|
+
}, [host, activeSceneId, uiKey]);
|
|
8604
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_jsx_runtime29.Fragment, { children: ext.renderGroup(group, { ...groupCtx, collapsed, onToggleCollapse }) });
|
|
8605
|
+
}
|
|
8345
8606
|
function GeneratorPanelShell({ core, slots }) {
|
|
8346
8607
|
const {
|
|
8347
8608
|
ui,
|
|
@@ -8364,6 +8625,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8364
8625
|
soundImportTarget,
|
|
8365
8626
|
setSoundImportTarget,
|
|
8366
8627
|
handleSoundImportPick,
|
|
8628
|
+
handleRestoreSound,
|
|
8367
8629
|
handlePortTrack,
|
|
8368
8630
|
transition,
|
|
8369
8631
|
crossfadePairsMeta,
|
|
@@ -8459,7 +8721,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8459
8721
|
soundHistory: soundHistory.list(id).entries,
|
|
8460
8722
|
soundHistoryCursor: soundHistory.list(id).cursor,
|
|
8461
8723
|
onRestoreSound: (i) => {
|
|
8462
|
-
void
|
|
8724
|
+
void handleRestoreSound(id, i);
|
|
8463
8725
|
},
|
|
8464
8726
|
onToggleFavorite: (i) => soundHistory.toggleFavorite(id, i),
|
|
8465
8727
|
...importSoundProps,
|
|
@@ -8490,6 +8752,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8490
8752
|
handleBackToInstruments,
|
|
8491
8753
|
setSoundImportTarget,
|
|
8492
8754
|
soundHistory,
|
|
8755
|
+
handleRestoreSound,
|
|
8493
8756
|
handleFxToggle,
|
|
8494
8757
|
handleFxPresetChange,
|
|
8495
8758
|
handleFxDryWetChange,
|
|
@@ -8497,12 +8760,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8497
8760
|
]
|
|
8498
8761
|
);
|
|
8499
8762
|
if (!activeSceneId) {
|
|
8500
|
-
return /* @__PURE__ */ (0,
|
|
8763
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8501
8764
|
"div",
|
|
8502
8765
|
{
|
|
8503
8766
|
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
8504
8767
|
className: "flex items-center justify-center py-8",
|
|
8505
|
-
children: /* @__PURE__ */ (0,
|
|
8768
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8506
8769
|
"button",
|
|
8507
8770
|
{
|
|
8508
8771
|
onClick: () => onSelectScene?.(),
|
|
@@ -8514,12 +8777,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8514
8777
|
);
|
|
8515
8778
|
}
|
|
8516
8779
|
if (!sceneContext?.hasContract) {
|
|
8517
|
-
return /* @__PURE__ */ (0,
|
|
8780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8518
8781
|
"div",
|
|
8519
8782
|
{
|
|
8520
8783
|
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
8521
8784
|
className: "flex items-center justify-center py-8",
|
|
8522
|
-
children: /* @__PURE__ */ (0,
|
|
8785
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8523
8786
|
"button",
|
|
8524
8787
|
{
|
|
8525
8788
|
onClick: () => onOpenContract?.(),
|
|
@@ -8531,7 +8794,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8531
8794
|
);
|
|
8532
8795
|
}
|
|
8533
8796
|
if (features.bulkComposePlaceholders && isComposing) {
|
|
8534
|
-
return /* @__PURE__ */ (0,
|
|
8797
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
8535
8798
|
}
|
|
8536
8799
|
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
8537
8800
|
if (activePlaceholders.length > 0) {
|
|
@@ -8542,18 +8805,18 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8542
8805
|
tracksByDbId.set(t.handle.id, t);
|
|
8543
8806
|
}
|
|
8544
8807
|
}
|
|
8545
|
-
return /* @__PURE__ */ (0,
|
|
8808
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
8546
8809
|
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
8547
8810
|
if (loadedTrack) {
|
|
8548
|
-
return /* @__PURE__ */ (0,
|
|
8811
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
8549
8812
|
}
|
|
8550
|
-
return /* @__PURE__ */ (0,
|
|
8813
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8551
8814
|
"div",
|
|
8552
8815
|
{
|
|
8553
8816
|
"data-testid": "bulk-placeholder-track",
|
|
8554
8817
|
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
8555
8818
|
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
8556
|
-
children: /* @__PURE__ */ (0,
|
|
8819
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
8557
8820
|
},
|
|
8558
8821
|
ph.id
|
|
8559
8822
|
);
|
|
@@ -8565,13 +8828,13 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8565
8828
|
supportsMeters,
|
|
8566
8829
|
levels: supportsMeters ? trackLevels : void 0,
|
|
8567
8830
|
handlers,
|
|
8568
|
-
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0,
|
|
8831
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
8569
8832
|
setGroupMute,
|
|
8570
8833
|
setGroupSolo,
|
|
8571
8834
|
deleteGroup
|
|
8572
8835
|
};
|
|
8573
|
-
return /* @__PURE__ */ (0,
|
|
8574
|
-
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
8836
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
8837
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8575
8838
|
ImportTrackModal,
|
|
8576
8839
|
{
|
|
8577
8840
|
host,
|
|
@@ -8584,7 +8847,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8584
8847
|
testIdPrefix: `${identity.familyKey}-import`
|
|
8585
8848
|
}
|
|
8586
8849
|
),
|
|
8587
|
-
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0,
|
|
8850
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8588
8851
|
ImportTrackModal,
|
|
8589
8852
|
{
|
|
8590
8853
|
host,
|
|
@@ -8599,7 +8862,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8599
8862
|
}
|
|
8600
8863
|
),
|
|
8601
8864
|
slots?.modals,
|
|
8602
|
-
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0,
|
|
8865
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8603
8866
|
TransitionDesigner,
|
|
8604
8867
|
{
|
|
8605
8868
|
host,
|
|
@@ -8618,8 +8881,8 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8618
8881
|
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
8619
8882
|
}
|
|
8620
8883
|
) }),
|
|
8621
|
-
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0,
|
|
8622
|
-
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0,
|
|
8884
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
|
|
8885
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8623
8886
|
PanelMasterStrip,
|
|
8624
8887
|
{
|
|
8625
8888
|
bus: panelBus.bus,
|
|
@@ -8640,7 +8903,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8640
8903
|
}
|
|
8641
8904
|
),
|
|
8642
8905
|
slots?.beforeRows,
|
|
8643
|
-
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0,
|
|
8906
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8644
8907
|
CrossfadeTrackRow,
|
|
8645
8908
|
{
|
|
8646
8909
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8677,7 +8940,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8677
8940
|
},
|
|
8678
8941
|
pair.groupId
|
|
8679
8942
|
)),
|
|
8680
|
-
resolvedSingleFades.map((fade) => /* @__PURE__ */ (0,
|
|
8943
|
+
resolvedSingleFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8681
8944
|
FadeTrackRow,
|
|
8682
8945
|
{
|
|
8683
8946
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8702,7 +8965,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8702
8965
|
},
|
|
8703
8966
|
fade.dbId
|
|
8704
8967
|
)),
|
|
8705
|
-
resolvedGroupFades.map((group) => /* @__PURE__ */ (0,
|
|
8968
|
+
resolvedGroupFades.map((group) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8706
8969
|
GroupFadeTrackRow,
|
|
8707
8970
|
{
|
|
8708
8971
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8738,20 +9001,28 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8738
9001
|
group.groupId
|
|
8739
9002
|
)),
|
|
8740
9003
|
(adapter.groupExtensions ?? []).flatMap(
|
|
8741
|
-
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ (0,
|
|
9004
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
9005
|
+
CollapsibleGroup,
|
|
9006
|
+
{
|
|
9007
|
+
ext,
|
|
9008
|
+
group,
|
|
9009
|
+
groupCtx
|
|
9010
|
+
},
|
|
9011
|
+
`${ext.metaKey}:${group.groupId}`
|
|
9012
|
+
))
|
|
8742
9013
|
),
|
|
8743
9014
|
tracks.map((track, index) => {
|
|
8744
9015
|
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
8745
9016
|
return null;
|
|
8746
9017
|
}
|
|
8747
|
-
return /* @__PURE__ */ (0,
|
|
9018
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
8748
9019
|
}),
|
|
8749
9020
|
slots?.afterRows
|
|
8750
9021
|
] })),
|
|
8751
9022
|
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
8752
9023
|
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
8753
9024
|
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
8754
|
-
return /* @__PURE__ */ (0,
|
|
9025
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8755
9026
|
"button",
|
|
8756
9027
|
{
|
|
8757
9028
|
"data-testid": "export-midi-tracks-button",
|
|
@@ -8806,6 +9077,15 @@ function createSurgeSoundAdapter(host, overrides = {}) {
|
|
|
8806
9077
|
});
|
|
8807
9078
|
return snap.label;
|
|
8808
9079
|
},
|
|
9080
|
+
persistDescriptor: async (trackId, descriptor, label) => {
|
|
9081
|
+
const { state, stateType } = descriptor;
|
|
9082
|
+
await host.persistTrackPresetState?.(trackId, {
|
|
9083
|
+
state,
|
|
9084
|
+
stateType: stateType ?? "valuetree",
|
|
9085
|
+
name: label
|
|
9086
|
+
}).catch(() => {
|
|
9087
|
+
});
|
|
9088
|
+
},
|
|
8809
9089
|
descriptorFromSnapshot: (snap) => {
|
|
8810
9090
|
const preset = snap;
|
|
8811
9091
|
return { state: preset.state, stateType: preset.stateType };
|
|
@@ -8890,18 +9170,183 @@ var SUB = {
|
|
|
8890
9170
|
rootOnly: true,
|
|
8891
9171
|
monoPreference: "low"
|
|
8892
9172
|
};
|
|
9173
|
+
var HORN_MAX_NOTES_PER_BAR = 12;
|
|
9174
|
+
var HORN_FOLLOW_PALETTE = "EXACTLY the lead voice's rhythm \u2014 every attack lands together with voice 1";
|
|
9175
|
+
var HORN_LEAD = {
|
|
9176
|
+
label: "lead trumpet",
|
|
9177
|
+
role: "brass",
|
|
9178
|
+
registerLow: 60,
|
|
9179
|
+
// C4
|
|
9180
|
+
registerHigh: 84,
|
|
9181
|
+
// C6
|
|
9182
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9183
|
+
rhythmPalette: "tight funk syncopation \u2014 8ths and 16ths, staccato punches, off-beat anticipations",
|
|
9184
|
+
harmonicDiscipline: "the section's melodic top \u2014 chord tones on every hit; quick chromatic approach tones only on pickups",
|
|
9185
|
+
monoPreference: "high"
|
|
9186
|
+
};
|
|
9187
|
+
var HORN_TRUMPET_2 = {
|
|
9188
|
+
label: "second trumpet",
|
|
9189
|
+
role: "brass",
|
|
9190
|
+
registerLow: 55,
|
|
9191
|
+
// G3
|
|
9192
|
+
registerHigh: 79,
|
|
9193
|
+
// G5
|
|
9194
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9195
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9196
|
+
harmonicDiscipline: "nearest chord tone directly below voice 1 \u2014 tight close voicing",
|
|
9197
|
+
monoPreference: "high"
|
|
9198
|
+
};
|
|
9199
|
+
var HORN_ALTO = {
|
|
9200
|
+
label: "alto sax",
|
|
9201
|
+
role: "brass",
|
|
9202
|
+
registerLow: 53,
|
|
9203
|
+
// F3
|
|
9204
|
+
registerHigh: 77,
|
|
9205
|
+
// F5
|
|
9206
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9207
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9208
|
+
harmonicDiscipline: "chord tone below the trumpets \u2014 keep the upper stack inside one octave",
|
|
9209
|
+
monoPreference: "high"
|
|
9210
|
+
};
|
|
9211
|
+
var HORN_TENOR = {
|
|
9212
|
+
label: "tenor sax",
|
|
9213
|
+
role: "brass",
|
|
9214
|
+
registerLow: 46,
|
|
9215
|
+
// Bb2
|
|
9216
|
+
registerHigh: 72,
|
|
9217
|
+
// C5
|
|
9218
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9219
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9220
|
+
harmonicDiscipline: "chord tone under the upper horns \u2014 complete the chord",
|
|
9221
|
+
monoPreference: "low"
|
|
9222
|
+
};
|
|
9223
|
+
var HORN_TROMBONE = {
|
|
9224
|
+
label: "trombone",
|
|
9225
|
+
role: "brass",
|
|
9226
|
+
registerLow: 43,
|
|
9227
|
+
// G2
|
|
9228
|
+
registerHigh: 67,
|
|
9229
|
+
// G4
|
|
9230
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9231
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9232
|
+
harmonicDiscipline: "chord tone or root below the saxes \u2014 weight in the middle-low stack",
|
|
9233
|
+
monoPreference: "low"
|
|
9234
|
+
};
|
|
9235
|
+
var HORN_BARI = {
|
|
9236
|
+
label: "baritone sax",
|
|
9237
|
+
role: "brass",
|
|
9238
|
+
registerLow: 36,
|
|
9239
|
+
// C2
|
|
9240
|
+
registerHigh: 60,
|
|
9241
|
+
// C4
|
|
9242
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9243
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9244
|
+
harmonicDiscipline: "chord roots and lower chord tones \u2014 the section's bottom anchor",
|
|
9245
|
+
monoPreference: "low"
|
|
9246
|
+
};
|
|
9247
|
+
var WIND_FLUTE = {
|
|
9248
|
+
label: "flute",
|
|
9249
|
+
role: "winds",
|
|
9250
|
+
registerLow: 60,
|
|
9251
|
+
// C4
|
|
9252
|
+
registerHigh: 93,
|
|
9253
|
+
// A6
|
|
9254
|
+
maxNotesPerBar: 8,
|
|
9255
|
+
rhythmPalette: "8ths and 16ths; runs, turns and trills welcome",
|
|
9256
|
+
harmonicDiscipline: "freest voice \u2014 non-chord tones as passing/neighbor tones on weak beats, resolving by step",
|
|
9257
|
+
monoPreference: "high"
|
|
9258
|
+
};
|
|
9259
|
+
var WIND_FLUTE_2 = {
|
|
9260
|
+
label: "second flute",
|
|
9261
|
+
role: "winds",
|
|
9262
|
+
registerLow: 60,
|
|
9263
|
+
// C4
|
|
9264
|
+
registerHigh: 86,
|
|
9265
|
+
// D6
|
|
9266
|
+
maxNotesPerBar: 5,
|
|
9267
|
+
rhythmPalette: "quarters with occasional 8th-note motion",
|
|
9268
|
+
harmonicDiscipline: "chord tones; fill gaps the other inner voices leave",
|
|
9269
|
+
monoPreference: "high"
|
|
9270
|
+
};
|
|
9271
|
+
var WIND_OBOE = {
|
|
9272
|
+
label: "oboe",
|
|
9273
|
+
role: "winds",
|
|
9274
|
+
registerLow: 58,
|
|
9275
|
+
// Bb3
|
|
9276
|
+
registerHigh: 84,
|
|
9277
|
+
// C6
|
|
9278
|
+
maxNotesPerBar: 6,
|
|
9279
|
+
rhythmPalette: "8ths and quarters; move when the flute rests",
|
|
9280
|
+
harmonicDiscipline: "mostly chord tones; may imitate the flute's motifs a bar later",
|
|
9281
|
+
monoPreference: "high"
|
|
9282
|
+
};
|
|
9283
|
+
var WIND_CLARINET = {
|
|
9284
|
+
label: "clarinet",
|
|
9285
|
+
role: "winds",
|
|
9286
|
+
registerLow: 50,
|
|
9287
|
+
// D3
|
|
9288
|
+
registerHigh: 81,
|
|
9289
|
+
// A5
|
|
9290
|
+
maxNotesPerBar: 4,
|
|
9291
|
+
rhythmPalette: "quarters and halves",
|
|
9292
|
+
harmonicDiscipline: "chord tones with smooth stepwise motion between them",
|
|
9293
|
+
monoPreference: "high"
|
|
9294
|
+
};
|
|
9295
|
+
var WIND_FRENCH_HORN = {
|
|
9296
|
+
label: "french horn",
|
|
9297
|
+
role: "winds",
|
|
9298
|
+
registerLow: 41,
|
|
9299
|
+
// F2
|
|
9300
|
+
registerHigh: 72,
|
|
9301
|
+
// C5
|
|
9302
|
+
maxNotesPerBar: 4,
|
|
9303
|
+
rhythmPalette: "quarters and halves; long held tones welcome",
|
|
9304
|
+
harmonicDiscipline: "chord tones \u2014 the warm glue in the middle of the ensemble",
|
|
9305
|
+
monoPreference: "low"
|
|
9306
|
+
};
|
|
9307
|
+
var WIND_BASSOON = {
|
|
9308
|
+
label: "bassoon",
|
|
9309
|
+
role: "winds",
|
|
9310
|
+
registerLow: 34,
|
|
9311
|
+
// Bb1
|
|
9312
|
+
registerHigh: 62,
|
|
9313
|
+
// D4
|
|
9314
|
+
maxNotesPerBar: 3,
|
|
9315
|
+
rhythmPalette: "quarters and halves; brief walking figures at cadences",
|
|
9316
|
+
harmonicDiscipline: "roots and fifths emphasized; passing tones only between chord tones",
|
|
9317
|
+
monoPreference: "low"
|
|
9318
|
+
};
|
|
8893
9319
|
var ENSEMBLE_MIN_VOICES = 2;
|
|
8894
9320
|
var ENSEMBLE_MAX_VOICES = 6;
|
|
8895
|
-
var
|
|
9321
|
+
var STRINGS_SPEC_TABLES = {
|
|
8896
9322
|
2: [TOP, BASS],
|
|
8897
9323
|
3: [TOP, INNER, BASS],
|
|
8898
9324
|
4: [TOP, COUNTER, TENOR, BASS],
|
|
8899
9325
|
5: [TOP, COUNTER, INNER, TENOR, SUB],
|
|
8900
9326
|
6: [TOP, COUNTER, INNER_2, INNER, TENOR, SUB]
|
|
8901
9327
|
};
|
|
8902
|
-
|
|
9328
|
+
var HORNS_SPEC_TABLES = {
|
|
9329
|
+
2: [HORN_LEAD, HORN_TENOR],
|
|
9330
|
+
3: [HORN_LEAD, HORN_TENOR, HORN_BARI],
|
|
9331
|
+
4: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_BARI],
|
|
9332
|
+
5: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_TROMBONE, HORN_BARI],
|
|
9333
|
+
6: [HORN_LEAD, HORN_TRUMPET_2, HORN_ALTO, HORN_TENOR, HORN_TROMBONE, HORN_BARI]
|
|
9334
|
+
};
|
|
9335
|
+
var WINDS_SPEC_TABLES = {
|
|
9336
|
+
2: [WIND_FLUTE, WIND_BASSOON],
|
|
9337
|
+
3: [WIND_FLUTE, WIND_CLARINET, WIND_BASSOON],
|
|
9338
|
+
4: [WIND_FLUTE, WIND_OBOE, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9339
|
+
5: [WIND_FLUTE, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9340
|
+
6: [WIND_FLUTE, WIND_FLUTE_2, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON]
|
|
9341
|
+
};
|
|
9342
|
+
var SPEC_TABLES_BY_INSTRUMENTATION = {
|
|
9343
|
+
strings: STRINGS_SPEC_TABLES,
|
|
9344
|
+
horns: HORNS_SPEC_TABLES,
|
|
9345
|
+
winds: WINDS_SPEC_TABLES
|
|
9346
|
+
};
|
|
9347
|
+
function defaultVoiceSpecs(voiceCount, instrumentation = "strings") {
|
|
8903
9348
|
const n = Math.max(ENSEMBLE_MIN_VOICES, Math.min(ENSEMBLE_MAX_VOICES, Math.round(voiceCount)));
|
|
8904
|
-
return
|
|
9349
|
+
return SPEC_TABLES_BY_INSTRUMENTATION[instrumentation][n].map((spec, voiceIndex) => ({ ...spec, voiceIndex }));
|
|
8905
9350
|
}
|
|
8906
9351
|
|
|
8907
9352
|
// src/ensemble-core/enforce-voice.ts
|
|
@@ -8941,10 +9386,14 @@ function enforceVoice(rawNotes, spec, opts) {
|
|
|
8941
9386
|
repairs.push(`voice ${spec.voiceIndex}: dropped note outside the ${opts.bars}-bar clip (start ${n.startBeat})`);
|
|
8942
9387
|
continue;
|
|
8943
9388
|
}
|
|
8944
|
-
|
|
9389
|
+
let durationBeats = Math.max(
|
|
8945
9390
|
MIN_NOTE_DURATION_BEATS,
|
|
8946
9391
|
Math.min(n.durationBeats, clipEnd - n.startBeat)
|
|
8947
9392
|
);
|
|
9393
|
+
if (opts.maxNoteDurationBeats !== void 0 && durationBeats > opts.maxNoteDurationBeats) {
|
|
9394
|
+
durationBeats = Math.max(MIN_NOTE_DURATION_BEATS, opts.maxNoteDurationBeats);
|
|
9395
|
+
repairs.push(`voice ${spec.voiceIndex}: trimmed note at beat ${n.startBeat} to the style's ${opts.maxNoteDurationBeats}-beat stab ceiling`);
|
|
9396
|
+
}
|
|
8948
9397
|
notes.push({ ...n, durationBeats });
|
|
8949
9398
|
}
|
|
8950
9399
|
notes = notes.map((n) => {
|
|
@@ -9105,12 +9554,19 @@ function describeViolations(analysis, rules) {
|
|
|
9105
9554
|
if (pair.onsetIndependence < rules.minOnsetIndependence) {
|
|
9106
9555
|
out.push(`Voices ${pair.upperVoice + 1} and ${pair.upperVoice + 2} attack together too often (independence ${pair.onsetIndependence.toFixed(2)} < ${rules.minOnsetIndependence}) \u2014 stagger entrances and let one voice move while the other holds.`);
|
|
9107
9556
|
}
|
|
9557
|
+
if (rules.maxOnsetIndependence !== void 0 && pair.onsetIndependence > rules.maxOnsetIndependence) {
|
|
9558
|
+
out.push(`Voices ${pair.upperVoice + 1} and ${pair.upperVoice + 2} don't attack together enough (independence ${pair.onsetIndependence.toFixed(2)} > ${rules.maxOnsetIndependence}) \u2014 this is a section: give every voice the SAME rhythm as the lead so the hits land as one.`);
|
|
9559
|
+
}
|
|
9108
9560
|
}
|
|
9109
9561
|
return out;
|
|
9110
9562
|
}
|
|
9111
9563
|
|
|
9112
9564
|
// src/ensemble-core/styles.ts
|
|
9113
9565
|
var ENSEMBLE_STYLES = ["counterpoint", "chorale", "interlock"];
|
|
9566
|
+
var STAB_MAX_ONSET_INDEPENDENCE = 0.25;
|
|
9567
|
+
var RIFF_MAX_ONSET_INDEPENDENCE = 0.3;
|
|
9568
|
+
var UNISON_MAX_ONSET_INDEPENDENCE = 0.15;
|
|
9569
|
+
var STAB_MAX_NOTE_DURATION_BEATS = 1;
|
|
9114
9570
|
var STYLE_RULES = {
|
|
9115
9571
|
counterpoint: {
|
|
9116
9572
|
forbidParallelPerfects: true,
|
|
@@ -9129,9 +9585,55 @@ var STYLE_RULES = {
|
|
|
9129
9585
|
forbidVoiceCrossing: false,
|
|
9130
9586
|
minOnsetIndependence: 0.6,
|
|
9131
9587
|
promptParagraph: "STYLE \u2014 INTERLOCK (minimal / systems music): short repeating cells that mesh like gears. Each voice keeps its own ostinato; onsets rarely coincide with the neighboring voice; parallel motion and doubling are welcome when the composite rhythm stays busy and even."
|
|
9588
|
+
},
|
|
9589
|
+
stabs: {
|
|
9590
|
+
forbidParallelPerfects: false,
|
|
9591
|
+
forbidVoiceCrossing: true,
|
|
9592
|
+
minOnsetIndependence: 0,
|
|
9593
|
+
maxOnsetIndependence: STAB_MAX_ONSET_INDEPENDENCE,
|
|
9594
|
+
maxNoteDurationBeats: STAB_MAX_NOTE_DURATION_BEATS,
|
|
9595
|
+
promptParagraph: `STYLE \u2014 STABS (James Brown funk-45 horn punches): short accented chord hits \u2014 single punches and two-to-four-note kicks \u2014 placed on the groove's pressure points: the one, off-beat "ands", and the pickup into the next bar. Every hit is staccato (a 16th to an 8th long) and voiced as ONE tight chord under the lead. Leave REAL space between hits \u2014 the section punctuates the groove, it never carpets it.`
|
|
9596
|
+
},
|
|
9597
|
+
riffs: {
|
|
9598
|
+
forbidParallelPerfects: false,
|
|
9599
|
+
forbidVoiceCrossing: true,
|
|
9600
|
+
minOnsetIndependence: 0,
|
|
9601
|
+
maxOnsetIndependence: RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9602
|
+
promptParagraph: 'STYLE \u2014 RIFFS (funk/soul section soli): ONE syncopated 8th/16th riff, one or two bars long, repeated with small variations for the whole clip \u2014 the entire section plays it together, harmonized in tight close voicings under the lead. Anticipate downbeats (attack the "and" just before the barline), leave air at phrase ends, and let the final hit of a phrase ring a little longer.'
|
|
9603
|
+
},
|
|
9604
|
+
unison: {
|
|
9605
|
+
forbidParallelPerfects: false,
|
|
9606
|
+
forbidVoiceCrossing: false,
|
|
9607
|
+
minOnsetIndependence: 0,
|
|
9608
|
+
maxOnsetIndependence: UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9609
|
+
promptParagraph: "STYLE \u2014 UNISON (the Cold Sweat power line): every voice doubles the SAME riff in octaves \u2014 no harmony, one line, maximum punch. Keep it syncopated and riff-based with space between phrases; octave doubling and parallel motion are the entire point."
|
|
9132
9610
|
}
|
|
9133
9611
|
};
|
|
9134
9612
|
|
|
9613
|
+
// src/ensemble-core/instrumentation.ts
|
|
9614
|
+
var ENSEMBLE_INSTRUMENTATIONS = [
|
|
9615
|
+
"strings",
|
|
9616
|
+
"horns",
|
|
9617
|
+
"winds"
|
|
9618
|
+
];
|
|
9619
|
+
var STYLES_FOR_INSTRUMENTATION = {
|
|
9620
|
+
strings: ["counterpoint", "chorale", "interlock"],
|
|
9621
|
+
horns: ["stabs", "riffs", "unison"],
|
|
9622
|
+
winds: ["counterpoint", "chorale", "interlock"]
|
|
9623
|
+
};
|
|
9624
|
+
var DEFAULT_STYLE_FOR_INSTRUMENTATION = {
|
|
9625
|
+
strings: "counterpoint",
|
|
9626
|
+
horns: "stabs",
|
|
9627
|
+
winds: "chorale"
|
|
9628
|
+
};
|
|
9629
|
+
function normalizeInstrumentation(raw) {
|
|
9630
|
+
return ENSEMBLE_INSTRUMENTATIONS.includes(raw) ? raw : "strings";
|
|
9631
|
+
}
|
|
9632
|
+
function styleForInstrumentation(instrumentation, rawStyle) {
|
|
9633
|
+
const allowed = STYLES_FOR_INSTRUMENTATION[instrumentation];
|
|
9634
|
+
return allowed.includes(rawStyle ?? "") ? rawStyle : DEFAULT_STYLE_FOR_INSTRUMENTATION[instrumentation];
|
|
9635
|
+
}
|
|
9636
|
+
|
|
9135
9637
|
// src/ensemble-core/ensemble-schema.ts
|
|
9136
9638
|
var SUBMIT_ENSEMBLE_TOOL_NAME = "submit_ensemble";
|
|
9137
9639
|
function buildSubmitEnsembleParameters(voiceCount) {
|
|
@@ -9213,9 +9715,13 @@ function voiceContractLine(spec) {
|
|
|
9213
9715
|
const root = spec.rootOnly ? " ROOT PITCH CLASS ONLY (each bar's chord root)." : "";
|
|
9214
9716
|
return `- Voice ${spec.voiceIndex + 1} (${spec.label}): MIDI ${spec.registerLow}-${spec.registerHigh}, max ${spec.maxNotesPerBar} notes/bar, rhythm: ${spec.rhythmPalette}. ${spec.harmonicDiscipline}.${root}`;
|
|
9215
9717
|
}
|
|
9216
|
-
|
|
9718
|
+
var WOVEN_PERSONA = {
|
|
9719
|
+
strings: "You are an ensemble composer.",
|
|
9720
|
+
winds: "You are a wind-ensemble composer writing for chamber winds (flutes, oboe, clarinet, french horn, bassoon)."
|
|
9721
|
+
};
|
|
9722
|
+
function buildWovenPrompt(specs, style, instrumentation) {
|
|
9217
9723
|
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9218
|
-
return
|
|
9724
|
+
return `${WOVEN_PERSONA[instrumentation]} Compose ${specs.length} voices as ONE piece of music \u2014 not ${specs.length} independent parts.
|
|
9219
9725
|
|
|
9220
9726
|
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9221
9727
|
|
|
@@ -9232,6 +9738,28 @@ ${styleParagraph}
|
|
|
9232
9738
|
PER-VOICE CONTRACTS:
|
|
9233
9739
|
${specs.map(voiceContractLine).join("\n")}`;
|
|
9234
9740
|
}
|
|
9741
|
+
function buildSectionPrompt(specs, style) {
|
|
9742
|
+
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9743
|
+
return `You are a funk horn-section arranger. Compose ${specs.length} horn parts as ONE section \u2014 ${specs.length} players phrasing like a single instrument, not ${specs.length} independent parts.
|
|
9744
|
+
|
|
9745
|
+
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9746
|
+
|
|
9747
|
+
THE SECTION RULES (most important):
|
|
9748
|
+
1. The section speaks with ONE rhythm: every voice attacks on the same beats as Voice 1 (the lead). Compose the lead line first, then stack the other voices onto its rhythm exactly \u2014 the style below dictates chord voicing vs octave doubling.
|
|
9749
|
+
2. Each voice is a SINGLE monophonic line \u2014 within one voice, no two notes overlap in time and no chords. The chord is the STACK of voices hitting together.
|
|
9750
|
+
3. SPACE IS THE GROOVE: short phrases and punches separated by real rests. Never carpet the bar \u2014 the section punctuates around the rhythm section, and the silence between hits is part of the riff.
|
|
9751
|
+
4. Follow the chord progression in the musical context exactly \u2014 every hit is voiced from the chord sounding at that beat.
|
|
9752
|
+
5. Make it DANCE: this is funk. Syncopate, anticipate downbeats (attack the "and" just before the barline), and accent with velocity \u2014 105-120 on accented hits, 70-90 on lighter punches. Repeat the riff so the clip locks like a loop.
|
|
9753
|
+
6. startBeat/durationBeats are in quarter-note beats from the start of the clip; respect each voice's register window and notes-per-bar cap exactly; fill the stated bar length and land the first hit in bar 1 (no long silent intro).
|
|
9754
|
+
|
|
9755
|
+
${styleParagraph}
|
|
9756
|
+
|
|
9757
|
+
PER-VOICE CONTRACTS:
|
|
9758
|
+
${specs.map(voiceContractLine).join("\n")}`;
|
|
9759
|
+
}
|
|
9760
|
+
function buildEnsembleSystemPrompt(specs, style, instrumentation = "strings") {
|
|
9761
|
+
return instrumentation === "horns" ? buildSectionPrompt(specs, style) : buildWovenPrompt(specs, style, instrumentation);
|
|
9762
|
+
}
|
|
9235
9763
|
function buildViolationRetrySuffix(violations) {
|
|
9236
9764
|
if (violations.length === 0) return "";
|
|
9237
9765
|
return `
|
|
@@ -9407,10 +9935,12 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9407
9935
|
DB_MIN,
|
|
9408
9936
|
DEFAULT_FX_CATEGORY_DETAIL,
|
|
9409
9937
|
DEFAULT_FX_DRY_WET,
|
|
9938
|
+
DEFAULT_STYLE_FOR_INSTRUMENTATION,
|
|
9410
9939
|
DRAG_DEAD_ZONE,
|
|
9411
9940
|
DownloadPackButton,
|
|
9412
9941
|
EMPTY_FX_DETAIL_STATE,
|
|
9413
9942
|
EMPTY_FX_STATE,
|
|
9943
|
+
ENSEMBLE_INSTRUMENTATIONS,
|
|
9414
9944
|
ENSEMBLE_MAX_VOICES,
|
|
9415
9945
|
ENSEMBLE_MIN_VOICES,
|
|
9416
9946
|
ENSEMBLE_STYLES,
|
|
@@ -9425,7 +9955,9 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9425
9955
|
FxToggleBar,
|
|
9426
9956
|
GUTTER_W,
|
|
9427
9957
|
GeneratorPanelShell,
|
|
9958
|
+
GroupCollapseChevron,
|
|
9428
9959
|
GroupFadeTrackRow,
|
|
9960
|
+
HORN_MAX_NOTES_PER_BAR,
|
|
9429
9961
|
ImportTrackModal,
|
|
9430
9962
|
InstrumentDrawer,
|
|
9431
9963
|
LevelMeter,
|
|
@@ -9439,8 +9971,12 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9439
9971
|
PianoRollEditor,
|
|
9440
9972
|
PluginError,
|
|
9441
9973
|
RESIZE_HANDLE_PX,
|
|
9974
|
+
RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9442
9975
|
ROW_HEIGHT,
|
|
9443
9976
|
SLIDER_UNITY,
|
|
9977
|
+
STAB_MAX_NOTE_DURATION_BEATS,
|
|
9978
|
+
STAB_MAX_ONSET_INDEPENDENCE,
|
|
9979
|
+
STYLES_FOR_INSTRUMENTATION,
|
|
9444
9980
|
STYLE_RULES,
|
|
9445
9981
|
SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
9446
9982
|
SamplePackCTACard,
|
|
@@ -9454,6 +9990,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9454
9990
|
TrackMeterStrip,
|
|
9455
9991
|
TrackRow,
|
|
9456
9992
|
TransitionDesigner,
|
|
9993
|
+
UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9457
9994
|
VolumeSlider,
|
|
9458
9995
|
WaveformView,
|
|
9459
9996
|
analyzeEnsemble,
|
|
@@ -9488,6 +10025,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9488
10025
|
moveItem,
|
|
9489
10026
|
nearestPitchWithPc,
|
|
9490
10027
|
newTrackState,
|
|
10028
|
+
normalizeInstrumentation,
|
|
9491
10029
|
normalizeSlots,
|
|
9492
10030
|
padPair,
|
|
9493
10031
|
padSlots,
|
|
@@ -9511,6 +10049,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9511
10049
|
slotsEqual,
|
|
9512
10050
|
soundIdentity,
|
|
9513
10051
|
splitFadeEntries,
|
|
10052
|
+
styleForInstrumentation,
|
|
9514
10053
|
synthesizeCuePoints,
|
|
9515
10054
|
tokenizePrompt,
|
|
9516
10055
|
trackDataKey,
|