@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.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,7 @@ function TrackRow({
|
|
|
2514
2523
|
onToggleFavorite,
|
|
2515
2524
|
onImportSound,
|
|
2516
2525
|
importSoundLabel,
|
|
2526
|
+
linkedSoundHint,
|
|
2517
2527
|
editNotes,
|
|
2518
2528
|
onNotesChange,
|
|
2519
2529
|
editBars,
|
|
@@ -2679,15 +2689,26 @@ function TrackRow({
|
|
|
2679
2689
|
)
|
|
2680
2690
|
] }),
|
|
2681
2691
|
/* @__PURE__ */ jsxs9("div", { className: "flex gap-1 items-center", children: [
|
|
2682
|
-
onShuffle && /* @__PURE__ */
|
|
2692
|
+
onShuffle && /* @__PURE__ */ jsxs9(
|
|
2683
2693
|
"button",
|
|
2684
2694
|
{
|
|
2685
2695
|
"data-testid": "sdk-shuffle-button",
|
|
2686
2696
|
onClick: onShuffle,
|
|
2687
2697
|
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:
|
|
2698
|
+
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"}`,
|
|
2699
|
+
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",
|
|
2700
|
+
children: [
|
|
2701
|
+
"Shuffle",
|
|
2702
|
+
linkedSoundHint && /* @__PURE__ */ jsx13(
|
|
2703
|
+
"span",
|
|
2704
|
+
{
|
|
2705
|
+
"aria-hidden": true,
|
|
2706
|
+
"data-testid": "sdk-shuffle-linked-marker",
|
|
2707
|
+
className: "absolute -top-1 -right-1 text-[8px] leading-none",
|
|
2708
|
+
children: "\u{1F517}"
|
|
2709
|
+
}
|
|
2710
|
+
)
|
|
2711
|
+
]
|
|
2691
2712
|
}
|
|
2692
2713
|
),
|
|
2693
2714
|
freezeBadge && /* @__PURE__ */ jsx13(
|
|
@@ -2771,6 +2792,7 @@ function TrackRow({
|
|
|
2771
2792
|
onToggleFavorite,
|
|
2772
2793
|
onImportSound,
|
|
2773
2794
|
importSoundLabel,
|
|
2795
|
+
linkedSoundHint,
|
|
2774
2796
|
editNotes,
|
|
2775
2797
|
onNotesChange,
|
|
2776
2798
|
editBars,
|
|
@@ -4998,9 +5020,38 @@ function TransitionDesigner({
|
|
|
4998
5020
|
] });
|
|
4999
5021
|
}
|
|
5000
5022
|
|
|
5023
|
+
// src/components/GroupCollapseChevron.tsx
|
|
5024
|
+
import { ChevronDown as ChevronDown2 } from "lucide-react";
|
|
5025
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
5026
|
+
function GroupCollapseChevron({
|
|
5027
|
+
collapsed = false,
|
|
5028
|
+
onToggle,
|
|
5029
|
+
what = "group"
|
|
5030
|
+
}) {
|
|
5031
|
+
if (!onToggle) return null;
|
|
5032
|
+
return /* @__PURE__ */ jsx21(
|
|
5033
|
+
"button",
|
|
5034
|
+
{
|
|
5035
|
+
type: "button",
|
|
5036
|
+
"data-testid": "sdk-group-collapse",
|
|
5037
|
+
onClick: onToggle,
|
|
5038
|
+
"aria-expanded": !collapsed,
|
|
5039
|
+
title: collapsed ? `Expand ${what}` : `Collapse ${what}`,
|
|
5040
|
+
className: "shrink-0 px-0.5 py-0.5 rounded-sm text-sas-muted hover:text-sas-accent transition-colors",
|
|
5041
|
+
children: /* @__PURE__ */ jsx21(
|
|
5042
|
+
ChevronDown2,
|
|
5043
|
+
{
|
|
5044
|
+
className: `w-3 h-3 transition-transform ${collapsed ? "-rotate-90" : ""}`,
|
|
5045
|
+
strokeWidth: 2.5
|
|
5046
|
+
}
|
|
5047
|
+
)
|
|
5048
|
+
}
|
|
5049
|
+
);
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5001
5052
|
// src/components/PanelMasterStrip.tsx
|
|
5002
5053
|
import { useMemo as useMemo7, useState as useState15 } from "react";
|
|
5003
|
-
import { jsx as
|
|
5054
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
5004
5055
|
function PanelMasterStrip({
|
|
5005
5056
|
bus,
|
|
5006
5057
|
levels = null,
|
|
@@ -5034,7 +5085,7 @@ function PanelMasterStrip({
|
|
|
5034
5085
|
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
5086
|
children: [
|
|
5036
5087
|
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
|
|
5037
|
-
/* @__PURE__ */
|
|
5088
|
+
/* @__PURE__ */ jsx22(
|
|
5038
5089
|
"span",
|
|
5039
5090
|
{
|
|
5040
5091
|
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
@@ -5043,7 +5094,7 @@ function PanelMasterStrip({
|
|
|
5043
5094
|
}
|
|
5044
5095
|
),
|
|
5045
5096
|
/* @__PURE__ */ jsxs17("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
|
|
5046
|
-
/* @__PURE__ */
|
|
5097
|
+
/* @__PURE__ */ jsx22(
|
|
5047
5098
|
VolumeSlider,
|
|
5048
5099
|
{
|
|
5049
5100
|
value: dbToSlider(bus.volume),
|
|
@@ -5052,7 +5103,7 @@ function PanelMasterStrip({
|
|
|
5052
5103
|
}
|
|
5053
5104
|
),
|
|
5054
5105
|
/* @__PURE__ */ jsxs17("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
|
|
5055
|
-
/* @__PURE__ */
|
|
5106
|
+
/* @__PURE__ */ jsx22(
|
|
5056
5107
|
LevelMeter,
|
|
5057
5108
|
{
|
|
5058
5109
|
peakDb: levels?.leftDb ?? -120,
|
|
@@ -5062,7 +5113,7 @@ function PanelMasterStrip({
|
|
|
5062
5113
|
"data-testid": "bus-meter-left"
|
|
5063
5114
|
}
|
|
5064
5115
|
),
|
|
5065
|
-
/* @__PURE__ */
|
|
5116
|
+
/* @__PURE__ */ jsx22(
|
|
5066
5117
|
LevelMeter,
|
|
5067
5118
|
{
|
|
5068
5119
|
peakDb: levels?.rightDb ?? -120,
|
|
@@ -5073,7 +5124,7 @@ function PanelMasterStrip({
|
|
|
5073
5124
|
)
|
|
5074
5125
|
] })
|
|
5075
5126
|
] }),
|
|
5076
|
-
/* @__PURE__ */
|
|
5127
|
+
/* @__PURE__ */ jsx22(
|
|
5077
5128
|
"button",
|
|
5078
5129
|
{
|
|
5079
5130
|
"data-testid": "bus-mute-button",
|
|
@@ -5084,7 +5135,7 @@ function PanelMasterStrip({
|
|
|
5084
5135
|
children: "M"
|
|
5085
5136
|
}
|
|
5086
5137
|
),
|
|
5087
|
-
/* @__PURE__ */
|
|
5138
|
+
/* @__PURE__ */ jsx22(
|
|
5088
5139
|
"button",
|
|
5089
5140
|
{
|
|
5090
5141
|
"data-testid": "bus-solo-button",
|
|
@@ -5095,14 +5146,14 @@ function PanelMasterStrip({
|
|
|
5095
5146
|
children: "S"
|
|
5096
5147
|
}
|
|
5097
5148
|
),
|
|
5098
|
-
/* @__PURE__ */
|
|
5149
|
+
/* @__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
5150
|
"span",
|
|
5100
5151
|
{
|
|
5101
5152
|
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
5102
5153
|
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
5154
|
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
5104
5155
|
children: [
|
|
5105
|
-
/* @__PURE__ */
|
|
5156
|
+
/* @__PURE__ */ jsx22(
|
|
5106
5157
|
"button",
|
|
5107
5158
|
{
|
|
5108
5159
|
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
@@ -5113,7 +5164,7 @@ function PanelMasterStrip({
|
|
|
5113
5164
|
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
5114
5165
|
}
|
|
5115
5166
|
),
|
|
5116
|
-
onShowFxEditor ? /* @__PURE__ */
|
|
5167
|
+
onShowFxEditor ? /* @__PURE__ */ jsx22(
|
|
5117
5168
|
"button",
|
|
5118
5169
|
{
|
|
5119
5170
|
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
@@ -5123,8 +5174,8 @@ function PanelMasterStrip({
|
|
|
5123
5174
|
title: `Open ${fx.name} editor`,
|
|
5124
5175
|
children: fx.name
|
|
5125
5176
|
}
|
|
5126
|
-
) : /* @__PURE__ */
|
|
5127
|
-
/* @__PURE__ */
|
|
5177
|
+
) : /* @__PURE__ */ jsx22("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
5178
|
+
/* @__PURE__ */ jsx22(
|
|
5128
5179
|
"button",
|
|
5129
5180
|
{
|
|
5130
5181
|
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
@@ -5139,7 +5190,7 @@ function PanelMasterStrip({
|
|
|
5139
5190
|
},
|
|
5140
5191
|
`${fx.index}:${fx.pluginId}`
|
|
5141
5192
|
)) }),
|
|
5142
|
-
/* @__PURE__ */
|
|
5193
|
+
/* @__PURE__ */ jsx22(
|
|
5143
5194
|
"button",
|
|
5144
5195
|
{
|
|
5145
5196
|
"data-testid": "bus-fx-add-button",
|
|
@@ -5153,7 +5204,7 @@ function PanelMasterStrip({
|
|
|
5153
5204
|
] }),
|
|
5154
5205
|
fxPickerOpen && /* @__PURE__ */ jsxs17("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
5155
5206
|
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
|
|
5156
|
-
/* @__PURE__ */
|
|
5207
|
+
/* @__PURE__ */ jsx22(
|
|
5157
5208
|
"input",
|
|
5158
5209
|
{
|
|
5159
5210
|
type: "text",
|
|
@@ -5163,7 +5214,7 @@ function PanelMasterStrip({
|
|
|
5163
5214
|
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
5164
5215
|
}
|
|
5165
5216
|
),
|
|
5166
|
-
onRefreshFx && /* @__PURE__ */
|
|
5217
|
+
onRefreshFx && /* @__PURE__ */ jsx22(
|
|
5167
5218
|
"button",
|
|
5168
5219
|
{
|
|
5169
5220
|
onClick: () => onRefreshFx(),
|
|
@@ -5174,7 +5225,7 @@ function PanelMasterStrip({
|
|
|
5174
5225
|
}
|
|
5175
5226
|
)
|
|
5176
5227
|
] }),
|
|
5177
|
-
fxLoading && availableFx.length === 0 ? /* @__PURE__ */
|
|
5228
|
+
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
5229
|
filtered.map((fx) => /* @__PURE__ */ jsxs17(
|
|
5179
5230
|
"button",
|
|
5180
5231
|
{
|
|
@@ -5183,13 +5234,13 @@ function PanelMasterStrip({
|
|
|
5183
5234
|
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
5235
|
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
5185
5236
|
children: [
|
|
5186
|
-
/* @__PURE__ */
|
|
5187
|
-
/* @__PURE__ */
|
|
5237
|
+
/* @__PURE__ */ jsx22("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
5238
|
+
/* @__PURE__ */ jsx22("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
5188
5239
|
]
|
|
5189
5240
|
},
|
|
5190
5241
|
fx.pluginId
|
|
5191
5242
|
)),
|
|
5192
|
-
filtered.length === 0 && /* @__PURE__ */
|
|
5243
|
+
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
5244
|
] })
|
|
5194
5245
|
] })
|
|
5195
5246
|
]
|
|
@@ -5316,7 +5367,7 @@ function usePanelBus(host, activeSceneId) {
|
|
|
5316
5367
|
|
|
5317
5368
|
// src/components/DownloadPackButton.tsx
|
|
5318
5369
|
import { useCallback as useCallback12, useEffect as useEffect13, useState as useState17 } from "react";
|
|
5319
|
-
import { jsx as
|
|
5370
|
+
import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
5320
5371
|
function formatSize(bytes) {
|
|
5321
5372
|
if (!bytes || bytes <= 0) return "";
|
|
5322
5373
|
const gb = bytes / 1024 ** 3;
|
|
@@ -5405,7 +5456,7 @@ var DownloadPackButton = ({
|
|
|
5405
5456
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
5406
5457
|
}
|
|
5407
5458
|
return /* @__PURE__ */ jsxs18("div", { children: [
|
|
5408
|
-
/* @__PURE__ */
|
|
5459
|
+
/* @__PURE__ */ jsx23(
|
|
5409
5460
|
"button",
|
|
5410
5461
|
{
|
|
5411
5462
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -5416,12 +5467,12 @@ var DownloadPackButton = ({
|
|
|
5416
5467
|
children: buttonLabel
|
|
5417
5468
|
}
|
|
5418
5469
|
),
|
|
5419
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */
|
|
5470
|
+
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
5471
|
] });
|
|
5421
5472
|
};
|
|
5422
5473
|
|
|
5423
5474
|
// src/components/SamplePackCTACard.tsx
|
|
5424
|
-
import { jsx as
|
|
5475
|
+
import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
5425
5476
|
var SamplePackCTACard = ({
|
|
5426
5477
|
host,
|
|
5427
5478
|
pack,
|
|
@@ -5429,7 +5480,7 @@ var SamplePackCTACard = ({
|
|
|
5429
5480
|
onDownloadComplete
|
|
5430
5481
|
}) => {
|
|
5431
5482
|
if (status === "checking") {
|
|
5432
|
-
return /* @__PURE__ */
|
|
5483
|
+
return /* @__PURE__ */ jsx24(
|
|
5433
5484
|
"div",
|
|
5434
5485
|
{
|
|
5435
5486
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -5446,10 +5497,10 @@ var SamplePackCTACard = ({
|
|
|
5446
5497
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
5447
5498
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
5448
5499
|
children: [
|
|
5449
|
-
/* @__PURE__ */
|
|
5450
|
-
/* @__PURE__ */
|
|
5451
|
-
/* @__PURE__ */
|
|
5452
|
-
/* @__PURE__ */
|
|
5500
|
+
/* @__PURE__ */ jsx24("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
5501
|
+
/* @__PURE__ */ jsx24("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
5502
|
+
/* @__PURE__ */ jsx24("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
5503
|
+
/* @__PURE__ */ jsx24(
|
|
5453
5504
|
DownloadPackButton,
|
|
5454
5505
|
{
|
|
5455
5506
|
host,
|
|
@@ -5529,7 +5580,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
5529
5580
|
}
|
|
5530
5581
|
|
|
5531
5582
|
// src/components/WaveformView.tsx
|
|
5532
|
-
import { jsx as
|
|
5583
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
5533
5584
|
var WaveformView = ({
|
|
5534
5585
|
host,
|
|
5535
5586
|
filePath,
|
|
@@ -5577,7 +5628,7 @@ var WaveformView = ({
|
|
|
5577
5628
|
observer.observe(canvas);
|
|
5578
5629
|
return () => observer.disconnect();
|
|
5579
5630
|
}, [peaks, fillStyle]);
|
|
5580
|
-
return /* @__PURE__ */
|
|
5631
|
+
return /* @__PURE__ */ jsx25(
|
|
5581
5632
|
"canvas",
|
|
5582
5633
|
{
|
|
5583
5634
|
ref: canvasRef,
|
|
@@ -5589,7 +5640,7 @@ var WaveformView = ({
|
|
|
5589
5640
|
|
|
5590
5641
|
// src/components/ScrollingWaveform.tsx
|
|
5591
5642
|
import { useEffect as useEffect15, useRef as useRef14 } from "react";
|
|
5592
|
-
import { jsx as
|
|
5643
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
5593
5644
|
var ScrollingWaveform = ({
|
|
5594
5645
|
getPeakDb,
|
|
5595
5646
|
active,
|
|
@@ -5665,7 +5716,7 @@ var ScrollingWaveform = ({
|
|
|
5665
5716
|
}
|
|
5666
5717
|
};
|
|
5667
5718
|
}, [active, getPeakDb, fillStyle]);
|
|
5668
|
-
return /* @__PURE__ */
|
|
5719
|
+
return /* @__PURE__ */ jsx26(
|
|
5669
5720
|
"canvas",
|
|
5670
5721
|
{
|
|
5671
5722
|
ref: canvasRef,
|
|
@@ -5677,7 +5728,7 @@ var ScrollingWaveform = ({
|
|
|
5677
5728
|
|
|
5678
5729
|
// src/components/OffsetScrubber.tsx
|
|
5679
5730
|
import { useCallback as useCallback13, useEffect as useEffect16, useMemo as useMemo8, useRef as useRef15, useState as useState19 } from "react";
|
|
5680
|
-
import { jsx as
|
|
5731
|
+
import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5681
5732
|
var SLIDER_HEIGHT_PX = 28;
|
|
5682
5733
|
var TICK_HEIGHT_PX = 14;
|
|
5683
5734
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -5793,7 +5844,7 @@ function OffsetScrubber({
|
|
|
5793
5844
|
}, [cuePoints, sampleToFraction]);
|
|
5794
5845
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
5795
5846
|
return /* @__PURE__ */ jsxs20("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
5796
|
-
/* @__PURE__ */
|
|
5847
|
+
/* @__PURE__ */ jsx27("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
5797
5848
|
/* @__PURE__ */ jsxs20(
|
|
5798
5849
|
"div",
|
|
5799
5850
|
{
|
|
@@ -5810,7 +5861,7 @@ function OffsetScrubber({
|
|
|
5810
5861
|
"aria-valuenow": draftOffset,
|
|
5811
5862
|
"aria-disabled": isDisabled,
|
|
5812
5863
|
children: [
|
|
5813
|
-
/* @__PURE__ */
|
|
5864
|
+
/* @__PURE__ */ jsx27(
|
|
5814
5865
|
"div",
|
|
5815
5866
|
{
|
|
5816
5867
|
"aria-hidden": "true",
|
|
@@ -5818,7 +5869,7 @@ function OffsetScrubber({
|
|
|
5818
5869
|
style: { left: "50%" }
|
|
5819
5870
|
}
|
|
5820
5871
|
),
|
|
5821
|
-
ticks.map((t) => /* @__PURE__ */
|
|
5872
|
+
ticks.map((t) => /* @__PURE__ */ jsx27(
|
|
5822
5873
|
"div",
|
|
5823
5874
|
{
|
|
5824
5875
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -5833,7 +5884,7 @@ function OffsetScrubber({
|
|
|
5833
5884
|
},
|
|
5834
5885
|
t.i
|
|
5835
5886
|
)),
|
|
5836
|
-
/* @__PURE__ */
|
|
5887
|
+
/* @__PURE__ */ jsx27(
|
|
5837
5888
|
"div",
|
|
5838
5889
|
{
|
|
5839
5890
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -5850,7 +5901,7 @@ function OffsetScrubber({
|
|
|
5850
5901
|
]
|
|
5851
5902
|
}
|
|
5852
5903
|
),
|
|
5853
|
-
/* @__PURE__ */
|
|
5904
|
+
/* @__PURE__ */ jsx27(
|
|
5854
5905
|
"span",
|
|
5855
5906
|
{
|
|
5856
5907
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -5858,7 +5909,7 @@ function OffsetScrubber({
|
|
|
5858
5909
|
children: formatOffset(draftOffset, sampleRate)
|
|
5859
5910
|
}
|
|
5860
5911
|
),
|
|
5861
|
-
/* @__PURE__ */
|
|
5912
|
+
/* @__PURE__ */ jsx27(
|
|
5862
5913
|
"button",
|
|
5863
5914
|
{
|
|
5864
5915
|
type: "button",
|
|
@@ -5870,7 +5921,7 @@ function OffsetScrubber({
|
|
|
5870
5921
|
children: "\u2316"
|
|
5871
5922
|
}
|
|
5872
5923
|
),
|
|
5873
|
-
bpmMismatch && /* @__PURE__ */
|
|
5924
|
+
bpmMismatch && /* @__PURE__ */ jsx27(
|
|
5874
5925
|
"span",
|
|
5875
5926
|
{
|
|
5876
5927
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -6883,7 +6934,7 @@ function useTransitionOps({
|
|
|
6883
6934
|
}
|
|
6884
6935
|
|
|
6885
6936
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
6886
|
-
import { jsx as
|
|
6937
|
+
import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
6887
6938
|
var EMPTY_PLACEHOLDERS = [];
|
|
6888
6939
|
function useGeneratorPanelCore({
|
|
6889
6940
|
ui,
|
|
@@ -7294,37 +7345,6 @@ function useGeneratorPanelCore({
|
|
|
7294
7345
|
},
|
|
7295
7346
|
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
7296
7347
|
);
|
|
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
7348
|
const [isExportingMidi, setIsExportingMidi] = useState24(false);
|
|
7329
7349
|
const handleExportMidi = useCallback17(async () => {
|
|
7330
7350
|
if (isExportingMidi) return;
|
|
@@ -7381,7 +7401,7 @@ function useGeneratorPanelCore({
|
|
|
7381
7401
|
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
7382
7402
|
onHeaderContent(
|
|
7383
7403
|
/* @__PURE__ */ jsxs21("div", { className: "flex gap-1 items-center", children: [
|
|
7384
|
-
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */
|
|
7404
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ jsx28(
|
|
7385
7405
|
"button",
|
|
7386
7406
|
{
|
|
7387
7407
|
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
@@ -7395,7 +7415,7 @@ function useGeneratorPanelCore({
|
|
|
7395
7415
|
children: identity.importTrackLabel ?? "Import Track"
|
|
7396
7416
|
}
|
|
7397
7417
|
),
|
|
7398
|
-
(!canCrossfade || !designerView) && /* @__PURE__ */
|
|
7418
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ jsx28(
|
|
7399
7419
|
"button",
|
|
7400
7420
|
{
|
|
7401
7421
|
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
@@ -7430,7 +7450,7 @@ function useGeneratorPanelCore({
|
|
|
7430
7450
|
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
7431
7451
|
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
7452
|
children: [
|
|
7433
|
-
transitionSourceTotal > 0 && /* @__PURE__ */
|
|
7453
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ jsx28(
|
|
7434
7454
|
"span",
|
|
7435
7455
|
{
|
|
7436
7456
|
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
@@ -7571,9 +7591,163 @@ function useGeneratorPanelCore({
|
|
|
7571
7591
|
name: `${identity.trackNamePrefix}-${Date.now()}${nameSuffix}`,
|
|
7572
7592
|
...adapter.createTrackOptions()
|
|
7573
7593
|
}),
|
|
7574
|
-
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
7594
|
+
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? [],
|
|
7595
|
+
sound: adapter.sound
|
|
7575
7596
|
};
|
|
7576
7597
|
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
7598
|
+
const broadcastSoundFromTrack = useCallback17(
|
|
7599
|
+
async (sourceTrackId, descriptor, label) => {
|
|
7600
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7601
|
+
if (!targetsOf) return;
|
|
7602
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7603
|
+
if (!source) return;
|
|
7604
|
+
let targets;
|
|
7605
|
+
try {
|
|
7606
|
+
targets = await targetsOf(source, makeServices());
|
|
7607
|
+
} catch (err) {
|
|
7608
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7609
|
+
return;
|
|
7610
|
+
}
|
|
7611
|
+
const sourceInstrument = source.instrumentPluginId ?? null;
|
|
7612
|
+
const others = (targets ?? []).filter((t) => {
|
|
7613
|
+
if (t.engineId === sourceTrackId) return false;
|
|
7614
|
+
const live = tracksRef.current.find((x) => x.handle.id === t.engineId);
|
|
7615
|
+
return (live?.instrumentPluginId ?? null) === sourceInstrument;
|
|
7616
|
+
});
|
|
7617
|
+
if (others.length === 0) return;
|
|
7618
|
+
try {
|
|
7619
|
+
await adapter.sound.persistDescriptor?.(sourceTrackId, descriptor, label);
|
|
7620
|
+
} catch {
|
|
7621
|
+
}
|
|
7622
|
+
const appliedIds = /* @__PURE__ */ new Set();
|
|
7623
|
+
const failed = [];
|
|
7624
|
+
for (const target of others) {
|
|
7625
|
+
try {
|
|
7626
|
+
if (soundHistory.list(target.engineId).entries.length === 0) {
|
|
7627
|
+
try {
|
|
7628
|
+
const cap = await adapter.sound.captureSoundDescriptor(target.engineId);
|
|
7629
|
+
if (cap) {
|
|
7630
|
+
soundHistory.record(target.engineId, cap.descriptor, adapter.sound.previousSoundLabel);
|
|
7631
|
+
}
|
|
7632
|
+
} catch {
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
await adapter.sound.applySound(target.engineId, descriptor);
|
|
7636
|
+
try {
|
|
7637
|
+
await adapter.sound.persistDescriptor?.(target.engineId, descriptor, label);
|
|
7638
|
+
} catch {
|
|
7639
|
+
}
|
|
7640
|
+
soundHistory.record(target.engineId, descriptor, label);
|
|
7641
|
+
appliedIds.add(target.engineId);
|
|
7642
|
+
} catch (err) {
|
|
7643
|
+
failed.push(target.label ?? target.engineId);
|
|
7644
|
+
console.warn(`[${logTag}] Linked sound apply failed for ${target.engineId}:`, err);
|
|
7645
|
+
}
|
|
7646
|
+
}
|
|
7647
|
+
if (appliedIds.size > 0) {
|
|
7648
|
+
setTracks(
|
|
7649
|
+
(prev) => prev.map(
|
|
7650
|
+
(t) => appliedIds.has(t.handle.id) ? { ...t, shuffleHistory: /* @__PURE__ */ new Set([...t.shuffleHistory, label]) } : t
|
|
7651
|
+
)
|
|
7652
|
+
);
|
|
7653
|
+
}
|
|
7654
|
+
if (failed.length > 0) {
|
|
7655
|
+
host.showToast(
|
|
7656
|
+
"warning",
|
|
7657
|
+
"Linked sound applied to some parts only",
|
|
7658
|
+
`Skipped: ${failed.join(", ")}`
|
|
7659
|
+
);
|
|
7660
|
+
}
|
|
7661
|
+
},
|
|
7662
|
+
[adapter, makeServices, soundHistory, host, logTag]
|
|
7663
|
+
);
|
|
7664
|
+
const broadcastInstrumentFromTrack = useCallback17(
|
|
7665
|
+
async (sourceTrackId, pluginId) => {
|
|
7666
|
+
const targetsOf = adapter.sound.broadcastTargets;
|
|
7667
|
+
if (!targetsOf) return;
|
|
7668
|
+
const source = tracksRef.current.find((t) => t.handle.id === sourceTrackId);
|
|
7669
|
+
if (!source) return;
|
|
7670
|
+
let targets;
|
|
7671
|
+
try {
|
|
7672
|
+
targets = await targetsOf(source, makeServices());
|
|
7673
|
+
} catch (err) {
|
|
7674
|
+
console.warn(`[${logTag}] broadcastTargets failed:`, err);
|
|
7675
|
+
return;
|
|
7676
|
+
}
|
|
7677
|
+
const others = (targets ?? []).filter((t) => t.engineId !== sourceTrackId);
|
|
7678
|
+
if (others.length === 0) return;
|
|
7679
|
+
const failed = [];
|
|
7680
|
+
for (const target of others) {
|
|
7681
|
+
try {
|
|
7682
|
+
await host.setTrackInstrument(target.engineId, pluginId);
|
|
7683
|
+
const descriptor = await host.getTrackInstrument(target.engineId);
|
|
7684
|
+
setTracks(
|
|
7685
|
+
(prev) => prev.map(
|
|
7686
|
+
(t) => t.handle.id === target.engineId ? {
|
|
7687
|
+
...t,
|
|
7688
|
+
instrumentPluginId: descriptor?.pluginId ?? null,
|
|
7689
|
+
instrumentName: descriptor?.name ?? null,
|
|
7690
|
+
instrumentMissing: descriptor?.missing ?? false
|
|
7691
|
+
} : t
|
|
7692
|
+
)
|
|
7693
|
+
);
|
|
7694
|
+
} catch (err) {
|
|
7695
|
+
failed.push(target.label ?? target.engineId);
|
|
7696
|
+
console.warn(`[${logTag}] Linked instrument apply failed for ${target.engineId}:`, err);
|
|
7697
|
+
}
|
|
7698
|
+
}
|
|
7699
|
+
if (failed.length > 0) {
|
|
7700
|
+
host.showToast(
|
|
7701
|
+
"warning",
|
|
7702
|
+
"Instrument applied to some parts only",
|
|
7703
|
+
`Skipped: ${failed.join(", ")}`
|
|
7704
|
+
);
|
|
7705
|
+
}
|
|
7706
|
+
},
|
|
7707
|
+
[adapter, makeServices, host, logTag]
|
|
7708
|
+
);
|
|
7709
|
+
const handleRestoreSound = useCallback17(
|
|
7710
|
+
async (trackId, index) => {
|
|
7711
|
+
const entry = soundHistory.list(trackId).entries[index];
|
|
7712
|
+
const moved = await soundHistory.restoreTo(trackId, index);
|
|
7713
|
+
if (moved && entry) {
|
|
7714
|
+
await broadcastSoundFromTrack(trackId, entry.descriptor, entry.label);
|
|
7715
|
+
}
|
|
7716
|
+
},
|
|
7717
|
+
[soundHistory, broadcastSoundFromTrack]
|
|
7718
|
+
);
|
|
7719
|
+
const handleSoundImportPick = useCallback17(
|
|
7720
|
+
async (sel) => {
|
|
7721
|
+
const target = soundImportTarget;
|
|
7722
|
+
if (!target || !host.getTrackSound) {
|
|
7723
|
+
setSoundImportTarget(null);
|
|
7724
|
+
return;
|
|
7725
|
+
}
|
|
7726
|
+
const noun = adapter.sound.importNoun;
|
|
7727
|
+
const nounTitle = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
7728
|
+
try {
|
|
7729
|
+
const snap = await host.getTrackSound(sel.sourceTrackDbId);
|
|
7730
|
+
if (!snap || snap.kind !== adapter.sound.acceptedSnapshotKind) {
|
|
7731
|
+
host.showToast(
|
|
7732
|
+
"error",
|
|
7733
|
+
`No ${noun} to import`,
|
|
7734
|
+
`${sel.trackName} has no ${identity.familyKey} ${noun}.`
|
|
7735
|
+
);
|
|
7736
|
+
return;
|
|
7737
|
+
}
|
|
7738
|
+
const descriptor = adapter.sound.descriptorFromSnapshot(snap);
|
|
7739
|
+
await adapter.sound.applySound(target.handle.id, descriptor);
|
|
7740
|
+
soundHistory.record(target.handle.id, descriptor, snap.label);
|
|
7741
|
+
host.showToast("success", `${nounTitle} imported`, `${snap.label} \u2192 ${target.handle.name}`);
|
|
7742
|
+
await broadcastSoundFromTrack(target.handle.id, descriptor, snap.label);
|
|
7743
|
+
} catch (err) {
|
|
7744
|
+
host.showToast("error", "Import failed", err instanceof Error ? err.message : String(err));
|
|
7745
|
+
} finally {
|
|
7746
|
+
setSoundImportTarget(null);
|
|
7747
|
+
}
|
|
7748
|
+
},
|
|
7749
|
+
[soundImportTarget, host, adapter, identity.familyKey, soundHistory, broadcastSoundFromTrack]
|
|
7750
|
+
);
|
|
7577
7751
|
const handleGenerate = useCallback17(
|
|
7578
7752
|
async (trackId) => {
|
|
7579
7753
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
@@ -7692,7 +7866,10 @@ function useGeneratorPanelCore({
|
|
|
7692
7866
|
);
|
|
7693
7867
|
try {
|
|
7694
7868
|
const cap = await adapter.sound.captureSoundDescriptor(trackId);
|
|
7695
|
-
if (cap)
|
|
7869
|
+
if (cap) {
|
|
7870
|
+
soundHistory.record(trackId, cap.descriptor, result.appliedName);
|
|
7871
|
+
await broadcastSoundFromTrack(trackId, cap.descriptor, result.appliedName);
|
|
7872
|
+
}
|
|
7696
7873
|
} catch {
|
|
7697
7874
|
}
|
|
7698
7875
|
console.log(`[${logTag}] Sound shuffled: ${result.appliedName} (history ${nextHistory.size})`);
|
|
@@ -7701,7 +7878,7 @@ function useGeneratorPanelCore({
|
|
|
7701
7878
|
host.showToast("error", "Shuffle failed", msg);
|
|
7702
7879
|
}
|
|
7703
7880
|
},
|
|
7704
|
-
[host, adapter, tracks, soundHistory, logTag]
|
|
7881
|
+
[host, adapter, tracks, soundHistory, logTag, broadcastSoundFromTrack]
|
|
7705
7882
|
);
|
|
7706
7883
|
const handleCopy = useCallback17(
|
|
7707
7884
|
async (trackId) => {
|
|
@@ -7904,6 +8081,7 @@ function useGeneratorPanelCore({
|
|
|
7904
8081
|
} : t
|
|
7905
8082
|
)
|
|
7906
8083
|
);
|
|
8084
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
7907
8085
|
} catch (err) {
|
|
7908
8086
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
7909
8087
|
host.showToast("error", "Instrument load failed", msg);
|
|
@@ -7926,6 +8104,7 @@ function useGeneratorPanelCore({
|
|
|
7926
8104
|
} : t
|
|
7927
8105
|
)
|
|
7928
8106
|
);
|
|
8107
|
+
await broadcastInstrumentFromTrack(trackId, pluginId);
|
|
7929
8108
|
} catch (err) {
|
|
7930
8109
|
const msg = err instanceof Error ? err.message : "Failed to load instrument";
|
|
7931
8110
|
console.error(`[${logTag}] Failed to set instrument:`, err);
|
|
@@ -7935,7 +8114,7 @@ function useGeneratorPanelCore({
|
|
|
7935
8114
|
);
|
|
7936
8115
|
}
|
|
7937
8116
|
},
|
|
7938
|
-
[host, identity.defaultInstrumentPluginId, logTag]
|
|
8117
|
+
[host, identity.defaultInstrumentPluginId, logTag, broadcastInstrumentFromTrack]
|
|
7939
8118
|
);
|
|
7940
8119
|
const handleShowEditor = useCallback17(
|
|
7941
8120
|
async (trackId) => {
|
|
@@ -8145,6 +8324,7 @@ function useGeneratorPanelCore({
|
|
|
8145
8324
|
handlers,
|
|
8146
8325
|
handleGenerate,
|
|
8147
8326
|
handleShuffle,
|
|
8327
|
+
handleRestoreSound,
|
|
8148
8328
|
handleAddTrack,
|
|
8149
8329
|
handleDeleteTrack,
|
|
8150
8330
|
handleExportMidi,
|
|
@@ -8175,8 +8355,41 @@ function useGeneratorPanelCore({
|
|
|
8175
8355
|
}
|
|
8176
8356
|
|
|
8177
8357
|
// src/panel-core/GeneratorPanelShell.tsx
|
|
8178
|
-
import
|
|
8179
|
-
import { Fragment as Fragment7, jsx as
|
|
8358
|
+
import { useCallback as useCallback18, useEffect as useEffect20, useState as useState25 } from "react";
|
|
8359
|
+
import { Fragment as Fragment7, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
8360
|
+
var GROUP_UI_KEY = "groupUi";
|
|
8361
|
+
function CollapsibleGroup({
|
|
8362
|
+
ext,
|
|
8363
|
+
group,
|
|
8364
|
+
groupCtx
|
|
8365
|
+
}) {
|
|
8366
|
+
const { host, activeSceneId, trackDataKey: trackDataKey2 } = groupCtx.services;
|
|
8367
|
+
const uiKey = trackDataKey2(group.groupId, GROUP_UI_KEY);
|
|
8368
|
+
const [collapsed, setCollapsed] = useState25(false);
|
|
8369
|
+
useEffect20(() => {
|
|
8370
|
+
let cancelled = false;
|
|
8371
|
+
if (!activeSceneId) return void 0;
|
|
8372
|
+
void host.getSceneData(activeSceneId, uiKey).then((raw) => {
|
|
8373
|
+
if (cancelled || !raw || typeof raw !== "object") return;
|
|
8374
|
+
setCollapsed(raw.collapsed === true);
|
|
8375
|
+
}).catch(() => {
|
|
8376
|
+
});
|
|
8377
|
+
return () => {
|
|
8378
|
+
cancelled = true;
|
|
8379
|
+
};
|
|
8380
|
+
}, [host, activeSceneId, uiKey]);
|
|
8381
|
+
const onToggleCollapse = useCallback18(() => {
|
|
8382
|
+
setCollapsed((prev) => {
|
|
8383
|
+
const next = !prev;
|
|
8384
|
+
if (activeSceneId) {
|
|
8385
|
+
void host.setSceneData(activeSceneId, uiKey, { collapsed: next }).catch(() => {
|
|
8386
|
+
});
|
|
8387
|
+
}
|
|
8388
|
+
return next;
|
|
8389
|
+
});
|
|
8390
|
+
}, [host, activeSceneId, uiKey]);
|
|
8391
|
+
return /* @__PURE__ */ jsx29(Fragment7, { children: ext.renderGroup(group, { ...groupCtx, collapsed, onToggleCollapse }) });
|
|
8392
|
+
}
|
|
8180
8393
|
function GeneratorPanelShell({ core, slots }) {
|
|
8181
8394
|
const {
|
|
8182
8395
|
ui,
|
|
@@ -8199,6 +8412,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8199
8412
|
soundImportTarget,
|
|
8200
8413
|
setSoundImportTarget,
|
|
8201
8414
|
handleSoundImportPick,
|
|
8415
|
+
handleRestoreSound,
|
|
8202
8416
|
handlePortTrack,
|
|
8203
8417
|
transition,
|
|
8204
8418
|
crossfadePairsMeta,
|
|
@@ -8294,7 +8508,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8294
8508
|
soundHistory: soundHistory.list(id).entries,
|
|
8295
8509
|
soundHistoryCursor: soundHistory.list(id).cursor,
|
|
8296
8510
|
onRestoreSound: (i) => {
|
|
8297
|
-
void
|
|
8511
|
+
void handleRestoreSound(id, i);
|
|
8298
8512
|
},
|
|
8299
8513
|
onToggleFavorite: (i) => soundHistory.toggleFavorite(id, i),
|
|
8300
8514
|
...importSoundProps,
|
|
@@ -8325,6 +8539,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8325
8539
|
handleBackToInstruments,
|
|
8326
8540
|
setSoundImportTarget,
|
|
8327
8541
|
soundHistory,
|
|
8542
|
+
handleRestoreSound,
|
|
8328
8543
|
handleFxToggle,
|
|
8329
8544
|
handleFxPresetChange,
|
|
8330
8545
|
handleFxDryWetChange,
|
|
@@ -8332,12 +8547,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8332
8547
|
]
|
|
8333
8548
|
);
|
|
8334
8549
|
if (!activeSceneId) {
|
|
8335
|
-
return /* @__PURE__ */
|
|
8550
|
+
return /* @__PURE__ */ jsx29(
|
|
8336
8551
|
"div",
|
|
8337
8552
|
{
|
|
8338
8553
|
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
8339
8554
|
className: "flex items-center justify-center py-8",
|
|
8340
|
-
children: /* @__PURE__ */
|
|
8555
|
+
children: /* @__PURE__ */ jsx29(
|
|
8341
8556
|
"button",
|
|
8342
8557
|
{
|
|
8343
8558
|
onClick: () => onSelectScene?.(),
|
|
@@ -8349,12 +8564,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8349
8564
|
);
|
|
8350
8565
|
}
|
|
8351
8566
|
if (!sceneContext?.hasContract) {
|
|
8352
|
-
return /* @__PURE__ */
|
|
8567
|
+
return /* @__PURE__ */ jsx29(
|
|
8353
8568
|
"div",
|
|
8354
8569
|
{
|
|
8355
8570
|
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
8356
8571
|
className: "flex items-center justify-center py-8",
|
|
8357
|
-
children: /* @__PURE__ */
|
|
8572
|
+
children: /* @__PURE__ */ jsx29(
|
|
8358
8573
|
"button",
|
|
8359
8574
|
{
|
|
8360
8575
|
onClick: () => onOpenContract?.(),
|
|
@@ -8366,7 +8581,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8366
8581
|
);
|
|
8367
8582
|
}
|
|
8368
8583
|
if (features.bulkComposePlaceholders && isComposing) {
|
|
8369
|
-
return /* @__PURE__ */
|
|
8584
|
+
return /* @__PURE__ */ jsx29("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ jsx29(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
8370
8585
|
}
|
|
8371
8586
|
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
8372
8587
|
if (activePlaceholders.length > 0) {
|
|
@@ -8377,18 +8592,18 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8377
8592
|
tracksByDbId.set(t.handle.id, t);
|
|
8378
8593
|
}
|
|
8379
8594
|
}
|
|
8380
|
-
return /* @__PURE__ */
|
|
8595
|
+
return /* @__PURE__ */ jsx29("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
8381
8596
|
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
8382
8597
|
if (loadedTrack) {
|
|
8383
|
-
return /* @__PURE__ */
|
|
8598
|
+
return /* @__PURE__ */ jsx29(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
8384
8599
|
}
|
|
8385
|
-
return /* @__PURE__ */
|
|
8600
|
+
return /* @__PURE__ */ jsx29(
|
|
8386
8601
|
"div",
|
|
8387
8602
|
{
|
|
8388
8603
|
"data-testid": "bulk-placeholder-track",
|
|
8389
8604
|
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
8390
8605
|
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
8391
|
-
children: /* @__PURE__ */
|
|
8606
|
+
children: /* @__PURE__ */ jsx29(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
8392
8607
|
},
|
|
8393
8608
|
ph.id
|
|
8394
8609
|
);
|
|
@@ -8400,13 +8615,13 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8400
8615
|
supportsMeters,
|
|
8401
8616
|
levels: supportsMeters ? trackLevels : void 0,
|
|
8402
8617
|
handlers,
|
|
8403
|
-
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */
|
|
8618
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ jsx29(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
8404
8619
|
setGroupMute,
|
|
8405
8620
|
setGroupSolo,
|
|
8406
8621
|
deleteGroup
|
|
8407
8622
|
};
|
|
8408
8623
|
return /* @__PURE__ */ jsxs22("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
8409
|
-
features.importTracks && host.listImportableTracks && /* @__PURE__ */
|
|
8624
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ jsx29(
|
|
8410
8625
|
ImportTrackModal,
|
|
8411
8626
|
{
|
|
8412
8627
|
host,
|
|
@@ -8419,7 +8634,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8419
8634
|
testIdPrefix: `${identity.familyKey}-import`
|
|
8420
8635
|
}
|
|
8421
8636
|
),
|
|
8422
|
-
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */
|
|
8637
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ jsx29(
|
|
8423
8638
|
ImportTrackModal,
|
|
8424
8639
|
{
|
|
8425
8640
|
host,
|
|
@@ -8434,7 +8649,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8434
8649
|
}
|
|
8435
8650
|
),
|
|
8436
8651
|
slots?.modals,
|
|
8437
|
-
canCrossfade && xfFromId && xfToId && /* @__PURE__ */
|
|
8652
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ jsx29("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ jsx29(
|
|
8438
8653
|
TransitionDesigner,
|
|
8439
8654
|
{
|
|
8440
8655
|
host,
|
|
@@ -8453,8 +8668,8 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8453
8668
|
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
8454
8669
|
}
|
|
8455
8670
|
) }),
|
|
8456
|
-
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */
|
|
8457
|
-
panelBus.supported && panelBus.bus && /* @__PURE__ */
|
|
8671
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ jsx29("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ jsxs22(Fragment7, { children: [
|
|
8672
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ jsx29(
|
|
8458
8673
|
PanelMasterStrip,
|
|
8459
8674
|
{
|
|
8460
8675
|
bus: panelBus.bus,
|
|
@@ -8475,7 +8690,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8475
8690
|
}
|
|
8476
8691
|
),
|
|
8477
8692
|
slots?.beforeRows,
|
|
8478
|
-
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */
|
|
8693
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ jsx29(
|
|
8479
8694
|
CrossfadeTrackRow,
|
|
8480
8695
|
{
|
|
8481
8696
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8512,7 +8727,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8512
8727
|
},
|
|
8513
8728
|
pair.groupId
|
|
8514
8729
|
)),
|
|
8515
|
-
resolvedSingleFades.map((fade) => /* @__PURE__ */
|
|
8730
|
+
resolvedSingleFades.map((fade) => /* @__PURE__ */ jsx29(
|
|
8516
8731
|
FadeTrackRow,
|
|
8517
8732
|
{
|
|
8518
8733
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8537,7 +8752,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8537
8752
|
},
|
|
8538
8753
|
fade.dbId
|
|
8539
8754
|
)),
|
|
8540
|
-
resolvedGroupFades.map((group) => /* @__PURE__ */
|
|
8755
|
+
resolvedGroupFades.map((group) => /* @__PURE__ */ jsx29(
|
|
8541
8756
|
GroupFadeTrackRow,
|
|
8542
8757
|
{
|
|
8543
8758
|
accentColor: identity.transitionAccentColor,
|
|
@@ -8573,20 +8788,28 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
8573
8788
|
group.groupId
|
|
8574
8789
|
)),
|
|
8575
8790
|
(adapter.groupExtensions ?? []).flatMap(
|
|
8576
|
-
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */
|
|
8791
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ jsx29(
|
|
8792
|
+
CollapsibleGroup,
|
|
8793
|
+
{
|
|
8794
|
+
ext,
|
|
8795
|
+
group,
|
|
8796
|
+
groupCtx
|
|
8797
|
+
},
|
|
8798
|
+
`${ext.metaKey}:${group.groupId}`
|
|
8799
|
+
))
|
|
8577
8800
|
),
|
|
8578
8801
|
tracks.map((track, index) => {
|
|
8579
8802
|
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
8580
8803
|
return null;
|
|
8581
8804
|
}
|
|
8582
|
-
return /* @__PURE__ */
|
|
8805
|
+
return /* @__PURE__ */ jsx29(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
8583
8806
|
}),
|
|
8584
8807
|
slots?.afterRows
|
|
8585
8808
|
] })),
|
|
8586
8809
|
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
8587
8810
|
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
8588
8811
|
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
8589
|
-
return /* @__PURE__ */
|
|
8812
|
+
return /* @__PURE__ */ jsx29("div", { className: "pt-2", children: /* @__PURE__ */ jsx29(
|
|
8590
8813
|
"button",
|
|
8591
8814
|
{
|
|
8592
8815
|
"data-testid": "export-midi-tracks-button",
|
|
@@ -8641,6 +8864,15 @@ function createSurgeSoundAdapter(host, overrides = {}) {
|
|
|
8641
8864
|
});
|
|
8642
8865
|
return snap.label;
|
|
8643
8866
|
},
|
|
8867
|
+
persistDescriptor: async (trackId, descriptor, label) => {
|
|
8868
|
+
const { state, stateType } = descriptor;
|
|
8869
|
+
await host.persistTrackPresetState?.(trackId, {
|
|
8870
|
+
state,
|
|
8871
|
+
stateType: stateType ?? "valuetree",
|
|
8872
|
+
name: label
|
|
8873
|
+
}).catch(() => {
|
|
8874
|
+
});
|
|
8875
|
+
},
|
|
8644
8876
|
descriptorFromSnapshot: (snap) => {
|
|
8645
8877
|
const preset = snap;
|
|
8646
8878
|
return { state: preset.state, stateType: preset.stateType };
|
|
@@ -8725,18 +8957,183 @@ var SUB = {
|
|
|
8725
8957
|
rootOnly: true,
|
|
8726
8958
|
monoPreference: "low"
|
|
8727
8959
|
};
|
|
8960
|
+
var HORN_MAX_NOTES_PER_BAR = 12;
|
|
8961
|
+
var HORN_FOLLOW_PALETTE = "EXACTLY the lead voice's rhythm \u2014 every attack lands together with voice 1";
|
|
8962
|
+
var HORN_LEAD = {
|
|
8963
|
+
label: "lead trumpet",
|
|
8964
|
+
role: "brass",
|
|
8965
|
+
registerLow: 60,
|
|
8966
|
+
// C4
|
|
8967
|
+
registerHigh: 84,
|
|
8968
|
+
// C6
|
|
8969
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
8970
|
+
rhythmPalette: "tight funk syncopation \u2014 8ths and 16ths, staccato punches, off-beat anticipations",
|
|
8971
|
+
harmonicDiscipline: "the section's melodic top \u2014 chord tones on every hit; quick chromatic approach tones only on pickups",
|
|
8972
|
+
monoPreference: "high"
|
|
8973
|
+
};
|
|
8974
|
+
var HORN_TRUMPET_2 = {
|
|
8975
|
+
label: "second trumpet",
|
|
8976
|
+
role: "brass",
|
|
8977
|
+
registerLow: 55,
|
|
8978
|
+
// G3
|
|
8979
|
+
registerHigh: 79,
|
|
8980
|
+
// G5
|
|
8981
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
8982
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
8983
|
+
harmonicDiscipline: "nearest chord tone directly below voice 1 \u2014 tight close voicing",
|
|
8984
|
+
monoPreference: "high"
|
|
8985
|
+
};
|
|
8986
|
+
var HORN_ALTO = {
|
|
8987
|
+
label: "alto sax",
|
|
8988
|
+
role: "brass",
|
|
8989
|
+
registerLow: 53,
|
|
8990
|
+
// F3
|
|
8991
|
+
registerHigh: 77,
|
|
8992
|
+
// F5
|
|
8993
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
8994
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
8995
|
+
harmonicDiscipline: "chord tone below the trumpets \u2014 keep the upper stack inside one octave",
|
|
8996
|
+
monoPreference: "high"
|
|
8997
|
+
};
|
|
8998
|
+
var HORN_TENOR = {
|
|
8999
|
+
label: "tenor sax",
|
|
9000
|
+
role: "brass",
|
|
9001
|
+
registerLow: 46,
|
|
9002
|
+
// Bb2
|
|
9003
|
+
registerHigh: 72,
|
|
9004
|
+
// C5
|
|
9005
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9006
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9007
|
+
harmonicDiscipline: "chord tone under the upper horns \u2014 complete the chord",
|
|
9008
|
+
monoPreference: "low"
|
|
9009
|
+
};
|
|
9010
|
+
var HORN_TROMBONE = {
|
|
9011
|
+
label: "trombone",
|
|
9012
|
+
role: "brass",
|
|
9013
|
+
registerLow: 43,
|
|
9014
|
+
// G2
|
|
9015
|
+
registerHigh: 67,
|
|
9016
|
+
// G4
|
|
9017
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9018
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9019
|
+
harmonicDiscipline: "chord tone or root below the saxes \u2014 weight in the middle-low stack",
|
|
9020
|
+
monoPreference: "low"
|
|
9021
|
+
};
|
|
9022
|
+
var HORN_BARI = {
|
|
9023
|
+
label: "baritone sax",
|
|
9024
|
+
role: "brass",
|
|
9025
|
+
registerLow: 36,
|
|
9026
|
+
// C2
|
|
9027
|
+
registerHigh: 60,
|
|
9028
|
+
// C4
|
|
9029
|
+
maxNotesPerBar: HORN_MAX_NOTES_PER_BAR,
|
|
9030
|
+
rhythmPalette: HORN_FOLLOW_PALETTE,
|
|
9031
|
+
harmonicDiscipline: "chord roots and lower chord tones \u2014 the section's bottom anchor",
|
|
9032
|
+
monoPreference: "low"
|
|
9033
|
+
};
|
|
9034
|
+
var WIND_FLUTE = {
|
|
9035
|
+
label: "flute",
|
|
9036
|
+
role: "winds",
|
|
9037
|
+
registerLow: 60,
|
|
9038
|
+
// C4
|
|
9039
|
+
registerHigh: 93,
|
|
9040
|
+
// A6
|
|
9041
|
+
maxNotesPerBar: 8,
|
|
9042
|
+
rhythmPalette: "8ths and 16ths; runs, turns and trills welcome",
|
|
9043
|
+
harmonicDiscipline: "freest voice \u2014 non-chord tones as passing/neighbor tones on weak beats, resolving by step",
|
|
9044
|
+
monoPreference: "high"
|
|
9045
|
+
};
|
|
9046
|
+
var WIND_FLUTE_2 = {
|
|
9047
|
+
label: "second flute",
|
|
9048
|
+
role: "winds",
|
|
9049
|
+
registerLow: 60,
|
|
9050
|
+
// C4
|
|
9051
|
+
registerHigh: 86,
|
|
9052
|
+
// D6
|
|
9053
|
+
maxNotesPerBar: 5,
|
|
9054
|
+
rhythmPalette: "quarters with occasional 8th-note motion",
|
|
9055
|
+
harmonicDiscipline: "chord tones; fill gaps the other inner voices leave",
|
|
9056
|
+
monoPreference: "high"
|
|
9057
|
+
};
|
|
9058
|
+
var WIND_OBOE = {
|
|
9059
|
+
label: "oboe",
|
|
9060
|
+
role: "winds",
|
|
9061
|
+
registerLow: 58,
|
|
9062
|
+
// Bb3
|
|
9063
|
+
registerHigh: 84,
|
|
9064
|
+
// C6
|
|
9065
|
+
maxNotesPerBar: 6,
|
|
9066
|
+
rhythmPalette: "8ths and quarters; move when the flute rests",
|
|
9067
|
+
harmonicDiscipline: "mostly chord tones; may imitate the flute's motifs a bar later",
|
|
9068
|
+
monoPreference: "high"
|
|
9069
|
+
};
|
|
9070
|
+
var WIND_CLARINET = {
|
|
9071
|
+
label: "clarinet",
|
|
9072
|
+
role: "winds",
|
|
9073
|
+
registerLow: 50,
|
|
9074
|
+
// D3
|
|
9075
|
+
registerHigh: 81,
|
|
9076
|
+
// A5
|
|
9077
|
+
maxNotesPerBar: 4,
|
|
9078
|
+
rhythmPalette: "quarters and halves",
|
|
9079
|
+
harmonicDiscipline: "chord tones with smooth stepwise motion between them",
|
|
9080
|
+
monoPreference: "high"
|
|
9081
|
+
};
|
|
9082
|
+
var WIND_FRENCH_HORN = {
|
|
9083
|
+
label: "french horn",
|
|
9084
|
+
role: "winds",
|
|
9085
|
+
registerLow: 41,
|
|
9086
|
+
// F2
|
|
9087
|
+
registerHigh: 72,
|
|
9088
|
+
// C5
|
|
9089
|
+
maxNotesPerBar: 4,
|
|
9090
|
+
rhythmPalette: "quarters and halves; long held tones welcome",
|
|
9091
|
+
harmonicDiscipline: "chord tones \u2014 the warm glue in the middle of the ensemble",
|
|
9092
|
+
monoPreference: "low"
|
|
9093
|
+
};
|
|
9094
|
+
var WIND_BASSOON = {
|
|
9095
|
+
label: "bassoon",
|
|
9096
|
+
role: "winds",
|
|
9097
|
+
registerLow: 34,
|
|
9098
|
+
// Bb1
|
|
9099
|
+
registerHigh: 62,
|
|
9100
|
+
// D4
|
|
9101
|
+
maxNotesPerBar: 3,
|
|
9102
|
+
rhythmPalette: "quarters and halves; brief walking figures at cadences",
|
|
9103
|
+
harmonicDiscipline: "roots and fifths emphasized; passing tones only between chord tones",
|
|
9104
|
+
monoPreference: "low"
|
|
9105
|
+
};
|
|
8728
9106
|
var ENSEMBLE_MIN_VOICES = 2;
|
|
8729
9107
|
var ENSEMBLE_MAX_VOICES = 6;
|
|
8730
|
-
var
|
|
9108
|
+
var STRINGS_SPEC_TABLES = {
|
|
8731
9109
|
2: [TOP, BASS],
|
|
8732
9110
|
3: [TOP, INNER, BASS],
|
|
8733
9111
|
4: [TOP, COUNTER, TENOR, BASS],
|
|
8734
9112
|
5: [TOP, COUNTER, INNER, TENOR, SUB],
|
|
8735
9113
|
6: [TOP, COUNTER, INNER_2, INNER, TENOR, SUB]
|
|
8736
9114
|
};
|
|
8737
|
-
|
|
9115
|
+
var HORNS_SPEC_TABLES = {
|
|
9116
|
+
2: [HORN_LEAD, HORN_TENOR],
|
|
9117
|
+
3: [HORN_LEAD, HORN_TENOR, HORN_BARI],
|
|
9118
|
+
4: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_BARI],
|
|
9119
|
+
5: [HORN_LEAD, HORN_TRUMPET_2, HORN_TENOR, HORN_TROMBONE, HORN_BARI],
|
|
9120
|
+
6: [HORN_LEAD, HORN_TRUMPET_2, HORN_ALTO, HORN_TENOR, HORN_TROMBONE, HORN_BARI]
|
|
9121
|
+
};
|
|
9122
|
+
var WINDS_SPEC_TABLES = {
|
|
9123
|
+
2: [WIND_FLUTE, WIND_BASSOON],
|
|
9124
|
+
3: [WIND_FLUTE, WIND_CLARINET, WIND_BASSOON],
|
|
9125
|
+
4: [WIND_FLUTE, WIND_OBOE, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9126
|
+
5: [WIND_FLUTE, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON],
|
|
9127
|
+
6: [WIND_FLUTE, WIND_FLUTE_2, WIND_OBOE, WIND_CLARINET, WIND_FRENCH_HORN, WIND_BASSOON]
|
|
9128
|
+
};
|
|
9129
|
+
var SPEC_TABLES_BY_INSTRUMENTATION = {
|
|
9130
|
+
strings: STRINGS_SPEC_TABLES,
|
|
9131
|
+
horns: HORNS_SPEC_TABLES,
|
|
9132
|
+
winds: WINDS_SPEC_TABLES
|
|
9133
|
+
};
|
|
9134
|
+
function defaultVoiceSpecs(voiceCount, instrumentation = "strings") {
|
|
8738
9135
|
const n = Math.max(ENSEMBLE_MIN_VOICES, Math.min(ENSEMBLE_MAX_VOICES, Math.round(voiceCount)));
|
|
8739
|
-
return
|
|
9136
|
+
return SPEC_TABLES_BY_INSTRUMENTATION[instrumentation][n].map((spec, voiceIndex) => ({ ...spec, voiceIndex }));
|
|
8740
9137
|
}
|
|
8741
9138
|
|
|
8742
9139
|
// src/ensemble-core/enforce-voice.ts
|
|
@@ -8776,10 +9173,14 @@ function enforceVoice(rawNotes, spec, opts) {
|
|
|
8776
9173
|
repairs.push(`voice ${spec.voiceIndex}: dropped note outside the ${opts.bars}-bar clip (start ${n.startBeat})`);
|
|
8777
9174
|
continue;
|
|
8778
9175
|
}
|
|
8779
|
-
|
|
9176
|
+
let durationBeats = Math.max(
|
|
8780
9177
|
MIN_NOTE_DURATION_BEATS,
|
|
8781
9178
|
Math.min(n.durationBeats, clipEnd - n.startBeat)
|
|
8782
9179
|
);
|
|
9180
|
+
if (opts.maxNoteDurationBeats !== void 0 && durationBeats > opts.maxNoteDurationBeats) {
|
|
9181
|
+
durationBeats = Math.max(MIN_NOTE_DURATION_BEATS, opts.maxNoteDurationBeats);
|
|
9182
|
+
repairs.push(`voice ${spec.voiceIndex}: trimmed note at beat ${n.startBeat} to the style's ${opts.maxNoteDurationBeats}-beat stab ceiling`);
|
|
9183
|
+
}
|
|
8783
9184
|
notes.push({ ...n, durationBeats });
|
|
8784
9185
|
}
|
|
8785
9186
|
notes = notes.map((n) => {
|
|
@@ -8940,12 +9341,19 @@ function describeViolations(analysis, rules) {
|
|
|
8940
9341
|
if (pair.onsetIndependence < rules.minOnsetIndependence) {
|
|
8941
9342
|
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
9343
|
}
|
|
9344
|
+
if (rules.maxOnsetIndependence !== void 0 && pair.onsetIndependence > rules.maxOnsetIndependence) {
|
|
9345
|
+
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.`);
|
|
9346
|
+
}
|
|
8943
9347
|
}
|
|
8944
9348
|
return out;
|
|
8945
9349
|
}
|
|
8946
9350
|
|
|
8947
9351
|
// src/ensemble-core/styles.ts
|
|
8948
9352
|
var ENSEMBLE_STYLES = ["counterpoint", "chorale", "interlock"];
|
|
9353
|
+
var STAB_MAX_ONSET_INDEPENDENCE = 0.25;
|
|
9354
|
+
var RIFF_MAX_ONSET_INDEPENDENCE = 0.3;
|
|
9355
|
+
var UNISON_MAX_ONSET_INDEPENDENCE = 0.15;
|
|
9356
|
+
var STAB_MAX_NOTE_DURATION_BEATS = 1;
|
|
8949
9357
|
var STYLE_RULES = {
|
|
8950
9358
|
counterpoint: {
|
|
8951
9359
|
forbidParallelPerfects: true,
|
|
@@ -8964,9 +9372,55 @@ var STYLE_RULES = {
|
|
|
8964
9372
|
forbidVoiceCrossing: false,
|
|
8965
9373
|
minOnsetIndependence: 0.6,
|
|
8966
9374
|
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."
|
|
9375
|
+
},
|
|
9376
|
+
stabs: {
|
|
9377
|
+
forbidParallelPerfects: false,
|
|
9378
|
+
forbidVoiceCrossing: true,
|
|
9379
|
+
minOnsetIndependence: 0,
|
|
9380
|
+
maxOnsetIndependence: STAB_MAX_ONSET_INDEPENDENCE,
|
|
9381
|
+
maxNoteDurationBeats: STAB_MAX_NOTE_DURATION_BEATS,
|
|
9382
|
+
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.`
|
|
9383
|
+
},
|
|
9384
|
+
riffs: {
|
|
9385
|
+
forbidParallelPerfects: false,
|
|
9386
|
+
forbidVoiceCrossing: true,
|
|
9387
|
+
minOnsetIndependence: 0,
|
|
9388
|
+
maxOnsetIndependence: RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9389
|
+
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.'
|
|
9390
|
+
},
|
|
9391
|
+
unison: {
|
|
9392
|
+
forbidParallelPerfects: false,
|
|
9393
|
+
forbidVoiceCrossing: false,
|
|
9394
|
+
minOnsetIndependence: 0,
|
|
9395
|
+
maxOnsetIndependence: UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9396
|
+
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
9397
|
}
|
|
8968
9398
|
};
|
|
8969
9399
|
|
|
9400
|
+
// src/ensemble-core/instrumentation.ts
|
|
9401
|
+
var ENSEMBLE_INSTRUMENTATIONS = [
|
|
9402
|
+
"strings",
|
|
9403
|
+
"horns",
|
|
9404
|
+
"winds"
|
|
9405
|
+
];
|
|
9406
|
+
var STYLES_FOR_INSTRUMENTATION = {
|
|
9407
|
+
strings: ["counterpoint", "chorale", "interlock"],
|
|
9408
|
+
horns: ["stabs", "riffs", "unison"],
|
|
9409
|
+
winds: ["counterpoint", "chorale", "interlock"]
|
|
9410
|
+
};
|
|
9411
|
+
var DEFAULT_STYLE_FOR_INSTRUMENTATION = {
|
|
9412
|
+
strings: "counterpoint",
|
|
9413
|
+
horns: "stabs",
|
|
9414
|
+
winds: "chorale"
|
|
9415
|
+
};
|
|
9416
|
+
function normalizeInstrumentation(raw) {
|
|
9417
|
+
return ENSEMBLE_INSTRUMENTATIONS.includes(raw) ? raw : "strings";
|
|
9418
|
+
}
|
|
9419
|
+
function styleForInstrumentation(instrumentation, rawStyle) {
|
|
9420
|
+
const allowed = STYLES_FOR_INSTRUMENTATION[instrumentation];
|
|
9421
|
+
return allowed.includes(rawStyle ?? "") ? rawStyle : DEFAULT_STYLE_FOR_INSTRUMENTATION[instrumentation];
|
|
9422
|
+
}
|
|
9423
|
+
|
|
8970
9424
|
// src/ensemble-core/ensemble-schema.ts
|
|
8971
9425
|
var SUBMIT_ENSEMBLE_TOOL_NAME = "submit_ensemble";
|
|
8972
9426
|
function buildSubmitEnsembleParameters(voiceCount) {
|
|
@@ -9048,9 +9502,13 @@ function voiceContractLine(spec) {
|
|
|
9048
9502
|
const root = spec.rootOnly ? " ROOT PITCH CLASS ONLY (each bar's chord root)." : "";
|
|
9049
9503
|
return `- Voice ${spec.voiceIndex + 1} (${spec.label}): MIDI ${spec.registerLow}-${spec.registerHigh}, max ${spec.maxNotesPerBar} notes/bar, rhythm: ${spec.rhythmPalette}. ${spec.harmonicDiscipline}.${root}`;
|
|
9050
9504
|
}
|
|
9051
|
-
|
|
9505
|
+
var WOVEN_PERSONA = {
|
|
9506
|
+
strings: "You are an ensemble composer.",
|
|
9507
|
+
winds: "You are a wind-ensemble composer writing for chamber winds (flutes, oboe, clarinet, french horn, bassoon)."
|
|
9508
|
+
};
|
|
9509
|
+
function buildWovenPrompt(specs, style, instrumentation) {
|
|
9052
9510
|
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9053
|
-
return
|
|
9511
|
+
return `${WOVEN_PERSONA[instrumentation]} Compose ${specs.length} voices as ONE piece of music \u2014 not ${specs.length} independent parts.
|
|
9054
9512
|
|
|
9055
9513
|
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9056
9514
|
|
|
@@ -9067,6 +9525,28 @@ ${styleParagraph}
|
|
|
9067
9525
|
PER-VOICE CONTRACTS:
|
|
9068
9526
|
${specs.map(voiceContractLine).join("\n")}`;
|
|
9069
9527
|
}
|
|
9528
|
+
function buildSectionPrompt(specs, style) {
|
|
9529
|
+
const styleParagraph = STYLE_RULES[style].promptParagraph;
|
|
9530
|
+
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.
|
|
9531
|
+
|
|
9532
|
+
Submit your composition by calling the ${SUBMIT_ENSEMBLE_TOOL_NAME} tool with all ${specs.length} voices.
|
|
9533
|
+
|
|
9534
|
+
THE SECTION RULES (most important):
|
|
9535
|
+
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.
|
|
9536
|
+
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.
|
|
9537
|
+
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.
|
|
9538
|
+
4. Follow the chord progression in the musical context exactly \u2014 every hit is voiced from the chord sounding at that beat.
|
|
9539
|
+
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.
|
|
9540
|
+
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).
|
|
9541
|
+
|
|
9542
|
+
${styleParagraph}
|
|
9543
|
+
|
|
9544
|
+
PER-VOICE CONTRACTS:
|
|
9545
|
+
${specs.map(voiceContractLine).join("\n")}`;
|
|
9546
|
+
}
|
|
9547
|
+
function buildEnsembleSystemPrompt(specs, style, instrumentation = "strings") {
|
|
9548
|
+
return instrumentation === "horns" ? buildSectionPrompt(specs, style) : buildWovenPrompt(specs, style, instrumentation);
|
|
9549
|
+
}
|
|
9070
9550
|
function buildViolationRetrySuffix(violations) {
|
|
9071
9551
|
if (violations.length === 0) return "";
|
|
9072
9552
|
return `
|
|
@@ -9241,10 +9721,12 @@ export {
|
|
|
9241
9721
|
DB_MIN,
|
|
9242
9722
|
DEFAULT_FX_CATEGORY_DETAIL,
|
|
9243
9723
|
DEFAULT_FX_DRY_WET,
|
|
9724
|
+
DEFAULT_STYLE_FOR_INSTRUMENTATION,
|
|
9244
9725
|
DRAG_DEAD_ZONE,
|
|
9245
9726
|
DownloadPackButton,
|
|
9246
9727
|
EMPTY_FX_DETAIL_STATE,
|
|
9247
9728
|
EMPTY_FX_STATE,
|
|
9729
|
+
ENSEMBLE_INSTRUMENTATIONS,
|
|
9248
9730
|
ENSEMBLE_MAX_VOICES,
|
|
9249
9731
|
ENSEMBLE_MIN_VOICES,
|
|
9250
9732
|
ENSEMBLE_STYLES,
|
|
@@ -9259,7 +9741,9 @@ export {
|
|
|
9259
9741
|
FxToggleBar,
|
|
9260
9742
|
GUTTER_W,
|
|
9261
9743
|
GeneratorPanelShell,
|
|
9744
|
+
GroupCollapseChevron,
|
|
9262
9745
|
GroupFadeTrackRow,
|
|
9746
|
+
HORN_MAX_NOTES_PER_BAR,
|
|
9263
9747
|
ImportTrackModal,
|
|
9264
9748
|
TrackDrawer as InstrumentDrawer,
|
|
9265
9749
|
LevelMeter,
|
|
@@ -9273,8 +9757,12 @@ export {
|
|
|
9273
9757
|
PianoRollEditor,
|
|
9274
9758
|
PluginError,
|
|
9275
9759
|
RESIZE_HANDLE_PX,
|
|
9760
|
+
RIFF_MAX_ONSET_INDEPENDENCE,
|
|
9276
9761
|
ROW_HEIGHT,
|
|
9277
9762
|
SLIDER_UNITY,
|
|
9763
|
+
STAB_MAX_NOTE_DURATION_BEATS,
|
|
9764
|
+
STAB_MAX_ONSET_INDEPENDENCE,
|
|
9765
|
+
STYLES_FOR_INSTRUMENTATION,
|
|
9278
9766
|
STYLE_RULES,
|
|
9279
9767
|
SUBMIT_ENSEMBLE_TOOL_NAME,
|
|
9280
9768
|
SamplePackCTACard,
|
|
@@ -9288,6 +9776,7 @@ export {
|
|
|
9288
9776
|
TrackMeterStrip,
|
|
9289
9777
|
TrackRow,
|
|
9290
9778
|
TransitionDesigner,
|
|
9779
|
+
UNISON_MAX_ONSET_INDEPENDENCE,
|
|
9291
9780
|
VolumeSlider,
|
|
9292
9781
|
WaveformView,
|
|
9293
9782
|
analyzeEnsemble,
|
|
@@ -9322,6 +9811,7 @@ export {
|
|
|
9322
9811
|
moveItem,
|
|
9323
9812
|
nearestPitchWithPc,
|
|
9324
9813
|
newTrackState,
|
|
9814
|
+
normalizeInstrumentation,
|
|
9325
9815
|
normalizeSlots,
|
|
9326
9816
|
padPair,
|
|
9327
9817
|
padSlots,
|
|
@@ -9345,6 +9835,7 @@ export {
|
|
|
9345
9835
|
slotsEqual,
|
|
9346
9836
|
soundIdentity,
|
|
9347
9837
|
splitFadeEntries,
|
|
9838
|
+
styleForInstrumentation,
|
|
9348
9839
|
synthesizeCuePoints,
|
|
9349
9840
|
tokenizePrompt,
|
|
9350
9841
|
trackDataKey,
|