@signalsandsorcery/plugin-sdk 2.3.1 → 2.24.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/README.md +58 -7
- package/dist/index.d.mts +2061 -60
- package/dist/index.d.ts +2061 -60
- package/dist/index.js +3559 -798
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3505 -799
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { ComponentType, ReactNode } from 'react';
|
|
1
|
+
import React, { ComponentType, ReactNode, DragEvent, Dispatch, SetStateAction } from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Plugin SDK Type Definitions
|
|
@@ -10,6 +10,67 @@ import React, { ComponentType, ReactNode } from 'react';
|
|
|
10
10
|
|
|
11
11
|
/** What kind of Tracktion content this plugin creates */
|
|
12
12
|
type GeneratorType = 'midi' | 'audio' | 'sample' | 'hybrid';
|
|
13
|
+
/**
|
|
14
|
+
* Drum-kit configuration for `host.setTrackDrumKit`. Prototype shape carries
|
|
15
|
+
* a single sample; future multi-slot kits will extend this with a `notes`
|
|
16
|
+
* map (`Record<midiNote, samplePath>`) for GM-style drum maps.
|
|
17
|
+
*/
|
|
18
|
+
interface DrumKit {
|
|
19
|
+
/** Absolute path to the sample (WAV, AIFF, FLAC). Triggered on every note-on. */
|
|
20
|
+
samplePath: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One key-mapped sample zone in a pitched, polyphonic instrument.
|
|
24
|
+
* Used by `host.setTrackInstrumentSampler`.
|
|
25
|
+
*
|
|
26
|
+
* Zones in an InstrumentSampler MUST be disjoint and ordered low to
|
|
27
|
+
* high by rootKey — the engine rejects overlap because Tracktion would
|
|
28
|
+
* otherwise double-trigger every matching sound on each note-on.
|
|
29
|
+
*/
|
|
30
|
+
interface InstrumentZone {
|
|
31
|
+
/** Absolute path to the zone's sample (WAV, FLAC, AIFF). */
|
|
32
|
+
samplePath: string;
|
|
33
|
+
/** MIDI note this sample sounds at unshifted (0-127). */
|
|
34
|
+
rootKey: number;
|
|
35
|
+
/** Inclusive low end of the key range that triggers this zone (0-127). */
|
|
36
|
+
minKey: number;
|
|
37
|
+
/** Inclusive high end of the key range that triggers this zone (0-127). */
|
|
38
|
+
maxKey: number;
|
|
39
|
+
/**
|
|
40
|
+
* If true, the sampler plays the sample for the duration the note is
|
|
41
|
+
* held and stops on note-off (good for sustaining pads, organs, etc.,
|
|
42
|
+
* whose source has been pre-trimmed to a steady-state region).
|
|
43
|
+
* If false, the sampler plays the sample through to its end ignoring
|
|
44
|
+
* note-off (good for plucks, mallets, percussion).
|
|
45
|
+
*/
|
|
46
|
+
openEnded: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pitched instrument configuration for `host.setTrackInstrumentSampler`.
|
|
50
|
+
* Parallel to `DrumKit` but multi-zone and pitch-aware. A manifest
|
|
51
|
+
* authored by the pitched-sample pipeline reduces to one of these.
|
|
52
|
+
*
|
|
53
|
+
* NOTE: This is distinct from `host.setTrackInstrument(trackId, pluginId)`
|
|
54
|
+
* which loads a VST3/AU synth plugin. `setTrackInstrumentSampler` loads
|
|
55
|
+
* the built-in Tracktion sampler with N pre-rendered zones.
|
|
56
|
+
*/
|
|
57
|
+
interface InstrumentSampler {
|
|
58
|
+
/** Display name (e.g. "Bright Warm Pluck"). Used for diagnostics. */
|
|
59
|
+
name: string;
|
|
60
|
+
/** Disjoint zones, ordered low->high by rootKey. At least one required. */
|
|
61
|
+
zones: ReadonlyArray<InstrumentZone>;
|
|
62
|
+
}
|
|
63
|
+
/** Options for `host.listAudioFiles`. */
|
|
64
|
+
interface ListAudioFilesOptions {
|
|
65
|
+
/**
|
|
66
|
+
* File extensions to include (dot-prefixed, lowercase). Defaults to
|
|
67
|
+
* `['.wav']`. Other audio formats (`.aif`, `.flac`, `.mp3`) are passed
|
|
68
|
+
* through verbatim; the host does not transcode.
|
|
69
|
+
*/
|
|
70
|
+
extensions?: string[];
|
|
71
|
+
/** Walk subdirectories. Defaults to `false`. */
|
|
72
|
+
recursive?: boolean;
|
|
73
|
+
}
|
|
13
74
|
/** Describes an available instrument plugin (VST3/AU synth) on the system. */
|
|
14
75
|
interface InstrumentDescriptor {
|
|
15
76
|
/** Stable plugin identifier for loading (VST3 TUID or AU component ID) */
|
|
@@ -122,6 +183,33 @@ interface PluginUIProps {
|
|
|
122
183
|
onOpenContract?: (() => void) | null;
|
|
123
184
|
/** Callback to expand this plugin's own accordion section. */
|
|
124
185
|
onExpandSelf?: (() => void) | null;
|
|
186
|
+
/**
|
|
187
|
+
* Whether the host's accordion section for this plugin is currently expanded.
|
|
188
|
+
* Plugin UIs can watch transitions to take focus, refresh data, etc. The host
|
|
189
|
+
* keeps the plugin mounted across collapse/expand to preserve state, so this
|
|
190
|
+
* prop (not mount/unmount) is the signal that the user is actively viewing.
|
|
191
|
+
*/
|
|
192
|
+
isExpanded?: boolean;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Canonical display metadata for a distributable sample pack, sourced from the
|
|
196
|
+
* HOST's pack registry (the same source it uses to download + version-check the
|
|
197
|
+
* bundle). Returned by `host.getSamplePackInfo` so a plugin's download CTA can
|
|
198
|
+
* show the live name / description / size instead of a hardcoded copy that
|
|
199
|
+
* drifts when a new pack version ships. Structurally compatible with
|
|
200
|
+
* `SamplePackCardInfo` (the CTA card prop).
|
|
201
|
+
*
|
|
202
|
+
* @since SDK 2.12.0
|
|
203
|
+
*/
|
|
204
|
+
interface SamplePackPublicInfo {
|
|
205
|
+
/** Stable pack identifier, e.g. `'sas-instrument-pack'`. */
|
|
206
|
+
packId: string;
|
|
207
|
+
/** Human-readable pack name for the CTA headline. */
|
|
208
|
+
displayName: string;
|
|
209
|
+
/** One-line description of the pack's contents. */
|
|
210
|
+
description: string;
|
|
211
|
+
/** Size in bytes of the default download variant. */
|
|
212
|
+
sizeBytes: number;
|
|
125
213
|
}
|
|
126
214
|
/** Scoped API surface that plugins interact with. Plugins NEVER get direct TracktionEngine access. */
|
|
127
215
|
interface PluginHost {
|
|
@@ -143,6 +231,10 @@ interface PluginHost {
|
|
|
143
231
|
setTrackPan(trackId: string, pan: number): Promise<void>;
|
|
144
232
|
/** Set track solo state. Only works on owned tracks. */
|
|
145
233
|
setTrackSolo(trackId: string, solo: boolean): Promise<void>;
|
|
234
|
+
/** Whether ANY track in the project is currently soloed (across all panels).
|
|
235
|
+
* Lets a panel dim its non-soloed rows (the engine silences them via the
|
|
236
|
+
* effective-mute model). Read-only; not ownership-scoped. */
|
|
237
|
+
isAnySoloActive(): Promise<boolean>;
|
|
146
238
|
/** Rename a track. Only works on owned tracks. */
|
|
147
239
|
setTrackName(trackId: string, name: string): Promise<void>;
|
|
148
240
|
/**
|
|
@@ -160,9 +252,31 @@ interface PluginHost {
|
|
|
160
252
|
*/
|
|
161
253
|
setTrackRole(trackId: string, role: string): Promise<void>;
|
|
162
254
|
/** Shuffle preset: keep MIDI, apply a random preset from the same category. Only works on owned tracks. */
|
|
163
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Shuffle preset: keep MIDI, apply a random preset from the same category.
|
|
257
|
+
* `excludeNames` (since SDK 1.5.0) filters preset names out of the random
|
|
258
|
+
* pool; the current preset is always implicitly excluded. Use this to
|
|
259
|
+
* implement a "no-repeat until full cycle" shuffle: the panel accumulates
|
|
260
|
+
* the history and resets when shufflePreset throws "no presets available".
|
|
261
|
+
*/
|
|
262
|
+
shufflePreset(trackId: string, excludeNames?: readonly string[]): Promise<ShufflePresetResult>;
|
|
164
263
|
/** Duplicate track: copy MIDI + role to a new track with a different preset. Only works on owned tracks. */
|
|
165
264
|
duplicateTrack(trackId: string): Promise<PluginTrackHandle>;
|
|
265
|
+
/**
|
|
266
|
+
* Persist this plugin's track row order for the active scene. Pass the stable
|
|
267
|
+
* track dbIds ({@link PluginTrackHandle.dbId}) in the desired top-to-bottom
|
|
268
|
+
* order. Reload-safe — {@link getPluginTracks} returns tracks in this order
|
|
269
|
+
* across scene switches and project reopen.
|
|
270
|
+
*
|
|
271
|
+
* Per-panel and decoupled from the engine-synced global track order, so
|
|
272
|
+
* reordering one panel never disturbs other plugins' tracks. Tracks omitted
|
|
273
|
+
* from the list (e.g. newly added or duplicated) keep their natural order at
|
|
274
|
+
* the end. Pairs with the {@link useTrackReorder} hook, which drives the
|
|
275
|
+
* drag-and-drop UI and calls this on drop.
|
|
276
|
+
*
|
|
277
|
+
* @since SDK 2.16.0
|
|
278
|
+
*/
|
|
279
|
+
reorderTracks(orderedTrackIds: readonly string[]): Promise<void>;
|
|
166
280
|
/**
|
|
167
281
|
* Return the canonical list of valid role tokens that the host's
|
|
168
282
|
* classifier and UI understand. Plugins should use this list when
|
|
@@ -208,6 +322,18 @@ interface PluginHost {
|
|
|
208
322
|
* Wraps MidiProcessor: quantize -> swing -> scale -> register -> overlaps -> humanize.
|
|
209
323
|
*/
|
|
210
324
|
postProcessMidi(notes: PluginMidiNote[], options: PostProcessOptions): Promise<PluginMidiNote[]>;
|
|
325
|
+
/**
|
|
326
|
+
* Read a track's current MIDI notes for in-place editing (e.g. a piano
|
|
327
|
+
* roll). Returns the track's clips with beat-based notes; an empty `clips`
|
|
328
|
+
* array means the track has no MIDI. Reads LIVE engine state (NOT the DB),
|
|
329
|
+
* so it reflects unsaved generator output too and needs no project_id
|
|
330
|
+
* scoping — do not "fix" this into a DB query.
|
|
331
|
+
*
|
|
332
|
+
* Ownership-gated like {@link writeMidiClip}. Optional so a plugin built
|
|
333
|
+
* against this SDK still loads on an older host — callers MUST null-check.
|
|
334
|
+
* @since SDK 2.15.0
|
|
335
|
+
*/
|
|
336
|
+
readMidiNotes?(trackId: string): Promise<ReadMidiResult>;
|
|
211
337
|
/** Place an audio file on a track this plugin owns. */
|
|
212
338
|
writeAudioClip(trackId: string, filePath: string, position?: number): Promise<void>;
|
|
213
339
|
/**
|
|
@@ -237,6 +363,16 @@ interface PluginHost {
|
|
|
237
363
|
setPluginState(trackId: string, pluginIndex: number, stateBase64: string): Promise<void>;
|
|
238
364
|
/** Get current plugin state (base64-encoded). */
|
|
239
365
|
getPluginState(trackId: string, pluginIndex: number): Promise<string>;
|
|
366
|
+
/**
|
|
367
|
+
* Set a plugin's RAW VST3/AU state — the plugin's own getStateInformation
|
|
368
|
+
* format, bypassing Tracktion's ValueTree wrapper. Use for third-party
|
|
369
|
+
* instruments (u-he Diva, Serum, …) whose patches the ValueTree round-trip
|
|
370
|
+
* does not faithfully preserve. Default Surge XT presets use setPluginState.
|
|
371
|
+
* @since SDK 2.15.0
|
|
372
|
+
*/
|
|
373
|
+
setRawPluginState(trackId: string, pluginIndex: number, stateBase64: string): Promise<void>;
|
|
374
|
+
/** Get a plugin's RAW VST3/AU state (see setRawPluginState). @since SDK 2.15.0 */
|
|
375
|
+
getRawPluginState(trackId: string, pluginIndex: number): Promise<string>;
|
|
240
376
|
/** List plugins currently loaded on a track. */
|
|
241
377
|
getTrackPlugins(trackId: string): Promise<PluginSynthInfo[]>;
|
|
242
378
|
/** Remove a plugin from a track. */
|
|
@@ -253,14 +389,154 @@ interface PluginHost {
|
|
|
253
389
|
showInstrumentEditor(trackId: string): Promise<void>;
|
|
254
390
|
/** Close the instrument plugin's editor window. */
|
|
255
391
|
hideInstrumentEditor(trackId: string): Promise<void>;
|
|
392
|
+
/**
|
|
393
|
+
* Load the engine's built-in sampler on the track (if not already
|
|
394
|
+
* present) and configure it with a single one-shot sound. Every MIDI
|
|
395
|
+
* note triggers the loaded sample regardless of pitch — used by the
|
|
396
|
+
* drum-generator plugin where the LLM's emitted pitch is advisory.
|
|
397
|
+
*
|
|
398
|
+
* Idempotent: calling repeatedly on the same track swaps the loaded
|
|
399
|
+
* sample without stacking more sampler instances. The sampler counts
|
|
400
|
+
* as the track's instrument; mixing it with `setTrackInstrument` on
|
|
401
|
+
* the same track is undefined behaviour for now.
|
|
402
|
+
*
|
|
403
|
+
* @since SDK 1.2.0
|
|
404
|
+
*/
|
|
405
|
+
setTrackDrumKit(trackId: string, kit: DrumKit): Promise<void>;
|
|
406
|
+
/**
|
|
407
|
+
* Load the engine's built-in sampler on the track (if not already
|
|
408
|
+
* present) and configure it with a pitched, polyphonic, multi-zone
|
|
409
|
+
* instrument. Each MIDI note triggers the zone whose [minKey,maxKey]
|
|
410
|
+
* range contains it; the zone is played back pitch-shifted relative
|
|
411
|
+
* to its rootKey. Polyphony is handled by the Tracktion sampler's
|
|
412
|
+
* voice allocator.
|
|
413
|
+
*
|
|
414
|
+
* Used by the instrument-generator plugin to load a pre-rendered
|
|
415
|
+
* pitched-sample manifest. Mutually exclusive with `setTrackDrumKit`
|
|
416
|
+
* on the same track (both occupy the sampler slot) and with
|
|
417
|
+
* `setTrackInstrument(pluginId)` (which loads a VST synth instead).
|
|
418
|
+
*
|
|
419
|
+
* Idempotent: calling repeatedly on the same track swaps the loaded
|
|
420
|
+
* zones without stacking sampler instances.
|
|
421
|
+
*
|
|
422
|
+
* @since SDK 1.3.0
|
|
423
|
+
*/
|
|
424
|
+
setTrackInstrumentSampler(trackId: string, instrument: InstrumentSampler): Promise<void>;
|
|
425
|
+
/**
|
|
426
|
+
* List audio files (by default `.wav`) under `rootPath`. Returns
|
|
427
|
+
* absolute file paths. `recursive` defaults to false; pass `true` to
|
|
428
|
+
* walk subdirectories. The drum-generator plugin uses this to
|
|
429
|
+
* lazily discover available samples without round-tripping each
|
|
430
|
+
* folder through `getSamples`.
|
|
431
|
+
*
|
|
432
|
+
* Plugins MUST NOT use this to read paths outside their declared
|
|
433
|
+
* sample roots — the host may add path validation in a later release.
|
|
434
|
+
*
|
|
435
|
+
* @since SDK 1.2.0
|
|
436
|
+
*/
|
|
437
|
+
listAudioFiles(rootPath: string, options?: ListAudioFilesOptions): Promise<string[]>;
|
|
438
|
+
/**
|
|
439
|
+
* Read a text file's contents from the host filesystem (UTF-8). Returns
|
|
440
|
+
* `null` on any read error (missing file, permission, etc.) — the
|
|
441
|
+
* caller does not need to wrap the call in try/catch.
|
|
442
|
+
*
|
|
443
|
+
* Intended for plugin sample-library metadata: instrument manifest
|
|
444
|
+
* JSON (`<instrument-id>/manifest.json`) and prompt-sibling text
|
|
445
|
+
* (`<id>.txt`). Plugins parse the returned string themselves so the
|
|
446
|
+
* host stays content-agnostic.
|
|
447
|
+
*
|
|
448
|
+
* Plugins MUST NOT use this to read paths outside their declared
|
|
449
|
+
* sample roots — the host may add path validation in a later release.
|
|
450
|
+
*
|
|
451
|
+
* @since SDK 1.4.0
|
|
452
|
+
*/
|
|
453
|
+
readTextFile(absolutePath: string): Promise<string | null>;
|
|
256
454
|
/** Get the FULL generation context for the active scene. */
|
|
257
455
|
getGenerationContext(excludeTrackId?: string): Promise<PluginGenerationContext>;
|
|
258
456
|
/** Get lightweight musical context (no concurrent track MIDI data). */
|
|
259
457
|
getMusicalContext(): Promise<MusicalContext>;
|
|
260
458
|
/** Get the active scene ID. Null if no scene is active. */
|
|
261
459
|
getActiveSceneId(): string | null;
|
|
460
|
+
/**
|
|
461
|
+
* Get the bound project's DB id. Null when no project is bound.
|
|
462
|
+
* Optional — older hosts and the renderer-side host proxy may omit it;
|
|
463
|
+
* callers MUST feature-check. Used e.g. to detect project switches for
|
|
464
|
+
* per-project conversation persistence.
|
|
465
|
+
* @since SDK 2.18.0
|
|
466
|
+
*/
|
|
467
|
+
getProjectId?(): string | null;
|
|
262
468
|
/** Get list of all scenes in the project. */
|
|
263
469
|
getSceneList(): Promise<PluginSceneInfo[]>;
|
|
470
|
+
/**
|
|
471
|
+
* Enumerate importable track candidates from OTHER scenes, scoped to this
|
|
472
|
+
* plugin's track type (derived from the plugin id). Each candidate is
|
|
473
|
+
* annotated with `importable` + `disabledReason` — the host computes the
|
|
474
|
+
* harmonic/length/tempo gate so the UI only renders it. By default the active
|
|
475
|
+
* scene is excluded; pass `includeSameScene` to also surface the active
|
|
476
|
+
* scene's MIDI tracks owned by OTHER panels (the cross-panel re-sound source).
|
|
477
|
+
* Scenes with no candidate of this type are omitted.
|
|
478
|
+
*
|
|
479
|
+
* Optional so a plugin built against this SDK still loads on an older host —
|
|
480
|
+
* callers MUST null-check and hide the affordance when absent.
|
|
481
|
+
* @since SDK 2.13.0
|
|
482
|
+
*/
|
|
483
|
+
listImportableTracks?(opts?: ListImportableTracksOptions): Promise<ImportCandidateScene[]>;
|
|
484
|
+
/**
|
|
485
|
+
* Import a source track (from another scene) into the active scene as a
|
|
486
|
+
* faithful, independent copy, delegating to the `import_track_from_scene`
|
|
487
|
+
* tool. Returns the new track's handle so the panel can append a row.
|
|
488
|
+
* Throws on a gate violation — call only for candidates with `importable`.
|
|
489
|
+
* Optional — callers MUST null-check (see `listImportableTracks`).
|
|
490
|
+
* @since SDK 2.13.0
|
|
491
|
+
*/
|
|
492
|
+
importTrack?(opts: {
|
|
493
|
+
sourceSceneId: string;
|
|
494
|
+
sourceTrackId: string;
|
|
495
|
+
}): Promise<PluginTrackHandle>;
|
|
496
|
+
/**
|
|
497
|
+
* Read a source track's CURRENT sound — sample path (drums), sampler zones
|
|
498
|
+
* (instruments), or Surge preset state (synths) — so a panel can copy just
|
|
499
|
+
* the sound onto another track, IGNORING the contract gate that `importTrack`
|
|
500
|
+
* enforces ("different contract, same preset"). Read-only: applies nothing.
|
|
501
|
+
* The selector is the source track's DB row id (`ImportCandidateTrack.dbId`).
|
|
502
|
+
* Returns null when the track has no stored sound. Optional — callers MUST
|
|
503
|
+
* null-check (see `listImportableTracks`).
|
|
504
|
+
* @since SDK 2.14.0
|
|
505
|
+
*/
|
|
506
|
+
getTrackSound?(sourceTrackDbId: string): Promise<TrackSoundSnapshot | null>;
|
|
507
|
+
/**
|
|
508
|
+
* Read a source track's persisted MIDI by its DB row id — the cross-panel
|
|
509
|
+
* READ half of "re-sound a part on a different instrument". Unlike
|
|
510
|
+
* `readMidiNotes` (engine-read, ownership-gated), this reads the DB and is
|
|
511
|
+
* NOT ownership-gated, so a panel can pull a part out of a track owned by a
|
|
512
|
+
* DIFFERENT panel in the same scene (the selector is
|
|
513
|
+
* `ImportCandidateTrack.dbId`, e.g. a `sameScene` candidate). Notes are
|
|
514
|
+
* beat-based, identical shape to `readMidiNotes`; the loop span comes from the
|
|
515
|
+
* source scene. Returns `{ clips: [] }` when the track has no MIDI. Optional —
|
|
516
|
+
* callers MUST null-check (see `listImportableTracks`).
|
|
517
|
+
* @since SDK 2.20.0
|
|
518
|
+
*/
|
|
519
|
+
readImportableTrackMidi?(sourceTrackDbId: string): Promise<ReadMidiResult>;
|
|
520
|
+
/**
|
|
521
|
+
* List THIS panel's family tracks in a specific scene (by DB id), WITHOUT the
|
|
522
|
+
* import key/length/tempo gate that `listImportableTracks` applies. Powers the
|
|
523
|
+
* crossfade picker: the origin (from) and target (to) scenes of a transition
|
|
524
|
+
* deliberately differ in key, so gating would wrongly hide valid candidates.
|
|
525
|
+
* Project-scoped, read-only. Returns [] for an unknown/empty scene. Optional —
|
|
526
|
+
* callers MUST null-check (see `listImportableTracks`).
|
|
527
|
+
* @since SDK 2.22.0
|
|
528
|
+
*/
|
|
529
|
+
listSceneFamilyTracks?(sceneDbId: string): Promise<SceneFamilyTrack[]>;
|
|
530
|
+
/**
|
|
531
|
+
* Read a specific scene's musical key (tonic + mode) by db id. Labels the
|
|
532
|
+
* SOURCE keys of a crossfade's origin/target patterns — the active-scene
|
|
533
|
+
* musical context only carries the transition scene's key. Optional — callers
|
|
534
|
+
* MUST null-check. @since SDK 2.24.0
|
|
535
|
+
*/
|
|
536
|
+
getSceneKey?(sceneDbId: string): Promise<{
|
|
537
|
+
key: string;
|
|
538
|
+
mode: string;
|
|
539
|
+
} | null>;
|
|
264
540
|
/** Subscribe to transport state changes. Returns unsubscribe function. */
|
|
265
541
|
onTransportEvent(listener: TransportEventListener): UnsubscribeFn;
|
|
266
542
|
/** Subscribe to deck boundary events. Returns unsubscribe function. */
|
|
@@ -269,8 +545,59 @@ interface PluginHost {
|
|
|
269
545
|
onSceneChange(listener: SceneChangeListener): UnsubscribeFn;
|
|
270
546
|
/** Get current transport state (one-shot). */
|
|
271
547
|
getTransportState(): Promise<PluginTransportState>;
|
|
548
|
+
/**
|
|
549
|
+
* One-shot mono peak level for every track this plugin owns. Drives the
|
|
550
|
+
* cosmetic per-track strip meters; poll at ~30Hz while the transport is
|
|
551
|
+
* playing. The host scopes the result to this plugin's tracks and coalesces
|
|
552
|
+
* the underlying engine read, so a busy engine yields a STALE meter rather
|
|
553
|
+
* than a backlog (playback always wins over the GUI). Optional: guard with
|
|
554
|
+
* `typeof host.getTrackLevels === 'function'` for older hosts.
|
|
555
|
+
* @since SDK 2.21.0
|
|
556
|
+
*/
|
|
557
|
+
getTrackLevels?(): Promise<PluginTrackLevel[]>;
|
|
272
558
|
/** Generate text/JSON via the host's authenticated LLM service. */
|
|
273
559
|
generateWithLLM(request: LLMGenerationRequest): Promise<LLMGenerationResult>;
|
|
560
|
+
/**
|
|
561
|
+
* Generate with native tool-use (function calling). Used by agentic plugins
|
|
562
|
+
* (chat panel, etc.) to drive an iterative loop where the model calls tools,
|
|
563
|
+
* observes results, and decides next steps — same loop class as Claude Code
|
|
564
|
+
* or VS Code agent mode.
|
|
565
|
+
*
|
|
566
|
+
* Shape mirrors Gemini's `generateContent` REST surface; the host forwards
|
|
567
|
+
* verbatim to the gateway's Gemini-native passthrough endpoint, which adds
|
|
568
|
+
* the central Google API key. Plugins never see provider credentials.
|
|
569
|
+
*
|
|
570
|
+
* Available since SDK 2.4.0.
|
|
571
|
+
*/
|
|
572
|
+
generateWithLLMTools(request: LLMToolUseRequest): Promise<LLMToolUseResponse>;
|
|
573
|
+
/**
|
|
574
|
+
* Resolve absolute paths for spawning the bundled `sas` CLI as a subprocess.
|
|
575
|
+
* Used by agentic plugins that drive the CLI as their tool surface (chat
|
|
576
|
+
* panel, etc.). Returns `null` when called from a renderer-side host or
|
|
577
|
+
* when the CLI isn't accessible.
|
|
578
|
+
*
|
|
579
|
+
* Available since SDK 2.4.0.
|
|
580
|
+
*/
|
|
581
|
+
getCliPaths(): {
|
|
582
|
+
appExe: string;
|
|
583
|
+
cliEntry: string;
|
|
584
|
+
} | null;
|
|
585
|
+
/**
|
|
586
|
+
* Resolve the absolute path to a bundled resource directory shipped with
|
|
587
|
+
* the app via `extraResources` (e.g. `'drum-samples'`,
|
|
588
|
+
* `'tracktion-presets'`). In dev, resolves to
|
|
589
|
+
* `<projectRoot>/resources/<name>`. In packaged builds, resolves to
|
|
590
|
+
* `<process.resourcesPath>/<name>`.
|
|
591
|
+
*
|
|
592
|
+
* Returns `null` if the host cannot resolve paths in this context
|
|
593
|
+
* (e.g. Electron mocked out in unit tests). Plugins MUST null-check and
|
|
594
|
+
* either degrade gracefully or fall back to a known dev path.
|
|
595
|
+
*
|
|
596
|
+
* Async by design: the renderer-side host proxy round-trips through IPC.
|
|
597
|
+
*
|
|
598
|
+
* @since SDK 2.7.0
|
|
599
|
+
*/
|
|
600
|
+
getBundledResourcePath(name: string): Promise<string | null>;
|
|
274
601
|
/** Check if LLM access is available (user authenticated + gateway reachable). */
|
|
275
602
|
isLLMAvailable(): Promise<boolean>;
|
|
276
603
|
/**
|
|
@@ -281,23 +608,59 @@ interface PluginHost {
|
|
|
281
608
|
* `'scene'` to hide project-level tools they shouldn't call. When omitted,
|
|
282
609
|
* every tool regardless of scope is returned.
|
|
283
610
|
*
|
|
611
|
+
* `opts.includeDeferred` (since SDK 2.18.0) opts in to tools flagged with
|
|
612
|
+
* `deferLoading` (progressive disclosure). Default `false` mirrors
|
|
613
|
+
* `/api/v1/actions` — the curated core surface. Used by curation layers
|
|
614
|
+
* that promote specific deferred/project tools onto an agent's default
|
|
615
|
+
* declaration set.
|
|
616
|
+
*
|
|
284
617
|
* @since SDK 1.2.0
|
|
285
618
|
*/
|
|
286
619
|
listAppTools(opts?: {
|
|
287
620
|
scope?: 'scene' | 'project';
|
|
621
|
+
includeDeferred?: boolean;
|
|
288
622
|
}): Promise<PluginAppTool[]>;
|
|
289
623
|
/**
|
|
290
624
|
* Execute a host app tool by name. Delegates to the in-process
|
|
291
|
-
* ToolRegistry — every
|
|
625
|
+
* ToolRegistry — every call (including this one) broadcasts to the
|
|
626
|
+
* UI's `mutations:tool-executed` channel so renderer state stays
|
|
627
|
+
* fresh whether the call mutates or is read-only. Read-only callers
|
|
628
|
+
* pay zero extra cost since the renderer debounces and skips
|
|
629
|
+
* redundant reloads.
|
|
292
630
|
*
|
|
293
631
|
* For scene-scoped tools tagged with `autoBindSceneId`, the host
|
|
294
632
|
* overrides the caller's `sceneId` param with the currently-active
|
|
295
633
|
* scene. That keeps a scene-bound caller from accidentally targeting
|
|
296
634
|
* another scene.
|
|
297
635
|
*
|
|
636
|
+
* `opts.provenance` (since SDK 2.18.0) stamps the originating actor onto
|
|
637
|
+
* every domain event this call emits — pass `'agent'` from autonomous
|
|
638
|
+
* agent loops so the UI orchestrator can gate auto-navigation, `'user'`
|
|
639
|
+
* when proxying a direct user gesture. Omitted = `'system'`.
|
|
640
|
+
*
|
|
298
641
|
* @since SDK 1.2.0
|
|
299
642
|
*/
|
|
300
|
-
executeAppTool(name: string, params: Record<string, unknown
|
|
643
|
+
executeAppTool(name: string, params: Record<string, unknown>, opts?: {
|
|
644
|
+
provenance?: 'agent' | 'user';
|
|
645
|
+
}): Promise<PluginAppToolResult>;
|
|
646
|
+
/**
|
|
647
|
+
* Monotonic counter that increments on every state mutation
|
|
648
|
+
* (`broadcastMutation('tool-executed', ...)`). Use as a cache key for
|
|
649
|
+
* derived state that depends on the project: when the counter changes,
|
|
650
|
+
* something mutated; when it doesn't, your cache is still valid.
|
|
651
|
+
*
|
|
652
|
+
* Mostly aimed at performance-sensitive callers like ambient-context
|
|
653
|
+
* builders that want to skip re-querying state when nothing has
|
|
654
|
+
* changed. The counter is process-local — it resets on app restart
|
|
655
|
+
* and is not durable across sessions.
|
|
656
|
+
*
|
|
657
|
+
* Implementation detail: the counter is bumped by `mutation-broadcaster`
|
|
658
|
+
* before the broadcaster fires, so a synchronous `getMutationSeq()`
|
|
659
|
+
* call from inside a mutation listener will see the post-bump value.
|
|
660
|
+
*
|
|
661
|
+
* @since SDK 2.6.0
|
|
662
|
+
*/
|
|
663
|
+
getMutationSeq(): number;
|
|
301
664
|
/** Get available preset categories for a synth plugin. */
|
|
302
665
|
getPresetCategories(pluginName: string): Promise<string[]>;
|
|
303
666
|
/** Get a random preset from a category. */
|
|
@@ -310,6 +673,150 @@ interface PluginHost {
|
|
|
310
673
|
getDataDirectory(): string;
|
|
311
674
|
/** Persisted key-value settings store. */
|
|
312
675
|
settings: PluginSettingsStore;
|
|
676
|
+
/**
|
|
677
|
+
* Return the absolute path to an installed sample pack's root directory,
|
|
678
|
+
* or `null` if the pack is missing OR its installed version doesn't match
|
|
679
|
+
* what the current app build expects.
|
|
680
|
+
*
|
|
681
|
+
* Plugins should treat `null` as "show the download CTA"; do NOT fall back
|
|
682
|
+
* to a hardcoded path. The host owns where samples live (currently
|
|
683
|
+
* `<userData>/samples/<installSubdir>/`).
|
|
684
|
+
*
|
|
685
|
+
* Stable packIds: `'sas-drum-pack'`, `'sas-instrument-pack'`. Both packs
|
|
686
|
+
* are downloaded on demand via the host's pack-download flow; see
|
|
687
|
+
* `host.isSamplePackCurrent` and the renderer-side `DownloadPackButton`.
|
|
688
|
+
*
|
|
689
|
+
* @since SDK 2.7.0
|
|
690
|
+
*/
|
|
691
|
+
getSamplePackRoot(packId: string): Promise<string | null>;
|
|
692
|
+
/**
|
|
693
|
+
* True if the installed version of `packId` matches the version this app
|
|
694
|
+
* build expects. False if the pack is missing OR the installed version
|
|
695
|
+
* differs (older or newer).
|
|
696
|
+
*
|
|
697
|
+
* Plugins call this on activate to decide between rendering their normal
|
|
698
|
+
* UI vs the "Sample library not installed / Update available" CTA.
|
|
699
|
+
*
|
|
700
|
+
* @since SDK 2.7.0
|
|
701
|
+
*/
|
|
702
|
+
isSamplePackCurrent(packId: string): Promise<boolean>;
|
|
703
|
+
/**
|
|
704
|
+
* Return the currently-installed version string for `packId` (e.g. `'1'`,
|
|
705
|
+
* `'2'`), or `null` if the pack is not installed at all. Reads the
|
|
706
|
+
* `_pack-version.json` marker inside the pack's install dir.
|
|
707
|
+
*
|
|
708
|
+
* Useful for distinguishing the "missing" CTA from the "stale, update
|
|
709
|
+
* available" CTA — plugins can call this when `isSamplePackCurrent`
|
|
710
|
+
* returns false to pick the right empty-state message.
|
|
711
|
+
*
|
|
712
|
+
* @since SDK 2.7.0
|
|
713
|
+
*/
|
|
714
|
+
getSamplePackInstalledVersion(packId: string): Promise<string | null>;
|
|
715
|
+
/**
|
|
716
|
+
* Trigger a download + install of `packId` via the host's pack system (the
|
|
717
|
+
* same flow `getSamplePackRoot` / `isSamplePackCurrent` report on). Resolves
|
|
718
|
+
* when the install completes or fails. Plugins call this from a "download
|
|
719
|
+
* library" CTA instead of reaching into the app's IPC (`window.electronAPI`)
|
|
720
|
+
* directly.
|
|
721
|
+
*
|
|
722
|
+
* @since SDK 2.8.0
|
|
723
|
+
*/
|
|
724
|
+
startSamplePackDownload(packId: string): Promise<{
|
|
725
|
+
success: boolean;
|
|
726
|
+
error?: string;
|
|
727
|
+
}>;
|
|
728
|
+
/**
|
|
729
|
+
* Subscribe to download/install progress for `packId`. Returns an unsubscribe
|
|
730
|
+
* fn. `status` mirrors the host's pack-download states (e.g. `'downloading' |
|
|
731
|
+
* 'extracting' | 'installing' | 'complete' | 'error'`); `progress` is 0-100.
|
|
732
|
+
*
|
|
733
|
+
* @since SDK 2.8.0
|
|
734
|
+
*/
|
|
735
|
+
onSamplePackProgress(packId: string, listener: (progress: {
|
|
736
|
+
packId?: string;
|
|
737
|
+
status: string;
|
|
738
|
+
progress: number;
|
|
739
|
+
message?: string;
|
|
740
|
+
}) => void): UnsubscribeFn;
|
|
741
|
+
/**
|
|
742
|
+
* Return the canonical display metadata (`displayName`, `description`,
|
|
743
|
+
* `sizeBytes`) for `packId` from the host's pack registry — the SAME source
|
|
744
|
+
* the host uses to download + version-check the pack. A plugin's download CTA
|
|
745
|
+
* should prefer this over a hardcoded copy so the size/description stay in
|
|
746
|
+
* sync with whatever bundle the host actually ships (no per-version drift).
|
|
747
|
+
* Resolves `null` for an unknown packId.
|
|
748
|
+
*
|
|
749
|
+
* Optional so a plugin built against this SDK still runs on an older host:
|
|
750
|
+
* callers should fall back to their own static copy when it is absent or
|
|
751
|
+
* returns `null`.
|
|
752
|
+
*
|
|
753
|
+
* @since SDK 2.12.0
|
|
754
|
+
*/
|
|
755
|
+
getSamplePackInfo?(packId: string): Promise<SamplePackPublicInfo | null>;
|
|
756
|
+
/**
|
|
757
|
+
* Per-pack roots of the USER's imported sample packs for `kind`. Each root
|
|
758
|
+
* is laid out exactly like the corresponding stock pack (drums:
|
|
759
|
+
* `<root>/<role>/<file>.wav` + `.txt` sidecars; instruments:
|
|
760
|
+
* `<root>/<category>/<id>/manifest.json`), so resolvers scan them as
|
|
761
|
+
* additional roots alongside `getSamplePackRoot`. `[]` when nothing is
|
|
762
|
+
* imported. User content lives under `<userData>/user-samples/` — strictly
|
|
763
|
+
* separate on disk; stock pack installs never touch it.
|
|
764
|
+
*
|
|
765
|
+
* Optional for older-host compat: feature-check
|
|
766
|
+
* (`host.getUserSampleRoots?.(...)`) and treat absence as `[]`.
|
|
767
|
+
*
|
|
768
|
+
* @since SDK 2.20.0
|
|
769
|
+
*/
|
|
770
|
+
getUserSampleRoots?(kind: 'drums' | 'instruments'): Promise<string[]>;
|
|
771
|
+
/**
|
|
772
|
+
* Ask the host app to open its sample-import wizard targeting `kind`.
|
|
773
|
+
* Fire-and-forget; renderer-hosted plugins only (the wizard is an app-level
|
|
774
|
+
* modal — the main-process host no-ops). Library changes land as
|
|
775
|
+
* `onSamplePackProgress` events with packId `user:<kind>` and
|
|
776
|
+
* `status: 'complete'`, so subscribe to that to refresh.
|
|
777
|
+
*
|
|
778
|
+
* @since SDK 2.20.0
|
|
779
|
+
*/
|
|
780
|
+
openSampleImportWizard?(kind: 'drums' | 'instruments'): void;
|
|
781
|
+
/**
|
|
782
|
+
* Start a deck playing the given scene/transition. Mirrors the workstation's
|
|
783
|
+
* transport play. `contentType` defaults to `'scene'`.
|
|
784
|
+
*
|
|
785
|
+
* @since SDK 2.9.0
|
|
786
|
+
*/
|
|
787
|
+
deckPlay(deckId: string, contentId?: string, contentType?: 'scene' | 'transition'): Promise<{
|
|
788
|
+
success: boolean;
|
|
789
|
+
error?: string;
|
|
790
|
+
code?: string;
|
|
791
|
+
}>;
|
|
792
|
+
/**
|
|
793
|
+
* Stop a deck.
|
|
794
|
+
*
|
|
795
|
+
* @since SDK 2.9.0
|
|
796
|
+
*/
|
|
797
|
+
deckStop(deckId: string): Promise<{
|
|
798
|
+
success: boolean;
|
|
799
|
+
error?: string;
|
|
800
|
+
}>;
|
|
801
|
+
/**
|
|
802
|
+
* Subscribe to per-deck state changes. Each event carries the `deckId`, the
|
|
803
|
+
* `property` that changed (e.g. `'playing'`), and its new `value`. Returns an
|
|
804
|
+
* unsubscribe fn.
|
|
805
|
+
*
|
|
806
|
+
* @since SDK 2.9.0
|
|
807
|
+
*/
|
|
808
|
+
onDeckStateChanged(listener: (event: {
|
|
809
|
+
deckId: string;
|
|
810
|
+
property: string;
|
|
811
|
+
value: unknown;
|
|
812
|
+
}) => void): UnsubscribeFn;
|
|
813
|
+
/**
|
|
814
|
+
* Subscribe to the "all decks stopped" engine event (e.g. global transport
|
|
815
|
+
* stop). Returns an unsubscribe fn.
|
|
816
|
+
*
|
|
817
|
+
* @since SDK 2.9.0
|
|
818
|
+
*/
|
|
819
|
+
onAllDecksStopped(listener: () => void): UnsubscribeFn;
|
|
313
820
|
/** Get a value from scene-scoped plugin data. */
|
|
314
821
|
getSceneData<T = unknown>(sceneId: string, key: string): Promise<T | null>;
|
|
315
822
|
/** Set a value in scene-scoped plugin data. */
|
|
@@ -449,6 +956,18 @@ interface PluginHost {
|
|
|
449
956
|
onComposeProgress(listener: ComposeProgressListener): UnsubscribeFn;
|
|
450
957
|
/** Subscribe to engine ready events (fires when the engine finishes loading tracks after a scene change). */
|
|
451
958
|
onEngineReady(listener: () => void): UnsubscribeFn;
|
|
959
|
+
/**
|
|
960
|
+
* Subscribe to external state mutations (CLI, MCP, or HTTP-API tool calls
|
|
961
|
+
* that bypass plugin-host methods). Fires after such a tool finishes,
|
|
962
|
+
* signalling that scene/track DB state may have changed underneath the
|
|
963
|
+
* plugin's local cache. Use it to refresh state that the plugin doesn't
|
|
964
|
+
* own — e.g. re-running adoptSceneTracks() so AI-created tracks become
|
|
965
|
+
* visible without requiring the user to switch scenes.
|
|
966
|
+
*
|
|
967
|
+
* Optional: only the renderer-side host implements this. Main-side
|
|
968
|
+
* plugins should subscribe to the typed domain-event bus instead.
|
|
969
|
+
*/
|
|
970
|
+
onAfterAgentMutation?(listener: () => void): UnsubscribeFn;
|
|
452
971
|
/** Audition a single note on a track (fire-and-forget preview). */
|
|
453
972
|
auditionNote(trackId: string, pitch: number, velocity: number, durationMs: number): Promise<void>;
|
|
454
973
|
/** Get presets for this plugin, optionally filtered by category. */
|
|
@@ -539,6 +1058,14 @@ interface PluginHost {
|
|
|
539
1058
|
* @since SDK 2.2.0
|
|
540
1059
|
*/
|
|
541
1060
|
getCurrentInputDevice(): Promise<AudioInputDevice | null>;
|
|
1061
|
+
/**
|
|
1062
|
+
* Subscribe to input-device changes (user picks a new mic in the
|
|
1063
|
+
* Audio Routing panel). Listeners should refetch via
|
|
1064
|
+
* `getCurrentInputDevice()`. Returns an unsubscribe fn.
|
|
1065
|
+
*
|
|
1066
|
+
* @since SDK 2.4.0
|
|
1067
|
+
*/
|
|
1068
|
+
onInputDeviceChange(listener: () => void): UnsubscribeFn;
|
|
542
1069
|
/**
|
|
543
1070
|
* Get the platform's mic-to-output round-trip latency offset in
|
|
544
1071
|
* samples. 0 = uncalibrated. Plugins recording audio apply this via
|
|
@@ -623,6 +1150,105 @@ interface PluginTrackHandle {
|
|
|
623
1150
|
/** Custom instrument display name (null = Surge XT) */
|
|
624
1151
|
instrumentName?: string | null;
|
|
625
1152
|
}
|
|
1153
|
+
/**
|
|
1154
|
+
* One source track offered by `listImportableTracks`, already filtered to the
|
|
1155
|
+
* calling panel's type. The host computes the gate; the UI only renders it.
|
|
1156
|
+
* @since SDK 2.13.0
|
|
1157
|
+
*/
|
|
1158
|
+
interface ImportCandidateTrack {
|
|
1159
|
+
/** Source track's engine track id (the selector passed back to importTrack). */
|
|
1160
|
+
trackId: string;
|
|
1161
|
+
/** Source track's DB row id (globally unique; good React key). */
|
|
1162
|
+
dbId: string;
|
|
1163
|
+
/** Display name shown in the modal row. */
|
|
1164
|
+
name: string;
|
|
1165
|
+
/** Musical role if set (drives the row icon). */
|
|
1166
|
+
role?: string;
|
|
1167
|
+
/** True when this track can be copied into the active scene as-is. */
|
|
1168
|
+
importable: boolean;
|
|
1169
|
+
/** Why the track is disabled (shown as a tooltip). Present iff `!importable`. */
|
|
1170
|
+
disabledReason?: string;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* One track in a specific scene, returned by `host.listSceneFamilyTracks`,
|
|
1174
|
+
* already narrowed to the calling panel's family. Unlike `ImportCandidateTrack`
|
|
1175
|
+
* it carries NO import gate — the crossfade picker lists every same-family track
|
|
1176
|
+
* in the origin/target scene regardless of key/length. @since SDK 2.22.0
|
|
1177
|
+
*/
|
|
1178
|
+
interface SceneFamilyTrack {
|
|
1179
|
+
/** Track's DB row id — the selector for getTrackSound + crossfade metadata. */
|
|
1180
|
+
dbId: string;
|
|
1181
|
+
/** Display name shown in the picker. */
|
|
1182
|
+
name: string;
|
|
1183
|
+
/** Musical role if set — used to enforce same-role crossfade pairing. */
|
|
1184
|
+
role?: string;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* One OTHER scene and its candidate tracks (already type-filtered). Scenes with
|
|
1188
|
+
* zero candidates of the panel's type are omitted by the host.
|
|
1189
|
+
* @since SDK 2.13.0
|
|
1190
|
+
*/
|
|
1191
|
+
interface ImportCandidateScene {
|
|
1192
|
+
/** Source scene's engine scene id. */
|
|
1193
|
+
sceneId: string;
|
|
1194
|
+
/** Source scene's display name. */
|
|
1195
|
+
sceneName: string;
|
|
1196
|
+
/** Candidate tracks of this panel's type (may include disabled ones). */
|
|
1197
|
+
tracks: ImportCandidateTrack[];
|
|
1198
|
+
/**
|
|
1199
|
+
* True for the synthetic "this scene — other panels" entry: the ACTIVE
|
|
1200
|
+
* scene's MIDI tracks owned by OTHER panels. Importing one re-sounds the part
|
|
1201
|
+
* on the calling panel's instrument (via `readImportableTrackMidi` +
|
|
1202
|
+
* `writeMidiClip`) rather than faithfully copying it. Absent/false for
|
|
1203
|
+
* ordinary cross-scene entries. @since SDK 2.20.0
|
|
1204
|
+
*/
|
|
1205
|
+
sameScene?: boolean;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* A source track's current sound, as returned by `host.getTrackSound`. The
|
|
1209
|
+
* discriminant matches the panel that reads it: drums → 'sample', instruments →
|
|
1210
|
+
* 'instrument', synths → 'preset'. `label` is the human name for the History row.
|
|
1211
|
+
* @since SDK 2.14.0
|
|
1212
|
+
*/
|
|
1213
|
+
/**
|
|
1214
|
+
* How a synth `state` blob is serialized. `valuetree` is Tracktion's wrapped
|
|
1215
|
+
* format (default Surge XT presets); `raw` is the plugin's own
|
|
1216
|
+
* getStateInformation format (third-party instruments). Absent ⇒ `valuetree`,
|
|
1217
|
+
* for backward compatibility with history recorded before SDK 2.15.0.
|
|
1218
|
+
* @since SDK 2.15.0
|
|
1219
|
+
*/
|
|
1220
|
+
type SynthStateType = 'raw' | 'valuetree';
|
|
1221
|
+
type TrackSoundSnapshot = {
|
|
1222
|
+
kind: 'sample';
|
|
1223
|
+
samplePath: string;
|
|
1224
|
+
label: string;
|
|
1225
|
+
} | {
|
|
1226
|
+
kind: 'instrument';
|
|
1227
|
+
displayName: string;
|
|
1228
|
+
instrumentId: string | null;
|
|
1229
|
+
zones: InstrumentZone[];
|
|
1230
|
+
label: string;
|
|
1231
|
+
} | {
|
|
1232
|
+
kind: 'preset';
|
|
1233
|
+
state: string;
|
|
1234
|
+
label: string;
|
|
1235
|
+
stateType?: SynthStateType;
|
|
1236
|
+
};
|
|
1237
|
+
/** Options for `PluginHost.listImportableTracks`. @since SDK 2.13.0 */
|
|
1238
|
+
interface ListImportableTracksOptions {
|
|
1239
|
+
/**
|
|
1240
|
+
* Coarse content family. 'midi' = synth/drum/instrument, 'audio' = stems,
|
|
1241
|
+
* 'sample' = loops. Defaults are derived from the calling plugin id, so
|
|
1242
|
+
* panels normally pass nothing.
|
|
1243
|
+
*/
|
|
1244
|
+
family?: 'midi' | 'audio' | 'sample';
|
|
1245
|
+
/**
|
|
1246
|
+
* When true, prepend the active scene's MIDI tracks owned by OTHER panels as a
|
|
1247
|
+
* `sameScene` entry (the cross-panel re-sound source). Off by default so the
|
|
1248
|
+
* plain cross-scene import is unchanged. MIDI panels only. @since SDK 2.20.0
|
|
1249
|
+
*/
|
|
1250
|
+
includeSameScene?: boolean;
|
|
1251
|
+
}
|
|
626
1252
|
interface PluginTrackInfo extends PluginTrackHandle {
|
|
627
1253
|
/** Is track muted? */
|
|
628
1254
|
muted: boolean;
|
|
@@ -691,6 +1317,29 @@ interface MidiWriteResult {
|
|
|
691
1317
|
/** Actual bars covered */
|
|
692
1318
|
bars: number;
|
|
693
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* One clip returned by {@link PluginHost.readMidiNotes}. `endTime - startTime`
|
|
1322
|
+
* (seconds) is the clip's loop span; round-trip it back into
|
|
1323
|
+
* {@link MidiClipData} on save so an edit never changes the clip length.
|
|
1324
|
+
* @since SDK 2.15.0
|
|
1325
|
+
*/
|
|
1326
|
+
interface ReadMidiClip {
|
|
1327
|
+
/** Clip start in seconds (engine timeline). */
|
|
1328
|
+
startTime: number;
|
|
1329
|
+
/** Clip end in seconds. Loop span = endTime - startTime. */
|
|
1330
|
+
endTime: number;
|
|
1331
|
+
/** Beat-based notes, identical shape to {@link MidiClipData.notes}. */
|
|
1332
|
+
notes: PluginMidiNote[];
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Result of {@link PluginHost.readMidiNotes}: every clip on the track. Drum /
|
|
1336
|
+
* instrument / synth tracks are single-clip, so callers normally use
|
|
1337
|
+
* `clips[0]`; the array form mirrors the engine and is future-proof.
|
|
1338
|
+
* @since SDK 2.15.0
|
|
1339
|
+
*/
|
|
1340
|
+
interface ReadMidiResult {
|
|
1341
|
+
clips: ReadMidiClip[];
|
|
1342
|
+
}
|
|
694
1343
|
/**
|
|
695
1344
|
* Options for {@link PluginHost.exportTracksAsMidiBundle}.
|
|
696
1345
|
* @since SDK 1.1.0
|
|
@@ -805,6 +1454,15 @@ interface MusicalContext {
|
|
|
805
1454
|
genre: string | null;
|
|
806
1455
|
timeSignature: string;
|
|
807
1456
|
chordProgression: PluginChordTiming[];
|
|
1457
|
+
/**
|
|
1458
|
+
* The scene's natural-language contract prompt (e.g. "dark psytrance,
|
|
1459
|
+
* driving 130 BPM, claustrophobic"). Null when the scene has no
|
|
1460
|
+
* contract set yet. Auto-prefixed to the LLM by `host.generateWithLLM`
|
|
1461
|
+
* so every per-track generation sees the scene-level intent without
|
|
1462
|
+
* each plugin having to plumb it through manually.
|
|
1463
|
+
* @since SDK 1.2.0
|
|
1464
|
+
*/
|
|
1465
|
+
contractPrompt: string | null;
|
|
808
1466
|
}
|
|
809
1467
|
interface PluginChordTiming {
|
|
810
1468
|
/** Chord symbol: 'Cm7', 'G', 'Fmaj7', etc. */
|
|
@@ -825,6 +1483,15 @@ interface PluginGenerationContext {
|
|
|
825
1483
|
genre: string | null;
|
|
826
1484
|
};
|
|
827
1485
|
concurrentTracks: PluginConcurrentTrackInfo[];
|
|
1486
|
+
/**
|
|
1487
|
+
* Count of tracks the host had to drop entirely from `concurrentTracks`
|
|
1488
|
+
* because their notes pushed the running total past the cross-track
|
|
1489
|
+
* budget. Panels should disclose this to the LLM (e.g. "… N additional
|
|
1490
|
+
* tracks omitted to fit token budget") so the model knows it is
|
|
1491
|
+
* working with partial context.
|
|
1492
|
+
* @since SDK 1.2.0
|
|
1493
|
+
*/
|
|
1494
|
+
truncatedTrackCount?: number;
|
|
828
1495
|
}
|
|
829
1496
|
interface PluginConcurrentTrackInfo {
|
|
830
1497
|
trackId: string;
|
|
@@ -832,6 +1499,22 @@ interface PluginConcurrentTrackInfo {
|
|
|
832
1499
|
presetCategory: string | null;
|
|
833
1500
|
/** Notes organized by which chord they fall under */
|
|
834
1501
|
notesByChord: PluginChordSegment[];
|
|
1502
|
+
/**
|
|
1503
|
+
* The user-typed prompt that produced this track's MIDI (from
|
|
1504
|
+
* `tracks.prompt`). Lets the LLM see *intent* alongside the notes —
|
|
1505
|
+
* "punchy 909 kick" carries more meaning than the kick MIDI alone.
|
|
1506
|
+
* @since SDK 1.2.0
|
|
1507
|
+
*/
|
|
1508
|
+
prompt?: string;
|
|
1509
|
+
/**
|
|
1510
|
+
* True when the host capped this track's notes (per-track budget).
|
|
1511
|
+
* The `notesByChord` payload is a prefix of the real content; the
|
|
1512
|
+
* total dropped count is `originalNoteCount - sum(notesByChord.notes.length)`.
|
|
1513
|
+
* @since SDK 1.2.0
|
|
1514
|
+
*/
|
|
1515
|
+
truncated?: boolean;
|
|
1516
|
+
/** The track's full note count before per-track truncation. */
|
|
1517
|
+
originalNoteCount?: number;
|
|
835
1518
|
}
|
|
836
1519
|
interface PluginChordSegment {
|
|
837
1520
|
chord: string;
|
|
@@ -871,6 +1554,20 @@ interface PluginTransportState {
|
|
|
871
1554
|
position: number;
|
|
872
1555
|
timeSignature: string;
|
|
873
1556
|
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Mono peak level for a single track, as reported by `getTrackLevels()`.
|
|
1559
|
+
* Drives the cosmetic per-track strip meters. `peakDb` is the max of the
|
|
1560
|
+
* L/R channels, floored at -120 (the "no signal" sentinel).
|
|
1561
|
+
* @since SDK 2.21.0
|
|
1562
|
+
*/
|
|
1563
|
+
interface PluginTrackLevel {
|
|
1564
|
+
/** Tracktion engine track id — matches `PluginTrackHandle.id`. */
|
|
1565
|
+
trackId: string;
|
|
1566
|
+
/** Mono peak in dBFS (max of L/R), floored at -120. */
|
|
1567
|
+
peakDb: number;
|
|
1568
|
+
/** Latched overload since the last poll. */
|
|
1569
|
+
clipped: boolean;
|
|
1570
|
+
}
|
|
874
1571
|
interface PluginSceneInfo {
|
|
875
1572
|
id: string;
|
|
876
1573
|
name: string;
|
|
@@ -899,6 +1596,17 @@ interface PluginSceneContext {
|
|
|
899
1596
|
hasTracks: boolean;
|
|
900
1597
|
/** Whether bulk generation is currently in progress */
|
|
901
1598
|
isBulkGenerating: boolean;
|
|
1599
|
+
/**
|
|
1600
|
+
* Scene kind. A 'transition' scene bridges two other scenes (the
|
|
1601
|
+
* transition-as-scene feature) and unlocks the crossfade-track UI in the
|
|
1602
|
+
* instrument panels; ordinary scenes are 'scene'. Absent on older hosts.
|
|
1603
|
+
* @since SDK 2.22.0
|
|
1604
|
+
*/
|
|
1605
|
+
sceneType?: 'scene' | 'transition';
|
|
1606
|
+
/** For a transition scene, the DB id of the scene it bridges FROM (origin). Null otherwise. @since SDK 2.22.0 */
|
|
1607
|
+
transitionFromSceneId?: string | null;
|
|
1608
|
+
/** For a transition scene, the DB id of the scene it bridges TO (target). Null otherwise. @since SDK 2.22.0 */
|
|
1609
|
+
transitionToSceneId?: string | null;
|
|
902
1610
|
}
|
|
903
1611
|
/** Placeholder track state for the progressive bulk-add UX */
|
|
904
1612
|
interface BulkAddPlaceholderTrack {
|
|
@@ -937,6 +1645,90 @@ interface LLMGenerationResult {
|
|
|
937
1645
|
/** Model that generated the response */
|
|
938
1646
|
model: string;
|
|
939
1647
|
}
|
|
1648
|
+
/** A single part of a Gemini-style content block. */
|
|
1649
|
+
interface LLMPart {
|
|
1650
|
+
/** Plain text. Mutually exclusive with functionCall / functionResponse. */
|
|
1651
|
+
text?: string;
|
|
1652
|
+
/** A tool/function the model is asking the host to invoke. */
|
|
1653
|
+
functionCall?: {
|
|
1654
|
+
name: string;
|
|
1655
|
+
args: Record<string, unknown>;
|
|
1656
|
+
/**
|
|
1657
|
+
* Opaque signature returned by Gemini 3+ tool-use models. Must be echoed
|
|
1658
|
+
* verbatim when the assistant turn is replayed on a later iteration, or
|
|
1659
|
+
* the API rejects the request with a 400 ("Function call is missing a
|
|
1660
|
+
* thought_signature in functionCall parts."). Pre-Gemini-3 models leave
|
|
1661
|
+
* this undefined; preserving it round-trip is safe across families.
|
|
1662
|
+
*/
|
|
1663
|
+
thoughtSignature?: string;
|
|
1664
|
+
};
|
|
1665
|
+
/** The result of a tool call, fed back into the loop on the next turn. */
|
|
1666
|
+
functionResponse?: {
|
|
1667
|
+
name: string;
|
|
1668
|
+
response: Record<string, unknown>;
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
interface LLMContent {
|
|
1672
|
+
/** 'user' = user/tool-result; 'model' = assistant. */
|
|
1673
|
+
role: 'user' | 'model';
|
|
1674
|
+
parts: LLMPart[];
|
|
1675
|
+
}
|
|
1676
|
+
interface LLMFunctionDeclaration {
|
|
1677
|
+
name: string;
|
|
1678
|
+
description: string;
|
|
1679
|
+
/** JSON Schema. Use `type: 'object'` with `properties` for any tool. */
|
|
1680
|
+
parameters: {
|
|
1681
|
+
type: 'object';
|
|
1682
|
+
properties?: Record<string, unknown>;
|
|
1683
|
+
required?: string[];
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
interface LLMTool {
|
|
1687
|
+
functionDeclarations: LLMFunctionDeclaration[];
|
|
1688
|
+
}
|
|
1689
|
+
interface LLMGenerationConfig {
|
|
1690
|
+
temperature?: number;
|
|
1691
|
+
topP?: number;
|
|
1692
|
+
topK?: number;
|
|
1693
|
+
maxOutputTokens?: number;
|
|
1694
|
+
}
|
|
1695
|
+
interface LLMSystemInstruction {
|
|
1696
|
+
parts: {
|
|
1697
|
+
text: string;
|
|
1698
|
+
}[];
|
|
1699
|
+
}
|
|
1700
|
+
interface LLMToolUseRequest {
|
|
1701
|
+
/** Gemini model id (e.g. 'gemini-2.5-flash'). */
|
|
1702
|
+
model: string;
|
|
1703
|
+
/** Conversation so far, including any tool-result turns. */
|
|
1704
|
+
contents: LLMContent[];
|
|
1705
|
+
/** System prompt as Gemini-native systemInstruction. */
|
|
1706
|
+
systemInstruction?: LLMSystemInstruction;
|
|
1707
|
+
/** Tool declarations the model may call. */
|
|
1708
|
+
tools?: LLMTool[];
|
|
1709
|
+
/** Optional tool-call mode override. */
|
|
1710
|
+
toolConfig?: {
|
|
1711
|
+
functionCallingConfig?: {
|
|
1712
|
+
mode?: 'AUTO' | 'ANY' | 'NONE';
|
|
1713
|
+
allowedFunctionNames?: string[];
|
|
1714
|
+
};
|
|
1715
|
+
};
|
|
1716
|
+
generationConfig?: LLMGenerationConfig;
|
|
1717
|
+
}
|
|
1718
|
+
interface LLMUsageMetadata {
|
|
1719
|
+
promptTokenCount: number;
|
|
1720
|
+
candidatesTokenCount: number;
|
|
1721
|
+
totalTokenCount: number;
|
|
1722
|
+
}
|
|
1723
|
+
interface LLMCandidate {
|
|
1724
|
+
content: LLMContent;
|
|
1725
|
+
finishReason?: string;
|
|
1726
|
+
index?: number;
|
|
1727
|
+
}
|
|
1728
|
+
interface LLMToolUseResponse {
|
|
1729
|
+
candidates: LLMCandidate[];
|
|
1730
|
+
usageMetadata?: LLMUsageMetadata;
|
|
1731
|
+
}
|
|
940
1732
|
interface PluginPresetData {
|
|
941
1733
|
name: string;
|
|
942
1734
|
category: string;
|
|
@@ -948,6 +1740,23 @@ interface ShufflePresetResult {
|
|
|
948
1740
|
presetName: string;
|
|
949
1741
|
presetCategory: string;
|
|
950
1742
|
}
|
|
1743
|
+
/**
|
|
1744
|
+
* One entry in a track's in-session "sound history" — the data behind the
|
|
1745
|
+
* TrackRow ↩ back-arrow and the drawer "History" tab (see `useSoundHistory`).
|
|
1746
|
+
*
|
|
1747
|
+
* `descriptor` is opaque to the SDK: each generator plugin defines its own shape
|
|
1748
|
+
* (a drum sample path string, an instrument `{ displayName, zones }`, a synth
|
|
1749
|
+
* `{ pluginIndex, stateBase64 }`) and is the value handed back to the plugin's
|
|
1750
|
+
* `applySound` callback to re-apply the sound.
|
|
1751
|
+
*/
|
|
1752
|
+
interface SoundHistoryEntry {
|
|
1753
|
+
/** Human-readable label shown in the History list (filename, preset/instrument name). */
|
|
1754
|
+
label: string;
|
|
1755
|
+
/** Opaque, plugin-defined value used to re-apply this sound. */
|
|
1756
|
+
descriptor: unknown;
|
|
1757
|
+
/** User-starred. Favorited entries are never auto-evicted by the history cap. */
|
|
1758
|
+
favorite?: boolean;
|
|
1759
|
+
}
|
|
951
1760
|
interface PluginSettingsSchema {
|
|
952
1761
|
type: 'object';
|
|
953
1762
|
properties: Record<string, SettingDefinition>;
|
|
@@ -1170,7 +1979,7 @@ interface PluginAudioTextureResult {
|
|
|
1170
1979
|
*/
|
|
1171
1980
|
cuePoints: PluginCuePoints | null;
|
|
1172
1981
|
/**
|
|
1173
|
-
* Path to the un-trimmed (raw) Lyria output. Used by the
|
|
1982
|
+
* Path to the un-trimmed (raw) Lyria output. Used by the stems
|
|
1174
1983
|
* trim editor to draw the full waveform. Persist via
|
|
1175
1984
|
* `host.setRawAudioFilePath`. Null when no raw file is available.
|
|
1176
1985
|
*/
|
|
@@ -1187,7 +1996,7 @@ interface PluginAudioTextureResult {
|
|
|
1187
1996
|
* Cue-points sidecar surfaced by the audio-processor `trim` command —
|
|
1188
1997
|
* sample positions for each detected beat inside the generated WAV.
|
|
1189
1998
|
* Mirrors the canonical `CuePoints` shape from the assistant; duplicated
|
|
1190
|
-
* here so external plugins don't reach into sas-
|
|
1999
|
+
* here so external plugins don't reach into sas-app internals.
|
|
1191
2000
|
*/
|
|
1192
2001
|
interface PluginCuePoints {
|
|
1193
2002
|
/** Schema version (currently 1). */
|
|
@@ -1266,6 +2075,14 @@ interface PluginAppTool {
|
|
|
1266
2075
|
inputSchema: PluginAppToolInputSchema;
|
|
1267
2076
|
/** `'scene'` = safe for scene-scoped callers. `'project'` = cross-scene. */
|
|
1268
2077
|
scope?: 'scene' | 'project';
|
|
2078
|
+
/**
|
|
2079
|
+
* `true` = the operation cannot be undone via the host's checkpoint/undo
|
|
2080
|
+
* system (project delete, disk overwrite, external export, …). The host
|
|
2081
|
+
* gates such calls behind a user-approval flow when invoked with agent
|
|
2082
|
+
* provenance; agent UIs may also surface the flag (e.g. ⚠ in a tool list).
|
|
2083
|
+
* @since SDK 2.18.0
|
|
2084
|
+
*/
|
|
2085
|
+
irreversible?: boolean;
|
|
1269
2086
|
}
|
|
1270
2087
|
/** Result shape returned by `PluginHost.executeAppTool`. */
|
|
1271
2088
|
interface PluginAppToolResult {
|
|
@@ -1377,37 +2194,257 @@ interface FxPresetDataEntry {
|
|
|
1377
2194
|
type FxPresetData = Partial<Record<FxCategory, FxPresetDataEntry>>;
|
|
1378
2195
|
|
|
1379
2196
|
/**
|
|
1380
|
-
*
|
|
2197
|
+
* TrackDrawer — the unified per-track drawer body.
|
|
1381
2198
|
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1384
|
-
* (
|
|
2199
|
+
* ONE drawer with a flat contextual tab strip. Which tabs appear is computed
|
|
2200
|
+
* from which callbacks the host panel provides:
|
|
2201
|
+
* - FX (onFxToggle) — the 6-category FX toggle bar
|
|
2202
|
+
* - Pick (onSelect) — instrument-plugin picker (+ native editor stage)
|
|
2203
|
+
* - History (onRestoreSound) — sounds this track has had (restore / favorite)
|
|
2204
|
+
* - Import (onImportSound) — copy a sound from a matching track in another scene
|
|
1385
2205
|
*
|
|
1386
|
-
*
|
|
2206
|
+
* The active tab is CONTROLLED by the host (activeTab / onTabChange) so the
|
|
2207
|
+
* track row's FX button and ▾ button can open the SAME drawer to a chosen tab.
|
|
2208
|
+
* When only one tab is enabled (e.g. loops = FX only) the strip is hidden and
|
|
2209
|
+
* that single view renders directly.
|
|
1387
2210
|
*
|
|
1388
|
-
*
|
|
2211
|
+
* (Was `InstrumentDrawer` — renamed once it grew an FX tab + Import tab. A
|
|
2212
|
+
* `TrackDrawer as InstrumentDrawer` alias is exported from the barrel for
|
|
2213
|
+
* backwards compatibility.)
|
|
1389
2214
|
*/
|
|
1390
2215
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
2216
|
+
/** The contextual tabs a track drawer can show, in display order. */
|
|
2217
|
+
type DrawerTab = 'fx' | 'pick' | 'history' | 'import' | 'edit';
|
|
2218
|
+
interface TrackDrawerProps {
|
|
2219
|
+
/** Which tab is active (controlled by the host TrackRow). */
|
|
2220
|
+
activeTab: DrawerTab;
|
|
2221
|
+
/** Switch tabs (strip clicks). */
|
|
2222
|
+
onTabChange?: (tab: DrawerTab) => void;
|
|
2223
|
+
trackId: string;
|
|
2224
|
+
fxState: TrackFxDetailState;
|
|
2225
|
+
onFxToggle?: (category: FxCategory, enabled: boolean) => void;
|
|
2226
|
+
onFxPresetChange?: (category: FxCategory, presetIndex: number) => void;
|
|
2227
|
+
onFxDryWetChange?: (category: FxCategory, value: number) => void;
|
|
2228
|
+
/** Disable FX controls (e.g. while the track is generating). */
|
|
2229
|
+
fxDisabled?: boolean;
|
|
2230
|
+
/** Available instrument plugins from engine scan. */
|
|
2231
|
+
instruments?: InstrumentDescriptor[];
|
|
2232
|
+
/** Currently loaded instrument plugin ID (null = default Surge XT). */
|
|
2233
|
+
currentPluginId?: string | null;
|
|
2234
|
+
/** Whether the instrument scan is still in progress. */
|
|
2235
|
+
isLoading?: boolean;
|
|
2236
|
+
/** Called when user selects an instrument (presence enables the Pick tab). */
|
|
2237
|
+
onSelect?: (pluginId: string) => void;
|
|
2238
|
+
/** Re-scan plugins. */
|
|
2239
|
+
onRefresh?: () => void;
|
|
2240
|
+
/** Pick-tab sub-view: show the native plugin editor instead of the grid. */
|
|
2241
|
+
editorStage?: boolean;
|
|
2242
|
+
/** Called when user clicks "Open Plugin Editor". */
|
|
2243
|
+
onShowEditor?: () => void;
|
|
2244
|
+
/** Called when user goes back from the editor to the instrument grid. */
|
|
2245
|
+
onBackToInstruments?: () => void;
|
|
2246
|
+
/** Name of the selected instrument (shown in the editor header). */
|
|
2247
|
+
selectedInstrumentName?: string | null;
|
|
2248
|
+
soundHistory?: readonly SoundHistoryEntry[];
|
|
2249
|
+
soundHistoryCursor?: number;
|
|
2250
|
+
/** Restore a sound by index; presence enables the History tab. */
|
|
2251
|
+
onRestoreSound?: (index: number) => void;
|
|
2252
|
+
/** Toggle the favorite (⭐) flag on a history entry; omit to hide the star. */
|
|
2253
|
+
onToggleFavorite?: (index: number) => void;
|
|
2254
|
+
/** Open the sound-import picker; presence enables the Import tab. */
|
|
2255
|
+
onImportSound?: () => void;
|
|
2256
|
+
/** Button label, e.g. "Import Sample" (drums/instruments) or "Import Preset" (synths). */
|
|
2257
|
+
importSoundLabel?: string;
|
|
2258
|
+
/** Current MIDI notes for the piano-roll editor. */
|
|
2259
|
+
editNotes?: readonly PluginMidiNote[];
|
|
2260
|
+
/** Persist edited notes; PRESENCE of this callback enables the Edit tab. */
|
|
2261
|
+
onNotesChange?: (notes: PluginMidiNote[]) => void;
|
|
2262
|
+
/** Scene length in bars (piano-roll grid width). Default 4. */
|
|
2263
|
+
editBars?: number;
|
|
2264
|
+
/** Scene BPM (piano-roll audition timing). Default 120. */
|
|
2265
|
+
editBpm?: number;
|
|
2266
|
+
/** Snap step in quarter notes for the piano roll (default 0.25). */
|
|
2267
|
+
editSnap?: number;
|
|
2268
|
+
/** Optional single-note preview when the user adds a note. */
|
|
2269
|
+
onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;
|
|
2270
|
+
}
|
|
2271
|
+
declare function TrackDrawer({ activeTab, onTabChange, trackId, fxState, onFxToggle, onFxPresetChange, onFxDryWetChange, fxDisabled, instruments, currentPluginId, isLoading, onSelect, onRefresh, editorStage, onShowEditor, onBackToInstruments, selectedInstrumentName, soundHistory, soundHistoryCursor, onRestoreSound, onToggleFavorite, onImportSound, importSoundLabel, editNotes, onNotesChange, editBars, editBpm, editSnap, onAuditionNote, }: TrackDrawerProps): React.ReactElement;
|
|
2272
|
+
|
|
2273
|
+
/**
|
|
2274
|
+
* useTrackLevels — drives the cosmetic per-track strip meters.
|
|
2275
|
+
*
|
|
2276
|
+
* The hard constraint for this feature is "playback ALWAYS wins over the GUI;
|
|
2277
|
+
* NO blocking threads." This hook is built around that:
|
|
2278
|
+
*
|
|
2279
|
+
* - It polls `host.getTrackLevels()` at ~30Hz with a recursive setTimeout that
|
|
2280
|
+
* only schedules the NEXT tick AFTER the previous await resolves. That is
|
|
2281
|
+
* automatic backpressure: a slow/stalled engine simply slows the meter, it
|
|
2282
|
+
* can never queue a backlog of requests. (The host + bridge also coalesce,
|
|
2283
|
+
* so a busy engine yields a STALE snapshot, never a pile-up.)
|
|
2284
|
+
* - It writes into a ref-held Map and notifies row subscribers, so the OWNING
|
|
2285
|
+
* panel never re-renders at 30Hz. Each row reads its own value via
|
|
2286
|
+
* `useTrackLevel` and re-renders only itself.
|
|
2287
|
+
* - It polls while the panel is mounted and the window is visible, and pauses
|
|
2288
|
+
* when the window is hidden. It deliberately does NOT gate on transport
|
|
2289
|
+
* "is playing": this app drives playback through decks / the clip launcher,
|
|
2290
|
+
* and the linear-transport play flag does not track that reliably. When
|
|
2291
|
+
* audio is stopped the engine simply returns floor levels, so the bars are
|
|
2292
|
+
* empty anyway — no need (and no reliable signal) to stop polling.
|
|
2293
|
+
*
|
|
2294
|
+
* Usage (panel):
|
|
2295
|
+
* const levels = useTrackLevels(host);
|
|
2296
|
+
* ...<TrackRow levels={levels} ... /> // row calls useTrackLevel(levels, id)
|
|
2297
|
+
*/
|
|
2298
|
+
|
|
2299
|
+
/**
|
|
2300
|
+
* Stable handle returned by {@link useTrackLevels}. Rows read their own level
|
|
2301
|
+
* and subscribe to per-tick notifications through it; its identity is stable
|
|
2302
|
+
* across renders so a row's subscription is set up once.
|
|
2303
|
+
*/
|
|
2304
|
+
interface TrackLevelsHandle {
|
|
2305
|
+
/** Current level for a track, or null when idle/absent (renders an empty bar). */
|
|
2306
|
+
getLevel(trackId: string): PluginTrackLevel | null;
|
|
2307
|
+
/** Subscribe to per-tick updates. Returns an unsubscribe function. */
|
|
2308
|
+
subscribe(listener: () => void): () => void;
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Poll every owned track's level while mounted + visible. Returns a stable
|
|
2312
|
+
* handle; the owning component does NOT re-render per tick. Pass `enabled =
|
|
2313
|
+
* false` to turn it off entirely (e.g. a panel that wants no meters). Safe to
|
|
2314
|
+
* call even when the host predates `getTrackLevels` (older SDK) — it stays idle.
|
|
2315
|
+
*/
|
|
2316
|
+
declare function useTrackLevels(host: PluginHost | null | undefined, enabled?: boolean): TrackLevelsHandle;
|
|
2317
|
+
/**
|
|
2318
|
+
* Per-row selector. Subscribes to the shared scheduler and re-renders ONLY the
|
|
2319
|
+
* calling component when this track's level changes. Returns null when idle
|
|
2320
|
+
* (transport stopped, window hidden, or the track has no meter yet).
|
|
2321
|
+
*/
|
|
2322
|
+
declare function useTrackLevel(handle: TrackLevelsHandle | null | undefined, trackId: string): PluginTrackLevel | null;
|
|
2323
|
+
/**
|
|
2324
|
+
* Per-row meter view-model: the current level plus a held peak for the meter UI.
|
|
2325
|
+
*/
|
|
2326
|
+
interface TrackMeterView {
|
|
2327
|
+
/** Current mono peak in dBFS (floored at -120). */
|
|
2328
|
+
peakDb: number;
|
|
2329
|
+
/** Held peak in dBFS — stays at the recent maximum for ~PEAK_HOLD_MS, then falls. */
|
|
2330
|
+
peakHoldDb: number;
|
|
2331
|
+
/** Latched clip flag for the last poll window. */
|
|
2332
|
+
clipped: boolean;
|
|
2333
|
+
/** True when the track currently has a live meter row. */
|
|
2334
|
+
active: boolean;
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Per-row meter selector WITH PEAK-HOLD. Like {@link useTrackLevel} it subscribes
|
|
2338
|
+
* to the shared ~30Hz scheduler and re-renders only the calling component, but it
|
|
2339
|
+
* also tracks a held peak that stays at the recent maximum for ~PEAK_HOLD_MS then
|
|
2340
|
+
* decays — so the eye can register where the signal peaked while the bar itself
|
|
2341
|
+
* moves fast. No extra timers or rAF: the held value is recomputed on each
|
|
2342
|
+
* scheduler notify, using performance.now() for hold/decay timing.
|
|
2343
|
+
*/
|
|
2344
|
+
declare function useTrackMeter(handle: TrackLevelsHandle | null | undefined, trackId: string): TrackMeterView;
|
|
2345
|
+
/**
|
|
2346
|
+
* Track the transport's play/stop state for a plugin. Seeds from
|
|
2347
|
+
* `getTransportState()` and follows `onTransportEvent`. Use its result as the
|
|
2348
|
+
* `active` arg to {@link useTrackLevels} so meters animate only during playback.
|
|
2349
|
+
*/
|
|
2350
|
+
declare function useTransportPlaying(host: PluginHost | null | undefined): boolean;
|
|
2351
|
+
|
|
2352
|
+
/**
|
|
2353
|
+
* Props the reorder machinery hands to a single row. Spread `handleProps` on the
|
|
2354
|
+
* drag grip and `rowProps` on the row's outer element; `isDragging` /
|
|
2355
|
+
* `isDragTarget` drive the visual state.
|
|
2356
|
+
*/
|
|
2357
|
+
interface TrackRowDragProps {
|
|
2358
|
+
handleProps: {
|
|
2359
|
+
draggable: true;
|
|
2360
|
+
onDragStart: (e: DragEvent<HTMLElement>) => void;
|
|
2361
|
+
onDragEnd: (e: DragEvent<HTMLElement>) => void;
|
|
2362
|
+
};
|
|
2363
|
+
rowProps: {
|
|
2364
|
+
onDragEnter: (e: DragEvent<HTMLElement>) => void;
|
|
2365
|
+
onDragOver: (e: DragEvent<HTMLElement>) => void;
|
|
2366
|
+
onDragLeave: (e: DragEvent<HTMLElement>) => void;
|
|
2367
|
+
onDrop: (e: DragEvent<HTMLElement>) => void;
|
|
2368
|
+
};
|
|
2369
|
+
/** This row is the one currently being dragged (dim it). */
|
|
2370
|
+
isDragging: boolean;
|
|
2371
|
+
/** This row is the current drop target (show an insertion accent). */
|
|
2372
|
+
isDragTarget: boolean;
|
|
2373
|
+
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Pure helper: return a NEW array with the item at `from` moved to `to`.
|
|
2376
|
+
* Out-of-range or no-op moves return a shallow copy unchanged. Exported for
|
|
2377
|
+
* unit testing the index math without a DOM.
|
|
2378
|
+
*/
|
|
2379
|
+
declare function moveItem<T>(arr: readonly T[], from: number, to: number): T[];
|
|
2380
|
+
interface UseTrackReorderOptions<T> {
|
|
2381
|
+
/** Host (only {@link PluginHost.reorderTracks} is used). */
|
|
2382
|
+
host: Pick<PluginHost, 'reorderTracks'>;
|
|
2383
|
+
/** The panel's current track array (also the render order). */
|
|
2384
|
+
items: T[];
|
|
2385
|
+
/** The panel's state setter for `items` (used for optimistic update + revert). */
|
|
2386
|
+
setItems: Dispatch<SetStateAction<T[]>>;
|
|
2387
|
+
/** Stable id for persistence — use the track's dbId, not its engine id. */
|
|
2388
|
+
getId: (item: T) => string;
|
|
2389
|
+
/** Called if persistence fails, after the optimistic update is reverted. */
|
|
2390
|
+
onError?: (err: unknown) => void;
|
|
2391
|
+
}
|
|
2392
|
+
interface UseTrackReorderResult {
|
|
2393
|
+
/** Build the drag props for the row at `index`; spread onto its TrackRow. */
|
|
2394
|
+
dragPropsFor: (index: number) => TrackRowDragProps;
|
|
2395
|
+
/** Index of the row being dragged, or null. */
|
|
2396
|
+
draggingIndex: number | null;
|
|
2397
|
+
/** Index of the current drop-target row, or null. */
|
|
2398
|
+
dragOverIndex: number | null;
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Drag-and-drop reordering for a panel's track list. Dropping a row onto another
|
|
2402
|
+
* row moves it into that row's position (everything between shifts); the top and
|
|
2403
|
+
* bottom are reachable by dropping on the first/last row.
|
|
2404
|
+
*/
|
|
2405
|
+
declare function useTrackReorder<T>({ host, items, setItems, getId, onError, }: UseTrackReorderOptions<T>): UseTrackReorderResult;
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* SDK TrackRow — Reusable track row component for generator plugins.
|
|
2409
|
+
*
|
|
2410
|
+
* Renders a complete track UI with prompt input, generation controls,
|
|
2411
|
+
* shuffle/copy, volume/pan, mute/solo, FX drawer, and visual states
|
|
2412
|
+
* (amber pulse for "needs generation", progress overlay, error indicator).
|
|
2413
|
+
*
|
|
2414
|
+
* Layout matches TrackInput (main branch) for visual parity.
|
|
2415
|
+
*
|
|
2416
|
+
* Depends only on PluginHost types + existing shared renderer components.
|
|
2417
|
+
*/
|
|
2418
|
+
|
|
2419
|
+
interface SDKTrackRowProps {
|
|
2420
|
+
/** Track identity */
|
|
2421
|
+
track: {
|
|
2422
|
+
id: string;
|
|
2423
|
+
name: string;
|
|
2424
|
+
role?: string;
|
|
2425
|
+
};
|
|
2426
|
+
/** Current prompt text (optional — omit when using contentSlot) */
|
|
2427
|
+
prompt?: string;
|
|
2428
|
+
/** Playback state */
|
|
2429
|
+
runtimeState: {
|
|
2430
|
+
muted: boolean;
|
|
1403
2431
|
solo: boolean;
|
|
1404
2432
|
volume: number;
|
|
1405
2433
|
pan: number;
|
|
1406
2434
|
};
|
|
2435
|
+
/** True when ANOTHER track is soloed, so this (non-soloed) track is currently
|
|
2436
|
+
* silenced. Renders the row dimmed while leaving its Mute button UNLIT — the
|
|
2437
|
+
* engine's effective-mute model silences it without touching user-mute. Purely
|
|
2438
|
+
* visual; does not change mute/solo state. */
|
|
2439
|
+
soloedOut?: boolean;
|
|
1407
2440
|
/** FX category states */
|
|
1408
2441
|
fxDetailState: TrackFxDetailState;
|
|
1409
|
-
/**
|
|
1410
|
-
|
|
2442
|
+
/** Whether the unified track drawer is open. */
|
|
2443
|
+
drawerOpen: boolean;
|
|
2444
|
+
/** Which tab the drawer is showing. */
|
|
2445
|
+
drawerTab: DrawerTab;
|
|
2446
|
+
/** Switch the active drawer tab (tab-strip clicks). Omit for single-tab panels (e.g. loops = FX only). */
|
|
2447
|
+
onTabChange?: (tab: DrawerTab) => void;
|
|
1411
2448
|
/** Generation in progress */
|
|
1412
2449
|
isGenerating?: boolean;
|
|
1413
2450
|
/** Auth state */
|
|
@@ -1428,8 +2465,9 @@ interface SDKTrackRowProps {
|
|
|
1428
2465
|
onShuffle?: () => void;
|
|
1429
2466
|
/** Duplicate track (optional — omit to hide Copy button) */
|
|
1430
2467
|
onCopy?: () => void;
|
|
1431
|
-
/** Delete track
|
|
1432
|
-
|
|
2468
|
+
/** Delete track. Optional — omit to hide the delete button (e.g. a composite
|
|
2469
|
+
* like CrossfadeTrackRow owns a single delete for the whole pair). */
|
|
2470
|
+
onDelete?: () => void;
|
|
1433
2471
|
/** Custom content replacing the prompt input (e.g., sample info display) */
|
|
1434
2472
|
contentSlot?: React.ReactNode;
|
|
1435
2473
|
/** Toggle mute */
|
|
@@ -1456,10 +2494,8 @@ interface SDKTrackRowProps {
|
|
|
1456
2494
|
instrumentName?: string | null;
|
|
1457
2495
|
/** Whether the current instrument plugin is missing from the system */
|
|
1458
2496
|
instrumentMissing?: boolean;
|
|
1459
|
-
/**
|
|
1460
|
-
|
|
1461
|
-
/** Toggle the instrument drawer */
|
|
1462
|
-
onToggleInstrumentDrawer?: () => void;
|
|
2497
|
+
/** Open/close the drawer to a non-FX tab (the ▾ button). Omit to hide it. */
|
|
2498
|
+
onToggleDrawer?: () => void;
|
|
1463
2499
|
/** Available instrument plugins for the drawer */
|
|
1464
2500
|
availableInstruments?: InstrumentDescriptor[];
|
|
1465
2501
|
/** Currently loaded instrument plugin ID */
|
|
@@ -1470,43 +2506,516 @@ interface SDKTrackRowProps {
|
|
|
1470
2506
|
instrumentsLoading?: boolean;
|
|
1471
2507
|
/** Re-scan for instruments */
|
|
1472
2508
|
onRefreshInstruments?: () => void;
|
|
1473
|
-
/**
|
|
1474
|
-
|
|
2509
|
+
/** Pick-tab sub-view: native plugin editor instead of the instrument grid. */
|
|
2510
|
+
editorStage?: boolean;
|
|
1475
2511
|
/** Called when user clicks "Open Editor" */
|
|
1476
2512
|
onShowEditor?: () => void;
|
|
1477
2513
|
/** Called when user wants to go back from editor view */
|
|
1478
2514
|
onBackToInstruments?: () => void;
|
|
2515
|
+
/** Ordered list of sounds this track has had this session. */
|
|
2516
|
+
soundHistory?: readonly SoundHistoryEntry[];
|
|
2517
|
+
/** Index into soundHistory of the currently-applied sound. */
|
|
2518
|
+
soundHistoryCursor?: number;
|
|
2519
|
+
/** Restore a sound from the History tab by index. */
|
|
2520
|
+
onRestoreSound?: (index: number) => void;
|
|
2521
|
+
/** Toggle the favorite (⭐) flag on a history entry. */
|
|
2522
|
+
onToggleFavorite?: (index: number) => void;
|
|
2523
|
+
/** Open the drawer's sound-import picker; omit to hide the button. */
|
|
2524
|
+
onImportSound?: () => void;
|
|
2525
|
+
/** Sound-import button label ("Import Sample" / "Import Preset"). */
|
|
2526
|
+
importSoundLabel?: string;
|
|
2527
|
+
/** Current MIDI notes for the piano-roll editor (the 'edit' tab). */
|
|
2528
|
+
editNotes?: readonly PluginMidiNote[];
|
|
2529
|
+
/** Persist edited notes; PRESENCE of this callback enables the Edit tab. */
|
|
2530
|
+
onNotesChange?: (notes: PluginMidiNote[]) => void;
|
|
2531
|
+
/** Scene length in bars (piano-roll grid width). */
|
|
2532
|
+
editBars?: number;
|
|
2533
|
+
/** Scene BPM (piano-roll audition timing). */
|
|
2534
|
+
editBpm?: number;
|
|
2535
|
+
/** Snap step in quarter notes for the piano roll (default 0.25). */
|
|
2536
|
+
editSnap?: number;
|
|
2537
|
+
/** Optional single-note preview when the user adds a note. */
|
|
2538
|
+
onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;
|
|
2539
|
+
/** Drag props from {@link useTrackReorder}. When present, renders the grip
|
|
2540
|
+
* handle and makes the row a drop target. Omit for non-reorderable lists. */
|
|
2541
|
+
drag?: TrackRowDragProps;
|
|
2542
|
+
/** Shared meter handle from `useTrackLevels(host, isPlaying)`. When present,
|
|
2543
|
+
* a thin peak meter welds to the bottom of the row. Omit to hide it. */
|
|
2544
|
+
levels?: TrackLevelsHandle;
|
|
2545
|
+
}
|
|
2546
|
+
declare function TrackRow({ track, prompt, runtimeState, soloedOut, fxDetailState, drawerOpen, drawerTab, onTabChange, isGenerating, isAuthenticated, error, hasMidi, generationProgress, estimatedGenerationMs, onPromptChange, onGenerate, onShuffle, onCopy, onDelete, contentSlot, onMuteToggle, onSoloToggle, onVolumeChange, onPanChange, onFxToggle, onFxPresetChange, onFxDryWetChange, onToggleFxDrawer, onProgressChange, accentColor, instrumentName, instrumentMissing, onToggleDrawer, availableInstruments, currentInstrumentPluginId, onInstrumentSelect, instrumentsLoading, onRefreshInstruments, editorStage, onShowEditor, onBackToInstruments, soundHistory, soundHistoryCursor, onRestoreSound, onToggleFavorite, onImportSound, importSoundLabel, editNotes, onNotesChange, editBars, editBpm, editSnap, onAuditionNote, drag, levels, }: SDKTrackRowProps): React.ReactElement;
|
|
2547
|
+
|
|
2548
|
+
/**
|
|
2549
|
+
* Crossfade-pair metadata — family-agnostic types + parsing shared by every
|
|
2550
|
+
* generator panel that supports transition crossfades (synth / drum / instrument).
|
|
2551
|
+
*
|
|
2552
|
+
* A crossfade pair is two normal tracks linked by a shared `groupId`, persisted
|
|
2553
|
+
* in scene plugin_data under `track:<dbId>:crossfade`. Both members play the
|
|
2554
|
+
* same MIDI; one wears the origin preset, the other the target preset. The panel
|
|
2555
|
+
* owns the family-specific create flow (how a preset/sample is copied) and the
|
|
2556
|
+
* render; this module owns only the shape + the scene-data → pairs parse so the
|
|
2557
|
+
* logic can't drift across the three panels.
|
|
2558
|
+
*
|
|
2559
|
+
* @since SDK 2.23.0
|
|
2560
|
+
*/
|
|
2561
|
+
/** Which half of the pair a per-layer control / member targets. */
|
|
2562
|
+
type CrossfadeSlot = 'origin' | 'target';
|
|
2563
|
+
/**
|
|
2564
|
+
* Equal-power center gain (~-3 dB, 1/√2) applied to BOTH crossfade layers so a
|
|
2565
|
+
* centered, non-functional slider already sounds like a midpoint blend. The
|
|
2566
|
+
* per-layer volume sliders start here; a later phase's fader drives them.
|
|
2567
|
+
*/
|
|
2568
|
+
declare const EQUAL_POWER_GAIN = 0.707;
|
|
2569
|
+
/**
|
|
2570
|
+
* Per-member crossfade metadata (one scene-data value per member track). The two
|
|
2571
|
+
* members (origin/target) of a pair share a `groupId`.
|
|
2572
|
+
*/
|
|
2573
|
+
interface CrossfadeMeta {
|
|
2574
|
+
groupId: string;
|
|
2575
|
+
slot: CrossfadeSlot;
|
|
2576
|
+
/** DB id of the partner member track. */
|
|
2577
|
+
partnerDbId: string;
|
|
2578
|
+
/** DB id of the SOURCE track this layer's preset/sample was copied from. */
|
|
2579
|
+
sourceTrackDbId: string;
|
|
2580
|
+
/** DB id of the scene the source track lives in (the from/to scene). */
|
|
2581
|
+
sourceSceneId: string;
|
|
2582
|
+
/** Source track display name (shown in the caption). */
|
|
2583
|
+
sourceName: string;
|
|
2584
|
+
/** Copied preset/sample label (shown in the caption). */
|
|
2585
|
+
soundLabel: string;
|
|
2586
|
+
/** Crossfade position 0..1 (kept identical on both members). */
|
|
2587
|
+
sliderPos: number;
|
|
2588
|
+
}
|
|
2589
|
+
/** A complete crossfade pair (both members present), keyed by groupId. */
|
|
2590
|
+
interface CrossfadePairMeta {
|
|
2591
|
+
groupId: string;
|
|
2592
|
+
sliderPos: number;
|
|
2593
|
+
originDbId: string;
|
|
2594
|
+
targetDbId: string;
|
|
2595
|
+
originSourceName: string;
|
|
2596
|
+
originSoundLabel: string;
|
|
2597
|
+
targetSourceName: string;
|
|
2598
|
+
targetSoundLabel: string;
|
|
2599
|
+
}
|
|
2600
|
+
/** Narrow an unknown scene-data value to CrossfadeMeta (defensive — survives partial blobs). */
|
|
2601
|
+
declare function asCrossfadeMeta(val: unknown): CrossfadeMeta | null;
|
|
2602
|
+
/**
|
|
2603
|
+
* Scan all `track:<dbId>:crossfade` keys in a scene's plugin_data and assemble
|
|
2604
|
+
* COMPLETE pairs (both origin + target present). A half-broken group (partner
|
|
2605
|
+
* deleted underneath) is omitted, so its surviving member falls back to a normal
|
|
2606
|
+
* row instead of vanishing.
|
|
2607
|
+
*/
|
|
2608
|
+
declare function parseCrossfadePairs(sceneData: Record<string, unknown>): CrossfadePairMeta[];
|
|
2609
|
+
|
|
2610
|
+
/**
|
|
2611
|
+
* CrossfadeTrackRow — a transition "crossfade track": two stacked TrackRows
|
|
2612
|
+
* (origin on top, target on bottom) joined by a horizontal crossfade slider.
|
|
2613
|
+
*
|
|
2614
|
+
* Both layers play the SAME generated MIDI; the top wears the ORIGIN scene
|
|
2615
|
+
* track's preset and the bottom wears the TARGET scene track's preset. The user
|
|
2616
|
+
* cannot regenerate, shuffle, or change the preset/sample on either layer —
|
|
2617
|
+
* those controls are simply not wired into the inner TrackRows (the SDK
|
|
2618
|
+
* TrackRow is "controlled by omission"). What remains: per-layer volume/pan,
|
|
2619
|
+
* GROUP mute/solo (both layers toggle together), and a single delete that
|
|
2620
|
+
* removes the whole pair.
|
|
2621
|
+
*
|
|
2622
|
+
* The slider represents WHERE the crossfade happens. In this phase it is
|
|
2623
|
+
* centered and non-functional (omit `onSliderChange` → it renders disabled); a
|
|
2624
|
+
* later phase wires it to fade origin→target across the bars.
|
|
2625
|
+
*
|
|
2626
|
+
* @since SDK 2.22.0
|
|
2627
|
+
*/
|
|
2628
|
+
|
|
2629
|
+
/** One layer (engine track) of a crossfade pair. */
|
|
2630
|
+
interface CrossfadeLayer {
|
|
2631
|
+
/** Engine track id of this layer's track (also the meter key). */
|
|
2632
|
+
trackId: string;
|
|
2633
|
+
/** Display name of this layer's (newly created) track. */
|
|
2634
|
+
name: string;
|
|
2635
|
+
/** Musical role (same for both layers — crossfades are same-role). */
|
|
2636
|
+
role?: string;
|
|
2637
|
+
/** Name of the SOURCE track this layer was cloned from (origin/target scene). */
|
|
2638
|
+
sourceName?: string;
|
|
2639
|
+
/** Human label of the copied preset/sound, shown in the caption. */
|
|
2640
|
+
soundLabel?: string;
|
|
2641
|
+
/** Playback state for this layer. */
|
|
2642
|
+
runtimeState: {
|
|
2643
|
+
muted: boolean;
|
|
2644
|
+
solo: boolean;
|
|
2645
|
+
volume: number;
|
|
2646
|
+
pan: number;
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
interface CrossfadeTrackRowProps {
|
|
2650
|
+
/** Top layer — wears the origin (from) scene track's preset. */
|
|
2651
|
+
origin: CrossfadeLayer;
|
|
2652
|
+
/** Bottom layer — wears the target (to) scene track's preset. */
|
|
2653
|
+
target: CrossfadeLayer;
|
|
2654
|
+
/** Crossfade position 0..1 (0 = all origin, 1 = all target). Defaults centered. */
|
|
2655
|
+
sliderPos?: number;
|
|
2656
|
+
/** Toggle mute on BOTH layers together (group mute). */
|
|
2657
|
+
onMuteToggle: () => void;
|
|
2658
|
+
/** Toggle solo on BOTH layers together (group solo). */
|
|
2659
|
+
onSoloToggle: () => void;
|
|
2660
|
+
/** Change one layer's volume (per-layer). */
|
|
2661
|
+
onVolumeChange: (slot: CrossfadeSlot, volume: number) => void;
|
|
2662
|
+
/** Change one layer's pan (per-layer). */
|
|
2663
|
+
onPanChange: (slot: CrossfadeSlot, pan: number) => void;
|
|
2664
|
+
/** Delete the whole pair. */
|
|
2665
|
+
onDelete: () => void;
|
|
2666
|
+
/** Move the crossfade point. Omit to render the slider read-only (phase 1). */
|
|
2667
|
+
onSliderChange?: (pos: number) => void;
|
|
2668
|
+
/** Shared meter handle (welds a peak meter to each layer). */
|
|
2669
|
+
levels?: TrackLevelsHandle;
|
|
2670
|
+
/** Left-border accent. Defaults to transition purple. */
|
|
2671
|
+
accentColor?: string;
|
|
1479
2672
|
}
|
|
1480
|
-
declare function
|
|
2673
|
+
declare function CrossfadeTrackRow({ origin, target, sliderPos, onMuteToggle, onSoloToggle, onVolumeChange, onPanChange, onDelete, onSliderChange, levels, accentColor, }: CrossfadeTrackRowProps): React.ReactElement;
|
|
1481
2674
|
|
|
1482
2675
|
/**
|
|
1483
|
-
*
|
|
2676
|
+
* Crossfade MIDI inpainting — builds the LLM user-prompt for a bridge that
|
|
2677
|
+
* MORPHS the ORIGIN part into the TARGET part.
|
|
2678
|
+
*
|
|
2679
|
+
* A normal scene generation composes a part standalone from the scene's chords.
|
|
2680
|
+
* A crossfade bridge is different: it is INPAINTING between two fixed endpoints.
|
|
2681
|
+
* The generated part must begin feeling continuous with the origin pattern and
|
|
2682
|
+
* end feeling continuous with the target pattern, transforming between them
|
|
2683
|
+
* across the transition's bars.
|
|
2684
|
+
*
|
|
2685
|
+
* The harmonic frame — Key / mode / BPM / bars / the transition chord
|
|
2686
|
+
* progression (with beat timing) / scene contract — is injected AUTOMATICALLY by
|
|
2687
|
+
* `host.generateWithLLM` (it prepends the active scene's "Musical Context" block
|
|
2688
|
+
* unless `skipContextPrefix` is set). So this prompt does NOT restate key/bpm/
|
|
2689
|
+
* chords — it adds only the two endpoint patterns + the morph instructions, and
|
|
2690
|
+
* references the harmonic frame as "given above".
|
|
2691
|
+
*
|
|
2692
|
+
* REPRESENTATION (researched for Gemini): ABC notation is the LLM-native format
|
|
2693
|
+
* for melodic generation, but it's weak for percussion, would need a separate
|
|
2694
|
+
* output parser (our output is JSON note-events, already proven with Gemini),
|
|
2695
|
+
* and an inpainting task wants input/output FORMAT SYMMETRY. So each endpoint is
|
|
2696
|
+
* given as the exact JSON note-events PLUS a pitch-named, bar-structured "gloss"
|
|
2697
|
+
* — the transferable wins from the research (pitch NAMES over raw MIDI numbers,
|
|
2698
|
+
* explicit bar/beat structure) layered on the precise, symmetric JSON. Drums
|
|
2699
|
+
* (uniform pitch) get a rhythmic gloss instead of pitch names.
|
|
2700
|
+
*
|
|
2701
|
+
* This changes only the LLM INPUT framing: the OUTPUT schema is unchanged, so the
|
|
2702
|
+
* calling panel keeps its system prompt + parser (and, for drums, its flatten step).
|
|
1484
2703
|
*
|
|
1485
|
-
*
|
|
1486
|
-
* Stage 2 (editor): Shows "Open Editor" button for the selected plugin's native GUI.
|
|
2704
|
+
* @since SDK 2.24.0
|
|
1487
2705
|
*/
|
|
1488
2706
|
|
|
1489
|
-
interface
|
|
1490
|
-
/**
|
|
1491
|
-
|
|
1492
|
-
/**
|
|
1493
|
-
|
|
1494
|
-
/**
|
|
1495
|
-
|
|
1496
|
-
/**
|
|
1497
|
-
|
|
1498
|
-
/**
|
|
1499
|
-
|
|
1500
|
-
/**
|
|
1501
|
-
|
|
1502
|
-
/**
|
|
1503
|
-
|
|
1504
|
-
/**
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
|
|
2707
|
+
interface CrossfadeInpaintInput {
|
|
2708
|
+
/** Musical role of the bridge part (e.g. 'bass'). '' falls back to "melodic". */
|
|
2709
|
+
role: string;
|
|
2710
|
+
/** Transition length in bars (the morph timeline). */
|
|
2711
|
+
bars: number;
|
|
2712
|
+
/** Display name of the ORIGIN source track (the part the bridge begins from). */
|
|
2713
|
+
originName: string;
|
|
2714
|
+
/** Display name of the TARGET source track (the part the bridge arrives at). */
|
|
2715
|
+
targetName: string;
|
|
2716
|
+
/** ORIGIN source scene's key label (e.g. "G minor"). Null/omitted = unknown. */
|
|
2717
|
+
originKey?: string | null;
|
|
2718
|
+
/** TARGET source scene's key label. Null/omitted = unknown. */
|
|
2719
|
+
targetKey?: string | null;
|
|
2720
|
+
/** ORIGIN pattern notes (beat-based; from the FROM scene). May be empty. */
|
|
2721
|
+
originNotes: readonly PluginMidiNote[];
|
|
2722
|
+
/** TARGET pattern notes (beat-based; from the TO scene). May be empty. */
|
|
2723
|
+
targetNotes: readonly PluginMidiNote[];
|
|
2724
|
+
/** Drums: pitch is uniform (flattened), so gloss RHYTHM instead of pitch names. */
|
|
2725
|
+
percussive?: boolean;
|
|
1508
2726
|
}
|
|
1509
|
-
|
|
2727
|
+
/**
|
|
2728
|
+
* Build the inpainting user-prompt. The result is the prompt BODY only — pass it
|
|
2729
|
+
* as `request.user` to `host.generateWithLLM` with the panel's normal system
|
|
2730
|
+
* prompt and `responseFormat: 'json'`; the harmonic context auto-prefixes.
|
|
2731
|
+
*/
|
|
2732
|
+
declare function buildCrossfadeInpaintPrompt(input: CrossfadeInpaintInput): string;
|
|
2733
|
+
|
|
2734
|
+
/**
|
|
2735
|
+
* ImportTrackModal — "import a track from another scene" picker (SDK component).
|
|
2736
|
+
*
|
|
2737
|
+
* Shared by all five generator panels (drums / instruments / synths / loops /
|
|
2738
|
+
* stems). Self-fetching: given the scoped `host`, it calls
|
|
2739
|
+
* `host.listImportableTracks()` to enumerate candidates (already filtered to
|
|
2740
|
+
* the calling panel's type and gate-annotated by the host) and
|
|
2741
|
+
* `host.importTrack()` to perform the copy. The UI only renders `importable` +
|
|
2742
|
+
* `disabledReason` — it never computes the harmonic/length/tempo gate itself.
|
|
2743
|
+
*
|
|
2744
|
+
* Two-step picker: choose a source scene, then a track in it. Incompatible
|
|
2745
|
+
* tracks render disabled with a reason tooltip (never hidden), per product
|
|
2746
|
+
* decision.
|
|
2747
|
+
*
|
|
2748
|
+
* @since SDK 2.13.0
|
|
2749
|
+
*/
|
|
2750
|
+
|
|
2751
|
+
interface ImportTrackModalProps {
|
|
2752
|
+
/** Scoped host — the modal calls listImportableTracks / importTrack itself. */
|
|
2753
|
+
host: PluginHost;
|
|
2754
|
+
/** Controls visibility (the panel owns open/closed from its header button). */
|
|
2755
|
+
open: boolean;
|
|
2756
|
+
/** Close handler (Escape, backdrop, Cancel, or after a successful import). */
|
|
2757
|
+
onClose: () => void;
|
|
2758
|
+
/** Fired after a successful import with the new track handle. */
|
|
2759
|
+
onImported: (handle: PluginTrackHandle) => void;
|
|
2760
|
+
/** Optional modal title (default names the whole-track import). */
|
|
2761
|
+
title?: string;
|
|
2762
|
+
/** data-testid prefix so each panel's modal is addressable in tests. */
|
|
2763
|
+
testIdPrefix?: string;
|
|
2764
|
+
/**
|
|
2765
|
+
* 'track' (default) imports a whole track via `importTrack`. 'sound' copies
|
|
2766
|
+
* ONLY the sound onto an existing track: every candidate is selectable (the
|
|
2767
|
+
* contract gate is ignored) and the chosen track is handed back via `onPick`
|
|
2768
|
+
* instead of being imported — the panel applies it via `host.getTrackSound`.
|
|
2769
|
+
*/
|
|
2770
|
+
mode?: 'track' | 'sound';
|
|
2771
|
+
/** Sound-mode pick handler — required when `mode='sound'`. */
|
|
2772
|
+
onPick?: (sel: {
|
|
2773
|
+
sourceTrackDbId: string;
|
|
2774
|
+
trackName: string;
|
|
2775
|
+
sceneName: string;
|
|
2776
|
+
}) => void | Promise<void>;
|
|
2777
|
+
/**
|
|
2778
|
+
* Cross-panel port handler (track mode). When provided, the modal also lists
|
|
2779
|
+
* the ACTIVE scene's tracks owned by OTHER panels as a `sameScene` group —
|
|
2780
|
+
* shown first and selected by default — and routes a pick there to this
|
|
2781
|
+
* callback instead of `importTrack`. The panel re-sounds the part on its own
|
|
2782
|
+
* instrument (create track → copy MIDI → load native sound). @since SDK 2.20.0
|
|
2783
|
+
*/
|
|
2784
|
+
onPortTrack?: (sel: {
|
|
2785
|
+
sourceTrackDbId: string;
|
|
2786
|
+
trackName: string;
|
|
2787
|
+
role?: string;
|
|
2788
|
+
}) => void | Promise<void>;
|
|
2789
|
+
}
|
|
2790
|
+
declare function ImportTrackModal({ host, open, onClose, onImported, title, testIdPrefix, mode, onPick, onPortTrack, }: ImportTrackModalProps): React.ReactElement | null;
|
|
2791
|
+
|
|
2792
|
+
/**
|
|
2793
|
+
* CrossfadeModal — "add a crossfade track" picker for a transition scene.
|
|
2794
|
+
*
|
|
2795
|
+
* Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN
|
|
2796
|
+
* track (from the transition's FROM scene) and a TARGET track (from its TO
|
|
2797
|
+
* scene). Crossfades are same-role: once an origin is chosen, the target
|
|
2798
|
+
* dropdown is filtered to the origin's role.
|
|
2799
|
+
*
|
|
2800
|
+
* Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`
|
|
2801
|
+
* for both scenes (ungated — a transition deliberately bridges different keys).
|
|
2802
|
+
* It does NOT build the pair itself; it hands the two selections to `onCreate`,
|
|
2803
|
+
* which the panel implements (create two tracks, generate one shared MIDI clip,
|
|
2804
|
+
* copy each preset). `onCreate` should reject on failure so the modal can show
|
|
2805
|
+
* it and stay open.
|
|
2806
|
+
*
|
|
2807
|
+
* @since SDK 2.22.0
|
|
2808
|
+
*/
|
|
2809
|
+
|
|
2810
|
+
/** A picked source track handed to `onCreate`. */
|
|
2811
|
+
interface CrossfadeSelection {
|
|
2812
|
+
/** Source track DB id (selector for getTrackSound + crossfade metadata). */
|
|
2813
|
+
dbId: string;
|
|
2814
|
+
/** Display name (for the row caption). */
|
|
2815
|
+
name: string;
|
|
2816
|
+
/** Musical role (same for both — enforced by the picker). */
|
|
2817
|
+
role?: string;
|
|
2818
|
+
}
|
|
2819
|
+
interface CrossfadeModalProps {
|
|
2820
|
+
/** Scoped host — the modal calls listSceneFamilyTracks itself. */
|
|
2821
|
+
host: PluginHost;
|
|
2822
|
+
/** Controls visibility (the panel owns open/closed from its header button). */
|
|
2823
|
+
open: boolean;
|
|
2824
|
+
/** DB id of the transition's FROM (origin) scene. */
|
|
2825
|
+
fromSceneId: string;
|
|
2826
|
+
/** DB id of the transition's TO (target) scene. */
|
|
2827
|
+
toSceneId: string;
|
|
2828
|
+
/** Display name for the origin scene heading (optional). */
|
|
2829
|
+
fromSceneName?: string;
|
|
2830
|
+
/** Display name for the target scene heading (optional). */
|
|
2831
|
+
toSceneName?: string;
|
|
2832
|
+
/** Close handler (Escape, backdrop, Cancel, or after a successful create). */
|
|
2833
|
+
onClose: () => void;
|
|
2834
|
+
/** Build the crossfade pair. Should reject on failure so the modal shows it. */
|
|
2835
|
+
onCreate: (origin: CrossfadeSelection, target: CrossfadeSelection) => Promise<void>;
|
|
2836
|
+
/** data-testid prefix. */
|
|
2837
|
+
testIdPrefix?: string;
|
|
2838
|
+
}
|
|
2839
|
+
declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
|
|
2840
|
+
|
|
2841
|
+
/**
|
|
2842
|
+
* ConfirmDialog — styled in-app confirmation modal (SDK component).
|
|
2843
|
+
*
|
|
2844
|
+
* A small, reusable "are you sure?" dialog matching the app's dark theme
|
|
2845
|
+
* (mirrors ImportTrackModal chrome: sas-panel / sas-border / shadow-xl). It
|
|
2846
|
+
* guards destructive actions; the first consumer is track deletion, which was
|
|
2847
|
+
* one stray click away from losing a track's MIDI + sound.
|
|
2848
|
+
*
|
|
2849
|
+
* Controlled component — the caller owns `open` and the confirm/cancel
|
|
2850
|
+
* handlers. Escape and a backdrop click both cancel, and the Cancel button is
|
|
2851
|
+
* auto-focused on open so a reflexive Enter dismisses rather than deletes.
|
|
2852
|
+
*
|
|
2853
|
+
* @since SDK 2.17.0
|
|
2854
|
+
*/
|
|
2855
|
+
|
|
2856
|
+
interface ConfirmDialogProps {
|
|
2857
|
+
/** Controls visibility (the caller owns open/closed). */
|
|
2858
|
+
open: boolean;
|
|
2859
|
+
/** Bold heading line. */
|
|
2860
|
+
title: string;
|
|
2861
|
+
/** Body copy — a string or richer node. */
|
|
2862
|
+
message: React.ReactNode;
|
|
2863
|
+
/** Confirm button label (default "Delete"). */
|
|
2864
|
+
confirmLabel?: string;
|
|
2865
|
+
/** Cancel button label (default "Cancel"). */
|
|
2866
|
+
cancelLabel?: string;
|
|
2867
|
+
/** When true (default), the confirm button reads as a destructive (red) action. */
|
|
2868
|
+
destructive?: boolean;
|
|
2869
|
+
/** Fired when the user confirms. */
|
|
2870
|
+
onConfirm: () => void;
|
|
2871
|
+
/** Fired on Cancel, Escape, or backdrop click. */
|
|
2872
|
+
onCancel: () => void;
|
|
2873
|
+
/** data-testid prefix so each dialog is addressable in tests. */
|
|
2874
|
+
testIdPrefix?: string;
|
|
2875
|
+
}
|
|
2876
|
+
declare function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, destructive, onConfirm, onCancel, testIdPrefix, }: ConfirmDialogProps): React.ReactElement | null;
|
|
2877
|
+
|
|
2878
|
+
/**
|
|
2879
|
+
* Modal — the SDK's one modal-stacking primitive (portal + z-tier + backdrop).
|
|
2880
|
+
*
|
|
2881
|
+
* Every SDK modal renders INSIDE a plugin's accordion section, whose animated
|
|
2882
|
+
* `overflow-hidden` + `transition-all` wrapper establishes a stacking context.
|
|
2883
|
+
* An inline `position: fixed` overlay is therefore scoped to that section and
|
|
2884
|
+
* can be painted UNDER a neighbouring panel (the "import modal invisible on a
|
|
2885
|
+
* later open" bug). This component solves that once: it portals the overlay to
|
|
2886
|
+
* <body> — out of every panel's stacking context — at a z-tier above all the
|
|
2887
|
+
* app's `z-50` dropdowns/banners but below the toast tier (`z-[9999]`), so
|
|
2888
|
+
* toasts still float over modals.
|
|
2889
|
+
*
|
|
2890
|
+
* Controlled: the caller owns `open` and `onClose`. The caller renders its own
|
|
2891
|
+
* dialog box as `children` (keep the box's `onClick={e => e.stopPropagation()}`
|
|
2892
|
+
* so inside-clicks don't dismiss). Escape and a backdrop click both close.
|
|
2893
|
+
*
|
|
2894
|
+
* @since SDK 2.21.0
|
|
2895
|
+
*/
|
|
2896
|
+
|
|
2897
|
+
interface ModalProps {
|
|
2898
|
+
/** Controls visibility (the caller owns open/closed). */
|
|
2899
|
+
open: boolean;
|
|
2900
|
+
/** Close handler — fired on Escape and backdrop click. */
|
|
2901
|
+
onClose: () => void;
|
|
2902
|
+
/** The dialog box. Give it `onClick={e => e.stopPropagation()}`. */
|
|
2903
|
+
children: React.ReactNode;
|
|
2904
|
+
/** data-testid prefix; the backdrop is `${testIdPrefix}-overlay`. */
|
|
2905
|
+
testIdPrefix?: string;
|
|
2906
|
+
/** Close when the backdrop is clicked (default true). */
|
|
2907
|
+
closeOnBackdrop?: boolean;
|
|
2908
|
+
/** Close on Escape (default true). */
|
|
2909
|
+
closeOnEscape?: boolean;
|
|
2910
|
+
/** Focused when the modal opens (e.g. a Cancel button) so a reflexive Enter is safe. */
|
|
2911
|
+
initialFocusRef?: React.RefObject<HTMLElement>;
|
|
2912
|
+
}
|
|
2913
|
+
declare function Modal({ open, onClose, children, testIdPrefix, closeOnBackdrop, closeOnEscape, initialFocusRef, }: ModalProps): React.ReactElement | null;
|
|
2914
|
+
|
|
2915
|
+
/**
|
|
2916
|
+
* PianoRollEditor — a compact, DOM-based MIDI note editor for the track drawer.
|
|
2917
|
+
*
|
|
2918
|
+
* Controlled: `notes` in, `onChange(next)` out. Notes render as absolutely-
|
|
2919
|
+
* positioned divs over a beat/pitch grid (DOM, not canvas — so it themes with
|
|
2920
|
+
* sas-* tokens and is fully driveable by React Testing Library). Supports:
|
|
2921
|
+
* - add : click an empty grid cell
|
|
2922
|
+
* - delete : click an existing note (no drag)
|
|
2923
|
+
* - move : drag a note's body (snap-quantised)
|
|
2924
|
+
* - resize : drag a note's right-edge handle (snap-quantised, ≥ one step)
|
|
2925
|
+
* - octave : shift the whole clip ±12 (toolbar) — no velocity lane
|
|
2926
|
+
* / marquee yet.
|
|
2927
|
+
* On load the viewport auto-scrolls to vertically center the note cluster, so a
|
|
2928
|
+
* low melody isn't stranded off-screen at the bottom of the pitch range.
|
|
2929
|
+
*
|
|
2930
|
+
* Coordinate spaces:
|
|
2931
|
+
* pitch (0-127) ── row = hi - pitch ── top px = row * ROW_HEIGHT
|
|
2932
|
+
* beat (¼ notes) ─────────────────────── left px = beat * PX_PER_BEAT
|
|
2933
|
+
*
|
|
2934
|
+
* The pure helpers (`cellToPx` / `pxToCell` / `transposeNotes`) and layout
|
|
2935
|
+
* constants are exported so coordinate math can be unit-tested without a DOM.
|
|
2936
|
+
*/
|
|
2937
|
+
|
|
2938
|
+
/** Horizontal pixels per quarter-note beat. */
|
|
2939
|
+
declare const PX_PER_BEAT = 24;
|
|
2940
|
+
/** Vertical pixels per semitone row. */
|
|
2941
|
+
declare const ROW_HEIGHT = 12;
|
|
2942
|
+
/** Left keyboard-gutter width (px). */
|
|
2943
|
+
declare const GUTTER_W = 28;
|
|
2944
|
+
/** Pointer travel (px) before a press on a note becomes a drag instead of a click. */
|
|
2945
|
+
declare const DRAG_DEAD_ZONE = 4;
|
|
2946
|
+
/** Width (px) of the right-edge grab handle that resizes a note's length. */
|
|
2947
|
+
declare const RESIZE_HANDLE_PX = 6;
|
|
2948
|
+
/** MIDI pitch → scientific note name (60 = C4). */
|
|
2949
|
+
declare function pitchToName(pitch: number): string;
|
|
2950
|
+
/**
|
|
2951
|
+
* Cell (pitch, startBeat) → top-left pixel offset within the grid.
|
|
2952
|
+
* `hi` is the highest (top) visible pitch.
|
|
2953
|
+
*/
|
|
2954
|
+
declare function cellToPx(pitch: number, startBeat: number, hi: number): {
|
|
2955
|
+
left: number;
|
|
2956
|
+
top: number;
|
|
2957
|
+
};
|
|
2958
|
+
/**
|
|
2959
|
+
* Grid-local pixel → snapped cell. `hi` is the highest visible pitch; the beat
|
|
2960
|
+
* snaps to the nearest `snap` step and clamps to `[0, totalBeats - snap]`;
|
|
2961
|
+
* pitch clamps to `[0, 127]`.
|
|
2962
|
+
*/
|
|
2963
|
+
declare function pxToCell(localX: number, localY: number, hi: number, snap: number, bars: number, beatsPerBar: number): {
|
|
2964
|
+
pitch: number;
|
|
2965
|
+
startBeat: number;
|
|
2966
|
+
};
|
|
2967
|
+
/**
|
|
2968
|
+
* New `durationBeats` for a note whose right edge is dragged to grid-local pixel
|
|
2969
|
+
* `localX`. The end snaps to the nearest `snap` step, is clamped to at least one
|
|
2970
|
+
* step past `startBeat`, and never extends beyond the grid's right edge
|
|
2971
|
+
* (`bars * beatsPerBar`). `startBeat` and `pitch` are untouched.
|
|
2972
|
+
*/
|
|
2973
|
+
declare function resizeNoteDuration(startBeat: number, localX: number, snap: number, bars: number, beatsPerBar: number): number;
|
|
2974
|
+
/**
|
|
2975
|
+
* `scrollTop` that vertically centers the bulk of the notes in a `viewportH`-px
|
|
2976
|
+
* window. Targets the MEDIAN pitch (robust to a stray high/low outlier — keeps
|
|
2977
|
+
* "where the majority of notes are" framed) and clamps to the valid scroll
|
|
2978
|
+
* range. `hi` is the top visible pitch; `rowCount` the total rows in the grid.
|
|
2979
|
+
* Returns 0 when there are no notes.
|
|
2980
|
+
*/
|
|
2981
|
+
declare function centerScrollTop(pitches: readonly number[], hi: number, rowCount: number, viewportH: number): number;
|
|
2982
|
+
/** Transpose every note by `semitones`, clamping pitch to [0,127] (never drops a note). */
|
|
2983
|
+
declare function transposeNotes(notes: readonly PluginMidiNote[], semitones: number): PluginMidiNote[];
|
|
2984
|
+
interface PianoRollEditorProps {
|
|
2985
|
+
/** Controlled note list (quarter-note beats). The editor never mutates this. */
|
|
2986
|
+
notes: readonly PluginMidiNote[];
|
|
2987
|
+
/** Emitted on every edit (add / delete / move / transpose) with the full next array. */
|
|
2988
|
+
onChange: (next: PluginMidiNote[]) => void;
|
|
2989
|
+
/** Scene length in bars → grid width = bars * beatsPerBar * PX_PER_BEAT. */
|
|
2990
|
+
bars: number;
|
|
2991
|
+
/** BPM — used only for audition timing in v1. */
|
|
2992
|
+
bpm: number;
|
|
2993
|
+
/** Beats per bar (time-signature numerator). Default 4. */
|
|
2994
|
+
beatsPerBar?: number;
|
|
2995
|
+
/** Snap step in quarter notes (1 = ¼ note, 0.25 = 1/16). Default 0.25. */
|
|
2996
|
+
snap?: number;
|
|
2997
|
+
/** Snap steps the toolbar selector offers. Default [1, 0.5, 0.25]. */
|
|
2998
|
+
snapOptions?: number[];
|
|
2999
|
+
/** Notified when the user changes snap (the editor still tracks it internally). */
|
|
3000
|
+
onSnapChange?: (snap: number) => void;
|
|
3001
|
+
/** Lowest pitch always visible. Default C2 (36). */
|
|
3002
|
+
minPitch?: number;
|
|
3003
|
+
/** Highest pitch always visible. Default C6 (84). */
|
|
3004
|
+
maxPitch?: number;
|
|
3005
|
+
/** Expand the visible window to include notes outside [minPitch,maxPitch]. Default true. */
|
|
3006
|
+
autoFit?: boolean;
|
|
3007
|
+
/** Optional single-note preview, fired when a note is added. */
|
|
3008
|
+
onAuditionNote?: (pitch: number, velocity: number, durationMs: number) => void;
|
|
3009
|
+
/** Velocity for newly-added notes. Default 100. */
|
|
3010
|
+
defaultVelocity?: number;
|
|
3011
|
+
/** Disable all interaction (e.g. while the track is generating). Default false. */
|
|
3012
|
+
disabled?: boolean;
|
|
3013
|
+
/** Extra className for the outer container. */
|
|
3014
|
+
className?: string;
|
|
3015
|
+
/** Test id for the outer container. Default "sdk-piano-roll". */
|
|
3016
|
+
testId?: string;
|
|
3017
|
+
}
|
|
3018
|
+
declare function PianoRollEditor({ notes, onChange, bars, bpm, beatsPerBar, snap, snapOptions, onSnapChange, minPitch, maxPitch, autoFit, onAuditionNote, defaultVelocity, disabled, className, testId, }: PianoRollEditorProps): React.ReactElement;
|
|
1510
3019
|
|
|
1511
3020
|
/**
|
|
1512
3021
|
* VolumeSlider Component
|
|
@@ -1621,6 +3130,323 @@ declare function calculateTimeBasedTarget(elapsedMs: number, estimatedDurationMs
|
|
|
1621
3130
|
*/
|
|
1622
3131
|
declare function SorceryProgressBar({ isLoading, statusText, completeText, onComplete, heightClass, initialProgress, onProgressChange, estimatedDurationMs, }: SorceryProgressBarProps): React.ReactElement | null;
|
|
1623
3132
|
|
|
3133
|
+
/**
|
|
3134
|
+
* DownloadPackButton — versioned-pack download trigger (SDK component).
|
|
3135
|
+
*
|
|
3136
|
+
* Parameterized by `packId`; drives the download through the host
|
|
3137
|
+
* (`host.startSamplePackDownload` / `host.onSamplePackProgress`) so plugins
|
|
3138
|
+
* never reach into the app's IPC (`window.electronAPI`). Two display variants:
|
|
3139
|
+
* - 'compact' (default) — small uppercase button for panel headers
|
|
3140
|
+
* - 'large' — bigger CTA used inside SamplePackCTACard
|
|
3141
|
+
*
|
|
3142
|
+
* @since SDK 2.8.0 (moved from the app and refactored onto PluginHost).
|
|
3143
|
+
*/
|
|
3144
|
+
|
|
3145
|
+
type DownloadPackButtonVariant = 'compact' | 'large';
|
|
3146
|
+
interface DownloadPackButtonProps {
|
|
3147
|
+
/** Host the plugin received; drives the download + progress. */
|
|
3148
|
+
host: PluginHost;
|
|
3149
|
+
packId: string;
|
|
3150
|
+
/** Pack display name, e.g. 'Drum Sample Library'. Used in tooltips/labels. */
|
|
3151
|
+
displayName: string;
|
|
3152
|
+
/** Bundle size in bytes (shown in the large-variant label). */
|
|
3153
|
+
sizeBytes?: number;
|
|
3154
|
+
variant?: DownloadPackButtonVariant;
|
|
3155
|
+
/** Called once after the install completes (status === 'complete'). */
|
|
3156
|
+
onDownloadComplete?: () => void;
|
|
3157
|
+
}
|
|
3158
|
+
declare const DownloadPackButton: React.FC<DownloadPackButtonProps>;
|
|
3159
|
+
|
|
3160
|
+
/**
|
|
3161
|
+
* SamplePackCTACard — empty-state card a generator panel renders when its
|
|
3162
|
+
* sample pack is missing OR a newer version is available. Wraps
|
|
3163
|
+
* DownloadPackButton in a centered card. The completion callback should
|
|
3164
|
+
* re-fetch pack status on the parent so the card unmounts and the normal panel
|
|
3165
|
+
* UI takes over.
|
|
3166
|
+
*
|
|
3167
|
+
* @since SDK 2.8.0 (moved from the app; download driven through PluginHost).
|
|
3168
|
+
*/
|
|
3169
|
+
|
|
3170
|
+
type SamplePackCTACardStatus = 'missing' | 'stale' | 'checking';
|
|
3171
|
+
/** Minimal pack info the card needs. A PackConfig is structurally compatible. */
|
|
3172
|
+
interface SamplePackCardInfo {
|
|
3173
|
+
packId: string;
|
|
3174
|
+
displayName: string;
|
|
3175
|
+
description: string;
|
|
3176
|
+
sizeBytes?: number;
|
|
3177
|
+
}
|
|
3178
|
+
interface SamplePackCTACardProps {
|
|
3179
|
+
/** Host the plugin received; drives the download. */
|
|
3180
|
+
host: PluginHost;
|
|
3181
|
+
pack: SamplePackCardInfo;
|
|
3182
|
+
status: SamplePackCTACardStatus;
|
|
3183
|
+
onDownloadComplete?: () => void;
|
|
3184
|
+
}
|
|
3185
|
+
declare const SamplePackCTACard: React.FC<SamplePackCTACardProps>;
|
|
3186
|
+
|
|
3187
|
+
/**
|
|
3188
|
+
* WaveformView — small canvas waveform for an audio file on disk.
|
|
3189
|
+
*
|
|
3190
|
+
* Reads bytes via `host.getAudioFileBytes`, decodes via
|
|
3191
|
+
* `AudioContext.decodeAudioData`, computes peaks, and renders to a
|
|
3192
|
+
* canvas. Suitable for take rows, sample previews, or any place a
|
|
3193
|
+
* decorative ~40px waveform makes sense.
|
|
3194
|
+
*
|
|
3195
|
+
* The component is self-contained: it owns the AudioContext and the
|
|
3196
|
+
* peak buffer, decodes once per `filePath` change, and tears down on
|
|
3197
|
+
* unmount. Failures (file missing, decode error) render as a silent
|
|
3198
|
+
* blank canvas — the caller can decide how to surface errors.
|
|
3199
|
+
*/
|
|
3200
|
+
|
|
3201
|
+
interface WaveformViewProps {
|
|
3202
|
+
host: PluginHost;
|
|
3203
|
+
filePath: string;
|
|
3204
|
+
/** Number of bins to compute. Default 256 — plenty for ~40px tall rows. */
|
|
3205
|
+
bins?: number;
|
|
3206
|
+
/** Tailwind / inline className for sizing. Default: w-full h-10. */
|
|
3207
|
+
className?: string;
|
|
3208
|
+
/** Override the bar fill style (e.g., to match a track color). */
|
|
3209
|
+
fillStyle?: string;
|
|
3210
|
+
/**
|
|
3211
|
+
* If set, the bin range spans `targetSamples` instead of the file's
|
|
3212
|
+
* actual length. Bins beyond the audio render as flat silence — used
|
|
3213
|
+
* to align a partial recording inside a full-loop-width canvas so
|
|
3214
|
+
* every take row has the same time scale.
|
|
3215
|
+
*/
|
|
3216
|
+
targetSamples?: number;
|
|
3217
|
+
}
|
|
3218
|
+
declare const WaveformView: React.FC<WaveformViewProps>;
|
|
3219
|
+
|
|
3220
|
+
/**
|
|
3221
|
+
* Shared level-meter component.
|
|
3222
|
+
*
|
|
3223
|
+
* Renders a horizontal LED-style bar over -60dBFS → 0dBFS:
|
|
3224
|
+
* - A fixed left-to-right gradient (green → orange → red), so the color is
|
|
3225
|
+
* tied to POSITION: a quiet signal lights only the green left, a hot signal
|
|
3226
|
+
* reaches the red right. An "unlit" mask hides the gradient beyond the
|
|
3227
|
+
* current level.
|
|
3228
|
+
* - A deterministic segment grid (the "LED monitor" look) drawn as a pure-CSS
|
|
3229
|
+
* repeating overlay — constant DOM, no per-frame cost.
|
|
3230
|
+
* - An optional peak-hold marker (`peakHoldDb`) — a bright line at the recent
|
|
3231
|
+
* maximum that the caller holds/decays (see `useTrackMeter`).
|
|
3232
|
+
* - An optional CLIP badge the caller wires up.
|
|
3233
|
+
*
|
|
3234
|
+
* Pure presentational: takes the current dB + `active` flag (+ optional held
|
|
3235
|
+
* peak) and draws. The only production consumer is the per-track strip
|
|
3236
|
+
* (`TrackMeterStrip`, via `compact`). `compact` shrinks the bar and drops the
|
|
3237
|
+
* numeric dB readout.
|
|
3238
|
+
*/
|
|
3239
|
+
|
|
3240
|
+
interface LevelMeterProps {
|
|
3241
|
+
/** Current peak level in dBFS. -120 means "no signal". */
|
|
3242
|
+
peakDb: number;
|
|
3243
|
+
/** True when the underlying audio callback is firing. False = floor. */
|
|
3244
|
+
active: boolean;
|
|
3245
|
+
/**
|
|
3246
|
+
* Held peak in dBFS for the peak-hold marker. Omit to draw no marker. The
|
|
3247
|
+
* marker is hidden when this is at/below the visible floor (-60).
|
|
3248
|
+
*/
|
|
3249
|
+
peakHoldDb?: number;
|
|
3250
|
+
/** Latched clip flag. When true, render the CLIP badge. */
|
|
3251
|
+
clipped?: boolean;
|
|
3252
|
+
/** User-clickable handler to clear the latched clip indicator. */
|
|
3253
|
+
onClearClip?: () => void;
|
|
3254
|
+
/**
|
|
3255
|
+
* Thin strip mode for per-track meters: hides the numeric dB readout and
|
|
3256
|
+
* shrinks the bar. Keeps the (rare) CLIP badge.
|
|
3257
|
+
*/
|
|
3258
|
+
compact?: boolean;
|
|
3259
|
+
/** Optional className overlaid on the wrapper for layout tweaks. */
|
|
3260
|
+
className?: string;
|
|
3261
|
+
/** Inline test id — make multiple instances distinguishable. */
|
|
3262
|
+
'data-testid'?: string;
|
|
3263
|
+
}
|
|
3264
|
+
declare const LevelMeter: React.FC<LevelMeterProps>;
|
|
3265
|
+
|
|
3266
|
+
/**
|
|
3267
|
+
* TrackMeterStrip — the thin per-track peak meter welded to the bottom of a
|
|
3268
|
+
* track row. Cosmetic: gives a general sense of each track's level and adds
|
|
3269
|
+
* motion during playback.
|
|
3270
|
+
*
|
|
3271
|
+
* This is deliberately its OWN component so the per-row meter selector
|
|
3272
|
+
* (`useTrackMeter`) re-renders ONLY this strip at ~30Hz, never the heavy
|
|
3273
|
+
* TrackRow around it. Render it as a full-width sibling directly under a row
|
|
3274
|
+
* body; it welds on with a squared top edge (like the track drawer does).
|
|
3275
|
+
*/
|
|
3276
|
+
|
|
3277
|
+
interface TrackMeterStripProps {
|
|
3278
|
+
/** Shared meter handle from `useTrackLevels(host, isPlaying)`. */
|
|
3279
|
+
levels: TrackLevelsHandle;
|
|
3280
|
+
/** Tracktion engine track id (matches `PluginTrackHandle.id`). */
|
|
3281
|
+
trackId: string;
|
|
3282
|
+
/** Round the bottom corners (false when a drawer welds on below). Default true. */
|
|
3283
|
+
roundBottom?: boolean;
|
|
3284
|
+
/** Optional className for layout tweaks on the wrapper. */
|
|
3285
|
+
className?: string;
|
|
3286
|
+
}
|
|
3287
|
+
declare const TrackMeterStrip: React.FC<TrackMeterStripProps>;
|
|
3288
|
+
|
|
3289
|
+
/**
|
|
3290
|
+
* ScrollingWaveform — live waveform during recording (Phase 8.10).
|
|
3291
|
+
*
|
|
3292
|
+
* Reads the platform's `peakDb` history and renders it as a horizontal
|
|
3293
|
+
* bar-graph that scrolls left as new samples arrive. Two halves: top
|
|
3294
|
+
* band shows positive amplitude, bottom band mirrors it (matches the
|
|
3295
|
+
* static waveform's min/max layout in `WaveformView`).
|
|
3296
|
+
*
|
|
3297
|
+
* The data source is a function the caller supplies — typically a ref
|
|
3298
|
+
* to the `inputLevelDb` value from `AudioRoutingContext` polled at
|
|
3299
|
+
* ~30Hz. The component samples that ref via requestAnimationFrame and
|
|
3300
|
+
* shifts a fixed-size float ring buffer one column per frame.
|
|
3301
|
+
*
|
|
3302
|
+
* Pure presentational + animation logic; no IPC. Stops animating
|
|
3303
|
+
* when `active` is false (engine isn't running the audio callback).
|
|
3304
|
+
*/
|
|
3305
|
+
|
|
3306
|
+
interface ScrollingWaveformProps {
|
|
3307
|
+
/** Function returning the latest peak in dBFS. Called per RAF. */
|
|
3308
|
+
getPeakDb: () => number;
|
|
3309
|
+
/** True while the audio callback is running; false freezes the wave. */
|
|
3310
|
+
active: boolean;
|
|
3311
|
+
/** Number of horizontal columns in the ring buffer. */
|
|
3312
|
+
columns?: number;
|
|
3313
|
+
/** Optional className for sizing. */
|
|
3314
|
+
className?: string;
|
|
3315
|
+
/** Highlight color for the wave. */
|
|
3316
|
+
fillStyle?: string;
|
|
3317
|
+
}
|
|
3318
|
+
declare const ScrollingWaveform: React.FC<ScrollingWaveformProps>;
|
|
3319
|
+
|
|
3320
|
+
/**
|
|
3321
|
+
* OffsetScrubber — manual sample-offset slider for Lyria-generated audio.
|
|
3322
|
+
*
|
|
3323
|
+
* Renders a thin horizontal track with one tick per detected beat (tall
|
|
3324
|
+
* tick on the downbeat) and a draggable thumb. Drag distance maps to a
|
|
3325
|
+
* sample offset that is applied to the audio clip via
|
|
3326
|
+
* `host.setAudioOffsetSamples(trackId, n)`.
|
|
3327
|
+
*
|
|
3328
|
+
* Snap behavior:
|
|
3329
|
+
* - Default: snap to the nearest beat in `cuePoints.beats`.
|
|
3330
|
+
* - Hold Shift: bypass snap (free 1-sample resolution).
|
|
3331
|
+
* - Click on a tick mark: jump to that beat exactly.
|
|
3332
|
+
*
|
|
3333
|
+
* The visible range is one bar (= meter beats) on each side of bar 1.
|
|
3334
|
+
* For a 4-bar / 4/4 clip at 44100 Hz, one bar at 120 BPM is 88_200
|
|
3335
|
+
* samples — so the slider covers ±88_200 samples, ~2 s either way. That
|
|
3336
|
+
* matches the alignment errors we observe from Lyria detection misses
|
|
3337
|
+
* (typically <1 beat off).
|
|
3338
|
+
*
|
|
3339
|
+
* BPM mismatch chip: shown when `cuePoints.detected_bpm` is more than
|
|
3340
|
+
* 1 BPM away from the project BPM, since the beat ticks won't line up
|
|
3341
|
+
* with the project grid in that case.
|
|
3342
|
+
*/
|
|
3343
|
+
|
|
3344
|
+
interface OffsetScrubberProps {
|
|
3345
|
+
/** Detected beat positions + sample rate. Slider is disabled when null. */
|
|
3346
|
+
cuePoints: PluginCuePoints | null;
|
|
3347
|
+
/** Current offset, in samples (signed). */
|
|
3348
|
+
offsetSamples: number;
|
|
3349
|
+
/** Project BPM — used to compute the visible range and the mismatch chip. */
|
|
3350
|
+
projectBpm: number;
|
|
3351
|
+
/** Beats per bar, defaults to 4. */
|
|
3352
|
+
meter?: number;
|
|
3353
|
+
/** Called on drag-end with the resolved offset (already snapped). */
|
|
3354
|
+
onChange: (offsetSamples: number) => void;
|
|
3355
|
+
/** Disable interaction (e.g., during generation / split). */
|
|
3356
|
+
disabled?: boolean;
|
|
3357
|
+
}
|
|
3358
|
+
declare function OffsetScrubber({ cuePoints, offsetSamples, projectBpm, meter, onChange, disabled, }: OffsetScrubberProps): React.ReactElement;
|
|
3359
|
+
|
|
3360
|
+
/**
|
|
3361
|
+
* Shared waveform peaks + canvas drawer.
|
|
3362
|
+
*
|
|
3363
|
+
* Originally inlined in `stems/TrimEditorDrawer.tsx`; lifted to
|
|
3364
|
+
* this module so the recorder plugin's per-take rows can render the
|
|
3365
|
+
* same compact min/max display without duplicating the math.
|
|
3366
|
+
*
|
|
3367
|
+
* Design:
|
|
3368
|
+
* - `computePeaks` reduces an AudioBuffer to `bins` min/max pairs (mono
|
|
3369
|
+
* average across channels). Output layout is interleaved
|
|
3370
|
+
* `[min0, max0, min1, max1, ...]` so the renderer reads pairs
|
|
3371
|
+
* sequentially without index arithmetic.
|
|
3372
|
+
* - `drawWaveform` paints one 1px vertical bar per canvas column,
|
|
3373
|
+
* dpr-aware so it stays crisp on retina displays.
|
|
3374
|
+
*
|
|
3375
|
+
* No host or React dependencies — pure functions are safe to use from
|
|
3376
|
+
* tests, web workers, or non-React renderers.
|
|
3377
|
+
*/
|
|
3378
|
+
interface WaveformPeaks {
|
|
3379
|
+
/** Sample rate of the source file (used to convert sample → seconds). */
|
|
3380
|
+
sampleRate: number;
|
|
3381
|
+
/** Total length of the raw file in samples. */
|
|
3382
|
+
totalSamples: number;
|
|
3383
|
+
/** Min/max pairs per bin (length = bins × 2). */
|
|
3384
|
+
peaks: Float32Array;
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* Reduce an AudioBuffer to `bins` min/max pairs. Mono averages across
|
|
3388
|
+
* channels. The output buffer is fixed-size (`bins * 2`) for fast canvas
|
|
3389
|
+
* traversal.
|
|
3390
|
+
*
|
|
3391
|
+
* `targetSamples` (optional) extends the bin range to a fixed sample
|
|
3392
|
+
* count larger than the buffer's actual length — bins falling beyond
|
|
3393
|
+
* the buffer get (0, 0) pairs, which renders as a flat tail. Used by
|
|
3394
|
+
* the recorder so a partial last chunk's waveform sits at the start of
|
|
3395
|
+
* a full-loop-width canvas instead of being stretched to fill.
|
|
3396
|
+
*/
|
|
3397
|
+
declare function computePeaks(audioBuffer: AudioBuffer, bins: number, targetSamples?: number): WaveformPeaks;
|
|
3398
|
+
/**
|
|
3399
|
+
* Draw min/max peaks to the given canvas. Resizes the canvas backing
|
|
3400
|
+
* store to CSS pixels × devicePixelRatio so the result is crisp on
|
|
3401
|
+
* retina. Caller controls CSS sizing via the `<canvas>` element's
|
|
3402
|
+
* className.
|
|
3403
|
+
*/
|
|
3404
|
+
declare function drawWaveform(canvas: HTMLCanvasElement, peaks: WaveformPeaks, options?: {
|
|
3405
|
+
fillStyle?: string;
|
|
3406
|
+
}): void;
|
|
3407
|
+
|
|
3408
|
+
/**
|
|
3409
|
+
* WAV peak analyzer (Phase 8.10).
|
|
3410
|
+
*
|
|
3411
|
+
* Reads a WAV file via the plugin host, decodes it via Web Audio,
|
|
3412
|
+
* scans every channel for the absolute maximum sample, and returns
|
|
3413
|
+
* peak dBFS + a clipped flag (true when the peak >= -1dBFS, matching
|
|
3414
|
+
* the engine's hard-limiter ceiling).
|
|
3415
|
+
*
|
|
3416
|
+
* Used by the recorder's take rows to surface "this take peaked at
|
|
3417
|
+
* -8dB" or "this take CLIPPED" without the user having to click play.
|
|
3418
|
+
*/
|
|
3419
|
+
|
|
3420
|
+
interface PeakAnalysis {
|
|
3421
|
+
peakLinear: number;
|
|
3422
|
+
peakDb: number;
|
|
3423
|
+
clipped: boolean;
|
|
3424
|
+
}
|
|
3425
|
+
declare function analyzeWavPeak(host: PluginHost, filePath: string): Promise<PeakAnalysis>;
|
|
3426
|
+
|
|
3427
|
+
/**
|
|
3428
|
+
* Synthesize a PluginCuePoints object from raw BPM/sample-rate inputs.
|
|
3429
|
+
*
|
|
3430
|
+
* The OffsetScrubber consumes PluginCuePoints — a beat grid plus
|
|
3431
|
+
* per-beat sample positions, normally produced by Lyria's onset
|
|
3432
|
+
* detector. The recorder doesn't have detected cue points (live
|
|
3433
|
+
* recordings have no detection pass), but it always knows the project
|
|
3434
|
+
* BPM, the engine sample rate, and the loop length in bars. That's
|
|
3435
|
+
* enough to construct a synthetic grid where every beat sits on a
|
|
3436
|
+
* regular interval — which is exactly what the scrubber needs to
|
|
3437
|
+
* provide tick marks + snap behavior for nudging the take's offset.
|
|
3438
|
+
*/
|
|
3439
|
+
|
|
3440
|
+
interface SynthesizeCuePointsOptions {
|
|
3441
|
+
bpm: number;
|
|
3442
|
+
sampleRate: number;
|
|
3443
|
+
/** Total bars in the clip (e.g. 4 for a 4-bar loop). */
|
|
3444
|
+
bars: number;
|
|
3445
|
+
/** Beats per bar. Defaults to 4 (4/4). */
|
|
3446
|
+
meter?: number;
|
|
3447
|
+
}
|
|
3448
|
+
declare function synthesizeCuePoints({ bpm, sampleRate, bars, meter, }: SynthesizeCuePointsOptions): PluginCuePoints;
|
|
3449
|
+
|
|
1624
3450
|
/**
|
|
1625
3451
|
* useSceneState — Scene-keyed state hook for plugin developers.
|
|
1626
3452
|
*
|
|
@@ -1646,6 +3472,87 @@ type SetSceneState<T> = (value: T | ((prev: T) => T)) => void;
|
|
|
1646
3472
|
type SetSceneStateForScene<T> = (sceneId: string, value: T | ((prev: T) => T)) => void;
|
|
1647
3473
|
declare function useSceneState<T>(activeSceneId: string | null, initialValue: T): [T, SetSceneState<T>, SetSceneStateForScene<T>];
|
|
1648
3474
|
|
|
3475
|
+
/**
|
|
3476
|
+
* useAnySolo — reactively reports whether ANY track in the project is soloed.
|
|
3477
|
+
*
|
|
3478
|
+
* Solo is cross-panel: when the user solos a track in ANY panel, the engine's
|
|
3479
|
+
* effective-mute model silences every non-soloed track. A panel uses this flag
|
|
3480
|
+
* to DIM its own non-soloed rows without lighting their Mute buttons:
|
|
3481
|
+
*
|
|
3482
|
+
* ```tsx
|
|
3483
|
+
* const anySolo = useAnySolo(host);
|
|
3484
|
+
* // ...
|
|
3485
|
+
* <TrackRow soloedOut={anySolo && !track.runtimeState.solo} ... />
|
|
3486
|
+
* ```
|
|
3487
|
+
*
|
|
3488
|
+
* Refreshes on mount and on every track-state change. `onTrackStateChange`
|
|
3489
|
+
* fires for tracks in ALL panels (not just this plugin's), so a solo toggled in
|
|
3490
|
+
* another panel updates this flag too.
|
|
3491
|
+
*/
|
|
3492
|
+
|
|
3493
|
+
declare function useAnySolo(host: Pick<PluginHost, 'isAnySoloActive' | 'onTrackStateChange'>): boolean;
|
|
3494
|
+
|
|
3495
|
+
/**
|
|
3496
|
+
* useSoundHistory — generic, per-track "what sounds has this track had?" stack.
|
|
3497
|
+
*
|
|
3498
|
+
* Powers the drawer "History" tab: restore any earlier sound, star favorites,
|
|
3499
|
+
* and (via the host plugin) persist across project reopen. The SDK is ignorant
|
|
3500
|
+
* of WHAT a sound is — each plugin records an opaque `descriptor` (a drum sample
|
|
3501
|
+
* path / an instrument `{ displayName, zones }` / a synth Surge state blob) plus
|
|
3502
|
+
* a human `label`, and supplies `applySound` to re-apply a chosen descriptor.
|
|
3503
|
+
*
|
|
3504
|
+
* Persistence is the plugin's job: pass `opts.onChange` (called after every
|
|
3505
|
+
* mutation with the new state) to save, and call `restore()` on load to seed.
|
|
3506
|
+
* Favorited entries are never auto-evicted by the cap.
|
|
3507
|
+
*
|
|
3508
|
+
* Robustness: `applySound` + `onChange` are read through refs, so the returned
|
|
3509
|
+
* object is referentially STABLE regardless of whether the caller memoizes them.
|
|
3510
|
+
* Plugins list this object in `loadTracks` deps — an unstable return previously
|
|
3511
|
+
* caused a render loop, so keep it stable.
|
|
3512
|
+
*
|
|
3513
|
+
* @since SDK 2.13.0
|
|
3514
|
+
*/
|
|
3515
|
+
|
|
3516
|
+
/** A track's ordered sound history plus the index of the currently-applied sound. */
|
|
3517
|
+
interface TrackSoundHistory {
|
|
3518
|
+
entries: readonly SoundHistoryEntry[];
|
|
3519
|
+
/** Index into `entries` of the currently-applied sound; -1 when empty. */
|
|
3520
|
+
cursor: number;
|
|
3521
|
+
}
|
|
3522
|
+
interface UseSoundHistoryOptions {
|
|
3523
|
+
/** Max non-favorited entries kept per track (favorites are never evicted). Default 24. */
|
|
3524
|
+
max?: number;
|
|
3525
|
+
/**
|
|
3526
|
+
* Called after every mutation (record/undo/restoreTo/toggleFavorite/clear) with the
|
|
3527
|
+
* track's new state — use it to persist. NOT called by `restore()` (that's a load).
|
|
3528
|
+
*/
|
|
3529
|
+
onChange?: (trackId: string, state: TrackSoundHistory) => void;
|
|
3530
|
+
}
|
|
3531
|
+
interface UseSoundHistoryResult {
|
|
3532
|
+
/** Remember a sound that was just applied (generation, scene-load, or shuffle). */
|
|
3533
|
+
record(trackId: string, descriptor: unknown, label: string): void;
|
|
3534
|
+
/** Re-apply the sound one step before the current one. Resolves true if it moved. */
|
|
3535
|
+
undo(trackId: string): Promise<boolean>;
|
|
3536
|
+
/** Re-apply a specific entry by index. Resolves true if it applied. */
|
|
3537
|
+
restoreTo(trackId: string, index: number): Promise<boolean>;
|
|
3538
|
+
/** The ordered history + cursor for a track (safe empty default). */
|
|
3539
|
+
list(trackId: string): TrackSoundHistory;
|
|
3540
|
+
/** Whether there is an earlier sound to step back to. */
|
|
3541
|
+
canUndo(trackId: string): boolean;
|
|
3542
|
+
/** Forget a track's history (e.g. on regenerate). Persists the cleared state. */
|
|
3543
|
+
clear(trackId: string): void;
|
|
3544
|
+
/** Forget ALL tracks' history in memory (e.g. before re-seeding on scene load). */
|
|
3545
|
+
reset(): void;
|
|
3546
|
+
/** Seed a track's full history (e.g. from persistence on load). Does NOT fire onChange. */
|
|
3547
|
+
restore(trackId: string, state: {
|
|
3548
|
+
entries?: readonly SoundHistoryEntry[];
|
|
3549
|
+
cursor?: number;
|
|
3550
|
+
} | null | undefined): void;
|
|
3551
|
+
/** Toggle the favorite flag on an entry (favorites survive cap eviction). */
|
|
3552
|
+
toggleFavorite(trackId: string, index: number): void;
|
|
3553
|
+
}
|
|
3554
|
+
declare function useSoundHistory(applySound: (trackId: string, descriptor: unknown) => Promise<void>, opts?: UseSoundHistoryOptions): UseSoundHistoryResult;
|
|
3555
|
+
|
|
1649
3556
|
/**
|
|
1650
3557
|
* Plugin SDK Version
|
|
1651
3558
|
*
|
|
@@ -1654,7 +3561,7 @@ declare function useSceneState<T>(activeSceneId: string | null, initialValue: T)
|
|
|
1654
3561
|
* Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
|
|
1655
3562
|
* during activation and marks incompatible plugins accordingly.
|
|
1656
3563
|
*/
|
|
1657
|
-
declare const PLUGIN_SDK_VERSION = "2.
|
|
3564
|
+
declare const PLUGIN_SDK_VERSION = "2.24.0";
|
|
1658
3565
|
|
|
1659
3566
|
/**
|
|
1660
3567
|
* FX Preset Definitions
|
|
@@ -1708,4 +3615,98 @@ declare function sliderToDb(slider: number): number;
|
|
|
1708
3615
|
*/
|
|
1709
3616
|
declare function dbToSlider(db: number): number;
|
|
1710
3617
|
|
|
1711
|
-
|
|
3618
|
+
/**
|
|
3619
|
+
* Format the cross-plugin concurrent-track context into a prose block
|
|
3620
|
+
* that's safe to drop straight into an LLM user-prompt. Both the synth
|
|
3621
|
+
* and drum builtin panels use this so the rendered prompt stays
|
|
3622
|
+
* consistent across generators — and so a single change here propagates
|
|
3623
|
+
* to every plugin that calls `host.getGenerationContext()`.
|
|
3624
|
+
*
|
|
3625
|
+
* Per-track payload follows the user's preferred shape (raw note JSON
|
|
3626
|
+
* grouped by chord) so the model sees velocity / start-beat /
|
|
3627
|
+
* duration / pitch verbatim and can reason about feel + harmony.
|
|
3628
|
+
*
|
|
3629
|
+
* Returns the empty string when there are no concurrent tracks — call
|
|
3630
|
+
* sites can `if (block) push(block)` rather than baking in a placeholder.
|
|
3631
|
+
*/
|
|
3632
|
+
|
|
3633
|
+
declare function formatConcurrentTracks(ctx: PluginGenerationContext): string;
|
|
3634
|
+
|
|
3635
|
+
/**
|
|
3636
|
+
* Lightweight, dependency-free semantic matching for sample selection.
|
|
3637
|
+
*
|
|
3638
|
+
* Sample generators (drums, instruments) ship a short StableAudio text
|
|
3639
|
+
* prompt next to every sample ("tight 909-style kick one shot, hard click
|
|
3640
|
+
* transient, short punchy body, dry, no hi hats, no loop"). When the user
|
|
3641
|
+
* asks for "a 1950s style boom bap kick" we want to pick the sample whose
|
|
3642
|
+
* prompt is closest to that intent — instead of a uniform random draw —
|
|
3643
|
+
* while still preserving variety so a vague "give me a kick" doesn't return
|
|
3644
|
+
* the identical sample every time.
|
|
3645
|
+
*
|
|
3646
|
+
* Design notes:
|
|
3647
|
+
* - Pure functions, no I/O, no SDK-type dependencies → trivially unit
|
|
3648
|
+
* testable with an injected `rng`, and safe to call from either the
|
|
3649
|
+
* main or renderer process.
|
|
3650
|
+
* - Scoring is IDF-weighted query-coverage (a TF-IDF / BM25-lite). The
|
|
3651
|
+
* IDF is derived from the candidate pool itself, so it is STRUCTURAL —
|
|
3652
|
+
* no hand-maintained synonym tables. Rare, discriminating tokens in the
|
|
3653
|
+
* prompts ("909", "dusty", "tube") dominate; corpus-universal filler
|
|
3654
|
+
* ("one", "shot", "dry") washes out to ~zero IDF on its own.
|
|
3655
|
+
* - The near-universal negative clauses StableAudio prompts carry
|
|
3656
|
+
* ("no hi hats", "no loop", "no melody") are stripped before tokenizing;
|
|
3657
|
+
* they are pure noise for matching.
|
|
3658
|
+
* - Selection is softmax-weighted random among the top-k. Flat scores →
|
|
3659
|
+
* ~uniform (≈ the old random behavior); a clear winner → tight
|
|
3660
|
+
* convergence. The all-zero (no-signal) case is intentionally left to
|
|
3661
|
+
* the caller to fall back to its existing random path over the full
|
|
3662
|
+
* pool — see `scorePromptMatch`'s contract below.
|
|
3663
|
+
*/
|
|
3664
|
+
/**
|
|
3665
|
+
* Tokenize a prompt or query into matchable lowercase tokens.
|
|
3666
|
+
*
|
|
3667
|
+
* 1. Drop comma-delimited negative clauses ("no hi hats", "no loop").
|
|
3668
|
+
* 2. Lowercase, split on any non-alphanumeric run.
|
|
3669
|
+
* 3. Drop stop-words and 1–2 digit numeric noise ("01", "02") while
|
|
3670
|
+
* keeping meaningful numerics ("808", "909", "1950").
|
|
3671
|
+
*/
|
|
3672
|
+
declare function tokenizePrompt(text: string): string[];
|
|
3673
|
+
/**
|
|
3674
|
+
* Score each candidate prompt against the query, returning a parallel array
|
|
3675
|
+
* of scores in [0, 1] (1 = the candidate covers all of the query's
|
|
3676
|
+
* discriminating intent).
|
|
3677
|
+
*
|
|
3678
|
+
* Contract: a returned max of 0 means the query shares NO matchable token
|
|
3679
|
+
* with any candidate (no signal). Callers should treat that as "fall back to
|
|
3680
|
+
* the existing uniform-random pick over the full pool" so vague queries keep
|
|
3681
|
+
* today's variety rather than biasing toward an arbitrary top-k slice.
|
|
3682
|
+
*/
|
|
3683
|
+
declare function scorePromptMatch(query: string, candidatePrompts: ReadonlyArray<string>): number[];
|
|
3684
|
+
/** One scored candidate. `key` (if present) is what `excludeKeys` matches on. */
|
|
3685
|
+
interface ScoredCandidate<T> {
|
|
3686
|
+
item: T;
|
|
3687
|
+
score: number;
|
|
3688
|
+
key?: string;
|
|
3689
|
+
}
|
|
3690
|
+
interface PickTopKOptions {
|
|
3691
|
+
/** Consider only the top-k by score (default 5). */
|
|
3692
|
+
k?: number;
|
|
3693
|
+
/**
|
|
3694
|
+
* Softmax temperature (default 0.3). Lower → sharper preference for the
|
|
3695
|
+
* top match; higher → flatter (more variety). Scores are in [0, 1].
|
|
3696
|
+
*/
|
|
3697
|
+
temperature?: number;
|
|
3698
|
+
/** Candidate keys to exclude (e.g. shuffle history). */
|
|
3699
|
+
excludeKeys?: ReadonlySet<string>;
|
|
3700
|
+
/** Injectable RNG in [0, 1) for deterministic tests (default Math.random). */
|
|
3701
|
+
rng?: () => number;
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Pick one candidate via softmax-weighted random selection among the top-k
|
|
3705
|
+
* by score. Returns null only when the pool is empty after exclusion.
|
|
3706
|
+
*
|
|
3707
|
+
* Equal scores → equal weights → uniform pick among the top-k, so this
|
|
3708
|
+
* degrades gracefully toward random when the query gives no preference.
|
|
3709
|
+
*/
|
|
3710
|
+
declare function pickTopKWeighted<T>(scored: ReadonlyArray<ScoredCandidate<T>>, options?: PickTopKOptions): T | null;
|
|
3711
|
+
|
|
3712
|
+
export { type AudioInputDevice, type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, ConfirmDialog, type ConfirmDialogProps, type CreateTrackOptions, type CrossfadeInpaintInput, type CrossfadeLayer, type CrossfadeMeta, CrossfadeModal, type CrossfadeModalProps, type CrossfadePairMeta, type CrossfadeSelection, type CrossfadeSlot, CrossfadeTrackRow, type CrossfadeTrackRowProps, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, DRAG_DEAD_ZONE, type DeckBoundaryEvent, type DeckBoundaryListener, DownloadPackButton, type DownloadPackButtonProps, type DownloadPackButtonVariant, type DrawerTab, type DrumKit, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, EQUAL_POWER_GAIN, type ExportMidiBundleOptions, type ExportMidiBundleResult, 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, GUTTER_W, type GeneratorPlugin, type GeneratorType, type ImportCandidateScene, type ImportCandidateTrack, ImportTrackModal, type ImportTrackModalProps, type InstrumentDescriptor, TrackDrawer as InstrumentDrawer, type TrackDrawerProps as InstrumentDrawerProps, type InstrumentSampler, type InstrumentZone, type LLMCandidate, type LLMContent, type LLMFunctionDeclaration, type LLMGenerationConfig, type LLMGenerationRequest, type LLMGenerationResult, type LLMPart, type LLMSystemInstruction, type LLMTool, type LLMToolUseRequest, type LLMToolUseResponse, type LLMUsageMetadata, LevelMeter, type LevelMeterProps, type ListAudioFilesOptions, type ListImportableTracksOptions, type MidiClipData, type MidiWriteResult, type MixInterpolation, Modal, type ModalProps, type MusicalContext, OffsetScrubber, type OffsetScrubberProps, PLUGIN_SDK_VERSION, PX_PER_BEAT, PanSlider, type PeakAnalysis, PianoRollEditor, type PianoRollEditorProps, type PickTopKOptions, type PluginAppTool, type PluginAppToolInputSchema, type PluginAppToolResult, type PluginAudioTextureRequest, type PluginAudioTextureResult, type PluginCapabilities, type PluginChordSegment, type PluginChordTiming, type PluginConcurrentTrackInfo, type PluginCuePoints, 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 PluginSkill, type PluginSkillInputSchema, type PluginStatus, type PluginStemSplitResult, type PluginStemTrackInfo, type PluginSynthInfo, type PluginTrackFxDetailState, type PluginTrackHandle, type PluginTrackInfo, type PluginTrackLevel, type PluginTrackRuntimeState, type PluginTransportState, type PluginTrimWindow, type PluginUIProps, type PostProcessOptions, RESIZE_HANDLE_PX, ROW_HEIGHT, type ReadMidiClip, type ReadMidiResult, type RecordingChunkFinalizedEvent, type RecordingTargetInfo, type SDKTrackRowProps, SLIDER_UNITY, SamplePackCTACard, type SamplePackCTACardProps, type SamplePackCTACardStatus, type SamplePackCardInfo, type SavePluginPresetOptions, type SceneChangeListener, type SceneFamilyTrack, type ScoredCandidate, ScrollingWaveform, type ScrollingWaveformProps, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type SoundHistoryEntry, type StemType, type SynthesizeCuePointsOptions, TrackDrawer, type TrackDrawerProps, type TrackFxDetailState, type TrackFxState, type TrackLevelsHandle, TrackMeterStrip, type TrackMeterStripProps, type TrackMeterView, TrackRow, type TrackRowDragProps, type TrackSoundHistory, type TrackSoundSnapshot, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, type UseSoundHistoryOptions, type UseSoundHistoryResult, type UseTrackReorderOptions, type UseTrackReorderResult, VolumeSlider, type WaveformPeaks, WaveformView, type WaveformViewProps, analyzeWavPeak, asCrossfadeMeta, buildCrossfadeInpaintPrompt, calculateTimeBasedTarget, cellToPx, centerScrollTop, computePeaks, dbToSlider, drawWaveform, formatConcurrentTracks, moveItem, parseCrossfadePairs, pickTopKWeighted, pitchToName, pxToCell, resizeNoteDuration, scorePromptMatch, sliderToDb, synthesizeCuePoints, tokenizePrompt, transposeNotes, useAnySolo, useSceneState, useSoundHistory, useTrackLevel, useTrackLevels, useTrackMeter, useTrackReorder, useTransportPlaying };
|