@signalsandsorcery/plugin-sdk 2.38.2 → 2.40.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 +229 -50
- package/dist/index.d.ts +229 -50
- package/dist/index.js +631 -129
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +605 -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,7 @@ function TrackRow({
|
|
|
2679
2699
|
onToggleFavorite,
|
|
2680
2700
|
onImportSound,
|
|
2681
2701
|
importSoundLabel,
|
|
2702
|
+
linkedSoundHint,
|
|
2682
2703
|
editNotes,
|
|
2683
2704
|
onNotesChange,
|
|
2684
2705
|
editBars,
|
|
@@ -2844,15 +2865,26 @@ function TrackRow({
|
|
|
2844
2865
|
)
|
|
2845
2866
|
] }),
|
|
2846
2867
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
2847
|
-
onShuffle && /* @__PURE__ */ (0, import_jsx_runtime13.
|
|
2868
|
+
onShuffle && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
2848
2869
|
"button",
|
|
2849
2870
|
{
|
|
2850
2871
|
"data-testid": "sdk-shuffle-button",
|
|
2851
2872
|
onClick: onShuffle,
|
|
2852
2873
|
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:
|
|
2874
|
+
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"}`,
|
|
2875
|
+
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",
|
|
2876
|
+
children: [
|
|
2877
|
+
"Shuffle",
|
|
2878
|
+
linkedSoundHint && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2879
|
+
"span",
|
|
2880
|
+
{
|
|
2881
|
+
"aria-hidden": true,
|
|
2882
|
+
"data-testid": "sdk-shuffle-linked-marker",
|
|
2883
|
+
className: "absolute -top-1 -right-1 text-[8px] leading-none",
|
|
2884
|
+
children: "\u{1F517}"
|
|
2885
|
+
}
|
|
2886
|
+
)
|
|
2887
|
+
]
|
|
2856
2888
|
}
|
|
2857
2889
|
),
|
|
2858
2890
|
freezeBadge && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
@@ -2936,6 +2968,7 @@ function TrackRow({
|
|
|
2936
2968
|
onToggleFavorite,
|
|
2937
2969
|
onImportSound,
|
|
2938
2970
|
importSoundLabel,
|
|
2971
|
+
linkedSoundHint,
|
|
2939
2972
|
editNotes,
|
|
2940
2973
|
onNotesChange,
|
|
2941
2974
|
editBars,
|
|
@@ -5163,9 +5196,38 @@ function TransitionDesigner({
|
|
|
5163
5196
|
] });
|
|
5164
5197
|
}
|
|
5165
5198
|
|
|
5199
|
+
// src/components/GroupCollapseChevron.tsx
|
|
5200
|
+
var import_lucide_react2 = require("lucide-react");
|
|
5201
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
5202
|
+
function GroupCollapseChevron({
|
|
5203
|
+
collapsed = false,
|
|
5204
|
+
onToggle,
|
|
5205
|
+
what = "group"
|
|
5206
|
+
}) {
|
|
5207
|
+
if (!onToggle) return null;
|
|
5208
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
5209
|
+
"button",
|
|
5210
|
+
{
|
|
5211
|
+
type: "button",
|
|
5212
|
+
"data-testid": "sdk-group-collapse",
|
|
5213
|
+
onClick: onToggle,
|
|
5214
|
+
"aria-expanded": !collapsed,
|
|
5215
|
+
title: collapsed ? `Expand ${what}` : `Collapse ${what}`,
|
|
5216
|
+
className: "shrink-0 px-0.5 py-0.5 rounded-sm text-sas-muted hover:text-sas-accent transition-colors",
|
|
5217
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
5218
|
+
import_lucide_react2.ChevronDown,
|
|
5219
|
+
{
|
|
5220
|
+
className: `w-3 h-3 transition-transform ${collapsed ? "-rotate-90" : ""}`,
|
|
5221
|
+
strokeWidth: 2.5
|
|
5222
|
+
}
|
|
5223
|
+
)
|
|
5224
|
+
}
|
|
5225
|
+
);
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5166
5228
|
// src/components/PanelMasterStrip.tsx
|
|
5167
5229
|
var import_react21 = require("react");
|
|
5168
|
-
var
|
|
5230
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5169
5231
|
function PanelMasterStrip({
|
|
5170
5232
|
bus,
|
|
5171
5233
|
levels = null,
|
|
@@ -5192,14 +5254,14 @@ function PanelMasterStrip({
|
|
|
5192
5254
|
(fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
|
|
5193
5255
|
);
|
|
5194
5256
|
}, [availableFx, search]);
|
|
5195
|
-
return /* @__PURE__ */ (0,
|
|
5257
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5196
5258
|
"div",
|
|
5197
5259
|
{
|
|
5198
5260
|
"data-testid": "panel-master-strip",
|
|
5199
5261
|
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
5262
|
children: [
|
|
5201
|
-
/* @__PURE__ */ (0,
|
|
5202
|
-
/* @__PURE__ */ (0,
|
|
5263
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
5264
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5203
5265
|
"span",
|
|
5204
5266
|
{
|
|
5205
5267
|
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
@@ -5207,8 +5269,8 @@ function PanelMasterStrip({
|
|
|
5207
5269
|
children: "BUS"
|
|
5208
5270
|
}
|
|
5209
5271
|
),
|
|
5210
|
-
/* @__PURE__ */ (0,
|
|
5211
|
-
/* @__PURE__ */ (0,
|
|
5272
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
|
|
5273
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5212
5274
|
VolumeSlider,
|
|
5213
5275
|
{
|
|
5214
5276
|
value: dbToSlider(bus.volume),
|
|
@@ -5216,8 +5278,8 @@ function PanelMasterStrip({
|
|
|
5216
5278
|
disabled
|
|
5217
5279
|
}
|
|
5218
5280
|
),
|
|
5219
|
-
/* @__PURE__ */ (0,
|
|
5220
|
-
/* @__PURE__ */ (0,
|
|
5281
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
|
|
5282
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5221
5283
|
LevelMeter,
|
|
5222
5284
|
{
|
|
5223
5285
|
peakDb: levels?.leftDb ?? -120,
|
|
@@ -5227,7 +5289,7 @@ function PanelMasterStrip({
|
|
|
5227
5289
|
"data-testid": "bus-meter-left"
|
|
5228
5290
|
}
|
|
5229
5291
|
),
|
|
5230
|
-
/* @__PURE__ */ (0,
|
|
5292
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5231
5293
|
LevelMeter,
|
|
5232
5294
|
{
|
|
5233
5295
|
peakDb: levels?.rightDb ?? -120,
|
|
@@ -5238,7 +5300,7 @@ function PanelMasterStrip({
|
|
|
5238
5300
|
)
|
|
5239
5301
|
] })
|
|
5240
5302
|
] }),
|
|
5241
|
-
/* @__PURE__ */ (0,
|
|
5303
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5242
5304
|
"button",
|
|
5243
5305
|
{
|
|
5244
5306
|
"data-testid": "bus-mute-button",
|
|
@@ -5249,7 +5311,7 @@ function PanelMasterStrip({
|
|
|
5249
5311
|
children: "M"
|
|
5250
5312
|
}
|
|
5251
5313
|
),
|
|
5252
|
-
/* @__PURE__ */ (0,
|
|
5314
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5253
5315
|
"button",
|
|
5254
5316
|
{
|
|
5255
5317
|
"data-testid": "bus-solo-button",
|
|
@@ -5260,14 +5322,14 @@ function PanelMasterStrip({
|
|
|
5260
5322
|
children: "S"
|
|
5261
5323
|
}
|
|
5262
5324
|
),
|
|
5263
|
-
/* @__PURE__ */ (0,
|
|
5325
|
+
/* @__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
5326
|
"span",
|
|
5265
5327
|
{
|
|
5266
5328
|
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
5267
5329
|
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
5330
|
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
5269
5331
|
children: [
|
|
5270
|
-
/* @__PURE__ */ (0,
|
|
5332
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5271
5333
|
"button",
|
|
5272
5334
|
{
|
|
5273
5335
|
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
@@ -5278,7 +5340,7 @@ function PanelMasterStrip({
|
|
|
5278
5340
|
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
5279
5341
|
}
|
|
5280
5342
|
),
|
|
5281
|
-
onShowFxEditor ? /* @__PURE__ */ (0,
|
|
5343
|
+
onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5282
5344
|
"button",
|
|
5283
5345
|
{
|
|
5284
5346
|
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
@@ -5288,8 +5350,8 @@ function PanelMasterStrip({
|
|
|
5288
5350
|
title: `Open ${fx.name} editor`,
|
|
5289
5351
|
children: fx.name
|
|
5290
5352
|
}
|
|
5291
|
-
) : /* @__PURE__ */ (0,
|
|
5292
|
-
/* @__PURE__ */ (0,
|
|
5353
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
5354
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5293
5355
|
"button",
|
|
5294
5356
|
{
|
|
5295
5357
|
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
@@ -5304,7 +5366,7 @@ function PanelMasterStrip({
|
|
|
5304
5366
|
},
|
|
5305
5367
|
`${fx.index}:${fx.pluginId}`
|
|
5306
5368
|
)) }),
|
|
5307
|
-
/* @__PURE__ */ (0,
|
|
5369
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5308
5370
|
"button",
|
|
5309
5371
|
{
|
|
5310
5372
|
"data-testid": "bus-fx-add-button",
|
|
@@ -5316,9 +5378,9 @@ function PanelMasterStrip({
|
|
|
5316
5378
|
}
|
|
5317
5379
|
)
|
|
5318
5380
|
] }),
|
|
5319
|
-
fxPickerOpen && /* @__PURE__ */ (0,
|
|
5320
|
-
/* @__PURE__ */ (0,
|
|
5321
|
-
/* @__PURE__ */ (0,
|
|
5381
|
+
fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
5382
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
5383
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5322
5384
|
"input",
|
|
5323
5385
|
{
|
|
5324
5386
|
type: "text",
|
|
@@ -5328,7 +5390,7 @@ function PanelMasterStrip({
|
|
|
5328
5390
|
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
5329
5391
|
}
|
|
5330
5392
|
),
|
|
5331
|
-
onRefreshFx && /* @__PURE__ */ (0,
|
|
5393
|
+
onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5332
5394
|
"button",
|
|
5333
5395
|
{
|
|
5334
5396
|
onClick: () => onRefreshFx(),
|
|
@@ -5339,8 +5401,8 @@ function PanelMasterStrip({
|
|
|
5339
5401
|
}
|
|
5340
5402
|
)
|
|
5341
5403
|
] }),
|
|
5342
|
-
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0,
|
|
5343
|
-
filtered.map((fx) => /* @__PURE__ */ (0,
|
|
5404
|
+
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: [
|
|
5405
|
+
filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5344
5406
|
"button",
|
|
5345
5407
|
{
|
|
5346
5408
|
"data-testid": `bus-fx-pick-${fx.pluginId}`,
|
|
@@ -5348,13 +5410,13 @@ function PanelMasterStrip({
|
|
|
5348
5410
|
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
5411
|
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
5350
5412
|
children: [
|
|
5351
|
-
/* @__PURE__ */ (0,
|
|
5352
|
-
/* @__PURE__ */ (0,
|
|
5413
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
5414
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
5353
5415
|
]
|
|
5354
5416
|
},
|
|
5355
5417
|
fx.pluginId
|
|
5356
5418
|
)),
|
|
5357
|
-
filtered.length === 0 && /* @__PURE__ */ (0,
|
|
5419
|
+
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
5420
|
] })
|
|
5359
5421
|
] })
|
|
5360
5422
|
]
|
|
@@ -5481,7 +5543,7 @@ function usePanelBus(host, activeSceneId) {
|
|
|
5481
5543
|
|
|
5482
5544
|
// src/components/DownloadPackButton.tsx
|
|
5483
5545
|
var import_react23 = require("react");
|
|
5484
|
-
var
|
|
5546
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
5485
5547
|
function formatSize(bytes) {
|
|
5486
5548
|
if (!bytes || bytes <= 0) return "";
|
|
5487
5549
|
const gb = bytes / 1024 ** 3;
|
|
@@ -5569,8 +5631,8 @@ var DownloadPackButton = ({
|
|
|
5569
5631
|
} else {
|
|
5570
5632
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
5571
5633
|
}
|
|
5572
|
-
return /* @__PURE__ */ (0,
|
|
5573
|
-
/* @__PURE__ */ (0,
|
|
5634
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
|
|
5635
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5574
5636
|
"button",
|
|
5575
5637
|
{
|
|
5576
5638
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -5581,12 +5643,12 @@ var DownloadPackButton = ({
|
|
|
5581
5643
|
children: buttonLabel
|
|
5582
5644
|
}
|
|
5583
5645
|
),
|
|
5584
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0,
|
|
5646
|
+
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
5647
|
] });
|
|
5586
5648
|
};
|
|
5587
5649
|
|
|
5588
5650
|
// src/components/SamplePackCTACard.tsx
|
|
5589
|
-
var
|
|
5651
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
5590
5652
|
var SamplePackCTACard = ({
|
|
5591
5653
|
host,
|
|
5592
5654
|
pack,
|
|
@@ -5594,7 +5656,7 @@ var SamplePackCTACard = ({
|
|
|
5594
5656
|
onDownloadComplete
|
|
5595
5657
|
}) => {
|
|
5596
5658
|
if (status === "checking") {
|
|
5597
|
-
return /* @__PURE__ */ (0,
|
|
5659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5598
5660
|
"div",
|
|
5599
5661
|
{
|
|
5600
5662
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -5605,16 +5667,16 @@ var SamplePackCTACard = ({
|
|
|
5605
5667
|
}
|
|
5606
5668
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
5607
5669
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
5608
|
-
return /* @__PURE__ */ (0,
|
|
5670
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
5609
5671
|
"div",
|
|
5610
5672
|
{
|
|
5611
5673
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
5612
5674
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
5613
5675
|
children: [
|
|
5614
|
-
/* @__PURE__ */ (0,
|
|
5615
|
-
/* @__PURE__ */ (0,
|
|
5616
|
-
/* @__PURE__ */ (0,
|
|
5617
|
-
/* @__PURE__ */ (0,
|
|
5676
|
+
/* @__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" }),
|
|
5677
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
5678
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
5679
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5618
5680
|
DownloadPackButton,
|
|
5619
5681
|
{
|
|
5620
5682
|
host,
|
|
@@ -5694,7 +5756,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
5694
5756
|
}
|
|
5695
5757
|
|
|
5696
5758
|
// src/components/WaveformView.tsx
|
|
5697
|
-
var
|
|
5759
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
5698
5760
|
var WaveformView = ({
|
|
5699
5761
|
host,
|
|
5700
5762
|
filePath,
|
|
@@ -5742,7 +5804,7 @@ var WaveformView = ({
|
|
|
5742
5804
|
observer.observe(canvas);
|
|
5743
5805
|
return () => observer.disconnect();
|
|
5744
5806
|
}, [peaks, fillStyle]);
|
|
5745
|
-
return /* @__PURE__ */ (0,
|
|
5807
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5746
5808
|
"canvas",
|
|
5747
5809
|
{
|
|
5748
5810
|
ref: canvasRef,
|
|
@@ -5754,7 +5816,7 @@ var WaveformView = ({
|
|
|
5754
5816
|
|
|
5755
5817
|
// src/components/ScrollingWaveform.tsx
|
|
5756
5818
|
var import_react25 = require("react");
|
|
5757
|
-
var
|
|
5819
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
5758
5820
|
var ScrollingWaveform = ({
|
|
5759
5821
|
getPeakDb,
|
|
5760
5822
|
active,
|
|
@@ -5830,7 +5892,7 @@ var ScrollingWaveform = ({
|
|
|
5830
5892
|
}
|
|
5831
5893
|
};
|
|
5832
5894
|
}, [active, getPeakDb, fillStyle]);
|
|
5833
|
-
return /* @__PURE__ */ (0,
|
|
5895
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
5834
5896
|
"canvas",
|
|
5835
5897
|
{
|
|
5836
5898
|
ref: canvasRef,
|
|
@@ -5842,7 +5904,7 @@ var ScrollingWaveform = ({
|
|
|
5842
5904
|
|
|
5843
5905
|
// src/components/OffsetScrubber.tsx
|
|
5844
5906
|
var import_react26 = require("react");
|
|
5845
|
-
var
|
|
5907
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
5846
5908
|
var SLIDER_HEIGHT_PX = 28;
|
|
5847
5909
|
var TICK_HEIGHT_PX = 14;
|
|
5848
5910
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -5957,9 +6019,9 @@ function OffsetScrubber({
|
|
|
5957
6019
|
});
|
|
5958
6020
|
}, [cuePoints, sampleToFraction]);
|
|
5959
6021
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
5960
|
-
return /* @__PURE__ */ (0,
|
|
5961
|
-
/* @__PURE__ */ (0,
|
|
5962
|
-
/* @__PURE__ */ (0,
|
|
6022
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
6023
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
6024
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
5963
6025
|
"div",
|
|
5964
6026
|
{
|
|
5965
6027
|
ref: trackRef,
|
|
@@ -5975,7 +6037,7 @@ function OffsetScrubber({
|
|
|
5975
6037
|
"aria-valuenow": draftOffset,
|
|
5976
6038
|
"aria-disabled": isDisabled,
|
|
5977
6039
|
children: [
|
|
5978
|
-
/* @__PURE__ */ (0,
|
|
6040
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
5979
6041
|
"div",
|
|
5980
6042
|
{
|
|
5981
6043
|
"aria-hidden": "true",
|
|
@@ -5983,7 +6045,7 @@ function OffsetScrubber({
|
|
|
5983
6045
|
style: { left: "50%" }
|
|
5984
6046
|
}
|
|
5985
6047
|
),
|
|
5986
|
-
ticks.map((t) => /* @__PURE__ */ (0,
|
|
6048
|
+
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
5987
6049
|
"div",
|
|
5988
6050
|
{
|
|
5989
6051
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -5998,7 +6060,7 @@ function OffsetScrubber({
|
|
|
5998
6060
|
},
|
|
5999
6061
|
t.i
|
|
6000
6062
|
)),
|
|
6001
|
-
/* @__PURE__ */ (0,
|
|
6063
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6002
6064
|
"div",
|
|
6003
6065
|
{
|
|
6004
6066
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -6015,7 +6077,7 @@ function OffsetScrubber({
|
|
|
6015
6077
|
]
|
|
6016
6078
|
}
|
|
6017
6079
|
),
|
|
6018
|
-
/* @__PURE__ */ (0,
|
|
6080
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6019
6081
|
"span",
|
|
6020
6082
|
{
|
|
6021
6083
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -6023,7 +6085,7 @@ function OffsetScrubber({
|
|
|
6023
6085
|
children: formatOffset(draftOffset, sampleRate)
|
|
6024
6086
|
}
|
|
6025
6087
|
),
|
|
6026
|
-
/* @__PURE__ */ (0,
|
|
6088
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6027
6089
|
"button",
|
|
6028
6090
|
{
|
|
6029
6091
|
type: "button",
|
|
@@ -6035,7 +6097,7 @@ function OffsetScrubber({
|
|
|
6035
6097
|
children: "\u2316"
|
|
6036
6098
|
}
|
|
6037
6099
|
),
|
|
6038
|
-
bpmMismatch && /* @__PURE__ */ (0,
|
|
6100
|
+
bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6039
6101
|
"span",
|
|
6040
6102
|
{
|
|
6041
6103
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -7048,7 +7110,7 @@ function useTransitionOps({
|
|
|
7048
7110
|
}
|
|
7049
7111
|
|
|
7050
7112
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
7051
|
-
var
|
|
7113
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
7052
7114
|
var EMPTY_PLACEHOLDERS = [];
|
|
7053
7115
|
function useGeneratorPanelCore({
|
|
7054
7116
|
ui,
|
|
@@ -7459,37 +7521,6 @@ function useGeneratorPanelCore({
|
|
|
7459
7521
|
},
|
|
7460
7522
|
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
7461
7523
|
);
|
|
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
7524
|
const [isExportingMidi, setIsExportingMidi] = (0, import_react31.useState)(false);
|
|
7494
7525
|
const handleExportMidi = (0, import_react31.useCallback)(async () => {
|
|
7495
7526
|
if (isExportingMidi) return;
|
|
@@ -7545,8 +7576,8 @@ function useGeneratorPanelCore({
|
|
|
7545
7576
|
if (!onHeaderContent) return;
|
|
7546
7577
|
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
7547
7578
|
onHeaderContent(
|
|
7548
|
-
/* @__PURE__ */ (0,
|
|
7549
|
-
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
7579
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
7580
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
7550
7581
|
"button",
|
|
7551
7582
|
{
|
|
7552
7583
|
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
@@ -7560,7 +7591,7 @@ function useGeneratorPanelCore({
|
|
|
7560
7591
|
children: identity.importTrackLabel ?? "Import Track"
|
|
7561
7592
|
}
|
|
7562
7593
|
),
|
|
7563
|
-
(!canCrossfade || !designerView) && /* @__PURE__ */ (0,
|
|
7594
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
7564
7595
|
"button",
|
|
7565
7596
|
{
|
|
7566
7597
|
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
@@ -7576,7 +7607,7 @@ function useGeneratorPanelCore({
|
|
|
7576
7607
|
children: identity.addTrackLabel ?? "Add Track"
|
|
7577
7608
|
}
|
|
7578
7609
|
),
|
|
7579
|
-
canCrossfade && /* @__PURE__ */ (0,
|
|
7610
|
+
canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
|
|
7580
7611
|
"button",
|
|
7581
7612
|
{
|
|
7582
7613
|
"data-testid": `${identity.familyKey}-view-toggle`,
|
|
@@ -7595,7 +7626,7 @@ function useGeneratorPanelCore({
|
|
|
7595
7626
|
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
7596
7627
|
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
7628
|
children: [
|
|
7598
|
-
transitionSourceTotal > 0 && /* @__PURE__ */ (0,
|
|
7629
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
7599
7630
|
"span",
|
|
7600
7631
|
{
|
|
7601
7632
|
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
@@ -7603,7 +7634,7 @@ function useGeneratorPanelCore({
|
|
|
7603
7634
|
"aria-hidden": true
|
|
7604
7635
|
}
|
|
7605
7636
|
),
|
|
7606
|
-
/* @__PURE__ */ (0,
|
|
7637
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "relative", children: [
|
|
7607
7638
|
"\u21C4 ",
|
|
7608
7639
|
designerView ? "Transition" : "Tracks",
|
|
7609
7640
|
transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
|
|
@@ -7736,9 +7767,163 @@ function useGeneratorPanelCore({
|
|
|
7736
7767
|
name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,
|
|
7737
7768
|
...adapter.createTrackOptions()
|
|
7738
7769
|
}),
|
|
7739
|
-
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
7770
|
+
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? [],
|
|
7771
|
+
sound: adapter.sound
|
|
7740
7772
|
};
|
|
7741
7773
|
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
7774
|
+
const broadcastSoundFromTrack = (0, import_react31.useCallback)(
|
|
7775
|
+
async (sourceTrackId, descriptor, label) => {
|
|
7776
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7777
|
+
if (!targetsOf) return;
|
|
7778
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7779
|
+
if (!source) return;
|
|
7780
|
+
let targets;
|
|
7781
|
+
try {
|
|
7782
|
+
targets = await targetsOf(source, makeServices());
|
|
7783
|
+
} catch (err) {
|
|
7784
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7785
|
+
return;
|
|
7786
|
+
}
|
|
7787
|
+
const sourceInstrument = source.instrumentPluginId ?? null;
|
|
7788
|
+
const others = (targets ?? []).filter((t) => {
|
|
7789
|
+
if (t.engineId === sourceTrackId) return false;
|
|
7790
|
+
const live = tracksRef.current.find((x) => x.handle.id === t.engineId);
|
|
7791
|
+
return (live?.instrumentPluginId ?? null) === sourceInstrument;
|
|
7792
|
+
});
|
|
7793
|
+
if (others.length === 0) return;
|
|
7794
|
+
try {
|
|
7795
|
+
await adapter.sound.persistDescriptor?.(sourceTrackId, descriptor, label);
|
|
7796
|
+
} catch {
|
|
7797
|
+
}
|
|
7798
|
+
const appliedIds = /* @__PURE__ */ new Set();
|
|
7799
|
+
const failed = [];
|
|
7800
|
+
for (const target of others) {
|
|
7801
|
+
try {
|
|
7802
|
+
if (soundHistory.list(target.engineId).entries.length === 0) {
|
|
7803
|
+
try {
|
|
7804
|
+
const cap = await adapter.sound.captureSoundDescriptor(target.engineId);
|
|
7805
|
+
if (cap) {
|
|
7806
|
+
soundHistory.record(target.engineId, cap.descriptor, adapter.sound.previousSoundLabel);
|
|
7807
|
+
}
|
|
7808
|
+
} catch {
|
|
7809
|
+
}
|
|
7810
|
+
}
|
|
7811
|
+
await adapter.sound.applySound(target.engineId, descriptor);
|
|
7812
|
+
try {
|
|
7813
|
+
await adapter.sound.persistDescriptor?.(target.engineId, descriptor, label);
|
|
7814
|
+
} catch {
|
|
7815
|
+
}
|
|
7816
|
+
soundHistory.record(target.engineId, descriptor, label);
|
|
7817
|
+
appliedIds.add(target.engineId);
|
|
7818
|
+
} catch (err) {
|
|
7819
|
+
failed.push(target.label ?? target.engineId);
|
|
7820
|
+
console.warn(`[${logTag}] Linked sound apply failed for ${target.engineId}:`, err);
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7823
|
+
if (appliedIds.size > 0) {
|
|
7824
|
+
setTracks(
|
|
7825
|
+
(prev) => prev.map(
|
|
7826
|
+
(t) => appliedIds.has(t.handle.id) ? { ...t, shuffleHistory: /* @__PURE__ */ new Set([...t.shuffleHistory, label]) } : t
|
|
7827
|
+
)
|
|
7828
|
+
);
|
|
7829
|
+
}
|
|
7830
|
+
if (failed.length > 0) {
|
|
7831
|
+
host.showToast(
|
|
7832
|
+
"warning",
|
|
7833
|
+
"Linked sound applied to some parts only",
|
|
7834
|
+
`Skipped: ${failed.join(", ")}`
|
|
7835
|
+
);
|
|
7836
|
+
}
|
|
7837
|
+
},
|
|
7838
|
+
[adapter, makeServices, soundHistory, host, logTag]
|
|
7839
|
+
);
|
|
7840
|
+
const broadcastInstrumentFromTrack = (0, import_react31.useCallback)(
|
|
7841
|
+
async (sourceTrackId, pluginId) => {
|
|
7842
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7843
|
+
if (!targetsOf) return;
|
|
7844
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7845
|
+
if (!source) return;
|
|
7846
|
+
let targets;
|
|
7847
|
+
try {
|
|
7848
|
+
targets = await targetsOf(source, makeServices());
|
|
7849
|
+
} catch (err) {
|
|
7850
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7853
|
+
const others = (targets ?? []).filter((t) => t.engineId !== sourceTrackId);
|
|
7854
|
+
if (others.length === 0) return;
|
|
7855
|
+
const failed = [];
|
|
7856
|
+
for (const target of others) {
|
|
7857
|
+
try {
|
|
7858
|
+
await host.setTrackInstrument(target.engineId, pluginId);
|
|
7859
|
+
const descriptor = await host.getTrackInstrument(target.engineId);
|
|
7860
|
+
setTracks(
|
|
7861
|
+
(prev) => prev.map(
|
|
7862
|
+
(t) => t.handle.id === target.engineId ? {
|
|
7863
|
+
...t,
|
|
7864
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
7865
|
+
instrumentName: descriptor?.name ?? null,
|
|
7866
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
7867
|
+
} : t
|
|
7868
|
+
)
|
|
7869
|
+
);
|
|
7870
|
+
} catch (err) {
|
|
7871
|
+
failed.push(target.label ?? target.engineId);
|
|
7872
|
+
console.warn(`[${logTag}] Linked instrument apply failed for ${target.engineId}:`, err);
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
if (failed.length > 0) {
|
|
7876
|
+
host.showToast(
|
|
7877
|
+
"warning",
|
|
7878
|
+
"Instrument applied to some parts only",
|
|
7879
|
+
`Skipped: ${failed.join(", ")}`
|
|
7880
|
+
);
|
|
7881
|
+
}
|
|
7882
|
+
},
|
|
7883
|
+
[adapter, makeServices, host, logTag]
|
|
7884
|
+
);
|
|
7885
|
+
const handleRestoreSound = (0, import_react31.useCallback)(
|
|
7886
|
+
async (trackId, index) => {
|
|
7887
|
+
const entry = soundHistory.list(trackId).entries[index];
|
|
7888
|
+
const moved = await soundHistory.restoreTo(trackId, index);
|
|
7889
|
+
if (moved && entry) {
|
|
7890
|
+
await broadcastSoundFromTrack(trackId, entry.descriptor, entry.label);
|
|
7891
|
+
}
|
|
7892
|
+
},
|
|
7893
|
+
[soundHistory, broadcastSoundFromTrack]
|
|
7894
|
+
);
|
|
7895
|
+
const handleSoundImportPick = (0, import_react31.useCallback)(
|
|
7896
|
+
async (sel) => {
|
|
7897
|
+
const target = soundImportTarget;
|
|
7898
|
+
if (!target || !host.getTrackSound) {
|
|
7899
|
+
setSoundImportTarget(null);
|
|
7900
|
+
return;
|
|
7901
|
+
}
|
|
7902
|
+
const noun = adapter.sound.importNoun;
|
|
7903
|
+
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
7904
|
+
try {
|
|
7905
|
+
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
7906
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
7907
|
+
host.showToast(
|
|
7908
|
+
"error",
|
|
7909
|
+
`No ${noun} to import`,
|
|
7910
|
+
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
7911
|
+
);
|
|
7912
|
+
return;
|
|
7913
|
+
}
|
|
7914
|
+
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
7915
|
+
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
7916
|
+
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
7917
|
+
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
7918
|
+
await broadcastSoundFromTrack(target.handle.id, descriptor, snap.label);
|
|
7919
|
+
} catch (err) {
|
|
7920
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
7921
|
+
} finally {
|
|
7922
|
+
setSoundImportTarget(null);
|
|
7923
|
+
}
|
|
7924
|
+
},
|
|
7925
|
+
[soundImportTarget, host, adapter, identity.familyKey, soundHistory, broadcastSoundFromTrack]
|
|
7926
|
+
);
|
|
7742
7927
|
const handleGenerate = (0, import_react31.useCallback)(
|
|
7743
7928
|
async (trackId) => {
|
|
7744
7929
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
@@ -7857,7 +8042,10 @@ function useGeneratorPanelCore({
|
|
|
7857
8042
|
);
|
|
7858
8043
|
try {
|
|
7859
8044
|
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
7860
|
-
if (cap)
|
|
8045
|
+
if (cap) {
|
|
8046
|
+
soundHistory.record(trackId, cap.descriptor, result.appliedName);
|
|
8047
|
+
await broadcastSoundFromTrack(trackId, cap.descriptor, result.appliedName);
|
|
8048
|
+
}
|
|
7861
8049
|
} catch {
|
|
7862
8050
|
}
|
|
7863
8051
|
console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);
|
|
@@ -7866,7 +8054,7 @@ function useGeneratorPanelCore({
|
|
|
7866
8054
|
host.showToast("error", "Shuffle failed", msg);
|
|
7867
8055
|
}
|
|
7868
8056
|
},
|
|
7869
|
-
[host, adapter, tracks, soundHistory, logTag]
|
|
8057
|
+
[host, adapter, tracks, soundHistory, logTag, broadcastSoundFromTrack]
|
|
7870
8058
|
);
|
|
7871
8059
|
const handleCopy = (0, import_react31.useCallback)(
|
|
7872
8060
|
async (trackId) => {
|
|
@@ -8069,6 +8257,7 @@ function useGeneratorPanelCore({
|
|
|
8069
8257
|
} : t
|
|
8070
8258
|
)
|
|
8071
8259
|
);
|
|
8260
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
8072
8261
|
} catch (err) {
|
|
8073
8262
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
8074
8263
|
host.showToast("error", "Instrument load failed", msg);
|
|
@@ -8091,6 +8280,7 @@ function useGeneratorPanelCore({
|
|
|
8091
8280
|
} : t
|
|
8092
8281
|
)
|
|
8093
8282
|
);
|
|
8283
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
8094
8284
|
} catch (err) {
|
|
8095
8285
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
8096
8286
|
console.error(`[${logTag}] Failed to set instrument:`, err);
|
|
@@ -8100,7 +8290,7 @@ function useGeneratorPanelCore({
|
|
|
8100
8290
|
);
|
|
8101
8291
|
}
|
|
8102
8292
|
},
|
|
8103
|
-
[host, identity.defaultInstrumentPluginId, logTag]
|
|
8293
|
+
[host, identity.defaultInstrumentPluginId, logTag, broadcastInstrumentFromTrack]
|
|
8104
8294
|
);
|
|
8105
8295
|
const handleShowEditor = (0, import_react31.useCallback)(
|
|
8106
8296
|
async (trackId) => {
|
|
@@ -8310,6 +8500,7 @@ function useGeneratorPanelCore({
|
|
|
8310
8500
|
handlers,
|
|
8311
8501
|
handleGenerate,
|
|
8312
8502
|
handleShuffle,
|
|
8503
|
+
handleRestoreSound,
|
|
8313
8504
|
handleAddTrack,
|
|
8314
8505
|
handleDeleteTrack,
|
|
8315
8506
|
handleExportMidi,
|
|
@@ -8340,8 +8531,41 @@ function useGeneratorPanelCore({
|
|
|
8340
8531
|
}
|
|
8341
8532
|
|
|
8342
8533
|
// src/panel-core/GeneratorPanelShell.tsx
|
|
8343
|
-
var import_react32 =
|
|
8344
|
-
var
|
|
8534
|
+
var import_react32 = require("react");
|
|
8535
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
8536
|
+
var GROUP_UI_KEY = "groupUi";
|
|
8537
|
+
function CollapsibleGroup({
|
|
8538
|
+
ext,
|
|
8539
|
+
group,
|
|
8540
|
+
groupCtx
|
|
8541
|
+
}) {
|
|
8542
|
+
const { host, activeSceneId, trackDataKey: trackDataKey2 } = groupCtx.services;
|
|
8543
|
+
const uiKey = trackDataKey2(group.groupId, GROUP_UI_KEY);
|
|
8544
|
+
const [collapsed, setCollapsed] = (0, import_react32.useState)(false);
|
|
8545
|
+
(0, import_react32.useEffect)(() => {
|
|
8546
|
+
let cancelled = false;
|
|
8547
|
+
if (!activeSceneId) return void 0;
|
|
8548
|
+
void host.getSceneData(activeSceneId, uiKey).then((raw) => {
|
|
8549
|
+
if (cancelled || !raw || typeof raw !== "object") return;
|
|
8550
|
+
setCollapsed(raw.collapsed === true);
|
|
8551
|
+
}).catch(() => {
|
|
8552
|
+
});
|
|
8553
|
+
return () => {
|
|
8554
|
+
cancelled = true;
|
|
8555
|
+
};
|
|
8556
|
+
}, [host, activeSceneId, uiKey]);
|
|
8557
|
+
const onToggleCollapse = (0, import_react32.useCallback)(() => {
|
|
8558
|
+
setCollapsed((prev) => {
|
|
8559
|
+
const next = !prev;
|
|
8560
|
+
if (activeSceneId) {
|
|
8561
|
+
void host.setSceneData(activeSceneId, uiKey, { collapsed: next }).catch(() => {
|
|
8562
|
+
});
|
|
8563
|
+
}
|
|
8564
|
+
return next;
|
|
8565
|
+
});
|
|
8566
|
+
}, [host, activeSceneId, uiKey]);
|
|
8567
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_jsx_runtime29.Fragment, { children: ext.renderGroup(group, { ...groupCtx, collapsed, onToggleCollapse }) });
|
|
8568
|
+
}
|
|
8345
8569
|
function GeneratorPanelShell({ core, slots }) {
|
|
8346
8570
|
const {
|
|
8347
8571
|
ui,
|
|
@@ -8364,6 +8588,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8364
8588
|
soundImportTarget,
|
|
8365
8589
|
setSoundImportTarget,
|
|
8366
8590
|
handleSoundImportPick,
|
|
8591
|
+
handleRestoreSound,
|
|
8367
8592
|
handlePortTrack,
|
|
8368
8593
|
transition,
|
|
8369
8594
|
crossfadePairsMeta,
|
|
@@ -8459,7 +8684,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8459
8684
|
soundHistory: soundHistory.list(id).entries,
|
|
8460
8685
|
soundHistoryCursor: soundHistory.list(id).cursor,
|
|
8461
8686
|
onRestoreSound: (i) => {
|
|
8462
|
-
void
|
|
8687
|
+
void handleRestoreSound(id, i);
|
|
8463
8688
|
},
|
|
8464
8689
|
onToggleFavorite: (i) => soundHistory.toggleFavorite(id, i),
|
|
8465
8690
|
...importSoundProps,
|
|
@@ -8490,6 +8715,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8490
8715
|
handleBackToInstruments,
|
|
8491
8716
|
setSoundImportTarget,
|
|
8492
8717
|
soundHistory,
|
|
8718
|
+
handleRestoreSound,
|
|
8493
8719
|
handleFxToggle,
|
|
8494
8720
|
handleFxPresetChange,
|
|
8495
8721
|
handleFxDryWetChange,
|
|
@@ -8497,12 +8723,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8497
8723
|
]
|
|
8498
8724
|
);
|
|
8499
8725
|
if (!activeSceneId) {
|
|
8500
|
-
return /* @__PURE__ */ (0,
|
|
8726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8501
8727
|
"div",
|
|
8502
8728
|
{
|
|
8503
8729
|
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
8504
8730
|
className: "flex items-center justify-center py-8",
|
|
8505
|
-
children: /* @__PURE__ */ (0,
|
|
8731
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8506
8732
|
"button",
|
|
8507
8733
|
{
|
|
8508
8734
|
onClick: () => onSelectScene?.(),
|
|
@@ -8514,12 +8740,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8514
8740
|
);
|
|
8515
8741
|
}
|
|
8516
8742
|
if (!sceneContext?.hasContract) {
|
|
8517
|
-
return /* @__PURE__ */ (0,
|
|
8743
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8518
8744
|
"div",
|
|
8519
8745
|
{
|
|
8520
8746
|
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
8521
8747
|
className: "flex items-center justify-center py-8",
|
|
8522
|
-
children: /* @__PURE__ */ (0,
|
|
8748
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8523
8749
|
"button",
|
|
8524
8750
|
{
|
|
8525
8751
|
onClick: () => onOpenContract?.(),
|
|
@@ -8531,7 +8757,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8531
8757
|
);
|
|
8532
8758
|
}
|
|
8533
8759
|
if (features.bulkComposePlaceholders && isComposing) {
|
|
8534
|
-
return /* @__PURE__ */ (0,
|
|
8760
|
+
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
8761
|
}
|
|
8536
8762
|
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
8537
8763
|
if (activePlaceholders.length > 0) {
|
|
@@ -8542,18 +8768,18 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8542
8768
|
tracksByDbId.set(t.handle.id, t);
|
|
8543
8769
|
}
|
|
8544
8770
|
}
|
|
8545
|
-
return /* @__PURE__ */ (0,
|
|
8771
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
8546
8772
|
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
8547
8773
|
if (loadedTrack) {
|
|
8548
|
-
return /* @__PURE__ */ (0,
|
|
8774
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
8549
8775
|
}
|
|
8550
|
-
return /* @__PURE__ */ (0,
|
|
8776
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8551
8777
|
"div",
|
|
8552
8778
|
{
|
|
8553
8779
|
"data-testid": "bulk-placeholder-track",
|
|
8554
8780
|
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
8555
8781
|
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
8556
|
-
children: /* @__PURE__ */ (0,
|
|
8782
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
8557
8783
|
},
|
|
8558
8784
|
ph.id
|
|
8559
8785
|
);
|
|
@@ -8565,13 +8791,13 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8565
8791
|
supportsMeters,
|
|
8566
8792
|
levels: supportsMeters ? trackLevels : void 0,
|
|
8567
8793
|
handlers,
|
|
8568
|
-
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0,
|
|
8794
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
8569
8795
|
setGroupMute,
|
|
8570
8796
|
setGroupSolo,
|
|
8571
8797
|
deleteGroup
|
|
8572
8798
|
};
|
|
8573
|
-
return /* @__PURE__ */ (0,
|
|
8574
|
-
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
8799
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
8800
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8575
8801
|
ImportTrackModal,
|
|
8576
8802
|
{
|
|
8577
8803
|
host,
|
|
@@ -8584,7 +8810,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8584
8810
|
testIdPrefix: `${identity.familyKey}-import`
|
|
8585
8811
|
}
|
|
8586
8812
|
),
|
|
8587
|
-
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0,
|
|
8813
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8588
8814
|
ImportTrackModal,
|
|
8589
8815
|
{
|
|
8590
8816
|
host,
|
|
@@ -8599,7 +8825,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8599
8825
|
}
|
|
8600
8826
|
),
|
|
8601
8827
|
slots?.modals,
|
|
8602
|
-
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0,
|
|
8828
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8603
8829
|
TransitionDesigner,
|
|
8604
8830
|
{
|
|
8605
8831
|
host,
|
|
@@ -8618,8 +8844,8 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8618
8844
|
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
8619
8845
|
}
|
|
8620
8846
|
) }),
|
|
8621
|
-
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0,
|
|
8622
|
-
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0,
|
|
8847
|
+
!(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: [
|
|
8848
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8623
8849
|
PanelMasterStrip,
|
|
8624
8850
|
{
|
|
8625
8851
|
bus: panelBus.bus,
|
|
@@ -8640,7 +8866,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8640
8866
|
}
|
|
8641
8867
|
),
|
|
8642
8868
|
slots?.beforeRows,
|
|
8643
|
-
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0,
|
|
8869
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8644
8870
|
CrossfadeTrackRow,
|
|
8645
8871
|
{
|
|
8646
8872
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8677,7 +8903,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8677
8903
|
},
|
|
8678
8904
|
pair.groupId
|
|
8679
8905
|
)),
|
|
8680
|
-
resolvedSingleFades.map((fade) => /* @__PURE__ */ (0,
|
|
8906
|
+
resolvedSingleFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8681
8907
|
FadeTrackRow,
|
|
8682
8908
|
{
|
|
8683
8909
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8702,7 +8928,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8702
8928
|
},
|
|
8703
8929
|
fade.dbId
|
|
8704
8930
|
)),
|
|
8705
|
-
resolvedGroupFades.map((group) => /* @__PURE__ */ (0,
|
|
8931
|
+
resolvedGroupFades.map((group) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8706
8932
|
GroupFadeTrackRow,
|
|
8707
8933
|
{
|
|
8708
8934
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8738,20 +8964,28 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8738
8964
|
group.groupId
|
|
8739
8965
|
)),
|
|
8740
8966
|
(adapter.groupExtensions ?? []).flatMap(
|
|
8741
|
-
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ (0,
|
|
8967
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8968
|
+
CollapsibleGroup,
|
|
8969
|
+
{
|
|
8970
|
+
ext,
|
|
8971
|
+
group,
|
|
8972
|
+
groupCtx
|
|
8973
|
+
},
|
|
8974
|
+
`${ext.metaKey}:${group.groupId}`
|
|
8975
|
+
))
|
|
8742
8976
|
),
|
|
8743
8977
|
tracks.map((track, index) => {
|
|
8744
8978
|
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
8745
8979
|
return null;
|
|
8746
8980
|
}
|
|
8747
|
-
return /* @__PURE__ */ (0,
|
|
8981
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
8748
8982
|
}),
|
|
8749
8983
|
slots?.afterRows
|
|
8750
8984
|
] })),
|
|
8751
8985
|
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
8752
8986
|
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
8753
8987
|
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
8754
|
-
return /* @__PURE__ */ (0,
|
|
8988
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
8755
8989
|
"button",
|
|
8756
8990
|
{
|
|
8757
8991
|
"data-testid": "export-midi-tracks-button",
|
|
@@ -8806,6 +9040,15 @@ function createSurgeSoundAdapter(host, overrides = {}) {
|
|
|
8806
9040
|
});
|
|
8807
9041
|
return snap.label;
|
|
8808
9042
|
},
|
|
9043
|
+
persistDescriptor: async (trackId, descriptor, label) => {
|
|
9044
|
+
const { state, stateType } = descriptor;
|
|
9045
|
+
await host.persistTrackPresetState?.(trackId, {
|
|
9046
|
+
state,
|
|
9047
|
+
stateType: stateType ?? "valuetree",
|
|
9048
|
+
name: label
|
|
9049
|
+
}).catch(() => {
|
|
9050
|
+
});
|
|
9051
|
+
},
|
|
8809
9052
|
descriptorFromSnapshot: (snap) => {
|
|
8810
9053
|
const preset = snap;
|
|
8811
9054
|
return { state: preset.state, stateType: preset.stateType };
|
|
@@ -8890,18 +9133,183 @@ var SUB = {
|
|
|
8890
9133
|
rootOnly: true,
|
|
8891
9134
|
monoPreference: "low"
|
|
8892
9135
|
};
|
|
9136
|
+
var HORN_MAX_NOTES_PER_BAR = 12;
|
|
9137
|
+
var HORN_FOLLOW_PALETTE = "EXACTLY the lead voice's rhythm \u2014 every attack lands together with voice 1";
|
|
9138
|
+
var HORN_LEAD = {
|
|
9139
|
+
label: "lead trumpet",
|
|
9140
|
+
role: "brass",
|
|
9141
|
+
registerLow: 60,
|
|
9142
|
+
// C4
|
|
9143
|
+
registerHigh: 84,
|
|
9144
|
+
// C6
|
|
9145
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9146
|
+
rhythmPalette: "tight funk syncopation \u2014 8ths and 16ths, staccato punches, off-beat anticipations",
|
|
9147
|
+
harmonicDiscipline: "the section's melodic top \u2014 chord tones on every hit; quick chromatic approach tones only on pickups",
|
|
9148
|
+
monoPreference: "high"
|
|
9149
|
+
};
|
|
9150
|
+
var HORN_TRUMPET_2 = {
|
|
9151
|
+
label: "second trumpet",
|
|
9152
|
+
role: "brass",
|
|
9153
|
+
registerLow: 55,
|
|
9154
|
+
// G3
|
|
9155
|
+
registerHigh: 79,
|
|
9156
|
+
// G5
|
|
9157
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9158
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9159
|
+
harmonicDiscipline: "nearest chord tone directly below voice 1 \u2014 tight close voicing",
|
|
9160
|
+
monoPreference: "high"
|
|
9161
|
+
};
|
|
9162
|
+
var HORN_ALTO = {
|
|
9163
|
+
label: "alto sax",
|
|
9164
|
+
role: "brass",
|
|
9165
|
+
registerLow: 53,
|
|
9166
|
+
// F3
|
|
9167
|
+
registerHigh: 77,
|
|
9168
|
+
// F5
|
|
9169
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9170
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9171
|
+
harmonicDiscipline: "chord tone below the trumpets \u2014 keep the upper stack inside one octave",
|
|
9172
|
+
monoPreference: "high"
|
|
9173
|
+
};
|
|
9174
|
+
var HORN_TENOR = {
|
|
9175
|
+
label: "tenor sax",
|
|
9176
|
+
role: "brass",
|
|
9177
|
+
registerLow: 46,
|
|
9178
|
+
// Bb2
|
|
9179
|
+
registerHigh: 72,
|
|
9180
|
+
// C5
|
|
9181
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9182
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9183
|
+
harmonicDiscipline: "chord tone under the upper horns \u2014 complete the chord",
|
|
9184
|
+
monoPreference: "low"
|
|
9185
|
+
};
|
|
9186
|
+
var HORN_TROMBONE = {
|
|
9187
|
+
label: "trombone",
|
|
9188
|
+
role: "brass",
|
|
9189
|
+
registerLow: 43,
|
|
9190
|
+
// G2
|
|
9191
|
+
registerHigh: 67,
|
|
9192
|
+
// G4
|
|
9193
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9194
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9195
|
+
harmonicDiscipline: "chord tone or root below the saxes \u2014 weight in the middle-low stack",
|
|
9196
|
+
monoPreference: "low"
|
|
9197
|
+
};
|
|
9198
|
+
var HORN_BARI = {
|
|
9199
|
+
label: "baritone sax",
|
|
9200
|
+
role: "brass",
|
|
9201
|
+
registerLow: 36,
|
|
9202
|
+
// C2
|
|
9203
|
+
registerHigh: 60,
|
|
9204
|
+
// C4
|
|
9205
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9206
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9207
|
+
harmonicDiscipline: "chord roots and lower chord tones \u2014 the section's bottom anchor",
|
|
9208
|
+
monoPreference: "low"
|
|
9209
|
+
};
|
|
9210
|
+
var WIND_FLUTE = {
|
|
9211
|
+
label: "flute",
|
|
9212
|
+
role: "winds",
|
|
9213
|
+
registerLow: 60,
|
|
9214
|
+
// C4
|
|
9215
|
+
registerHigh: 93,
|
|
9216
|
+
// A6
|
|
9217
|
+
maxNotesPerBar: 8,
|
|
9218
|
+
rhythmPalette: "8ths and 16ths; runs, turns and trills welcome",
|
|
9219
|
+
harmonicDiscipline: "freest voice \u2014 non-chord tones as passing/neighbor tones on weak beats, resolving by step",
|
|
9220
|
+
monoPreference: "high"
|
|
9221
|
+
};
|
|
9222
|
+
var WIND_FLUTE_2 = {
|
|
9223
|
+
label: "second flute",
|
|
9224
|
+
role: "winds",
|
|
9225
|
+
registerLow: 60,
|
|
9226
|
+
// C4
|
|
9227
|
+
registerHigh: 86,
|
|
9228
|
+
// D6
|
|
9229
|
+
maxNotesPerBar: 5,
|
|
9230
|
+
rhythmPalette: "quarters with occasional 8th-note motion",
|
|
9231
|
+
harmonicDiscipline: "chord tones; fill gaps the other inner voices leave",
|
|
9232
|
+
monoPreference: "high"
|
|
9233
|
+
};
|
|
9234
|
+
var WIND_OBOE = {
|
|
9235
|
+
label: "oboe",
|
|
9236
|
+
role: "winds",
|
|
9237
|
+
registerLow: 58,
|
|
9238
|
+
// Bb3
|
|
9239
|
+
registerHigh: 84,
|
|
9240
|
+
// C6
|
|
9241
|
+
maxNotesPerBar: 6,
|
|
9242
|
+
rhythmPalette: "8ths and quarters; move when the flute rests",
|
|
9243
|
+
harmonicDiscipline: "mostly chord tones; may imitate the flute's motifs a bar later",
|
|
9244
|
+
monoPreference: "high"
|
|
9245
|
+
};
|
|
9246
|
+
var WIND_CLARINET = {
|
|
9247
|
+
label: "clarinet",
|
|
9248
|
+
role: "winds",
|
|
9249
|
+
registerLow: 50,
|
|
9250
|
+
// D3
|
|
9251
|
+
registerHigh: 81,
|
|
9252
|
+
// A5
|
|
9253
|
+
maxNotesPerBar: 4,
|
|
9254
|
+
rhythmPalette: "quarters and halves",
|
|
9255
|
+
harmonicDiscipline: "chord tones with smooth stepwise motion between them",
|
|
9256
|
+
monoPreference: "high"
|
|
9257
|
+
};
|
|
9258
|
+
var WIND_FRENCH_HORN = {
|
|
9259
|
+
label: "french horn",
|
|
9260
|
+
role: "winds",
|
|
9261
|
+
registerLow: 41,
|
|
9262
|
+
// F2
|
|
9263
|
+
registerHigh: 72,
|
|
9264
|
+
// C5
|
|
9265
|
+
maxNotesPerBar: 4,
|
|
9266
|
+
rhythmPalette: "quarters and halves; long held tones welcome",
|
|
9267
|
+
harmonicDiscipline: "chord tones \u2014 the warm glue in the middle of the ensemble",
|
|
9268
|
+
monoPreference: "low"
|
|
9269
|
+
};
|
|
9270
|
+
var WIND_BASSOON = {
|
|
9271
|
+
label: "bassoon",
|
|
9272
|
+
role: "winds",
|
|
9273
|
+
registerLow: 34,
|
|
9274
|
+
// Bb1
|
|
9275
|
+
registerHigh: 62,
|
|
9276
|
+
// D4
|
|
9277
|
+
maxNotesPerBar: 3,
|
|
9278
|
+
rhythmPalette: "quarters and halves; brief walking figures at cadences",
|
|
9279
|
+
harmonicDiscipline: "roots and fifths emphasized; passing tones only between chord tones",
|
|
9280
|
+
monoPreference: "low"
|
|
9281
|
+
};
|
|
8893
9282
|
var ENSEMBLE_MIN_VOICES = 2;
|
|
8894
9283
|
var ENSEMBLE_MAX_VOICES = 6;
|
|
8895
|
-
var
|
|
9284
|
+
var STRINGS_SPEC_TABLES = {
|
|
8896
9285
|
2: [TOP, BASS],
|
|
8897
9286
|
3: [TOP, INNER, BASS],
|
|
8898
9287
|
4: [TOP, COUNTER, TENOR, BASS],
|
|
8899
9288
|
5: [TOP, COUNTER, INNER, TENOR, SUB],
|
|
8900
9289
|
6: [TOP, COUNTER, INNER_2, INNER, TENOR, SUB]
|
|
8901
9290
|
};
|
|
8902
|
-
|
|
9291
|
+
var HORNS_SPEC_TABLES = {
|
|
9292
|
+
2: [HORN_LEAD, HORN_TENOR],
|
|
9293
|
+
3: [HORN_LEAD, HORN_TENOR, HORN_BARI],
|
|
9294
|
+
4: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_BARI],
|
|
9295
|
+
5: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_TROMBONE, HORN_BARI],
|
|
9296
|
+
6: [HORN_LEAD, HORN_TRUMPET_2, HORN_ALTO, HORN_TENOR, HORN_TROMBONE, HORN_BARI]
|
|
9297
|
+
};
|
|
9298
|
+
var WINDS_SPEC_TABLES = {
|
|
9299
|
+
2: [WIND_FLUTE, WIND_BASSOON],
|
|
9300
|
+
3: [WIND_FLUTE, WIND_CLARINET, WIND_BASSOON],
|
|
9301
|
+
4: [WIND_FLUTE, WIND_OBOE, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9302
|
+
5: [WIND_FLUTE, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9303
|
+
6: [WIND_FLUTE, WIND_FLUTE_2, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON]
|
|
9304
|
+
};
|
|
9305
|
+
var SPEC_TABLES_BY_INSTRUMENTATION = {
|
|
9306
|
+
strings: STRINGS_SPEC_TABLES,
|
|
9307
|
+
horns: HORNS_SPEC_TABLES,
|
|
9308
|
+
winds: WINDS_SPEC_TABLES
|
|
9309
|
+
};
|
|
9310
|
+
function defaultVoiceSpecs(voiceCount, instrumentation = "strings") {
|
|
8903
9311
|
const n = Math.max(ENSEMBLE_MIN_VOICES, Math.min(ENSEMBLE_MAX_VOICES, Math.round(voiceCount)));
|
|
8904
|
-
return
|
|
9312
|
+
return SPEC_TABLES_BY_INSTRUMENTATION[instrumentation][n].map((spec, voiceIndex) => ({ ...spec, voiceIndex }));
|
|
8905
9313
|
}
|
|
8906
9314
|
|
|
8907
9315
|
// src/ensemble-core/enforce-voice.ts
|
|
@@ -8941,10 +9349,14 @@ function enforceVoice(rawNotes, spec, opts) {
|
|
|
8941
9349
|
repairs.push(`voice ${spec.voiceIndex}: dropped note outside the ${opts.bars}-bar clip (start ${n.startBeat})`);
|
|
8942
9350
|
continue;
|
|
8943
9351
|
}
|
|
8944
|
-
|
|
9352
|
+
let durationBeats = Math.max(
|
|
8945
9353
|
MIN_NOTE_DURATION_BEATS,
|
|
8946
9354
|
Math.min(n.durationBeats, clipEnd - n.startBeat)
|
|
8947
9355
|
);
|
|
9356
|
+
if (opts.maxNoteDurationBeats !== void 0 && durationBeats > opts.maxNoteDurationBeats) {
|
|
9357
|
+
durationBeats = Math.max(MIN_NOTE_DURATION_BEATS, opts.maxNoteDurationBeats);
|
|
9358
|
+
repairs.push(`voice ${spec.voiceIndex}: trimmed note at beat ${n.startBeat} to the style's ${opts.maxNoteDurationBeats}-beat stab ceiling`);
|
|
9359
|
+
}
|
|
8948
9360
|
notes.push({ ...n, durationBeats });
|
|
8949
9361
|
}
|
|
8950
9362
|
notes = notes.map((n) => {
|
|
@@ -9105,12 +9517,19 @@ function describeViolations(analysis, rules) {
|
|
|
9105
9517
|
if (pair.onsetIndependence < rules.minOnsetIndependence) {
|
|
9106
9518
|
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
9519
|
}
|
|
9520
|
+
if (rules.maxOnsetIndependence !== void 0 && pair.onsetIndependence > rules.maxOnsetIndependence) {
|
|
9521
|
+
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.`);
|
|
9522
|
+
}
|
|
9108
9523
|
}
|
|
9109
9524
|
return out;
|
|
9110
9525
|
}
|
|
9111
9526
|
|
|
9112
9527
|
// src/ensemble-core/styles.ts
|
|
9113
9528
|
var ENSEMBLE_STYLES = ["counterpoint", "chorale", "interlock"];
|
|
9529
|
+
var STAB_MAX_ONSET_INDEPENDENCE = 0.25;
|
|
9530
|
+
var RIFF_MAX_ONSET_INDEPENDENCE = 0.3;
|
|
9531
|
+
var UNISON_MAX_ONSET_INDEPENDENCE = 0.15;
|
|
9532
|
+
var STAB_MAX_NOTE_DURATION_BEATS = 1;
|
|
9114
9533
|
var STYLE_RULES = {
|
|
9115
9534
|
counterpoint: {
|
|
9116
9535
|
forbidParallelPerfects: true,
|
|
@@ -9129,9 +9548,55 @@ var STYLE_RULES = {
|
|
|
9129
9548
|
forbidVoiceCrossing: false,
|
|
9130
9549
|
minOnsetIndependence: 0.6,
|
|
9131
9550
|
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."
|
|
9551
|
+
},
|
|
9552
|
+
stabs: {
|
|
9553
|
+
forbidParallelPerfects: false,
|
|
9554
|
+
forbidVoiceCrossing: true,
|
|
9555
|
+
minOnsetIndependence: 0,
|
|
9556
|
+
maxOnsetIndependence: STAB_MAX_ONSET_INDEPENDENCE,
|
|
9557
|
+
maxNoteDurationBeats: STAB_MAX_NOTE_DURATION_BEATS,
|
|
9558
|
+
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.`
|
|
9559
|
+
},
|
|
9560
|
+
riffs: {
|
|
9561
|
+
forbidParallelPerfects: false,
|
|
9562
|
+
forbidVoiceCrossing: true,
|
|
9563
|
+
minOnsetIndependence: 0,
|
|
9564
|
+
maxOnsetIndependence: RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9565
|
+
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.'
|
|
9566
|
+
},
|
|
9567
|
+
unison: {
|
|
9568
|
+
forbidParallelPerfects: false,
|
|
9569
|
+
forbidVoiceCrossing: false,
|
|
9570
|
+
minOnsetIndependence: 0,
|
|
9571
|
+
maxOnsetIndependence: UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9572
|
+
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
9573
|
}
|
|
9133
9574
|
};
|
|
9134
9575
|
|
|
9576
|
+
// src/ensemble-core/instrumentation.ts
|
|
9577
|
+
var ENSEMBLE_INSTRUMENTATIONS = [
|
|
9578
|
+
"strings",
|
|
9579
|
+
"horns",
|
|
9580
|
+
"winds"
|
|
9581
|
+
];
|
|
9582
|
+
var STYLES_FOR_INSTRUMENTATION = {
|
|
9583
|
+
strings: ["counterpoint", "chorale", "interlock"],
|
|
9584
|
+
horns: ["stabs", "riffs", "unison"],
|
|
9585
|
+
winds: ["counterpoint", "chorale", "interlock"]
|
|
9586
|
+
};
|
|
9587
|
+
var DEFAULT_STYLE_FOR_INSTRUMENTATION = {
|
|
9588
|
+
strings: "counterpoint",
|
|
9589
|
+
horns: "stabs",
|
|
9590
|
+
winds: "chorale"
|
|
9591
|
+
};
|
|
9592
|
+
function normalizeInstrumentation(raw) {
|
|
9593
|
+
return ENSEMBLE_INSTRUMENTATIONS.includes(raw) ? raw : "strings";
|
|
9594
|
+
}
|
|
9595
|
+
function styleForInstrumentation(instrumentation, rawStyle) {
|
|
9596
|
+
const allowed = STYLES_FOR_INSTRUMENTATION[instrumentation];
|
|
9597
|
+
return allowed.includes(rawStyle ?? "") ? rawStyle : DEFAULT_STYLE_FOR_INSTRUMENTATION[instrumentation];
|
|
9598
|
+
}
|
|
9599
|
+
|
|
9135
9600
|
// src/ensemble-core/ensemble-schema.ts
|
|
9136
9601
|
var SUBMIT_ENSEMBLE_TOOL_NAME = "submit_ensemble";
|
|
9137
9602
|
function buildSubmitEnsembleParameters(voiceCount) {
|
|
@@ -9213,9 +9678,13 @@ function voiceContractLine(spec) {
|
|
|
9213
9678
|
const root = spec.rootOnly ? " ROOT PITCH CLASS ONLY (each bar's chord root)." : "";
|
|
9214
9679
|
return `- Voice ${spec.voiceIndex + 1} (${spec.label}): MIDI ${spec.registerLow}-${spec.registerHigh}, max ${spec.maxNotesPerBar} notes/bar, rhythm: ${spec.rhythmPalette}. ${spec.harmonicDiscipline}.${root}`;
|
|
9215
9680
|
}
|
|
9216
|
-
|
|
9681
|
+
var WOVEN_PERSONA = {
|
|
9682
|
+
strings: "You are an ensemble composer.",
|
|
9683
|
+
winds: "You are a wind-ensemble composer writing for chamber winds (flutes, oboe, clarinet, french horn, bassoon)."
|
|
9684
|
+
};
|
|
9685
|
+
function buildWovenPrompt(specs, style, instrumentation) {
|
|
9217
9686
|
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9218
|
-
return
|
|
9687
|
+
return `${WOVEN_PERSONA[instrumentation]} Compose ${specs.length} voices as ONE piece of music \u2014 not ${specs.length} independent parts.
|
|
9219
9688
|
|
|
9220
9689
|
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9221
9690
|
|
|
@@ -9232,6 +9701,28 @@ ${styleParagraph}
|
|
|
9232
9701
|
PER-VOICE CONTRACTS:
|
|
9233
9702
|
${specs.map(voiceContractLine).join("\n")}`;
|
|
9234
9703
|
}
|
|
9704
|
+
function buildSectionPrompt(specs, style) {
|
|
9705
|
+
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9706
|
+
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.
|
|
9707
|
+
|
|
9708
|
+
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9709
|
+
|
|
9710
|
+
THE SECTION RULES (most important):
|
|
9711
|
+
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.
|
|
9712
|
+
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.
|
|
9713
|
+
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.
|
|
9714
|
+
4. Follow the chord progression in the musical context exactly \u2014 every hit is voiced from the chord sounding at that beat.
|
|
9715
|
+
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.
|
|
9716
|
+
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).
|
|
9717
|
+
|
|
9718
|
+
${styleParagraph}
|
|
9719
|
+
|
|
9720
|
+
PER-VOICE CONTRACTS:
|
|
9721
|
+
${specs.map(voiceContractLine).join("\n")}`;
|
|
9722
|
+
}
|
|
9723
|
+
function buildEnsembleSystemPrompt(specs, style, instrumentation = "strings") {
|
|
9724
|
+
return instrumentation === "horns" ? buildSectionPrompt(specs, style) : buildWovenPrompt(specs, style, instrumentation);
|
|
9725
|
+
}
|
|
9235
9726
|
function buildViolationRetrySuffix(violations) {
|
|
9236
9727
|
if (violations.length === 0) return "";
|
|
9237
9728
|
return `
|
|
@@ -9407,10 +9898,12 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9407
9898
|
DB_MIN,
|
|
9408
9899
|
DEFAULT_FX_CATEGORY_DETAIL,
|
|
9409
9900
|
DEFAULT_FX_DRY_WET,
|
|
9901
|
+
DEFAULT_STYLE_FOR_INSTRUMENTATION,
|
|
9410
9902
|
DRAG_DEAD_ZONE,
|
|
9411
9903
|
DownloadPackButton,
|
|
9412
9904
|
EMPTY_FX_DETAIL_STATE,
|
|
9413
9905
|
EMPTY_FX_STATE,
|
|
9906
|
+
ENSEMBLE_INSTRUMENTATIONS,
|
|
9414
9907
|
ENSEMBLE_MAX_VOICES,
|
|
9415
9908
|
ENSEMBLE_MIN_VOICES,
|
|
9416
9909
|
ENSEMBLE_STYLES,
|
|
@@ -9425,7 +9918,9 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9425
9918
|
FxToggleBar,
|
|
9426
9919
|
GUTTER_W,
|
|
9427
9920
|
GeneratorPanelShell,
|
|
9921
|
+
GroupCollapseChevron,
|
|
9428
9922
|
GroupFadeTrackRow,
|
|
9923
|
+
HORN_MAX_NOTES_PER_BAR,
|
|
9429
9924
|
ImportTrackModal,
|
|
9430
9925
|
InstrumentDrawer,
|
|
9431
9926
|
LevelMeter,
|
|
@@ -9439,8 +9934,12 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9439
9934
|
PianoRollEditor,
|
|
9440
9935
|
PluginError,
|
|
9441
9936
|
RESIZE_HANDLE_PX,
|
|
9937
|
+
RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9442
9938
|
ROW_HEIGHT,
|
|
9443
9939
|
SLIDER_UNITY,
|
|
9940
|
+
STAB_MAX_NOTE_DURATION_BEATS,
|
|
9941
|
+
STAB_MAX_ONSET_INDEPENDENCE,
|
|
9942
|
+
STYLES_FOR_INSTRUMENTATION,
|
|
9444
9943
|
STYLE_RULES,
|
|
9445
9944
|
SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
9446
9945
|
SamplePackCTACard,
|
|
@@ -9454,6 +9953,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9454
9953
|
TrackMeterStrip,
|
|
9455
9954
|
TrackRow,
|
|
9456
9955
|
TransitionDesigner,
|
|
9956
|
+
UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9457
9957
|
VolumeSlider,
|
|
9458
9958
|
WaveformView,
|
|
9459
9959
|
analyzeEnsemble,
|
|
@@ -9488,6 +9988,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9488
9988
|
moveItem,
|
|
9489
9989
|
nearestPitchWithPc,
|
|
9490
9990
|
newTrackState,
|
|
9991
|
+
normalizeInstrumentation,
|
|
9491
9992
|
normalizeSlots,
|
|
9492
9993
|
padPair,
|
|
9493
9994
|
padSlots,
|
|
@@ -9511,6 +10012,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
9511
10012
|
slotsEqual,
|
|
9512
10013
|
soundIdentity,
|
|
9513
10014
|
splitFadeEntries,
|
|
10015
|
+
styleForInstrumentation,
|
|
9514
10016
|
synthesizeCuePoints,
|
|
9515
10017
|
tokenizePrompt,
|
|
9516
10018
|
trackDataKey,
|