@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.mjs
CHANGED
|
@@ -1204,6 +1204,7 @@ function TrackDrawer({
|
|
|
1204
1204
|
onToggleFavorite,
|
|
1205
1205
|
onImportSound,
|
|
1206
1206
|
importSoundLabel,
|
|
1207
|
+
linkedSoundHint,
|
|
1207
1208
|
editNotes,
|
|
1208
1209
|
onNotesChange,
|
|
1209
1210
|
editBars,
|
|
@@ -1270,7 +1271,7 @@ function TrackDrawer({
|
|
|
1270
1271
|
}
|
|
1271
1272
|
) : null;
|
|
1272
1273
|
const currentSound = soundHistoryCursor >= 0 && soundHistoryCursor < history.length ? history[soundHistoryCursor].label : null;
|
|
1273
|
-
const header = strip || currentSound ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
|
|
1274
|
+
const header = strip || currentSound || linkedSoundHint ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", "data-testid": "sdk-drawer-header", children: [
|
|
1274
1275
|
strip,
|
|
1275
1276
|
currentSound && /* @__PURE__ */ jsx5(
|
|
1276
1277
|
"span",
|
|
@@ -1279,6 +1280,14 @@ function TrackDrawer({
|
|
|
1279
1280
|
title: currentSound,
|
|
1280
1281
|
children: currentSound
|
|
1281
1282
|
}
|
|
1283
|
+
),
|
|
1284
|
+
linkedSoundHint && /* @__PURE__ */ jsx5(
|
|
1285
|
+
"span",
|
|
1286
|
+
{
|
|
1287
|
+
"data-testid": "sdk-drawer-linked-hint",
|
|
1288
|
+
className: "text-[10px] text-sas-accent/80 px-0.5",
|
|
1289
|
+
children: linkedSoundHint
|
|
1290
|
+
}
|
|
1282
1291
|
)
|
|
1283
1292
|
] }) : null;
|
|
1284
1293
|
if (effectiveTab === "freeze") {
|
|
@@ -2514,6 +2523,8 @@ function TrackRow({
|
|
|
2514
2523
|
onToggleFavorite,
|
|
2515
2524
|
onImportSound,
|
|
2516
2525
|
importSoundLabel,
|
|
2526
|
+
linkedSoundHint,
|
|
2527
|
+
onSyncSoundToGroup,
|
|
2517
2528
|
editNotes,
|
|
2518
2529
|
onNotesChange,
|
|
2519
2530
|
editBars,
|
|
@@ -2679,15 +2690,37 @@ function TrackRow({
|
|
|
2679
2690
|
)
|
|
2680
2691
|
] }),
|
|
2681
2692
|
/* @__PURE__ */ jsxs9("div", { className: "flex gap-1 items-center", children: [
|
|
2682
|
-
onShuffle && /* @__PURE__ */
|
|
2693
|
+
onShuffle && /* @__PURE__ */ jsxs9(
|
|
2683
2694
|
"button",
|
|
2684
2695
|
{
|
|
2685
2696
|
"data-testid": "sdk-shuffle-button",
|
|
2686
2697
|
onClick: onShuffle,
|
|
2687
2698
|
disabled: !hasMidi || isGenerating || !!currentInstrumentPluginId,
|
|
2688
|
-
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"}`,
|
|
2689
|
-
title: currentInstrumentPluginId ? "Shuffle only works with default Surge XT" : hasMidi ? "Re-roll sound (keep MIDI)" : "Generate MIDI first",
|
|
2690
|
-
children:
|
|
2699
|
+
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"}`,
|
|
2700
|
+
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",
|
|
2701
|
+
children: [
|
|
2702
|
+
"Shuffle",
|
|
2703
|
+
linkedSoundHint && /* @__PURE__ */ jsx13(
|
|
2704
|
+
"span",
|
|
2705
|
+
{
|
|
2706
|
+
"aria-hidden": true,
|
|
2707
|
+
"data-testid": "sdk-shuffle-linked-marker",
|
|
2708
|
+
className: "absolute -top-1 -right-1 text-[8px] leading-none",
|
|
2709
|
+
children: "\u{1F517}"
|
|
2710
|
+
}
|
|
2711
|
+
)
|
|
2712
|
+
]
|
|
2713
|
+
}
|
|
2714
|
+
),
|
|
2715
|
+
onSyncSoundToGroup && /* @__PURE__ */ jsx13(
|
|
2716
|
+
"button",
|
|
2717
|
+
{
|
|
2718
|
+
"data-testid": "sdk-sync-sound-button",
|
|
2719
|
+
onClick: onSyncSoundToGroup,
|
|
2720
|
+
disabled: isGenerating,
|
|
2721
|
+
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"}`,
|
|
2722
|
+
title: "Apply THIS part's current sound to all linked parts \u2014 use after picking a patch inside the plugin's own editor",
|
|
2723
|
+
children: "\u2192 All"
|
|
2691
2724
|
}
|
|
2692
2725
|
),
|
|
2693
2726
|
freezeBadge && /* @__PURE__ */ jsx13(
|
|
@@ -2771,6 +2804,7 @@ function TrackRow({
|
|
|
2771
2804
|
onToggleFavorite,
|
|
2772
2805
|
onImportSound,
|
|
2773
2806
|
importSoundLabel,
|
|
2807
|
+
linkedSoundHint,
|
|
2774
2808
|
editNotes,
|
|
2775
2809
|
onNotesChange,
|
|
2776
2810
|
editBars,
|
|
@@ -4998,9 +5032,38 @@ function TransitionDesigner({
|
|
|
4998
5032
|
] });
|
|
4999
5033
|
}
|
|
5000
5034
|
|
|
5035
|
+
// src/components/GroupCollapseChevron.tsx
|
|
5036
|
+
import { ChevronDown as ChevronDown2 } from "lucide-react";
|
|
5037
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
5038
|
+
function GroupCollapseChevron({
|
|
5039
|
+
collapsed = false,
|
|
5040
|
+
onToggle,
|
|
5041
|
+
what = "group"
|
|
5042
|
+
}) {
|
|
5043
|
+
if (!onToggle) return null;
|
|
5044
|
+
return /* @__PURE__ */ jsx21(
|
|
5045
|
+
"button",
|
|
5046
|
+
{
|
|
5047
|
+
type: "button",
|
|
5048
|
+
"data-testid": "sdk-group-collapse",
|
|
5049
|
+
onClick: onToggle,
|
|
5050
|
+
"aria-expanded": !collapsed,
|
|
5051
|
+
title: collapsed ? `Expand ${what}` : `Collapse ${what}`,
|
|
5052
|
+
className: "shrink-0 px-0.5 py-0.5 rounded-sm text-sas-muted hover:text-sas-accent transition-colors",
|
|
5053
|
+
children: /* @__PURE__ */ jsx21(
|
|
5054
|
+
ChevronDown2,
|
|
5055
|
+
{
|
|
5056
|
+
className: `w-3 h-3 transition-transform ${collapsed ? "-rotate-90" : ""}`,
|
|
5057
|
+
strokeWidth: 2.5
|
|
5058
|
+
}
|
|
5059
|
+
)
|
|
5060
|
+
}
|
|
5061
|
+
);
|
|
5062
|
+
}
|
|
5063
|
+
|
|
5001
5064
|
// src/components/PanelMasterStrip.tsx
|
|
5002
5065
|
import { useMemo as useMemo7, useState as useState15 } from "react";
|
|
5003
|
-
import { jsx as
|
|
5066
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
5004
5067
|
function PanelMasterStrip({
|
|
5005
5068
|
bus,
|
|
5006
5069
|
levels = null,
|
|
@@ -5034,7 +5097,7 @@ function PanelMasterStrip({
|
|
|
5034
5097
|
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" : ""}`,
|
|
5035
5098
|
children: [
|
|
5036
5099
|
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
|
|
5037
|
-
/* @__PURE__ */
|
|
5100
|
+
/* @__PURE__ */ jsx22(
|
|
5038
5101
|
"span",
|
|
5039
5102
|
{
|
|
5040
5103
|
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
@@ -5043,7 +5106,7 @@ function PanelMasterStrip({
|
|
|
5043
5106
|
}
|
|
5044
5107
|
),
|
|
5045
5108
|
/* @__PURE__ */ jsxs17("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
|
|
5046
|
-
/* @__PURE__ */
|
|
5109
|
+
/* @__PURE__ */ jsx22(
|
|
5047
5110
|
VolumeSlider,
|
|
5048
5111
|
{
|
|
5049
5112
|
value: dbToSlider(bus.volume),
|
|
@@ -5052,7 +5115,7 @@ function PanelMasterStrip({
|
|
|
5052
5115
|
}
|
|
5053
5116
|
),
|
|
5054
5117
|
/* @__PURE__ */ jsxs17("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
|
|
5055
|
-
/* @__PURE__ */
|
|
5118
|
+
/* @__PURE__ */ jsx22(
|
|
5056
5119
|
LevelMeter,
|
|
5057
5120
|
{
|
|
5058
5121
|
peakDb: levels?.leftDb ?? -120,
|
|
@@ -5062,7 +5125,7 @@ function PanelMasterStrip({
|
|
|
5062
5125
|
"data-testid": "bus-meter-left"
|
|
5063
5126
|
}
|
|
5064
5127
|
),
|
|
5065
|
-
/* @__PURE__ */
|
|
5128
|
+
/* @__PURE__ */ jsx22(
|
|
5066
5129
|
LevelMeter,
|
|
5067
5130
|
{
|
|
5068
5131
|
peakDb: levels?.rightDb ?? -120,
|
|
@@ -5073,7 +5136,7 @@ function PanelMasterStrip({
|
|
|
5073
5136
|
)
|
|
5074
5137
|
] })
|
|
5075
5138
|
] }),
|
|
5076
|
-
/* @__PURE__ */
|
|
5139
|
+
/* @__PURE__ */ jsx22(
|
|
5077
5140
|
"button",
|
|
5078
5141
|
{
|
|
5079
5142
|
"data-testid": "bus-mute-button",
|
|
@@ -5084,7 +5147,7 @@ function PanelMasterStrip({
|
|
|
5084
5147
|
children: "M"
|
|
5085
5148
|
}
|
|
5086
5149
|
),
|
|
5087
|
-
/* @__PURE__ */
|
|
5150
|
+
/* @__PURE__ */ jsx22(
|
|
5088
5151
|
"button",
|
|
5089
5152
|
{
|
|
5090
5153
|
"data-testid": "bus-solo-button",
|
|
@@ -5095,14 +5158,14 @@ function PanelMasterStrip({
|
|
|
5095
5158
|
children: "S"
|
|
5096
5159
|
}
|
|
5097
5160
|
),
|
|
5098
|
-
/* @__PURE__ */
|
|
5161
|
+
/* @__PURE__ */ jsx22("div", { className: "flex items-center gap-1 max-w-[45%] min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ jsxs17(
|
|
5099
5162
|
"span",
|
|
5100
5163
|
{
|
|
5101
5164
|
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
5102
5165
|
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"}`,
|
|
5103
5166
|
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
5104
5167
|
children: [
|
|
5105
|
-
/* @__PURE__ */
|
|
5168
|
+
/* @__PURE__ */ jsx22(
|
|
5106
5169
|
"button",
|
|
5107
5170
|
{
|
|
5108
5171
|
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
@@ -5113,7 +5176,7 @@ function PanelMasterStrip({
|
|
|
5113
5176
|
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
5114
5177
|
}
|
|
5115
5178
|
),
|
|
5116
|
-
onShowFxEditor ? /* @__PURE__ */
|
|
5179
|
+
onShowFxEditor ? /* @__PURE__ */ jsx22(
|
|
5117
5180
|
"button",
|
|
5118
5181
|
{
|
|
5119
5182
|
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
@@ -5123,8 +5186,8 @@ function PanelMasterStrip({
|
|
|
5123
5186
|
title: `Open ${fx.name} editor`,
|
|
5124
5187
|
children: fx.name
|
|
5125
5188
|
}
|
|
5126
|
-
) : /* @__PURE__ */
|
|
5127
|
-
/* @__PURE__ */
|
|
5189
|
+
) : /* @__PURE__ */ jsx22("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
5190
|
+
/* @__PURE__ */ jsx22(
|
|
5128
5191
|
"button",
|
|
5129
5192
|
{
|
|
5130
5193
|
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
@@ -5139,7 +5202,7 @@ function PanelMasterStrip({
|
|
|
5139
5202
|
},
|
|
5140
5203
|
`${fx.index}:${fx.pluginId}`
|
|
5141
5204
|
)) }),
|
|
5142
|
-
/* @__PURE__ */
|
|
5205
|
+
/* @__PURE__ */ jsx22(
|
|
5143
5206
|
"button",
|
|
5144
5207
|
{
|
|
5145
5208
|
"data-testid": "bus-fx-add-button",
|
|
@@ -5153,7 +5216,7 @@ function PanelMasterStrip({
|
|
|
5153
5216
|
] }),
|
|
5154
5217
|
fxPickerOpen && /* @__PURE__ */ jsxs17("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
5155
5218
|
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
|
|
5156
|
-
/* @__PURE__ */
|
|
5219
|
+
/* @__PURE__ */ jsx22(
|
|
5157
5220
|
"input",
|
|
5158
5221
|
{
|
|
5159
5222
|
type: "text",
|
|
@@ -5163,7 +5226,7 @@ function PanelMasterStrip({
|
|
|
5163
5226
|
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
5164
5227
|
}
|
|
5165
5228
|
),
|
|
5166
|
-
onRefreshFx && /* @__PURE__ */
|
|
5229
|
+
onRefreshFx && /* @__PURE__ */ jsx22(
|
|
5167
5230
|
"button",
|
|
5168
5231
|
{
|
|
5169
5232
|
onClick: () => onRefreshFx(),
|
|
@@ -5174,7 +5237,7 @@ function PanelMasterStrip({
|
|
|
5174
5237
|
}
|
|
5175
5238
|
)
|
|
5176
5239
|
] }),
|
|
5177
|
-
fxLoading && availableFx.length === 0 ? /* @__PURE__ */
|
|
5240
|
+
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ jsx22("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ jsxs17("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
|
|
5178
5241
|
filtered.map((fx) => /* @__PURE__ */ jsxs17(
|
|
5179
5242
|
"button",
|
|
5180
5243
|
{
|
|
@@ -5183,13 +5246,13 @@ function PanelMasterStrip({
|
|
|
5183
5246
|
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",
|
|
5184
5247
|
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
5185
5248
|
children: [
|
|
5186
|
-
/* @__PURE__ */
|
|
5187
|
-
/* @__PURE__ */
|
|
5249
|
+
/* @__PURE__ */ jsx22("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
5250
|
+
/* @__PURE__ */ jsx22("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
5188
5251
|
]
|
|
5189
5252
|
},
|
|
5190
5253
|
fx.pluginId
|
|
5191
5254
|
)),
|
|
5192
|
-
filtered.length === 0 && /* @__PURE__ */
|
|
5255
|
+
filtered.length === 0 && /* @__PURE__ */ jsx22("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
|
|
5193
5256
|
] })
|
|
5194
5257
|
] })
|
|
5195
5258
|
]
|
|
@@ -5316,7 +5379,7 @@ function usePanelBus(host, activeSceneId) {
|
|
|
5316
5379
|
|
|
5317
5380
|
// src/components/DownloadPackButton.tsx
|
|
5318
5381
|
import { useCallback as useCallback12, useEffect as useEffect13, useState as useState17 } from "react";
|
|
5319
|
-
import { jsx as
|
|
5382
|
+
import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
5320
5383
|
function formatSize(bytes) {
|
|
5321
5384
|
if (!bytes || bytes <= 0) return "";
|
|
5322
5385
|
const gb = bytes / 1024 ** 3;
|
|
@@ -5405,7 +5468,7 @@ var DownloadPackButton = ({
|
|
|
5405
5468
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
5406
5469
|
}
|
|
5407
5470
|
return /* @__PURE__ */ jsxs18("div", { children: [
|
|
5408
|
-
/* @__PURE__ */
|
|
5471
|
+
/* @__PURE__ */ jsx23(
|
|
5409
5472
|
"button",
|
|
5410
5473
|
{
|
|
5411
5474
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -5416,12 +5479,12 @@ var DownloadPackButton = ({
|
|
|
5416
5479
|
children: buttonLabel
|
|
5417
5480
|
}
|
|
5418
5481
|
),
|
|
5419
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */
|
|
5482
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx23("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
5420
5483
|
] });
|
|
5421
5484
|
};
|
|
5422
5485
|
|
|
5423
5486
|
// src/components/SamplePackCTACard.tsx
|
|
5424
|
-
import { jsx as
|
|
5487
|
+
import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
5425
5488
|
var SamplePackCTACard = ({
|
|
5426
5489
|
host,
|
|
5427
5490
|
pack,
|
|
@@ -5429,7 +5492,7 @@ var SamplePackCTACard = ({
|
|
|
5429
5492
|
onDownloadComplete
|
|
5430
5493
|
}) => {
|
|
5431
5494
|
if (status === "checking") {
|
|
5432
|
-
return /* @__PURE__ */
|
|
5495
|
+
return /* @__PURE__ */ jsx24(
|
|
5433
5496
|
"div",
|
|
5434
5497
|
{
|
|
5435
5498
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -5446,10 +5509,10 @@ var SamplePackCTACard = ({
|
|
|
5446
5509
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
5447
5510
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
5448
5511
|
children: [
|
|
5449
|
-
/* @__PURE__ */
|
|
5450
|
-
/* @__PURE__ */
|
|
5451
|
-
/* @__PURE__ */
|
|
5452
|
-
/* @__PURE__ */
|
|
5512
|
+
/* @__PURE__ */ jsx24("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
5513
|
+
/* @__PURE__ */ jsx24("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
5514
|
+
/* @__PURE__ */ jsx24("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
5515
|
+
/* @__PURE__ */ jsx24(
|
|
5453
5516
|
DownloadPackButton,
|
|
5454
5517
|
{
|
|
5455
5518
|
host,
|
|
@@ -5529,7 +5592,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
5529
5592
|
}
|
|
5530
5593
|
|
|
5531
5594
|
// src/components/WaveformView.tsx
|
|
5532
|
-
import { jsx as
|
|
5595
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
5533
5596
|
var WaveformView = ({
|
|
5534
5597
|
host,
|
|
5535
5598
|
filePath,
|
|
@@ -5577,7 +5640,7 @@ var WaveformView = ({
|
|
|
5577
5640
|
observer.observe(canvas);
|
|
5578
5641
|
return () => observer.disconnect();
|
|
5579
5642
|
}, [peaks, fillStyle]);
|
|
5580
|
-
return /* @__PURE__ */
|
|
5643
|
+
return /* @__PURE__ */ jsx25(
|
|
5581
5644
|
"canvas",
|
|
5582
5645
|
{
|
|
5583
5646
|
ref: canvasRef,
|
|
@@ -5589,7 +5652,7 @@ var WaveformView = ({
|
|
|
5589
5652
|
|
|
5590
5653
|
// src/components/ScrollingWaveform.tsx
|
|
5591
5654
|
import { useEffect as useEffect15, useRef as useRef14 } from "react";
|
|
5592
|
-
import { jsx as
|
|
5655
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
5593
5656
|
var ScrollingWaveform = ({
|
|
5594
5657
|
getPeakDb,
|
|
5595
5658
|
active,
|
|
@@ -5665,7 +5728,7 @@ var ScrollingWaveform = ({
|
|
|
5665
5728
|
}
|
|
5666
5729
|
};
|
|
5667
5730
|
}, [active, getPeakDb, fillStyle]);
|
|
5668
|
-
return /* @__PURE__ */
|
|
5731
|
+
return /* @__PURE__ */ jsx26(
|
|
5669
5732
|
"canvas",
|
|
5670
5733
|
{
|
|
5671
5734
|
ref: canvasRef,
|
|
@@ -5677,7 +5740,7 @@ var ScrollingWaveform = ({
|
|
|
5677
5740
|
|
|
5678
5741
|
// src/components/OffsetScrubber.tsx
|
|
5679
5742
|
import { useCallback as useCallback13, useEffect as useEffect16, useMemo as useMemo8, useRef as useRef15, useState as useState19 } from "react";
|
|
5680
|
-
import { jsx as
|
|
5743
|
+
import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5681
5744
|
var SLIDER_HEIGHT_PX = 28;
|
|
5682
5745
|
var TICK_HEIGHT_PX = 14;
|
|
5683
5746
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -5793,7 +5856,7 @@ function OffsetScrubber({
|
|
|
5793
5856
|
}, [cuePoints, sampleToFraction]);
|
|
5794
5857
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
5795
5858
|
return /* @__PURE__ */ jsxs20("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
5796
|
-
/* @__PURE__ */
|
|
5859
|
+
/* @__PURE__ */ jsx27("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
5797
5860
|
/* @__PURE__ */ jsxs20(
|
|
5798
5861
|
"div",
|
|
5799
5862
|
{
|
|
@@ -5810,7 +5873,7 @@ function OffsetScrubber({
|
|
|
5810
5873
|
"aria-valuenow": draftOffset,
|
|
5811
5874
|
"aria-disabled": isDisabled,
|
|
5812
5875
|
children: [
|
|
5813
|
-
/* @__PURE__ */
|
|
5876
|
+
/* @__PURE__ */ jsx27(
|
|
5814
5877
|
"div",
|
|
5815
5878
|
{
|
|
5816
5879
|
"aria-hidden": "true",
|
|
@@ -5818,7 +5881,7 @@ function OffsetScrubber({
|
|
|
5818
5881
|
style: { left: "50%" }
|
|
5819
5882
|
}
|
|
5820
5883
|
),
|
|
5821
|
-
ticks.map((t) => /* @__PURE__ */
|
|
5884
|
+
ticks.map((t) => /* @__PURE__ */ jsx27(
|
|
5822
5885
|
"div",
|
|
5823
5886
|
{
|
|
5824
5887
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -5833,7 +5896,7 @@ function OffsetScrubber({
|
|
|
5833
5896
|
},
|
|
5834
5897
|
t.i
|
|
5835
5898
|
)),
|
|
5836
|
-
/* @__PURE__ */
|
|
5899
|
+
/* @__PURE__ */ jsx27(
|
|
5837
5900
|
"div",
|
|
5838
5901
|
{
|
|
5839
5902
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -5850,7 +5913,7 @@ function OffsetScrubber({
|
|
|
5850
5913
|
]
|
|
5851
5914
|
}
|
|
5852
5915
|
),
|
|
5853
|
-
/* @__PURE__ */
|
|
5916
|
+
/* @__PURE__ */ jsx27(
|
|
5854
5917
|
"span",
|
|
5855
5918
|
{
|
|
5856
5919
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -5858,7 +5921,7 @@ function OffsetScrubber({
|
|
|
5858
5921
|
children: formatOffset(draftOffset, sampleRate)
|
|
5859
5922
|
}
|
|
5860
5923
|
),
|
|
5861
|
-
/* @__PURE__ */
|
|
5924
|
+
/* @__PURE__ */ jsx27(
|
|
5862
5925
|
"button",
|
|
5863
5926
|
{
|
|
5864
5927
|
type: "button",
|
|
@@ -5870,7 +5933,7 @@ function OffsetScrubber({
|
|
|
5870
5933
|
children: "\u2316"
|
|
5871
5934
|
}
|
|
5872
5935
|
),
|
|
5873
|
-
bpmMismatch && /* @__PURE__ */
|
|
5936
|
+
bpmMismatch && /* @__PURE__ */ jsx27(
|
|
5874
5937
|
"span",
|
|
5875
5938
|
{
|
|
5876
5939
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -6883,7 +6946,7 @@ function useTransitionOps({
|
|
|
6883
6946
|
}
|
|
6884
6947
|
|
|
6885
6948
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
6886
|
-
import { jsx as
|
|
6949
|
+
import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
6887
6950
|
var EMPTY_PLACEHOLDERS = [];
|
|
6888
6951
|
function useGeneratorPanelCore({
|
|
6889
6952
|
ui,
|
|
@@ -7294,37 +7357,6 @@ function useGeneratorPanelCore({
|
|
|
7294
7357
|
},
|
|
7295
7358
|
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
7296
7359
|
);
|
|
7297
|
-
const handleSoundImportPick = useCallback17(
|
|
7298
|
-
async (sel) => {
|
|
7299
|
-
const target = soundImportTarget;
|
|
7300
|
-
if (!target || !host.getTrackSound) {
|
|
7301
|
-
setSoundImportTarget(null);
|
|
7302
|
-
return;
|
|
7303
|
-
}
|
|
7304
|
-
const noun = adapter.sound.importNoun;
|
|
7305
|
-
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
7306
|
-
try {
|
|
7307
|
-
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
7308
|
-
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
7309
|
-
host.showToast(
|
|
7310
|
-
"error",
|
|
7311
|
-
`No ${noun} to import`,
|
|
7312
|
-
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
7313
|
-
);
|
|
7314
|
-
return;
|
|
7315
|
-
}
|
|
7316
|
-
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
7317
|
-
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
7318
|
-
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
7319
|
-
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
7320
|
-
} catch (err) {
|
|
7321
|
-
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
7322
|
-
} finally {
|
|
7323
|
-
setSoundImportTarget(null);
|
|
7324
|
-
}
|
|
7325
|
-
},
|
|
7326
|
-
[soundImportTarget, host, adapter, identity.familyKey, soundHistory]
|
|
7327
|
-
);
|
|
7328
7360
|
const [isExportingMidi, setIsExportingMidi] = useState24(false);
|
|
7329
7361
|
const handleExportMidi = useCallback17(async () => {
|
|
7330
7362
|
if (isExportingMidi) return;
|
|
@@ -7381,7 +7413,7 @@ function useGeneratorPanelCore({
|
|
|
7381
7413
|
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
7382
7414
|
onHeaderContent(
|
|
7383
7415
|
/* @__PURE__ */ jsxs21("div", { className: "flex gap-1 items-center", children: [
|
|
7384
|
-
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */
|
|
7416
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ jsx28(
|
|
7385
7417
|
"button",
|
|
7386
7418
|
{
|
|
7387
7419
|
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
@@ -7395,7 +7427,7 @@ function useGeneratorPanelCore({
|
|
|
7395
7427
|
children: identity.importTrackLabel ?? "Import Track"
|
|
7396
7428
|
}
|
|
7397
7429
|
),
|
|
7398
|
-
(!canCrossfade || !designerView) && /* @__PURE__ */
|
|
7430
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ jsx28(
|
|
7399
7431
|
"button",
|
|
7400
7432
|
{
|
|
7401
7433
|
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
@@ -7430,7 +7462,7 @@ function useGeneratorPanelCore({
|
|
|
7430
7462
|
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
7431
7463
|
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",
|
|
7432
7464
|
children: [
|
|
7433
|
-
transitionSourceTotal > 0 && /* @__PURE__ */
|
|
7465
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ jsx28(
|
|
7434
7466
|
"span",
|
|
7435
7467
|
{
|
|
7436
7468
|
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
@@ -7571,9 +7603,183 @@ function useGeneratorPanelCore({
|
|
|
7571
7603
|
name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,
|
|
7572
7604
|
...adapter.createTrackOptions()
|
|
7573
7605
|
}),
|
|
7574
|
-
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
7606
|
+
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? [],
|
|
7607
|
+
sound: adapter.sound
|
|
7575
7608
|
};
|
|
7576
7609
|
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
7610
|
+
const broadcastSoundFromTrack = useCallback17(
|
|
7611
|
+
async (sourceTrackId, descriptor, label) => {
|
|
7612
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7613
|
+
if (!targetsOf) return;
|
|
7614
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7615
|
+
if (!source) return;
|
|
7616
|
+
let targets;
|
|
7617
|
+
try {
|
|
7618
|
+
targets = await targetsOf(source, makeServices());
|
|
7619
|
+
} catch (err) {
|
|
7620
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7621
|
+
return;
|
|
7622
|
+
}
|
|
7623
|
+
const sourceInstrument = source.instrumentPluginId ?? null;
|
|
7624
|
+
const others = (targets ?? []).filter((t) => {
|
|
7625
|
+
if (t.engineId === sourceTrackId) return false;
|
|
7626
|
+
const live = tracksRef.current.find((x) => x.handle.id === t.engineId);
|
|
7627
|
+
return (live?.instrumentPluginId ?? null) === sourceInstrument;
|
|
7628
|
+
});
|
|
7629
|
+
if (others.length === 0) return;
|
|
7630
|
+
try {
|
|
7631
|
+
await adapter.sound.persistDescriptor?.(sourceTrackId, descriptor, label);
|
|
7632
|
+
} catch {
|
|
7633
|
+
}
|
|
7634
|
+
const appliedIds = /* @__PURE__ */ new Set();
|
|
7635
|
+
const failed = [];
|
|
7636
|
+
for (const target of others) {
|
|
7637
|
+
try {
|
|
7638
|
+
if (soundHistory.list(target.engineId).entries.length === 0) {
|
|
7639
|
+
try {
|
|
7640
|
+
const cap = await adapter.sound.captureSoundDescriptor(target.engineId);
|
|
7641
|
+
if (cap) {
|
|
7642
|
+
soundHistory.record(target.engineId, cap.descriptor, adapter.sound.previousSoundLabel);
|
|
7643
|
+
}
|
|
7644
|
+
} catch {
|
|
7645
|
+
}
|
|
7646
|
+
}
|
|
7647
|
+
await adapter.sound.applySound(target.engineId, descriptor);
|
|
7648
|
+
try {
|
|
7649
|
+
await adapter.sound.persistDescriptor?.(target.engineId, descriptor, label);
|
|
7650
|
+
} catch {
|
|
7651
|
+
}
|
|
7652
|
+
soundHistory.record(target.engineId, descriptor, label);
|
|
7653
|
+
appliedIds.add(target.engineId);
|
|
7654
|
+
} catch (err) {
|
|
7655
|
+
failed.push(target.label ?? target.engineId);
|
|
7656
|
+
console.warn(`[${logTag}] Linked sound apply failed for ${target.engineId}:`, err);
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
if (appliedIds.size > 0) {
|
|
7660
|
+
setTracks(
|
|
7661
|
+
(prev) => prev.map(
|
|
7662
|
+
(t) => appliedIds.has(t.handle.id) ? { ...t, shuffleHistory: /* @__PURE__ */ new Set([...t.shuffleHistory, label]) } : t
|
|
7663
|
+
)
|
|
7664
|
+
);
|
|
7665
|
+
}
|
|
7666
|
+
if (failed.length > 0) {
|
|
7667
|
+
host.showToast(
|
|
7668
|
+
"warning",
|
|
7669
|
+
"Linked sound applied to some parts only",
|
|
7670
|
+
`Skipped: ${failed.join(", ")}`
|
|
7671
|
+
);
|
|
7672
|
+
}
|
|
7673
|
+
},
|
|
7674
|
+
[adapter, makeServices, soundHistory, host, logTag]
|
|
7675
|
+
);
|
|
7676
|
+
const broadcastInstrumentFromTrack = useCallback17(
|
|
7677
|
+
async (sourceTrackId, pluginId) => {
|
|
7678
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7679
|
+
if (!targetsOf) return;
|
|
7680
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7681
|
+
if (!source) return;
|
|
7682
|
+
let targets;
|
|
7683
|
+
try {
|
|
7684
|
+
targets = await targetsOf(source, makeServices());
|
|
7685
|
+
} catch (err) {
|
|
7686
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7687
|
+
return;
|
|
7688
|
+
}
|
|
7689
|
+
const others = (targets ?? []).filter((t) => t.engineId !== sourceTrackId);
|
|
7690
|
+
if (others.length === 0) return;
|
|
7691
|
+
const failed = [];
|
|
7692
|
+
for (const target of others) {
|
|
7693
|
+
try {
|
|
7694
|
+
await host.setTrackInstrument(target.engineId, pluginId);
|
|
7695
|
+
const descriptor = await host.getTrackInstrument(target.engineId);
|
|
7696
|
+
setTracks(
|
|
7697
|
+
(prev) => prev.map(
|
|
7698
|
+
(t) => t.handle.id === target.engineId ? {
|
|
7699
|
+
...t,
|
|
7700
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
7701
|
+
instrumentName: descriptor?.name ?? null,
|
|
7702
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
7703
|
+
} : t
|
|
7704
|
+
)
|
|
7705
|
+
);
|
|
7706
|
+
} catch (err) {
|
|
7707
|
+
failed.push(target.label ?? target.engineId);
|
|
7708
|
+
console.warn(`[${logTag}] Linked instrument apply failed for ${target.engineId}:`, err);
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7711
|
+
if (failed.length > 0) {
|
|
7712
|
+
host.showToast(
|
|
7713
|
+
"warning",
|
|
7714
|
+
"Instrument applied to some parts only",
|
|
7715
|
+
`Skipped: ${failed.join(", ")}`
|
|
7716
|
+
);
|
|
7717
|
+
}
|
|
7718
|
+
},
|
|
7719
|
+
[adapter, makeServices, host, logTag]
|
|
7720
|
+
);
|
|
7721
|
+
const handleSyncSoundToGroup = useCallback17(
|
|
7722
|
+
async (trackId) => {
|
|
7723
|
+
const track = tracksRef.current.find((t) => t.handle.id === trackId);
|
|
7724
|
+
if (!track) return;
|
|
7725
|
+
let cap = null;
|
|
7726
|
+
try {
|
|
7727
|
+
cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
7728
|
+
} catch {
|
|
7729
|
+
cap = null;
|
|
7730
|
+
}
|
|
7731
|
+
if (!cap) {
|
|
7732
|
+
host.showToast("warning", "Nothing to sync", "This track has no sound to copy yet.");
|
|
7733
|
+
return;
|
|
7734
|
+
}
|
|
7735
|
+
const hist = soundHistory.list(trackId);
|
|
7736
|
+
const label = (hist.cursor >= 0 ? hist.entries[hist.cursor]?.label : void 0) ?? track.instrumentName ?? "Current sound";
|
|
7737
|
+
await broadcastSoundFromTrack(trackId, cap.descriptor, label);
|
|
7738
|
+
},
|
|
7739
|
+
[adapter, soundHistory, host, broadcastSoundFromTrack]
|
|
7740
|
+
);
|
|
7741
|
+
const handleRestoreSound = useCallback17(
|
|
7742
|
+
async (trackId, index) => {
|
|
7743
|
+
const entry = soundHistory.list(trackId).entries[index];
|
|
7744
|
+
const moved = await soundHistory.restoreTo(trackId, index);
|
|
7745
|
+
if (moved && entry) {
|
|
7746
|
+
await broadcastSoundFromTrack(trackId, entry.descriptor, entry.label);
|
|
7747
|
+
}
|
|
7748
|
+
},
|
|
7749
|
+
[soundHistory, broadcastSoundFromTrack]
|
|
7750
|
+
);
|
|
7751
|
+
const handleSoundImportPick = useCallback17(
|
|
7752
|
+
async (sel) => {
|
|
7753
|
+
const target = soundImportTarget;
|
|
7754
|
+
if (!target || !host.getTrackSound) {
|
|
7755
|
+
setSoundImportTarget(null);
|
|
7756
|
+
return;
|
|
7757
|
+
}
|
|
7758
|
+
const noun = adapter.sound.importNoun;
|
|
7759
|
+
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
7760
|
+
try {
|
|
7761
|
+
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
7762
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
7763
|
+
host.showToast(
|
|
7764
|
+
"error",
|
|
7765
|
+
`No ${noun} to import`,
|
|
7766
|
+
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
7767
|
+
);
|
|
7768
|
+
return;
|
|
7769
|
+
}
|
|
7770
|
+
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
7771
|
+
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
7772
|
+
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
7773
|
+
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
7774
|
+
await broadcastSoundFromTrack(target.handle.id, descriptor, snap.label);
|
|
7775
|
+
} catch (err) {
|
|
7776
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
7777
|
+
} finally {
|
|
7778
|
+
setSoundImportTarget(null);
|
|
7779
|
+
}
|
|
7780
|
+
},
|
|
7781
|
+
[soundImportTarget, host, adapter, identity.familyKey, soundHistory, broadcastSoundFromTrack]
|
|
7782
|
+
);
|
|
7577
7783
|
const handleGenerate = useCallback17(
|
|
7578
7784
|
async (trackId) => {
|
|
7579
7785
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
@@ -7692,7 +7898,10 @@ function useGeneratorPanelCore({
|
|
|
7692
7898
|
);
|
|
7693
7899
|
try {
|
|
7694
7900
|
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
7695
|
-
if (cap)
|
|
7901
|
+
if (cap) {
|
|
7902
|
+
soundHistory.record(trackId, cap.descriptor, result.appliedName);
|
|
7903
|
+
await broadcastSoundFromTrack(trackId, cap.descriptor, result.appliedName);
|
|
7904
|
+
}
|
|
7696
7905
|
} catch {
|
|
7697
7906
|
}
|
|
7698
7907
|
console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);
|
|
@@ -7701,7 +7910,7 @@ function useGeneratorPanelCore({
|
|
|
7701
7910
|
host.showToast("error", "Shuffle failed", msg);
|
|
7702
7911
|
}
|
|
7703
7912
|
},
|
|
7704
|
-
[host, adapter, tracks, soundHistory, logTag]
|
|
7913
|
+
[host, adapter, tracks, soundHistory, logTag, broadcastSoundFromTrack]
|
|
7705
7914
|
);
|
|
7706
7915
|
const handleCopy = useCallback17(
|
|
7707
7916
|
async (trackId) => {
|
|
@@ -7904,6 +8113,7 @@ function useGeneratorPanelCore({
|
|
|
7904
8113
|
} : t
|
|
7905
8114
|
)
|
|
7906
8115
|
);
|
|
8116
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
7907
8117
|
} catch (err) {
|
|
7908
8118
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
7909
8119
|
host.showToast("error", "Instrument load failed", msg);
|
|
@@ -7926,6 +8136,7 @@ function useGeneratorPanelCore({
|
|
|
7926
8136
|
} : t
|
|
7927
8137
|
)
|
|
7928
8138
|
);
|
|
8139
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
7929
8140
|
} catch (err) {
|
|
7930
8141
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
7931
8142
|
console.error(`[${logTag}] Failed to set instrument:`, err);
|
|
@@ -7935,7 +8146,7 @@ function useGeneratorPanelCore({
|
|
|
7935
8146
|
);
|
|
7936
8147
|
}
|
|
7937
8148
|
},
|
|
7938
|
-
[host, identity.defaultInstrumentPluginId, logTag]
|
|
8149
|
+
[host, identity.defaultInstrumentPluginId, logTag, broadcastInstrumentFromTrack]
|
|
7939
8150
|
);
|
|
7940
8151
|
const handleShowEditor = useCallback17(
|
|
7941
8152
|
async (trackId) => {
|
|
@@ -8068,6 +8279,9 @@ function useGeneratorPanelCore({
|
|
|
8068
8279
|
shuffle: (trackId) => {
|
|
8069
8280
|
void handleShuffle(trackId);
|
|
8070
8281
|
},
|
|
8282
|
+
syncSoundToGroup: (trackId) => {
|
|
8283
|
+
void handleSyncSoundToGroup(trackId);
|
|
8284
|
+
},
|
|
8071
8285
|
copy: (trackId) => {
|
|
8072
8286
|
void handleCopy(trackId);
|
|
8073
8287
|
},
|
|
@@ -8088,6 +8302,7 @@ function useGeneratorPanelCore({
|
|
|
8088
8302
|
handlePromptChange,
|
|
8089
8303
|
handleGenerate,
|
|
8090
8304
|
handleShuffle,
|
|
8305
|
+
handleSyncSoundToGroup,
|
|
8091
8306
|
handleCopy,
|
|
8092
8307
|
handleDeleteTrack,
|
|
8093
8308
|
handleMuteToggle,
|
|
@@ -8145,6 +8360,8 @@ function useGeneratorPanelCore({
|
|
|
8145
8360
|
handlers,
|
|
8146
8361
|
handleGenerate,
|
|
8147
8362
|
handleShuffle,
|
|
8363
|
+
handleRestoreSound,
|
|
8364
|
+
handleSyncSoundToGroup,
|
|
8148
8365
|
handleAddTrack,
|
|
8149
8366
|
handleDeleteTrack,
|
|
8150
8367
|
handleExportMidi,
|
|
@@ -8175,8 +8392,41 @@ function useGeneratorPanelCore({
|
|
|
8175
8392
|
}
|
|
8176
8393
|
|
|
8177
8394
|
// src/panel-core/GeneratorPanelShell.tsx
|
|
8178
|
-
import
|
|
8179
|
-
import { Fragment as Fragment7, jsx as
|
|
8395
|
+
import { useCallback as useCallback18, useEffect as useEffect20, useState as useState25 } from "react";
|
|
8396
|
+
import { Fragment as Fragment7, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
8397
|
+
var GROUP_UI_KEY = "groupUi";
|
|
8398
|
+
function CollapsibleGroup({
|
|
8399
|
+
ext,
|
|
8400
|
+
group,
|
|
8401
|
+
groupCtx
|
|
8402
|
+
}) {
|
|
8403
|
+
const { host, activeSceneId, trackDataKey: trackDataKey2 } = groupCtx.services;
|
|
8404
|
+
const uiKey = trackDataKey2(group.groupId, GROUP_UI_KEY);
|
|
8405
|
+
const [collapsed, setCollapsed] = useState25(false);
|
|
8406
|
+
useEffect20(() => {
|
|
8407
|
+
let cancelled = false;
|
|
8408
|
+
if (!activeSceneId) return void 0;
|
|
8409
|
+
void host.getSceneData(activeSceneId, uiKey).then((raw) => {
|
|
8410
|
+
if (cancelled || !raw || typeof raw !== "object") return;
|
|
8411
|
+
setCollapsed(raw.collapsed === true);
|
|
8412
|
+
}).catch(() => {
|
|
8413
|
+
});
|
|
8414
|
+
return () => {
|
|
8415
|
+
cancelled = true;
|
|
8416
|
+
};
|
|
8417
|
+
}, [host, activeSceneId, uiKey]);
|
|
8418
|
+
const onToggleCollapse = useCallback18(() => {
|
|
8419
|
+
setCollapsed((prev) => {
|
|
8420
|
+
const next = !prev;
|
|
8421
|
+
if (activeSceneId) {
|
|
8422
|
+
void host.setSceneData(activeSceneId, uiKey, { collapsed: next }).catch(() => {
|
|
8423
|
+
});
|
|
8424
|
+
}
|
|
8425
|
+
return next;
|
|
8426
|
+
});
|
|
8427
|
+
}, [host, activeSceneId, uiKey]);
|
|
8428
|
+
return /* @__PURE__ */ jsx29(Fragment7, { children: ext.renderGroup(group, { ...groupCtx, collapsed, onToggleCollapse }) });
|
|
8429
|
+
}
|
|
8180
8430
|
function GeneratorPanelShell({ core, slots }) {
|
|
8181
8431
|
const {
|
|
8182
8432
|
ui,
|
|
@@ -8199,6 +8449,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8199
8449
|
soundImportTarget,
|
|
8200
8450
|
setSoundImportTarget,
|
|
8201
8451
|
handleSoundImportPick,
|
|
8452
|
+
handleRestoreSound,
|
|
8202
8453
|
handlePortTrack,
|
|
8203
8454
|
transition,
|
|
8204
8455
|
crossfadePairsMeta,
|
|
@@ -8294,7 +8545,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8294
8545
|
soundHistory: soundHistory.list(id).entries,
|
|
8295
8546
|
soundHistoryCursor: soundHistory.list(id).cursor,
|
|
8296
8547
|
onRestoreSound: (i) => {
|
|
8297
|
-
void
|
|
8548
|
+
void handleRestoreSound(id, i);
|
|
8298
8549
|
},
|
|
8299
8550
|
onToggleFavorite: (i) => soundHistory.toggleFavorite(id, i),
|
|
8300
8551
|
...importSoundProps,
|
|
@@ -8325,6 +8576,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8325
8576
|
handleBackToInstruments,
|
|
8326
8577
|
setSoundImportTarget,
|
|
8327
8578
|
soundHistory,
|
|
8579
|
+
handleRestoreSound,
|
|
8328
8580
|
handleFxToggle,
|
|
8329
8581
|
handleFxPresetChange,
|
|
8330
8582
|
handleFxDryWetChange,
|
|
@@ -8332,12 +8584,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8332
8584
|
]
|
|
8333
8585
|
);
|
|
8334
8586
|
if (!activeSceneId) {
|
|
8335
|
-
return /* @__PURE__ */
|
|
8587
|
+
return /* @__PURE__ */ jsx29(
|
|
8336
8588
|
"div",
|
|
8337
8589
|
{
|
|
8338
8590
|
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
8339
8591
|
className: "flex items-center justify-center py-8",
|
|
8340
|
-
children: /* @__PURE__ */
|
|
8592
|
+
children: /* @__PURE__ */ jsx29(
|
|
8341
8593
|
"button",
|
|
8342
8594
|
{
|
|
8343
8595
|
onClick: () => onSelectScene?.(),
|
|
@@ -8349,12 +8601,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8349
8601
|
);
|
|
8350
8602
|
}
|
|
8351
8603
|
if (!sceneContext?.hasContract) {
|
|
8352
|
-
return /* @__PURE__ */
|
|
8604
|
+
return /* @__PURE__ */ jsx29(
|
|
8353
8605
|
"div",
|
|
8354
8606
|
{
|
|
8355
8607
|
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
8356
8608
|
className: "flex items-center justify-center py-8",
|
|
8357
|
-
children: /* @__PURE__ */
|
|
8609
|
+
children: /* @__PURE__ */ jsx29(
|
|
8358
8610
|
"button",
|
|
8359
8611
|
{
|
|
8360
8612
|
onClick: () => onOpenContract?.(),
|
|
@@ -8366,7 +8618,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8366
8618
|
);
|
|
8367
8619
|
}
|
|
8368
8620
|
if (features.bulkComposePlaceholders && isComposing) {
|
|
8369
|
-
return /* @__PURE__ */
|
|
8621
|
+
return /* @__PURE__ */ jsx29("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ jsx29(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
8370
8622
|
}
|
|
8371
8623
|
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
8372
8624
|
if (activePlaceholders.length > 0) {
|
|
@@ -8377,18 +8629,18 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8377
8629
|
tracksByDbId.set(t.handle.id, t);
|
|
8378
8630
|
}
|
|
8379
8631
|
}
|
|
8380
|
-
return /* @__PURE__ */
|
|
8632
|
+
return /* @__PURE__ */ jsx29("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
8381
8633
|
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
8382
8634
|
if (loadedTrack) {
|
|
8383
|
-
return /* @__PURE__ */
|
|
8635
|
+
return /* @__PURE__ */ jsx29(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
8384
8636
|
}
|
|
8385
|
-
return /* @__PURE__ */
|
|
8637
|
+
return /* @__PURE__ */ jsx29(
|
|
8386
8638
|
"div",
|
|
8387
8639
|
{
|
|
8388
8640
|
"data-testid": "bulk-placeholder-track",
|
|
8389
8641
|
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
8390
8642
|
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
8391
|
-
children: /* @__PURE__ */
|
|
8643
|
+
children: /* @__PURE__ */ jsx29(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
8392
8644
|
},
|
|
8393
8645
|
ph.id
|
|
8394
8646
|
);
|
|
@@ -8400,13 +8652,13 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8400
8652
|
supportsMeters,
|
|
8401
8653
|
levels: supportsMeters ? trackLevels : void 0,
|
|
8402
8654
|
handlers,
|
|
8403
|
-
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */
|
|
8655
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ jsx29(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
8404
8656
|
setGroupMute,
|
|
8405
8657
|
setGroupSolo,
|
|
8406
8658
|
deleteGroup
|
|
8407
8659
|
};
|
|
8408
8660
|
return /* @__PURE__ */ jsxs22("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
8409
|
-
features.importTracks && host.listImportableTracks && /* @__PURE__ */
|
|
8661
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ jsx29(
|
|
8410
8662
|
ImportTrackModal,
|
|
8411
8663
|
{
|
|
8412
8664
|
host,
|
|
@@ -8419,7 +8671,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8419
8671
|
testIdPrefix: `${identity.familyKey}-import`
|
|
8420
8672
|
}
|
|
8421
8673
|
),
|
|
8422
|
-
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */
|
|
8674
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ jsx29(
|
|
8423
8675
|
ImportTrackModal,
|
|
8424
8676
|
{
|
|
8425
8677
|
host,
|
|
@@ -8434,7 +8686,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8434
8686
|
}
|
|
8435
8687
|
),
|
|
8436
8688
|
slots?.modals,
|
|
8437
|
-
canCrossfade && xfFromId && xfToId && /* @__PURE__ */
|
|
8689
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ jsx29("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ jsx29(
|
|
8438
8690
|
TransitionDesigner,
|
|
8439
8691
|
{
|
|
8440
8692
|
host,
|
|
@@ -8453,8 +8705,8 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8453
8705
|
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
8454
8706
|
}
|
|
8455
8707
|
) }),
|
|
8456
|
-
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */
|
|
8457
|
-
panelBus.supported && panelBus.bus && /* @__PURE__ */
|
|
8708
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ jsx29("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ jsxs22(Fragment7, { children: [
|
|
8709
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ jsx29(
|
|
8458
8710
|
PanelMasterStrip,
|
|
8459
8711
|
{
|
|
8460
8712
|
bus: panelBus.bus,
|
|
@@ -8475,7 +8727,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8475
8727
|
}
|
|
8476
8728
|
),
|
|
8477
8729
|
slots?.beforeRows,
|
|
8478
|
-
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */
|
|
8730
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ jsx29(
|
|
8479
8731
|
CrossfadeTrackRow,
|
|
8480
8732
|
{
|
|
8481
8733
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8512,7 +8764,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8512
8764
|
},
|
|
8513
8765
|
pair.groupId
|
|
8514
8766
|
)),
|
|
8515
|
-
resolvedSingleFades.map((fade) => /* @__PURE__ */
|
|
8767
|
+
resolvedSingleFades.map((fade) => /* @__PURE__ */ jsx29(
|
|
8516
8768
|
FadeTrackRow,
|
|
8517
8769
|
{
|
|
8518
8770
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8537,7 +8789,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8537
8789
|
},
|
|
8538
8790
|
fade.dbId
|
|
8539
8791
|
)),
|
|
8540
|
-
resolvedGroupFades.map((group) => /* @__PURE__ */
|
|
8792
|
+
resolvedGroupFades.map((group) => /* @__PURE__ */ jsx29(
|
|
8541
8793
|
GroupFadeTrackRow,
|
|
8542
8794
|
{
|
|
8543
8795
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8573,20 +8825,28 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8573
8825
|
group.groupId
|
|
8574
8826
|
)),
|
|
8575
8827
|
(adapter.groupExtensions ?? []).flatMap(
|
|
8576
|
-
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */
|
|
8828
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ jsx29(
|
|
8829
|
+
CollapsibleGroup,
|
|
8830
|
+
{
|
|
8831
|
+
ext,
|
|
8832
|
+
group,
|
|
8833
|
+
groupCtx
|
|
8834
|
+
},
|
|
8835
|
+
`${ext.metaKey}:${group.groupId}`
|
|
8836
|
+
))
|
|
8577
8837
|
),
|
|
8578
8838
|
tracks.map((track, index) => {
|
|
8579
8839
|
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
8580
8840
|
return null;
|
|
8581
8841
|
}
|
|
8582
|
-
return /* @__PURE__ */
|
|
8842
|
+
return /* @__PURE__ */ jsx29(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
8583
8843
|
}),
|
|
8584
8844
|
slots?.afterRows
|
|
8585
8845
|
] })),
|
|
8586
8846
|
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
8587
8847
|
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
8588
8848
|
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
8589
|
-
return /* @__PURE__ */
|
|
8849
|
+
return /* @__PURE__ */ jsx29("div", { className: "pt-2", children: /* @__PURE__ */ jsx29(
|
|
8590
8850
|
"button",
|
|
8591
8851
|
{
|
|
8592
8852
|
"data-testid": "export-midi-tracks-button",
|
|
@@ -8641,6 +8901,15 @@ function createSurgeSoundAdapter(host, overrides = {}) {
|
|
|
8641
8901
|
});
|
|
8642
8902
|
return snap.label;
|
|
8643
8903
|
},
|
|
8904
|
+
persistDescriptor: async (trackId, descriptor, label) => {
|
|
8905
|
+
const { state, stateType } = descriptor;
|
|
8906
|
+
await host.persistTrackPresetState?.(trackId, {
|
|
8907
|
+
state,
|
|
8908
|
+
stateType: stateType ?? "valuetree",
|
|
8909
|
+
name: label
|
|
8910
|
+
}).catch(() => {
|
|
8911
|
+
});
|
|
8912
|
+
},
|
|
8644
8913
|
descriptorFromSnapshot: (snap) => {
|
|
8645
8914
|
const preset = snap;
|
|
8646
8915
|
return { state: preset.state, stateType: preset.stateType };
|
|
@@ -8725,18 +8994,183 @@ var SUB = {
|
|
|
8725
8994
|
rootOnly: true,
|
|
8726
8995
|
monoPreference: "low"
|
|
8727
8996
|
};
|
|
8997
|
+
var HORN_MAX_NOTES_PER_BAR = 12;
|
|
8998
|
+
var HORN_FOLLOW_PALETTE = "EXACTLY the lead voice's rhythm \u2014 every attack lands together with voice 1";
|
|
8999
|
+
var HORN_LEAD = {
|
|
9000
|
+
label: "lead trumpet",
|
|
9001
|
+
role: "brass",
|
|
9002
|
+
registerLow: 60,
|
|
9003
|
+
// C4
|
|
9004
|
+
registerHigh: 84,
|
|
9005
|
+
// C6
|
|
9006
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9007
|
+
rhythmPalette: "tight funk syncopation \u2014 8ths and 16ths, staccato punches, off-beat anticipations",
|
|
9008
|
+
harmonicDiscipline: "the section's melodic top \u2014 chord tones on every hit; quick chromatic approach tones only on pickups",
|
|
9009
|
+
monoPreference: "high"
|
|
9010
|
+
};
|
|
9011
|
+
var HORN_TRUMPET_2 = {
|
|
9012
|
+
label: "second trumpet",
|
|
9013
|
+
role: "brass",
|
|
9014
|
+
registerLow: 55,
|
|
9015
|
+
// G3
|
|
9016
|
+
registerHigh: 79,
|
|
9017
|
+
// G5
|
|
9018
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9019
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9020
|
+
harmonicDiscipline: "nearest chord tone directly below voice 1 \u2014 tight close voicing",
|
|
9021
|
+
monoPreference: "high"
|
|
9022
|
+
};
|
|
9023
|
+
var HORN_ALTO = {
|
|
9024
|
+
label: "alto sax",
|
|
9025
|
+
role: "brass",
|
|
9026
|
+
registerLow: 53,
|
|
9027
|
+
// F3
|
|
9028
|
+
registerHigh: 77,
|
|
9029
|
+
// F5
|
|
9030
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9031
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9032
|
+
harmonicDiscipline: "chord tone below the trumpets \u2014 keep the upper stack inside one octave",
|
|
9033
|
+
monoPreference: "high"
|
|
9034
|
+
};
|
|
9035
|
+
var HORN_TENOR = {
|
|
9036
|
+
label: "tenor sax",
|
|
9037
|
+
role: "brass",
|
|
9038
|
+
registerLow: 46,
|
|
9039
|
+
// Bb2
|
|
9040
|
+
registerHigh: 72,
|
|
9041
|
+
// C5
|
|
9042
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9043
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9044
|
+
harmonicDiscipline: "chord tone under the upper horns \u2014 complete the chord",
|
|
9045
|
+
monoPreference: "low"
|
|
9046
|
+
};
|
|
9047
|
+
var HORN_TROMBONE = {
|
|
9048
|
+
label: "trombone",
|
|
9049
|
+
role: "brass",
|
|
9050
|
+
registerLow: 43,
|
|
9051
|
+
// G2
|
|
9052
|
+
registerHigh: 67,
|
|
9053
|
+
// G4
|
|
9054
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9055
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9056
|
+
harmonicDiscipline: "chord tone or root below the saxes \u2014 weight in the middle-low stack",
|
|
9057
|
+
monoPreference: "low"
|
|
9058
|
+
};
|
|
9059
|
+
var HORN_BARI = {
|
|
9060
|
+
label: "baritone sax",
|
|
9061
|
+
role: "brass",
|
|
9062
|
+
registerLow: 36,
|
|
9063
|
+
// C2
|
|
9064
|
+
registerHigh: 60,
|
|
9065
|
+
// C4
|
|
9066
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9067
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9068
|
+
harmonicDiscipline: "chord roots and lower chord tones \u2014 the section's bottom anchor",
|
|
9069
|
+
monoPreference: "low"
|
|
9070
|
+
};
|
|
9071
|
+
var WIND_FLUTE = {
|
|
9072
|
+
label: "flute",
|
|
9073
|
+
role: "winds",
|
|
9074
|
+
registerLow: 60,
|
|
9075
|
+
// C4
|
|
9076
|
+
registerHigh: 93,
|
|
9077
|
+
// A6
|
|
9078
|
+
maxNotesPerBar: 8,
|
|
9079
|
+
rhythmPalette: "8ths and 16ths; runs, turns and trills welcome",
|
|
9080
|
+
harmonicDiscipline: "freest voice \u2014 non-chord tones as passing/neighbor tones on weak beats, resolving by step",
|
|
9081
|
+
monoPreference: "high"
|
|
9082
|
+
};
|
|
9083
|
+
var WIND_FLUTE_2 = {
|
|
9084
|
+
label: "second flute",
|
|
9085
|
+
role: "winds",
|
|
9086
|
+
registerLow: 60,
|
|
9087
|
+
// C4
|
|
9088
|
+
registerHigh: 86,
|
|
9089
|
+
// D6
|
|
9090
|
+
maxNotesPerBar: 5,
|
|
9091
|
+
rhythmPalette: "quarters with occasional 8th-note motion",
|
|
9092
|
+
harmonicDiscipline: "chord tones; fill gaps the other inner voices leave",
|
|
9093
|
+
monoPreference: "high"
|
|
9094
|
+
};
|
|
9095
|
+
var WIND_OBOE = {
|
|
9096
|
+
label: "oboe",
|
|
9097
|
+
role: "winds",
|
|
9098
|
+
registerLow: 58,
|
|
9099
|
+
// Bb3
|
|
9100
|
+
registerHigh: 84,
|
|
9101
|
+
// C6
|
|
9102
|
+
maxNotesPerBar: 6,
|
|
9103
|
+
rhythmPalette: "8ths and quarters; move when the flute rests",
|
|
9104
|
+
harmonicDiscipline: "mostly chord tones; may imitate the flute's motifs a bar later",
|
|
9105
|
+
monoPreference: "high"
|
|
9106
|
+
};
|
|
9107
|
+
var WIND_CLARINET = {
|
|
9108
|
+
label: "clarinet",
|
|
9109
|
+
role: "winds",
|
|
9110
|
+
registerLow: 50,
|
|
9111
|
+
// D3
|
|
9112
|
+
registerHigh: 81,
|
|
9113
|
+
// A5
|
|
9114
|
+
maxNotesPerBar: 4,
|
|
9115
|
+
rhythmPalette: "quarters and halves",
|
|
9116
|
+
harmonicDiscipline: "chord tones with smooth stepwise motion between them",
|
|
9117
|
+
monoPreference: "high"
|
|
9118
|
+
};
|
|
9119
|
+
var WIND_FRENCH_HORN = {
|
|
9120
|
+
label: "french horn",
|
|
9121
|
+
role: "winds",
|
|
9122
|
+
registerLow: 41,
|
|
9123
|
+
// F2
|
|
9124
|
+
registerHigh: 72,
|
|
9125
|
+
// C5
|
|
9126
|
+
maxNotesPerBar: 4,
|
|
9127
|
+
rhythmPalette: "quarters and halves; long held tones welcome",
|
|
9128
|
+
harmonicDiscipline: "chord tones \u2014 the warm glue in the middle of the ensemble",
|
|
9129
|
+
monoPreference: "low"
|
|
9130
|
+
};
|
|
9131
|
+
var WIND_BASSOON = {
|
|
9132
|
+
label: "bassoon",
|
|
9133
|
+
role: "winds",
|
|
9134
|
+
registerLow: 34,
|
|
9135
|
+
// Bb1
|
|
9136
|
+
registerHigh: 62,
|
|
9137
|
+
// D4
|
|
9138
|
+
maxNotesPerBar: 3,
|
|
9139
|
+
rhythmPalette: "quarters and halves; brief walking figures at cadences",
|
|
9140
|
+
harmonicDiscipline: "roots and fifths emphasized; passing tones only between chord tones",
|
|
9141
|
+
monoPreference: "low"
|
|
9142
|
+
};
|
|
8728
9143
|
var ENSEMBLE_MIN_VOICES = 2;
|
|
8729
9144
|
var ENSEMBLE_MAX_VOICES = 6;
|
|
8730
|
-
var
|
|
9145
|
+
var STRINGS_SPEC_TABLES = {
|
|
8731
9146
|
2: [TOP, BASS],
|
|
8732
9147
|
3: [TOP, INNER, BASS],
|
|
8733
9148
|
4: [TOP, COUNTER, TENOR, BASS],
|
|
8734
9149
|
5: [TOP, COUNTER, INNER, TENOR, SUB],
|
|
8735
9150
|
6: [TOP, COUNTER, INNER_2, INNER, TENOR, SUB]
|
|
8736
9151
|
};
|
|
8737
|
-
|
|
9152
|
+
var HORNS_SPEC_TABLES = {
|
|
9153
|
+
2: [HORN_LEAD, HORN_TENOR],
|
|
9154
|
+
3: [HORN_LEAD, HORN_TENOR, HORN_BARI],
|
|
9155
|
+
4: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_BARI],
|
|
9156
|
+
5: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_TROMBONE, HORN_BARI],
|
|
9157
|
+
6: [HORN_LEAD, HORN_TRUMPET_2, HORN_ALTO, HORN_TENOR, HORN_TROMBONE, HORN_BARI]
|
|
9158
|
+
};
|
|
9159
|
+
var WINDS_SPEC_TABLES = {
|
|
9160
|
+
2: [WIND_FLUTE, WIND_BASSOON],
|
|
9161
|
+
3: [WIND_FLUTE, WIND_CLARINET, WIND_BASSOON],
|
|
9162
|
+
4: [WIND_FLUTE, WIND_OBOE, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9163
|
+
5: [WIND_FLUTE, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9164
|
+
6: [WIND_FLUTE, WIND_FLUTE_2, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON]
|
|
9165
|
+
};
|
|
9166
|
+
var SPEC_TABLES_BY_INSTRUMENTATION = {
|
|
9167
|
+
strings: STRINGS_SPEC_TABLES,
|
|
9168
|
+
horns: HORNS_SPEC_TABLES,
|
|
9169
|
+
winds: WINDS_SPEC_TABLES
|
|
9170
|
+
};
|
|
9171
|
+
function defaultVoiceSpecs(voiceCount, instrumentation = "strings") {
|
|
8738
9172
|
const n = Math.max(ENSEMBLE_MIN_VOICES, Math.min(ENSEMBLE_MAX_VOICES, Math.round(voiceCount)));
|
|
8739
|
-
return
|
|
9173
|
+
return SPEC_TABLES_BY_INSTRUMENTATION[instrumentation][n].map((spec, voiceIndex) => ({ ...spec, voiceIndex }));
|
|
8740
9174
|
}
|
|
8741
9175
|
|
|
8742
9176
|
// src/ensemble-core/enforce-voice.ts
|
|
@@ -8776,10 +9210,14 @@ function enforceVoice(rawNotes, spec, opts) {
|
|
|
8776
9210
|
repairs.push(`voice ${spec.voiceIndex}: dropped note outside the ${opts.bars}-bar clip (start ${n.startBeat})`);
|
|
8777
9211
|
continue;
|
|
8778
9212
|
}
|
|
8779
|
-
|
|
9213
|
+
let durationBeats = Math.max(
|
|
8780
9214
|
MIN_NOTE_DURATION_BEATS,
|
|
8781
9215
|
Math.min(n.durationBeats, clipEnd - n.startBeat)
|
|
8782
9216
|
);
|
|
9217
|
+
if (opts.maxNoteDurationBeats !== void 0 && durationBeats > opts.maxNoteDurationBeats) {
|
|
9218
|
+
durationBeats = Math.max(MIN_NOTE_DURATION_BEATS, opts.maxNoteDurationBeats);
|
|
9219
|
+
repairs.push(`voice ${spec.voiceIndex}: trimmed note at beat ${n.startBeat} to the style's ${opts.maxNoteDurationBeats}-beat stab ceiling`);
|
|
9220
|
+
}
|
|
8783
9221
|
notes.push({ ...n, durationBeats });
|
|
8784
9222
|
}
|
|
8785
9223
|
notes = notes.map((n) => {
|
|
@@ -8940,12 +9378,19 @@ function describeViolations(analysis, rules) {
|
|
|
8940
9378
|
if (pair.onsetIndependence < rules.minOnsetIndependence) {
|
|
8941
9379
|
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.`);
|
|
8942
9380
|
}
|
|
9381
|
+
if (rules.maxOnsetIndependence !== void 0 && pair.onsetIndependence > rules.maxOnsetIndependence) {
|
|
9382
|
+
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.`);
|
|
9383
|
+
}
|
|
8943
9384
|
}
|
|
8944
9385
|
return out;
|
|
8945
9386
|
}
|
|
8946
9387
|
|
|
8947
9388
|
// src/ensemble-core/styles.ts
|
|
8948
9389
|
var ENSEMBLE_STYLES = ["counterpoint", "chorale", "interlock"];
|
|
9390
|
+
var STAB_MAX_ONSET_INDEPENDENCE = 0.25;
|
|
9391
|
+
var RIFF_MAX_ONSET_INDEPENDENCE = 0.3;
|
|
9392
|
+
var UNISON_MAX_ONSET_INDEPENDENCE = 0.15;
|
|
9393
|
+
var STAB_MAX_NOTE_DURATION_BEATS = 1;
|
|
8949
9394
|
var STYLE_RULES = {
|
|
8950
9395
|
counterpoint: {
|
|
8951
9396
|
forbidParallelPerfects: true,
|
|
@@ -8964,9 +9409,55 @@ var STYLE_RULES = {
|
|
|
8964
9409
|
forbidVoiceCrossing: false,
|
|
8965
9410
|
minOnsetIndependence: 0.6,
|
|
8966
9411
|
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."
|
|
9412
|
+
},
|
|
9413
|
+
stabs: {
|
|
9414
|
+
forbidParallelPerfects: false,
|
|
9415
|
+
forbidVoiceCrossing: true,
|
|
9416
|
+
minOnsetIndependence: 0,
|
|
9417
|
+
maxOnsetIndependence: STAB_MAX_ONSET_INDEPENDENCE,
|
|
9418
|
+
maxNoteDurationBeats: STAB_MAX_NOTE_DURATION_BEATS,
|
|
9419
|
+
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.`
|
|
9420
|
+
},
|
|
9421
|
+
riffs: {
|
|
9422
|
+
forbidParallelPerfects: false,
|
|
9423
|
+
forbidVoiceCrossing: true,
|
|
9424
|
+
minOnsetIndependence: 0,
|
|
9425
|
+
maxOnsetIndependence: RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9426
|
+
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.'
|
|
9427
|
+
},
|
|
9428
|
+
unison: {
|
|
9429
|
+
forbidParallelPerfects: false,
|
|
9430
|
+
forbidVoiceCrossing: false,
|
|
9431
|
+
minOnsetIndependence: 0,
|
|
9432
|
+
maxOnsetIndependence: UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9433
|
+
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."
|
|
8967
9434
|
}
|
|
8968
9435
|
};
|
|
8969
9436
|
|
|
9437
|
+
// src/ensemble-core/instrumentation.ts
|
|
9438
|
+
var ENSEMBLE_INSTRUMENTATIONS = [
|
|
9439
|
+
"strings",
|
|
9440
|
+
"horns",
|
|
9441
|
+
"winds"
|
|
9442
|
+
];
|
|
9443
|
+
var STYLES_FOR_INSTRUMENTATION = {
|
|
9444
|
+
strings: ["counterpoint", "chorale", "interlock"],
|
|
9445
|
+
horns: ["stabs", "riffs", "unison"],
|
|
9446
|
+
winds: ["counterpoint", "chorale", "interlock"]
|
|
9447
|
+
};
|
|
9448
|
+
var DEFAULT_STYLE_FOR_INSTRUMENTATION = {
|
|
9449
|
+
strings: "counterpoint",
|
|
9450
|
+
horns: "stabs",
|
|
9451
|
+
winds: "chorale"
|
|
9452
|
+
};
|
|
9453
|
+
function normalizeInstrumentation(raw) {
|
|
9454
|
+
return ENSEMBLE_INSTRUMENTATIONS.includes(raw) ? raw : "strings";
|
|
9455
|
+
}
|
|
9456
|
+
function styleForInstrumentation(instrumentation, rawStyle) {
|
|
9457
|
+
const allowed = STYLES_FOR_INSTRUMENTATION[instrumentation];
|
|
9458
|
+
return allowed.includes(rawStyle ?? "") ? rawStyle : DEFAULT_STYLE_FOR_INSTRUMENTATION[instrumentation];
|
|
9459
|
+
}
|
|
9460
|
+
|
|
8970
9461
|
// src/ensemble-core/ensemble-schema.ts
|
|
8971
9462
|
var SUBMIT_ENSEMBLE_TOOL_NAME = "submit_ensemble";
|
|
8972
9463
|
function buildSubmitEnsembleParameters(voiceCount) {
|
|
@@ -9048,9 +9539,13 @@ function voiceContractLine(spec) {
|
|
|
9048
9539
|
const root = spec.rootOnly ? " ROOT PITCH CLASS ONLY (each bar's chord root)." : "";
|
|
9049
9540
|
return `- Voice ${spec.voiceIndex + 1} (${spec.label}): MIDI ${spec.registerLow}-${spec.registerHigh}, max ${spec.maxNotesPerBar} notes/bar, rhythm: ${spec.rhythmPalette}. ${spec.harmonicDiscipline}.${root}`;
|
|
9050
9541
|
}
|
|
9051
|
-
|
|
9542
|
+
var WOVEN_PERSONA = {
|
|
9543
|
+
strings: "You are an ensemble composer.",
|
|
9544
|
+
winds: "You are a wind-ensemble composer writing for chamber winds (flutes, oboe, clarinet, french horn, bassoon)."
|
|
9545
|
+
};
|
|
9546
|
+
function buildWovenPrompt(specs, style, instrumentation) {
|
|
9052
9547
|
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9053
|
-
return
|
|
9548
|
+
return `${WOVEN_PERSONA[instrumentation]} Compose ${specs.length} voices as ONE piece of music \u2014 not ${specs.length} independent parts.
|
|
9054
9549
|
|
|
9055
9550
|
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9056
9551
|
|
|
@@ -9067,6 +9562,28 @@ ${styleParagraph}
|
|
|
9067
9562
|
PER-VOICE CONTRACTS:
|
|
9068
9563
|
${specs.map(voiceContractLine).join("\n")}`;
|
|
9069
9564
|
}
|
|
9565
|
+
function buildSectionPrompt(specs, style) {
|
|
9566
|
+
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9567
|
+
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.
|
|
9568
|
+
|
|
9569
|
+
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9570
|
+
|
|
9571
|
+
THE SECTION RULES (most important):
|
|
9572
|
+
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.
|
|
9573
|
+
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.
|
|
9574
|
+
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.
|
|
9575
|
+
4. Follow the chord progression in the musical context exactly \u2014 every hit is voiced from the chord sounding at that beat.
|
|
9576
|
+
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.
|
|
9577
|
+
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).
|
|
9578
|
+
|
|
9579
|
+
${styleParagraph}
|
|
9580
|
+
|
|
9581
|
+
PER-VOICE CONTRACTS:
|
|
9582
|
+
${specs.map(voiceContractLine).join("\n")}`;
|
|
9583
|
+
}
|
|
9584
|
+
function buildEnsembleSystemPrompt(specs, style, instrumentation = "strings") {
|
|
9585
|
+
return instrumentation === "horns" ? buildSectionPrompt(specs, style) : buildWovenPrompt(specs, style, instrumentation);
|
|
9586
|
+
}
|
|
9070
9587
|
function buildViolationRetrySuffix(violations) {
|
|
9071
9588
|
if (violations.length === 0) return "";
|
|
9072
9589
|
return `
|
|
@@ -9241,10 +9758,12 @@ export {
|
|
|
9241
9758
|
DB_MIN,
|
|
9242
9759
|
DEFAULT_FX_CATEGORY_DETAIL,
|
|
9243
9760
|
DEFAULT_FX_DRY_WET,
|
|
9761
|
+
DEFAULT_STYLE_FOR_INSTRUMENTATION,
|
|
9244
9762
|
DRAG_DEAD_ZONE,
|
|
9245
9763
|
DownloadPackButton,
|
|
9246
9764
|
EMPTY_FX_DETAIL_STATE,
|
|
9247
9765
|
EMPTY_FX_STATE,
|
|
9766
|
+
ENSEMBLE_INSTRUMENTATIONS,
|
|
9248
9767
|
ENSEMBLE_MAX_VOICES,
|
|
9249
9768
|
ENSEMBLE_MIN_VOICES,
|
|
9250
9769
|
ENSEMBLE_STYLES,
|
|
@@ -9259,7 +9778,9 @@ export {
|
|
|
9259
9778
|
FxToggleBar,
|
|
9260
9779
|
GUTTER_W,
|
|
9261
9780
|
GeneratorPanelShell,
|
|
9781
|
+
GroupCollapseChevron,
|
|
9262
9782
|
GroupFadeTrackRow,
|
|
9783
|
+
HORN_MAX_NOTES_PER_BAR,
|
|
9263
9784
|
ImportTrackModal,
|
|
9264
9785
|
TrackDrawer as InstrumentDrawer,
|
|
9265
9786
|
LevelMeter,
|
|
@@ -9273,8 +9794,12 @@ export {
|
|
|
9273
9794
|
PianoRollEditor,
|
|
9274
9795
|
PluginError,
|
|
9275
9796
|
RESIZE_HANDLE_PX,
|
|
9797
|
+
RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9276
9798
|
ROW_HEIGHT,
|
|
9277
9799
|
SLIDER_UNITY,
|
|
9800
|
+
STAB_MAX_NOTE_DURATION_BEATS,
|
|
9801
|
+
STAB_MAX_ONSET_INDEPENDENCE,
|
|
9802
|
+
STYLES_FOR_INSTRUMENTATION,
|
|
9278
9803
|
STYLE_RULES,
|
|
9279
9804
|
SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
9280
9805
|
SamplePackCTACard,
|
|
@@ -9288,6 +9813,7 @@ export {
|
|
|
9288
9813
|
TrackMeterStrip,
|
|
9289
9814
|
TrackRow,
|
|
9290
9815
|
TransitionDesigner,
|
|
9816
|
+
UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9291
9817
|
VolumeSlider,
|
|
9292
9818
|
WaveformView,
|
|
9293
9819
|
analyzeEnsemble,
|
|
@@ -9322,6 +9848,7 @@ export {
|
|
|
9322
9848
|
moveItem,
|
|
9323
9849
|
nearestPitchWithPc,
|
|
9324
9850
|
newTrackState,
|
|
9851
|
+
normalizeInstrumentation,
|
|
9325
9852
|
normalizeSlots,
|
|
9326
9853
|
padPair,
|
|
9327
9854
|
padSlots,
|
|
@@ -9345,6 +9872,7 @@ export {
|
|
|
9345
9872
|
slotsEqual,
|
|
9346
9873
|
soundIdentity,
|
|
9347
9874
|
splitFadeEntries,
|
|
9875
|
+
styleForInstrumentation,
|
|
9348
9876
|
synthesizeCuePoints,
|
|
9349
9877
|
tokenizePrompt,
|
|
9350
9878
|
trackDataKey,
|