@signalsandsorcery/plugin-sdk 1.0.0 → 1.1.0
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/README.md +368 -0
- package/dist/index.d.mts +42 -6
- package/dist/index.d.ts +42 -6
- package/dist/index.js +56 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# @signalsandsorcery/plugin-sdk
|
|
2
|
+
|
|
3
|
+
Plugin SDK for building custom generator plugins for [Signals & Sorcery](https://signalsandsorcery.com) — an AI-powered music production workstation.
|
|
4
|
+
|
|
5
|
+
Plugins extend the Loop Workstation with custom input generators that create MIDI patterns, manage audio samples, generate AI audio textures, or combine all three. Each plugin gets its own accordion section in the workstation UI and a scoped `PluginHost` API for interacting with tracks, MIDI, audio, and more.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @signalsandsorcery/plugin-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Documentation
|
|
14
|
+
|
|
15
|
+
Full documentation is available at [signalsandsorcery.com/plugin-sdk](https://signalsandsorcery.com/plugin-sdk/):
|
|
16
|
+
|
|
17
|
+
- [Getting Started](https://signalsandsorcery.com/plugin-sdk/getting-started.html) — Directory structure, manifest, installation, debugging
|
|
18
|
+
- [API Reference](https://signalsandsorcery.com/plugin-sdk/api-reference.html) — Complete PluginHost API with type signatures and examples
|
|
19
|
+
- [Tutorial: Euclidean Rhythm Generator](https://signalsandsorcery.com/plugin-sdk/tutorial.html) — Build a working plugin from scratch
|
|
20
|
+
|
|
21
|
+
## Reference Plugins
|
|
22
|
+
|
|
23
|
+
These built-in plugins serve as reference implementations:
|
|
24
|
+
|
|
25
|
+
| Plugin | Type | Description | Source |
|
|
26
|
+
|--------|------|-------------|--------|
|
|
27
|
+
| Synth Generator | `midi` | AI-powered MIDI generation with Surge XT presets | [sas-synth-plugin](https://github.com/shiehn/sas-synth-plugin) |
|
|
28
|
+
| Sample Player | `sample` | Sample library browser with time-stretching | [sas-sample-plugin](https://github.com/shiehn/sas-sample-plugin) |
|
|
29
|
+
| Audio Texture | `audio` | AI audio texture generation | [sas-audio-plugin](https://github.com/shiehn/sas-audio-plugin) |
|
|
30
|
+
|
|
31
|
+
## What's in the SDK
|
|
32
|
+
|
|
33
|
+
### Types
|
|
34
|
+
|
|
35
|
+
The core plugin contract — everything you need to implement a generator plugin:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import type {
|
|
39
|
+
GeneratorPlugin, // Interface your plugin class implements
|
|
40
|
+
PluginHost, // Scoped API surface (tracks, MIDI, audio, LLM, etc.)
|
|
41
|
+
PluginUIProps, // Props passed to your React component
|
|
42
|
+
PluginManifest, // plugin.json schema
|
|
43
|
+
MusicalContext, // Key, mode, BPM, bars, chords
|
|
44
|
+
MidiClipData, // MIDI clip payload
|
|
45
|
+
PluginMidiNote, // Individual MIDI note
|
|
46
|
+
PluginTrackHandle, // Track identity returned by createTrack()
|
|
47
|
+
PluginError, // Typed error class with error codes
|
|
48
|
+
} from '@signalsandsorcery/plugin-sdk';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### UI Components
|
|
52
|
+
|
|
53
|
+
Pre-built components that match the host app's visual style:
|
|
54
|
+
|
|
55
|
+
| Component | Description |
|
|
56
|
+
|-----------|-------------|
|
|
57
|
+
| `TrackRow` | Full-featured track row with prompt input, generate/shuffle/copy buttons, mute/solo, volume/pan, FX drawer, instrument drawer, and progress overlay |
|
|
58
|
+
| `VolumeSlider` | Compact horizontal volume slider (0-1) with dB tooltip |
|
|
59
|
+
| `PanSlider` | Compact horizontal pan slider (-1 to +1) with double-click to center |
|
|
60
|
+
| `FxToggleBar` | Per-track FX control panel with 6 categories, 5 presets each, and dry/wet sliders |
|
|
61
|
+
| `SorceryProgressBar` | Animated progress bar with time-based pacing for long operations |
|
|
62
|
+
| `InstrumentDrawer` | Searchable grid of available VST3/AU instrument plugins |
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { TrackRow, VolumeSlider, PanSlider, FxToggleBar, SorceryProgressBar } from '@signalsandsorcery/plugin-sdk';
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Hooks
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { useSceneState } from '@signalsandsorcery/plugin-sdk';
|
|
72
|
+
|
|
73
|
+
// Maintains separate state per scene — preserved across scene switches
|
|
74
|
+
const [prompts, setPrompts, setPromptsForScene] = useSceneState(activeSceneId, {});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Constants
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import {
|
|
81
|
+
VALID_INSTRUMENT_ROLES, // ['bass', 'kick', 'snare', 'lead', 'pad', ...]
|
|
82
|
+
PLUGIN_SDK_VERSION, // '1.0.0'
|
|
83
|
+
FX_CATEGORIES, // ['eq', 'compressor', 'chorus', 'phaser', 'delay', 'reverb']
|
|
84
|
+
FX_PRESET_CONFIGS, // Preset definitions for all 6 FX categories
|
|
85
|
+
} from '@signalsandsorcery/plugin-sdk';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
### 1. Create the manifest (`plugin.json`)
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "@my-org/my-plugin",
|
|
95
|
+
"displayName": "My Plugin",
|
|
96
|
+
"version": "1.0.0",
|
|
97
|
+
"description": "A custom generator plugin",
|
|
98
|
+
"generatorType": "midi",
|
|
99
|
+
"main": "index.js",
|
|
100
|
+
"minHostVersion": "1.0.0",
|
|
101
|
+
"capabilities": {
|
|
102
|
+
"requiresLLM": true,
|
|
103
|
+
"requiresSurgeXT": true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Generator types: `midi` | `audio` | `sample` | `hybrid`
|
|
109
|
+
|
|
110
|
+
### 2. Implement the plugin class
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import type { GeneratorPlugin, PluginHost, PluginUIProps, PluginSettingsSchema, MusicalContext } from '@signalsandsorcery/plugin-sdk';
|
|
114
|
+
import { MyPanel } from './components/Panel';
|
|
115
|
+
|
|
116
|
+
export class MyPlugin implements GeneratorPlugin {
|
|
117
|
+
readonly id = '@my-org/my-plugin';
|
|
118
|
+
readonly displayName = 'My Plugin';
|
|
119
|
+
readonly version = '1.0.0';
|
|
120
|
+
readonly description = 'A custom generator plugin';
|
|
121
|
+
readonly generatorType = 'midi' as const;
|
|
122
|
+
|
|
123
|
+
private host: PluginHost | null = null;
|
|
124
|
+
|
|
125
|
+
async activate(host: PluginHost): Promise<void> {
|
|
126
|
+
this.host = host;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async deactivate(): Promise<void> {
|
|
130
|
+
this.host = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getUIComponent() {
|
|
134
|
+
return MyPanel;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getSettingsSchema(): PluginSettingsSchema | null {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Optional lifecycle hooks
|
|
142
|
+
async onSceneChanged(sceneId: string | null): Promise<void> { }
|
|
143
|
+
onContextChanged(context: MusicalContext): void { }
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 3. Build the UI
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import type { PluginUIProps } from '@signalsandsorcery/plugin-sdk';
|
|
151
|
+
|
|
152
|
+
export function MyPanel({ host, activeSceneId, isAuthenticated, isConnected }: PluginUIProps) {
|
|
153
|
+
const handleGenerate = async () => {
|
|
154
|
+
if (!activeSceneId) {
|
|
155
|
+
host.showToast('warning', 'No Scene', 'Select a scene first');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create a track
|
|
160
|
+
const track = await host.createTrack({ name: 'My Track', role: 'lead', loadSynth: true });
|
|
161
|
+
|
|
162
|
+
// Get musical context
|
|
163
|
+
const context = await host.getMusicalContext();
|
|
164
|
+
|
|
165
|
+
// Write MIDI
|
|
166
|
+
await host.writeMidiClip(track.id, {
|
|
167
|
+
startTime: 0,
|
|
168
|
+
endTime: (context.bars * 4 * 60) / context.bpm,
|
|
169
|
+
tempo: context.bpm,
|
|
170
|
+
notes: [
|
|
171
|
+
{ pitch: 60, startBeat: 0, durationBeats: 1, velocity: 100 },
|
|
172
|
+
{ pitch: 64, startBeat: 1, durationBeats: 1, velocity: 90 },
|
|
173
|
+
{ pitch: 67, startBeat: 2, durationBeats: 1, velocity: 85 },
|
|
174
|
+
{ pitch: 72, startBeat: 3, durationBeats: 1, velocity: 100 },
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
host.showToast('success', 'Done', 'Pattern generated');
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div>
|
|
183
|
+
<button onClick={handleGenerate} disabled={!isConnected}>
|
|
184
|
+
Generate
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 4. Install the plugin
|
|
192
|
+
|
|
193
|
+
Place the compiled plugin in:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
~/.signals-and-sorcery/plugins/my-plugin/
|
|
197
|
+
plugin.json
|
|
198
|
+
index.js
|
|
199
|
+
...
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Restart Signals & Sorcery. The plugin appears in the workstation accordion.
|
|
203
|
+
|
|
204
|
+
## PluginHost API Overview
|
|
205
|
+
|
|
206
|
+
All methods are available on the `host` object your plugin receives in `activate()` and via `PluginUIProps.host`. Methods marked with **ownership** can only modify tracks the calling plugin created.
|
|
207
|
+
|
|
208
|
+
### Track Management
|
|
209
|
+
| Method | Description |
|
|
210
|
+
|--------|-------------|
|
|
211
|
+
| `createTrack(options)` | Create a track with name, role, synth, instrument |
|
|
212
|
+
| `deleteTrack(trackId)` | Delete an owned track |
|
|
213
|
+
| `getPluginTracks()` | List all tracks this plugin owns in the active scene |
|
|
214
|
+
| `getTrackInfo(trackId)` | Detailed track state (mute, volume, pan, plugins) |
|
|
215
|
+
| `adoptSceneTracks()` | Re-claim unowned tracks matching generator type |
|
|
216
|
+
| `setTrackMute/Volume/Pan/Solo/Name` | Track property setters |
|
|
217
|
+
| `shufflePreset(trackId)` | Randomize Surge XT preset (keeps MIDI) |
|
|
218
|
+
| `duplicateTrack(trackId)` | Clone track with MIDI + new preset |
|
|
219
|
+
|
|
220
|
+
### MIDI Operations
|
|
221
|
+
| Method | Description |
|
|
222
|
+
|--------|-------------|
|
|
223
|
+
| `writeMidiClip(trackId, clip)` | Write MIDI notes (replaces existing) |
|
|
224
|
+
| `clearMidi(trackId)` | Clear all MIDI from a track |
|
|
225
|
+
| `postProcessMidi(notes, options)` | Quantize, swing, scale enforcement, humanization |
|
|
226
|
+
| `auditionNote(trackId, pitch, velocity, durationMs)` | Preview a single note |
|
|
227
|
+
|
|
228
|
+
### Audio Operations
|
|
229
|
+
| Method | Description |
|
|
230
|
+
|--------|-------------|
|
|
231
|
+
| `writeAudioClip(trackId, filePath, position?)` | Place audio file on track |
|
|
232
|
+
| `generateAudioTexture(request)` | AI audio generation from text prompt |
|
|
233
|
+
|
|
234
|
+
### Plugin/Synth Operations
|
|
235
|
+
| Method | Description |
|
|
236
|
+
|--------|-------------|
|
|
237
|
+
| `loadSynthPlugin(trackId, pluginName)` | Load VST3/AU plugin |
|
|
238
|
+
| `setPluginState/getPluginState` | Save/restore base64-encoded preset data |
|
|
239
|
+
| `getTrackPlugins(trackId)` | List loaded plugins |
|
|
240
|
+
| `getAvailableInstruments()` | Get scanned VST3/AU instruments |
|
|
241
|
+
| `setTrackInstrument(trackId, pluginId)` | Change instrument (preserves MIDI) |
|
|
242
|
+
|
|
243
|
+
### FX Operations
|
|
244
|
+
Six categories in signal chain order: `eq` > `compressor` > `chorus` > `phaser` > `delay` > `reverb`
|
|
245
|
+
|
|
246
|
+
| Method | Description |
|
|
247
|
+
|--------|-------------|
|
|
248
|
+
| `getTrackFxState(trackId)` | Get enabled/preset/dryWet per category |
|
|
249
|
+
| `toggleTrackFx(trackId, category, enabled)` | Enable/disable FX category |
|
|
250
|
+
| `setTrackFxPreset(trackId, category, presetIndex)` | Set FX preset (0-4) |
|
|
251
|
+
| `setTrackFxDryWet(trackId, category, value)` | Set dry/wet mix (0.0-1.0) |
|
|
252
|
+
|
|
253
|
+
### Scene Context
|
|
254
|
+
| Method | Description |
|
|
255
|
+
|--------|-------------|
|
|
256
|
+
| `getGenerationContext(excludeTrackId?)` | Full context + concurrent track MIDI data |
|
|
257
|
+
| `getMusicalContext()` | Key, mode, BPM, bars, genre, chords |
|
|
258
|
+
| `getActiveSceneId()` | Current scene ID (synchronous) |
|
|
259
|
+
| `getSceneList()` | All scenes in the project |
|
|
260
|
+
|
|
261
|
+
### Transport & Events
|
|
262
|
+
| Method | Description |
|
|
263
|
+
|--------|-------------|
|
|
264
|
+
| `onTrackStateChange(listener)` | Real-time mute, solo, volume, pan updates |
|
|
265
|
+
| `onTransportEvent(listener)` | Play, stop, BPM change, position |
|
|
266
|
+
| `onDeckBoundary(listener)` | Loop boundary events (bar, beat, loopCount) |
|
|
267
|
+
| `onSceneChange(listener)` | Scene selection changes |
|
|
268
|
+
| `onEngineReady(listener)` | Engine finished loading tracks |
|
|
269
|
+
| `getTransportState()` | Current playback state snapshot |
|
|
270
|
+
|
|
271
|
+
### LLM Access
|
|
272
|
+
| Method | Description |
|
|
273
|
+
|--------|-------------|
|
|
274
|
+
| `generateWithLLM(request)` | Generate text/JSON (metered, requires auth) |
|
|
275
|
+
| `isLLMAvailable()` | Check auth + gateway reachability |
|
|
276
|
+
|
|
277
|
+
### Preset System
|
|
278
|
+
| Method | Description |
|
|
279
|
+
|--------|-------------|
|
|
280
|
+
| `getPresetCategories(pluginName)` | Available Surge XT categories |
|
|
281
|
+
| `getRandomPreset(category)` | Random preset from category |
|
|
282
|
+
| `getPresetByName(category, name)` | Specific preset lookup |
|
|
283
|
+
| `classifyPresetCategory(description)` | LLM-based text-to-category |
|
|
284
|
+
|
|
285
|
+
### Scene Composition
|
|
286
|
+
| Method | Description |
|
|
287
|
+
|--------|-------------|
|
|
288
|
+
| `composeScene(options)` | Bulk LLM arrangement generation |
|
|
289
|
+
| `onComposeProgress(listener)` | Progress events (planning, generating, complete) |
|
|
290
|
+
|
|
291
|
+
### Data Persistence
|
|
292
|
+
| Method | Description |
|
|
293
|
+
|--------|-------------|
|
|
294
|
+
| `getSceneData/setSceneData/getAllSceneData/deleteSceneData` | Per-scene key-value storage |
|
|
295
|
+
| `getProjectData/setProjectData` | Project-wide storage |
|
|
296
|
+
| `settings.get/set/getAll/onChange` | Global settings (cross-project) |
|
|
297
|
+
| `getDataDirectory()` | Plugin's isolated data directory path |
|
|
298
|
+
|
|
299
|
+
### Plugin Presets
|
|
300
|
+
| Method | Description |
|
|
301
|
+
|--------|-------------|
|
|
302
|
+
| `getPluginPresets(category?)` | Get saved presets for this plugin |
|
|
303
|
+
| `savePluginPreset(options)` | Save a preset (name, category, data) |
|
|
304
|
+
| `deletePluginPreset(id)` | Delete a preset |
|
|
305
|
+
|
|
306
|
+
### File System & Network
|
|
307
|
+
| Method | Description |
|
|
308
|
+
|--------|-------------|
|
|
309
|
+
| `showOpenDialog/showSaveDialog` | Native file dialogs (requires `fileDialog` capability) |
|
|
310
|
+
| `downloadFile/importFile` | Download/copy files to plugin data directory |
|
|
311
|
+
| `httpRequest(options)` | HTTP requests (requires `network` capability with `allowedHosts`) |
|
|
312
|
+
|
|
313
|
+
### Secure Storage
|
|
314
|
+
| Method | Description |
|
|
315
|
+
|--------|-------------|
|
|
316
|
+
| `storeSecret/getSecret/deleteSecret` | OS keychain encryption, per-plugin scoped |
|
|
317
|
+
|
|
318
|
+
### Sample Library
|
|
319
|
+
| Method | Description |
|
|
320
|
+
|--------|-------------|
|
|
321
|
+
| `getSamples/getSampleById` | Query sample library |
|
|
322
|
+
| `importSamples(filePaths)` | Import audio files |
|
|
323
|
+
| `createSampleTrack/deleteSampleTrack` | Manage sample tracks |
|
|
324
|
+
| `getPluginSampleTracks()` | List owned sample tracks |
|
|
325
|
+
| `timeStretchSample(sampleId, targetBpm)` | Time-stretch to target BPM |
|
|
326
|
+
|
|
327
|
+
### Notifications & Progress
|
|
328
|
+
| Method | Description |
|
|
329
|
+
|--------|-------------|
|
|
330
|
+
| `showToast(type, title, message?)` | Toast notification (info/success/warning/error) |
|
|
331
|
+
| `setProgress(trackId, progress)` | Track progress bar (0-100, -1 to hide) |
|
|
332
|
+
| `setStatusMessage(message)` | Accordion header status text |
|
|
333
|
+
| `confirmAction(title, message)` | Confirmation dialog |
|
|
334
|
+
|
|
335
|
+
## Error Codes
|
|
336
|
+
|
|
337
|
+
All errors are `PluginError` instances with a typed `code` property:
|
|
338
|
+
|
|
339
|
+
| Code | Description |
|
|
340
|
+
|------|-------------|
|
|
341
|
+
| `NOT_OWNED` | Tried to modify a track not owned by this plugin |
|
|
342
|
+
| `TRACK_NOT_FOUND` | Track ID doesn't exist in engine |
|
|
343
|
+
| `TRACK_LIMIT_EXCEEDED` | Plugin has too many tracks (default: 16 per scene) |
|
|
344
|
+
| `NO_ACTIVE_SCENE` | No scene is selected |
|
|
345
|
+
| `ENGINE_ERROR` | Audio engine call failed |
|
|
346
|
+
| `INVALID_MIDI` | Malformed MIDI data |
|
|
347
|
+
| `FILE_NOT_FOUND` | Referenced file doesn't exist |
|
|
348
|
+
| `INVALID_FORMAT` | Unsupported audio format |
|
|
349
|
+
| `PLUGIN_NOT_FOUND` | VST/AU plugin not installed |
|
|
350
|
+
| `LLM_BUDGET_EXCEEDED` | Over daily token limit |
|
|
351
|
+
| `LLM_UNAVAILABLE` | LLM gateway unreachable |
|
|
352
|
+
| `NOT_AUTHENTICATED` | User not logged in |
|
|
353
|
+
| `TIMEOUT` | Operation timed out |
|
|
354
|
+
| `CANCELLED` | User cancelled the operation |
|
|
355
|
+
| `INCOMPATIBLE` | Plugin requires newer SDK version |
|
|
356
|
+
| `CAPABILITY_DENIED` | Plugin lacks required capability in manifest |
|
|
357
|
+
| `SECRET_NOT_FOUND` | Secret key doesn't exist |
|
|
358
|
+
|
|
359
|
+
## Security Model
|
|
360
|
+
|
|
361
|
+
- **Ownership scoping** — Plugins can only modify tracks they created (enforced at runtime)
|
|
362
|
+
- **Capability gating** — Network and file system access require manifest declarations
|
|
363
|
+
- **Secret isolation** — Each plugin's secrets are encrypted and scoped per plugin
|
|
364
|
+
- **Track limits** — 16 tracks per plugin per scene (configurable)
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -163,6 +163,10 @@ interface PluginHost {
|
|
|
163
163
|
getTrackInstrument(trackId: string): Promise<InstrumentDescriptor | null>;
|
|
164
164
|
/** Change the instrument plugin on a track. Preserves MIDI data. */
|
|
165
165
|
setTrackInstrument(trackId: string, pluginId: string): Promise<void>;
|
|
166
|
+
/** Open the instrument plugin's native editor GUI as a floating window. */
|
|
167
|
+
showInstrumentEditor(trackId: string): Promise<void>;
|
|
168
|
+
/** Close the instrument plugin's editor window. */
|
|
169
|
+
hideInstrumentEditor(trackId: string): Promise<void>;
|
|
166
170
|
/** Get the FULL generation context for the active scene. */
|
|
167
171
|
getGenerationContext(excludeTrackId?: string): Promise<PluginGenerationContext>;
|
|
168
172
|
/** Get lightweight musical context (no concurrent track MIDI data). */
|
|
@@ -267,6 +271,24 @@ interface PluginHost {
|
|
|
267
271
|
logMetric(name: string, durationMs: number, metadata?: Record<string, unknown>): void;
|
|
268
272
|
/** Start a timer. Returns a stop function that logs the duration. */
|
|
269
273
|
startTimer(name: string): () => void;
|
|
274
|
+
/** Split an audio track into stems (vocals, drums, bass, other). Creates new muted tracks. */
|
|
275
|
+
splitStems(trackId: string): Promise<PluginStemSplitResult>;
|
|
276
|
+
/** Check if the stem splitter binary is available. */
|
|
277
|
+
isStemSplitterAvailable(): Promise<boolean>;
|
|
278
|
+
}
|
|
279
|
+
/** Stem type identifiers */
|
|
280
|
+
type StemType = 'vocals' | 'drums' | 'bass' | 'other';
|
|
281
|
+
/** Result of splitting an audio track into stems */
|
|
282
|
+
interface PluginStemSplitResult {
|
|
283
|
+
/** Created stem tracks with audio loaded (all auto-muted) */
|
|
284
|
+
stems: PluginStemTrackInfo[];
|
|
285
|
+
}
|
|
286
|
+
/** Information about a single stem track created by stem splitting */
|
|
287
|
+
interface PluginStemTrackInfo {
|
|
288
|
+
/** The stem type (vocals, drums, bass, other) */
|
|
289
|
+
stemType: StemType;
|
|
290
|
+
/** Track handle for the new stem track */
|
|
291
|
+
track: PluginTrackHandle;
|
|
270
292
|
}
|
|
271
293
|
interface ExportedPluginData {
|
|
272
294
|
pluginId: string;
|
|
@@ -902,14 +924,20 @@ interface SDKTrackRowProps {
|
|
|
902
924
|
instrumentsLoading?: boolean;
|
|
903
925
|
/** Re-scan for instruments */
|
|
904
926
|
onRefreshInstruments?: () => void;
|
|
927
|
+
/** Which stage the instrument drawer is in */
|
|
928
|
+
instrumentDrawerStage?: 'instruments' | 'editor';
|
|
929
|
+
/** Called when user clicks "Open Editor" */
|
|
930
|
+
onShowEditor?: () => void;
|
|
931
|
+
/** Called when user wants to go back from editor view */
|
|
932
|
+
onBackToInstruments?: () => void;
|
|
905
933
|
}
|
|
906
|
-
declare function TrackRow({ track, prompt, runtimeState, fxDetailState, fxDrawerOpen, isGenerating, isAuthenticated, error, hasMidi, generationProgress, estimatedGenerationMs, onPromptChange, onGenerate, onShuffle, onCopy, onDelete, contentSlot, onMuteToggle, onSoloToggle, onVolumeChange, onPanChange, onFxToggle, onFxPresetChange, onFxDryWetChange, onToggleFxDrawer, onProgressChange, accentColor, instrumentName, instrumentMissing, instrumentDrawerOpen, onToggleInstrumentDrawer, availableInstruments, currentInstrumentPluginId, onInstrumentSelect, instrumentsLoading, onRefreshInstruments, }: SDKTrackRowProps): React.ReactElement;
|
|
934
|
+
declare function TrackRow({ track, prompt, runtimeState, fxDetailState, fxDrawerOpen, isGenerating, isAuthenticated, error, hasMidi, generationProgress, estimatedGenerationMs, onPromptChange, onGenerate, onShuffle, onCopy, onDelete, contentSlot, onMuteToggle, onSoloToggle, onVolumeChange, onPanChange, onFxToggle, onFxPresetChange, onFxDryWetChange, onToggleFxDrawer, onProgressChange, accentColor, instrumentName, instrumentMissing, instrumentDrawerOpen, onToggleInstrumentDrawer, availableInstruments, currentInstrumentPluginId, onInstrumentSelect, instrumentsLoading, onRefreshInstruments, instrumentDrawerStage, onShowEditor, onBackToInstruments, }: SDKTrackRowProps): React.ReactElement;
|
|
907
935
|
|
|
908
936
|
/**
|
|
909
|
-
* InstrumentDrawer —
|
|
937
|
+
* InstrumentDrawer — Two-stage nested menu for instrument selection + editor access.
|
|
910
938
|
*
|
|
911
|
-
*
|
|
912
|
-
* Shows
|
|
939
|
+
* Stage 1 (instruments): Searchable grid of available VST3/AU instrument plugins.
|
|
940
|
+
* Stage 2 (editor): Shows "Open Editor" button for the selected plugin's native GUI.
|
|
913
941
|
*/
|
|
914
942
|
|
|
915
943
|
interface InstrumentDrawerProps {
|
|
@@ -923,8 +951,16 @@ interface InstrumentDrawerProps {
|
|
|
923
951
|
onSelect: (pluginId: string) => void;
|
|
924
952
|
/** Called when user clicks refresh to re-scan plugins */
|
|
925
953
|
onRefresh: () => void;
|
|
954
|
+
/** Which stage the drawer is in */
|
|
955
|
+
stage?: 'instruments' | 'editor';
|
|
956
|
+
/** Called when user clicks "Open Editor" */
|
|
957
|
+
onShowEditor?: () => void;
|
|
958
|
+
/** Called when user wants to go back from editor view to instrument list */
|
|
959
|
+
onBackToInstruments?: () => void;
|
|
960
|
+
/** Name of the selected instrument (for display in editor header) */
|
|
961
|
+
selectedInstrumentName?: string | null;
|
|
926
962
|
}
|
|
927
|
-
declare function InstrumentDrawer({ instruments, currentPluginId, isLoading, onSelect, onRefresh, }: InstrumentDrawerProps): React.ReactElement;
|
|
963
|
+
declare function InstrumentDrawer({ instruments, currentPluginId, isLoading, onSelect, onRefresh, stage, onShowEditor, onBackToInstruments, selectedInstrumentName, }: InstrumentDrawerProps): React.ReactElement;
|
|
928
964
|
|
|
929
965
|
/**
|
|
930
966
|
* VolumeSlider Component
|
|
@@ -1133,4 +1169,4 @@ declare function sliderToDb(slider: number): number;
|
|
|
1133
1169
|
*/
|
|
1134
1170
|
declare function dbToSlider(db: number): number;
|
|
1135
1171
|
|
|
1136
|
-
export { type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type ExportedPluginData, FX_CATEGORIES, FX_CHAIN_ORDER, FX_DISPLAY_LABELS, FX_ENGINE_PLUGIN_NAMES, FX_PRESET_CONFIGS, type FxCategory, type FxCategoryDetailState, type FxPreset, type FxPresetConfig, type FxPresetData, type FxPresetDataEntry, FxToggleBar, type FxToggleBarProps, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type PluginAudioTextureRequest, type PluginAudioTextureResult, type PluginCapabilities, type PluginChordSegment, type PluginChordTiming, type PluginConcurrentTrackInfo, type PluginDownloadOptions, PluginError, type PluginErrorCode, type PluginFileDialogOptions, type PluginFxCategoryDetailState, type PluginGenerationContext, type PluginHost, type PluginHttpRequestOptions, type PluginHttpResponse, type PluginManifest, type PluginMidiNote, type PluginPresetData, type PluginPresetInfo, type PluginRegistration, type PluginSampleFilter, type PluginSampleImportResult, type PluginSampleInfo, type PluginSampleTrackInfo, type PluginSceneContext, type PluginSceneInfo, type PluginSettingsSchema, type PluginSettingsStore, type PluginStatus, type PluginSynthInfo, type PluginTrackFxDetailState, type PluginTrackHandle, type PluginTrackInfo, type PluginTrackRuntimeState, type PluginTransportState, type PluginUIProps, type PostProcessOptions, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VALID_INSTRUMENT_ROLES, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
|
|
1172
|
+
export { type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type ExportedPluginData, FX_CATEGORIES, FX_CHAIN_ORDER, FX_DISPLAY_LABELS, FX_ENGINE_PLUGIN_NAMES, FX_PRESET_CONFIGS, type FxCategory, type FxCategoryDetailState, type FxPreset, type FxPresetConfig, type FxPresetData, type FxPresetDataEntry, FxToggleBar, type FxToggleBarProps, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type PluginAudioTextureRequest, type PluginAudioTextureResult, type PluginCapabilities, type PluginChordSegment, type PluginChordTiming, type PluginConcurrentTrackInfo, type PluginDownloadOptions, PluginError, type PluginErrorCode, type PluginFileDialogOptions, type PluginFxCategoryDetailState, type PluginGenerationContext, type PluginHost, type PluginHttpRequestOptions, type PluginHttpResponse, type PluginManifest, type PluginMidiNote, type PluginPresetData, type PluginPresetInfo, type PluginRegistration, type PluginSampleFilter, type PluginSampleImportResult, type PluginSampleInfo, type PluginSampleTrackInfo, type PluginSceneContext, type PluginSceneInfo, type PluginSettingsSchema, type PluginSettingsStore, type PluginStatus, type PluginStemSplitResult, type PluginStemTrackInfo, type PluginSynthInfo, type PluginTrackFxDetailState, type PluginTrackHandle, type PluginTrackInfo, type PluginTrackRuntimeState, type PluginTransportState, type PluginUIProps, type PostProcessOptions, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type StemType, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VALID_INSTRUMENT_ROLES, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
|
package/dist/index.d.ts
CHANGED
|
@@ -163,6 +163,10 @@ interface PluginHost {
|
|
|
163
163
|
getTrackInstrument(trackId: string): Promise<InstrumentDescriptor | null>;
|
|
164
164
|
/** Change the instrument plugin on a track. Preserves MIDI data. */
|
|
165
165
|
setTrackInstrument(trackId: string, pluginId: string): Promise<void>;
|
|
166
|
+
/** Open the instrument plugin's native editor GUI as a floating window. */
|
|
167
|
+
showInstrumentEditor(trackId: string): Promise<void>;
|
|
168
|
+
/** Close the instrument plugin's editor window. */
|
|
169
|
+
hideInstrumentEditor(trackId: string): Promise<void>;
|
|
166
170
|
/** Get the FULL generation context for the active scene. */
|
|
167
171
|
getGenerationContext(excludeTrackId?: string): Promise<PluginGenerationContext>;
|
|
168
172
|
/** Get lightweight musical context (no concurrent track MIDI data). */
|
|
@@ -267,6 +271,24 @@ interface PluginHost {
|
|
|
267
271
|
logMetric(name: string, durationMs: number, metadata?: Record<string, unknown>): void;
|
|
268
272
|
/** Start a timer. Returns a stop function that logs the duration. */
|
|
269
273
|
startTimer(name: string): () => void;
|
|
274
|
+
/** Split an audio track into stems (vocals, drums, bass, other). Creates new muted tracks. */
|
|
275
|
+
splitStems(trackId: string): Promise<PluginStemSplitResult>;
|
|
276
|
+
/** Check if the stem splitter binary is available. */
|
|
277
|
+
isStemSplitterAvailable(): Promise<boolean>;
|
|
278
|
+
}
|
|
279
|
+
/** Stem type identifiers */
|
|
280
|
+
type StemType = 'vocals' | 'drums' | 'bass' | 'other';
|
|
281
|
+
/** Result of splitting an audio track into stems */
|
|
282
|
+
interface PluginStemSplitResult {
|
|
283
|
+
/** Created stem tracks with audio loaded (all auto-muted) */
|
|
284
|
+
stems: PluginStemTrackInfo[];
|
|
285
|
+
}
|
|
286
|
+
/** Information about a single stem track created by stem splitting */
|
|
287
|
+
interface PluginStemTrackInfo {
|
|
288
|
+
/** The stem type (vocals, drums, bass, other) */
|
|
289
|
+
stemType: StemType;
|
|
290
|
+
/** Track handle for the new stem track */
|
|
291
|
+
track: PluginTrackHandle;
|
|
270
292
|
}
|
|
271
293
|
interface ExportedPluginData {
|
|
272
294
|
pluginId: string;
|
|
@@ -902,14 +924,20 @@ interface SDKTrackRowProps {
|
|
|
902
924
|
instrumentsLoading?: boolean;
|
|
903
925
|
/** Re-scan for instruments */
|
|
904
926
|
onRefreshInstruments?: () => void;
|
|
927
|
+
/** Which stage the instrument drawer is in */
|
|
928
|
+
instrumentDrawerStage?: 'instruments' | 'editor';
|
|
929
|
+
/** Called when user clicks "Open Editor" */
|
|
930
|
+
onShowEditor?: () => void;
|
|
931
|
+
/** Called when user wants to go back from editor view */
|
|
932
|
+
onBackToInstruments?: () => void;
|
|
905
933
|
}
|
|
906
|
-
declare function TrackRow({ track, prompt, runtimeState, fxDetailState, fxDrawerOpen, isGenerating, isAuthenticated, error, hasMidi, generationProgress, estimatedGenerationMs, onPromptChange, onGenerate, onShuffle, onCopy, onDelete, contentSlot, onMuteToggle, onSoloToggle, onVolumeChange, onPanChange, onFxToggle, onFxPresetChange, onFxDryWetChange, onToggleFxDrawer, onProgressChange, accentColor, instrumentName, instrumentMissing, instrumentDrawerOpen, onToggleInstrumentDrawer, availableInstruments, currentInstrumentPluginId, onInstrumentSelect, instrumentsLoading, onRefreshInstruments, }: SDKTrackRowProps): React.ReactElement;
|
|
934
|
+
declare function TrackRow({ track, prompt, runtimeState, fxDetailState, fxDrawerOpen, isGenerating, isAuthenticated, error, hasMidi, generationProgress, estimatedGenerationMs, onPromptChange, onGenerate, onShuffle, onCopy, onDelete, contentSlot, onMuteToggle, onSoloToggle, onVolumeChange, onPanChange, onFxToggle, onFxPresetChange, onFxDryWetChange, onToggleFxDrawer, onProgressChange, accentColor, instrumentName, instrumentMissing, instrumentDrawerOpen, onToggleInstrumentDrawer, availableInstruments, currentInstrumentPluginId, onInstrumentSelect, instrumentsLoading, onRefreshInstruments, instrumentDrawerStage, onShowEditor, onBackToInstruments, }: SDKTrackRowProps): React.ReactElement;
|
|
907
935
|
|
|
908
936
|
/**
|
|
909
|
-
* InstrumentDrawer —
|
|
937
|
+
* InstrumentDrawer — Two-stage nested menu for instrument selection + editor access.
|
|
910
938
|
*
|
|
911
|
-
*
|
|
912
|
-
* Shows
|
|
939
|
+
* Stage 1 (instruments): Searchable grid of available VST3/AU instrument plugins.
|
|
940
|
+
* Stage 2 (editor): Shows "Open Editor" button for the selected plugin's native GUI.
|
|
913
941
|
*/
|
|
914
942
|
|
|
915
943
|
interface InstrumentDrawerProps {
|
|
@@ -923,8 +951,16 @@ interface InstrumentDrawerProps {
|
|
|
923
951
|
onSelect: (pluginId: string) => void;
|
|
924
952
|
/** Called when user clicks refresh to re-scan plugins */
|
|
925
953
|
onRefresh: () => void;
|
|
954
|
+
/** Which stage the drawer is in */
|
|
955
|
+
stage?: 'instruments' | 'editor';
|
|
956
|
+
/** Called when user clicks "Open Editor" */
|
|
957
|
+
onShowEditor?: () => void;
|
|
958
|
+
/** Called when user wants to go back from editor view to instrument list */
|
|
959
|
+
onBackToInstruments?: () => void;
|
|
960
|
+
/** Name of the selected instrument (for display in editor header) */
|
|
961
|
+
selectedInstrumentName?: string | null;
|
|
926
962
|
}
|
|
927
|
-
declare function InstrumentDrawer({ instruments, currentPluginId, isLoading, onSelect, onRefresh, }: InstrumentDrawerProps): React.ReactElement;
|
|
963
|
+
declare function InstrumentDrawer({ instruments, currentPluginId, isLoading, onSelect, onRefresh, stage, onShowEditor, onBackToInstruments, selectedInstrumentName, }: InstrumentDrawerProps): React.ReactElement;
|
|
928
964
|
|
|
929
965
|
/**
|
|
930
966
|
* VolumeSlider Component
|
|
@@ -1133,4 +1169,4 @@ declare function sliderToDb(slider: number): number;
|
|
|
1133
1169
|
*/
|
|
1134
1170
|
declare function dbToSlider(db: number): number;
|
|
1135
1171
|
|
|
1136
|
-
export { type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type ExportedPluginData, FX_CATEGORIES, FX_CHAIN_ORDER, FX_DISPLAY_LABELS, FX_ENGINE_PLUGIN_NAMES, FX_PRESET_CONFIGS, type FxCategory, type FxCategoryDetailState, type FxPreset, type FxPresetConfig, type FxPresetData, type FxPresetDataEntry, FxToggleBar, type FxToggleBarProps, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type PluginAudioTextureRequest, type PluginAudioTextureResult, type PluginCapabilities, type PluginChordSegment, type PluginChordTiming, type PluginConcurrentTrackInfo, type PluginDownloadOptions, PluginError, type PluginErrorCode, type PluginFileDialogOptions, type PluginFxCategoryDetailState, type PluginGenerationContext, type PluginHost, type PluginHttpRequestOptions, type PluginHttpResponse, type PluginManifest, type PluginMidiNote, type PluginPresetData, type PluginPresetInfo, type PluginRegistration, type PluginSampleFilter, type PluginSampleImportResult, type PluginSampleInfo, type PluginSampleTrackInfo, type PluginSceneContext, type PluginSceneInfo, type PluginSettingsSchema, type PluginSettingsStore, type PluginStatus, type PluginSynthInfo, type PluginTrackFxDetailState, type PluginTrackHandle, type PluginTrackInfo, type PluginTrackRuntimeState, type PluginTransportState, type PluginUIProps, type PostProcessOptions, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VALID_INSTRUMENT_ROLES, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
|
|
1172
|
+
export { type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type ExportedPluginData, FX_CATEGORIES, FX_CHAIN_ORDER, FX_DISPLAY_LABELS, FX_ENGINE_PLUGIN_NAMES, FX_PRESET_CONFIGS, type FxCategory, type FxCategoryDetailState, type FxPreset, type FxPresetConfig, type FxPresetData, type FxPresetDataEntry, FxToggleBar, type FxToggleBarProps, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type PluginAudioTextureRequest, type PluginAudioTextureResult, type PluginCapabilities, type PluginChordSegment, type PluginChordTiming, type PluginConcurrentTrackInfo, type PluginDownloadOptions, PluginError, type PluginErrorCode, type PluginFileDialogOptions, type PluginFxCategoryDetailState, type PluginGenerationContext, type PluginHost, type PluginHttpRequestOptions, type PluginHttpResponse, type PluginManifest, type PluginMidiNote, type PluginPresetData, type PluginPresetInfo, type PluginRegistration, type PluginSampleFilter, type PluginSampleImportResult, type PluginSampleInfo, type PluginSampleTrackInfo, type PluginSceneContext, type PluginSceneInfo, type PluginSettingsSchema, type PluginSettingsStore, type PluginStatus, type PluginStemSplitResult, type PluginStemTrackInfo, type PluginSynthInfo, type PluginTrackFxDetailState, type PluginTrackHandle, type PluginTrackInfo, type PluginTrackRuntimeState, type PluginTransportState, type PluginUIProps, type PostProcessOptions, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type StemType, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VALID_INSTRUMENT_ROLES, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
|
package/dist/index.js
CHANGED
|
@@ -125,24 +125,60 @@ function InstrumentDrawer({
|
|
|
125
125
|
currentPluginId,
|
|
126
126
|
isLoading,
|
|
127
127
|
onSelect,
|
|
128
|
-
onRefresh
|
|
128
|
+
onRefresh,
|
|
129
|
+
stage = "instruments",
|
|
130
|
+
onShowEditor,
|
|
131
|
+
onBackToInstruments,
|
|
132
|
+
selectedInstrumentName
|
|
129
133
|
}) {
|
|
130
134
|
const [search, setSearch] = (0, import_react.useState)("");
|
|
131
135
|
const SURGE_XT_DEFAULT_ID = "Surge XT";
|
|
132
136
|
const filtered = (0, import_react.useMemo)(() => {
|
|
133
|
-
|
|
137
|
+
let all = instruments.filter(
|
|
134
138
|
(i) => i.name !== "Surge XT"
|
|
135
139
|
);
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
if (search.trim()) {
|
|
141
|
+
const q = search.toLowerCase();
|
|
142
|
+
all = all.filter(
|
|
143
|
+
(i) => i.name.toLowerCase().includes(q) || i.manufacturer.toLowerCase().includes(q)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (currentPluginId) {
|
|
147
|
+
const selectedIdx = all.findIndex((i) => i.pluginId === currentPluginId);
|
|
148
|
+
if (selectedIdx > 0) {
|
|
149
|
+
const [selected] = all.splice(selectedIdx, 1);
|
|
150
|
+
all.unshift(selected);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return all;
|
|
154
|
+
}, [instruments, search, currentPluginId]);
|
|
142
155
|
const isDefaultSelected = currentPluginId === null;
|
|
143
156
|
const isSelected = (pluginId) => {
|
|
144
157
|
return pluginId === currentPluginId;
|
|
145
158
|
};
|
|
159
|
+
if (stage === "editor") {
|
|
160
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
163
|
+
"button",
|
|
164
|
+
{
|
|
165
|
+
onClick: () => onBackToInstruments?.(),
|
|
166
|
+
className: "px-2 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-accent hover:border-sas-accent transition-colors",
|
|
167
|
+
children: "\u2190 Back"
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-sas-muted font-medium truncate flex-1", children: selectedInstrumentName ?? "Plugin" })
|
|
171
|
+
] }),
|
|
172
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
onClick: () => onShowEditor?.(),
|
|
176
|
+
className: "w-full py-2 text-xs font-medium rounded-sm border border-sas-accent bg-sas-accent/20 text-sas-accent hover:bg-sas-accent/40 transition-colors",
|
|
177
|
+
children: "Open Plugin Editor"
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
] });
|
|
181
|
+
}
|
|
146
182
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
147
183
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
148
184
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -1007,7 +1043,10 @@ function TrackRow({
|
|
|
1007
1043
|
currentInstrumentPluginId,
|
|
1008
1044
|
onInstrumentSelect,
|
|
1009
1045
|
instrumentsLoading,
|
|
1010
|
-
onRefreshInstruments
|
|
1046
|
+
onRefreshInstruments,
|
|
1047
|
+
instrumentDrawerStage,
|
|
1048
|
+
onShowEditor,
|
|
1049
|
+
onBackToInstruments
|
|
1011
1050
|
}) {
|
|
1012
1051
|
const { muted: isMuted, solo: isSoloed, volume: currentVolume, pan: currentPan } = runtimeState;
|
|
1013
1052
|
const needsGeneration = !!(prompt?.trim() && !hasMidi && !isGenerating);
|
|
@@ -1152,9 +1191,9 @@ function TrackRow({
|
|
|
1152
1191
|
{
|
|
1153
1192
|
"data-testid": "sdk-shuffle-button",
|
|
1154
1193
|
onClick: onShuffle,
|
|
1155
|
-
disabled: !hasMidi || isGenerating,
|
|
1156
|
-
className: `w-14 py-0.5 rounded-sm text-xs font-medium transition-colors border ${!hasMidi || 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"}`,
|
|
1157
|
-
title: hasMidi ? "Re-roll sound (keep MIDI)" : "Generate MIDI first",
|
|
1194
|
+
disabled: !hasMidi || isGenerating || !!currentInstrumentPluginId,
|
|
1195
|
+
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"}`,
|
|
1196
|
+
title: currentInstrumentPluginId ? "Shuffle only works with default Surge XT" : hasMidi ? "Re-roll sound (keep MIDI)" : "Generate MIDI first",
|
|
1158
1197
|
children: "Shuffle"
|
|
1159
1198
|
}
|
|
1160
1199
|
),
|
|
@@ -1214,7 +1253,11 @@ function TrackRow({
|
|
|
1214
1253
|
currentPluginId: currentInstrumentPluginId ?? null,
|
|
1215
1254
|
isLoading: instrumentsLoading ?? false,
|
|
1216
1255
|
onSelect: onInstrumentSelect,
|
|
1217
|
-
onRefresh: onRefreshInstruments
|
|
1256
|
+
onRefresh: onRefreshInstruments,
|
|
1257
|
+
stage: instrumentDrawerStage,
|
|
1258
|
+
onShowEditor,
|
|
1259
|
+
onBackToInstruments,
|
|
1260
|
+
selectedInstrumentName: instrumentName
|
|
1218
1261
|
}
|
|
1219
1262
|
) })
|
|
1220
1263
|
] });
|