@signalsandsorcery/plugin-sdk 2.24.1 → 2.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -227,6 +227,16 @@ interface PluginHost {
227
227
  setTrackMute(trackId: string, muted: boolean): Promise<void>;
228
228
  /** Set track volume (linear 0.0 - 1.0). Only works on owned tracks. */
229
229
  setTrackVolume(trackId: string, volume: number): Promise<void>;
230
+ /**
231
+ * Set/replace a time-based volume automation curve (a fade envelope) on a track,
232
+ * or clear it with an empty array. Points are {time: seconds, db}; linear between
233
+ * points. Used by crossfade tracks to fade origin↔target across the looped
234
+ * transition. Optional — callers MUST null-check. @since SDK 2.25.0
235
+ */
236
+ setTrackVolumeAutomation?(trackId: string, points: Array<{
237
+ time: number;
238
+ db: number;
239
+ }>): Promise<void>;
230
240
  /** Set track pan (-1.0 left to 1.0 right). Only works on owned tracks. */
231
241
  setTrackPan(trackId: string, pan: number): Promise<void>;
232
242
  /** Set track solo state. Only works on owned tracks. */
@@ -537,6 +547,11 @@ interface PluginHost {
537
547
  key: string;
538
548
  mode: string;
539
549
  } | null>;
550
+ /**
551
+ * Read a scene's human display name by db id (for labelling a crossfade's
552
+ * origin/target scenes). Optional — callers MUST null-check. @since SDK 2.26.0
553
+ */
554
+ getSceneName?(sceneDbId: string): Promise<string | null>;
540
555
  /** Subscribe to transport state changes. Returns unsubscribe function. */
541
556
  onTransportEvent(listener: TransportEventListener): UnsubscribeFn;
542
557
  /** Subscribe to deck boundary events. Returns unsubscribe function. */
@@ -2592,6 +2607,10 @@ interface CrossfadePairMeta {
2592
2607
  sliderPos: number;
2593
2608
  originDbId: string;
2594
2609
  targetDbId: string;
2610
+ /** DB id of the ORIGIN source track (in the from scene) — drives the "used once" exclusion. */
2611
+ originSourceDbId: string;
2612
+ /** DB id of the TARGET source track (in the to scene). */
2613
+ targetSourceDbId: string;
2595
2614
  originSourceName: string;
2596
2615
  originSoundLabel: string;
2597
2616
  targetSourceName: string;
@@ -2606,6 +2625,28 @@ declare function asCrossfadeMeta(val: unknown): CrossfadeMeta | null;
2606
2625
  * row instead of vanishing.
2607
2626
  */
2608
2627
  declare function parseCrossfadePairs(sceneData: Record<string, unknown>): CrossfadePairMeta[];
2628
+ /** One volume-automation point: a dB value at a time offset (seconds from clip start). */
2629
+ interface VolumeAutomationPoint {
2630
+ time: number;
2631
+ db: number;
2632
+ }
2633
+ /** Origin + target volume curves for one crossfade pair. */
2634
+ interface CrossfadeVolumeCurves {
2635
+ origin: VolumeAutomationPoint[];
2636
+ target: VolumeAutomationPoint[];
2637
+ }
2638
+ /**
2639
+ * Equal-power crossfade volume curves over a transition of `bars` at `bpm`.
2640
+ * The ORIGIN layer fades OUT and the TARGET fades IN; `sliderPos` (0..1) sets
2641
+ * WHERE in time the equal-power (-3 dB) crossover sits — 0 = hand off near the
2642
+ * start, 1 = hold the origin until near the end. Points span the clip window
2643
+ * [0, durationSeconds] so the engine re-reads them each loop (re-fade per loop).
2644
+ * `steps`+1 points with linear interpolation approximate the cos/sin curve.
2645
+ *
2646
+ * Returns dB point arrays for `host.setTrackVolumeAutomation` — origin on the top
2647
+ * layer, target on the bottom. @since SDK 2.25.0
2648
+ */
2649
+ declare function buildCrossfadeVolumeCurves(bars: number, bpm: number, sliderPos: number, steps?: number): CrossfadeVolumeCurves;
2609
2650
 
2610
2651
  /**
2611
2652
  * CrossfadeTrackRow — a transition "crossfade track": two stacked TrackRows
@@ -2794,8 +2835,9 @@ declare function ImportTrackModal({ host, open, onClose, onImported, title, test
2794
2835
  *
2795
2836
  * Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN
2796
2837
  * 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.
2838
+ * scene), in ANY order the only constraint is same plugin/family (the picker is
2839
+ * per-panel). A source track already used in a crossfade is hidden (via
2840
+ * excludeSourceDbIds), so each source is used at most once.
2799
2841
  *
2800
2842
  * Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`
2801
2843
  * for both scenes (ungated — a transition deliberately bridges different keys).
@@ -2813,7 +2855,7 @@ interface CrossfadeSelection {
2813
2855
  dbId: string;
2814
2856
  /** Display name (for the row caption). */
2815
2857
  name: string;
2816
- /** Musical role (same for both enforced by the picker). */
2858
+ /** Musical role of the source track (the panel uses the TARGET's for generation). */
2817
2859
  role?: string;
2818
2860
  }
2819
2861
  interface CrossfadeModalProps {
@@ -2829,6 +2871,12 @@ interface CrossfadeModalProps {
2829
2871
  fromSceneName?: string;
2830
2872
  /** Display name for the target scene heading (optional). */
2831
2873
  toSceneName?: string;
2874
+ /**
2875
+ * Source-track DB ids already used in a crossfade (origin + target of every
2876
+ * existing pair in this panel). Hidden from BOTH dropdowns so each source is
2877
+ * used at most once. @since SDK 2.26.0
2878
+ */
2879
+ excludeSourceDbIds?: readonly string[];
2832
2880
  /** Close handler (Escape, backdrop, Cancel, or after a successful create). */
2833
2881
  onClose: () => void;
2834
2882
  /** Build the crossfade pair. Should reject on failure so the modal shows it. */
@@ -2836,7 +2884,7 @@ interface CrossfadeModalProps {
2836
2884
  /** data-testid prefix. */
2837
2885
  testIdPrefix?: string;
2838
2886
  }
2839
- declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
2887
+ declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, excludeSourceDbIds, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
2840
2888
 
2841
2889
  /**
2842
2890
  * ConfirmDialog — styled in-app confirmation modal (SDK component).
@@ -3561,7 +3609,7 @@ declare function useSoundHistory(applySound: (trackId: string, descriptor: unkno
3561
3609
  * Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
3562
3610
  * during activation and marks incompatible plugins accordingly.
3563
3611
  */
3564
- declare const PLUGIN_SDK_VERSION = "2.24.0";
3612
+ declare const PLUGIN_SDK_VERSION = "2.26.0";
3565
3613
 
3566
3614
  /**
3567
3615
  * FX Preset Definitions
@@ -3709,4 +3757,4 @@ interface PickTopKOptions {
3709
3757
  */
3710
3758
  declare function pickTopKWeighted<T>(scored: ReadonlyArray<ScoredCandidate<T>>, options?: PickTopKOptions): T | null;
3711
3759
 
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 };
3760
+ 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, type CrossfadeVolumeCurves, 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, type VolumeAutomationPoint, VolumeSlider, type WaveformPeaks, WaveformView, type WaveformViewProps, analyzeWavPeak, asCrossfadeMeta, buildCrossfadeInpaintPrompt, buildCrossfadeVolumeCurves, 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 };
package/dist/index.d.ts CHANGED
@@ -227,6 +227,16 @@ interface PluginHost {
227
227
  setTrackMute(trackId: string, muted: boolean): Promise<void>;
228
228
  /** Set track volume (linear 0.0 - 1.0). Only works on owned tracks. */
229
229
  setTrackVolume(trackId: string, volume: number): Promise<void>;
230
+ /**
231
+ * Set/replace a time-based volume automation curve (a fade envelope) on a track,
232
+ * or clear it with an empty array. Points are {time: seconds, db}; linear between
233
+ * points. Used by crossfade tracks to fade origin↔target across the looped
234
+ * transition. Optional — callers MUST null-check. @since SDK 2.25.0
235
+ */
236
+ setTrackVolumeAutomation?(trackId: string, points: Array<{
237
+ time: number;
238
+ db: number;
239
+ }>): Promise<void>;
230
240
  /** Set track pan (-1.0 left to 1.0 right). Only works on owned tracks. */
231
241
  setTrackPan(trackId: string, pan: number): Promise<void>;
232
242
  /** Set track solo state. Only works on owned tracks. */
@@ -537,6 +547,11 @@ interface PluginHost {
537
547
  key: string;
538
548
  mode: string;
539
549
  } | null>;
550
+ /**
551
+ * Read a scene's human display name by db id (for labelling a crossfade's
552
+ * origin/target scenes). Optional — callers MUST null-check. @since SDK 2.26.0
553
+ */
554
+ getSceneName?(sceneDbId: string): Promise<string | null>;
540
555
  /** Subscribe to transport state changes. Returns unsubscribe function. */
541
556
  onTransportEvent(listener: TransportEventListener): UnsubscribeFn;
542
557
  /** Subscribe to deck boundary events. Returns unsubscribe function. */
@@ -2592,6 +2607,10 @@ interface CrossfadePairMeta {
2592
2607
  sliderPos: number;
2593
2608
  originDbId: string;
2594
2609
  targetDbId: string;
2610
+ /** DB id of the ORIGIN source track (in the from scene) — drives the "used once" exclusion. */
2611
+ originSourceDbId: string;
2612
+ /** DB id of the TARGET source track (in the to scene). */
2613
+ targetSourceDbId: string;
2595
2614
  originSourceName: string;
2596
2615
  originSoundLabel: string;
2597
2616
  targetSourceName: string;
@@ -2606,6 +2625,28 @@ declare function asCrossfadeMeta(val: unknown): CrossfadeMeta | null;
2606
2625
  * row instead of vanishing.
2607
2626
  */
2608
2627
  declare function parseCrossfadePairs(sceneData: Record<string, unknown>): CrossfadePairMeta[];
2628
+ /** One volume-automation point: a dB value at a time offset (seconds from clip start). */
2629
+ interface VolumeAutomationPoint {
2630
+ time: number;
2631
+ db: number;
2632
+ }
2633
+ /** Origin + target volume curves for one crossfade pair. */
2634
+ interface CrossfadeVolumeCurves {
2635
+ origin: VolumeAutomationPoint[];
2636
+ target: VolumeAutomationPoint[];
2637
+ }
2638
+ /**
2639
+ * Equal-power crossfade volume curves over a transition of `bars` at `bpm`.
2640
+ * The ORIGIN layer fades OUT and the TARGET fades IN; `sliderPos` (0..1) sets
2641
+ * WHERE in time the equal-power (-3 dB) crossover sits — 0 = hand off near the
2642
+ * start, 1 = hold the origin until near the end. Points span the clip window
2643
+ * [0, durationSeconds] so the engine re-reads them each loop (re-fade per loop).
2644
+ * `steps`+1 points with linear interpolation approximate the cos/sin curve.
2645
+ *
2646
+ * Returns dB point arrays for `host.setTrackVolumeAutomation` — origin on the top
2647
+ * layer, target on the bottom. @since SDK 2.25.0
2648
+ */
2649
+ declare function buildCrossfadeVolumeCurves(bars: number, bpm: number, sliderPos: number, steps?: number): CrossfadeVolumeCurves;
2609
2650
 
2610
2651
  /**
2611
2652
  * CrossfadeTrackRow — a transition "crossfade track": two stacked TrackRows
@@ -2794,8 +2835,9 @@ declare function ImportTrackModal({ host, open, onClose, onImported, title, test
2794
2835
  *
2795
2836
  * Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN
2796
2837
  * 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.
2838
+ * scene), in ANY order the only constraint is same plugin/family (the picker is
2839
+ * per-panel). A source track already used in a crossfade is hidden (via
2840
+ * excludeSourceDbIds), so each source is used at most once.
2799
2841
  *
2800
2842
  * Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`
2801
2843
  * for both scenes (ungated — a transition deliberately bridges different keys).
@@ -2813,7 +2855,7 @@ interface CrossfadeSelection {
2813
2855
  dbId: string;
2814
2856
  /** Display name (for the row caption). */
2815
2857
  name: string;
2816
- /** Musical role (same for both enforced by the picker). */
2858
+ /** Musical role of the source track (the panel uses the TARGET's for generation). */
2817
2859
  role?: string;
2818
2860
  }
2819
2861
  interface CrossfadeModalProps {
@@ -2829,6 +2871,12 @@ interface CrossfadeModalProps {
2829
2871
  fromSceneName?: string;
2830
2872
  /** Display name for the target scene heading (optional). */
2831
2873
  toSceneName?: string;
2874
+ /**
2875
+ * Source-track DB ids already used in a crossfade (origin + target of every
2876
+ * existing pair in this panel). Hidden from BOTH dropdowns so each source is
2877
+ * used at most once. @since SDK 2.26.0
2878
+ */
2879
+ excludeSourceDbIds?: readonly string[];
2832
2880
  /** Close handler (Escape, backdrop, Cancel, or after a successful create). */
2833
2881
  onClose: () => void;
2834
2882
  /** Build the crossfade pair. Should reject on failure so the modal shows it. */
@@ -2836,7 +2884,7 @@ interface CrossfadeModalProps {
2836
2884
  /** data-testid prefix. */
2837
2885
  testIdPrefix?: string;
2838
2886
  }
2839
- declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
2887
+ declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, excludeSourceDbIds, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
2840
2888
 
2841
2889
  /**
2842
2890
  * ConfirmDialog — styled in-app confirmation modal (SDK component).
@@ -3561,7 +3609,7 @@ declare function useSoundHistory(applySound: (trackId: string, descriptor: unkno
3561
3609
  * Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
3562
3610
  * during activation and marks incompatible plugins accordingly.
3563
3611
  */
3564
- declare const PLUGIN_SDK_VERSION = "2.24.0";
3612
+ declare const PLUGIN_SDK_VERSION = "2.26.0";
3565
3613
 
3566
3614
  /**
3567
3615
  * FX Preset Definitions
@@ -3709,4 +3757,4 @@ interface PickTopKOptions {
3709
3757
  */
3710
3758
  declare function pickTopKWeighted<T>(scored: ReadonlyArray<ScoredCandidate<T>>, options?: PickTopKOptions): T | null;
3711
3759
 
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 };
3760
+ 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, type CrossfadeVolumeCurves, 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, type VolumeAutomationPoint, VolumeSlider, type WaveformPeaks, WaveformView, type WaveformViewProps, analyzeWavPeak, asCrossfadeMeta, buildCrossfadeInpaintPrompt, buildCrossfadeVolumeCurves, 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 };
package/dist/index.js CHANGED
@@ -73,6 +73,7 @@ __export(index_exports, {
73
73
  analyzeWavPeak: () => analyzeWavPeak,
74
74
  asCrossfadeMeta: () => asCrossfadeMeta,
75
75
  buildCrossfadeInpaintPrompt: () => buildCrossfadeInpaintPrompt,
76
+ buildCrossfadeVolumeCurves: () => buildCrossfadeVolumeCurves,
76
77
  calculateTimeBasedTarget: () => calculateTimeBasedTarget,
77
78
  cellToPx: () => cellToPx,
78
79
  centerScrollTop: () => centerScrollTop,
@@ -2569,6 +2570,8 @@ function parseCrossfadePairs(sceneData) {
2569
2570
  sliderPos: g.origin.meta.sliderPos,
2570
2571
  originDbId: g.origin.dbId,
2571
2572
  targetDbId: g.target.dbId,
2573
+ originSourceDbId: g.origin.meta.sourceTrackDbId,
2574
+ targetSourceDbId: g.target.meta.sourceTrackDbId,
2572
2575
  originSourceName: g.origin.meta.sourceName,
2573
2576
  originSoundLabel: g.origin.meta.soundLabel,
2574
2577
  targetSourceName: g.target.meta.sourceName,
@@ -2577,6 +2580,25 @@ function parseCrossfadePairs(sceneData) {
2577
2580
  }
2578
2581
  return pairs;
2579
2582
  }
2583
+ var FADE_FLOOR_DB = -80;
2584
+ function gainToDb(gain) {
2585
+ return gain <= 1e-4 ? FADE_FLOOR_DB : Math.max(FADE_FLOOR_DB, 20 * Math.log10(gain));
2586
+ }
2587
+ function buildCrossfadeVolumeCurves(bars, bpm, sliderPos, steps = 32) {
2588
+ const durationSeconds = bars * 4 * 60 / Math.max(1, bpm);
2589
+ const s = Math.min(0.98, Math.max(0.02, sliderPos));
2590
+ const round = (n) => Math.round(n * 1e3) / 1e3;
2591
+ const origin = [];
2592
+ const target = [];
2593
+ for (let i = 0; i <= steps; i++) {
2594
+ const x = i / steps;
2595
+ const time = round(x * durationSeconds);
2596
+ const theta = x <= s ? x / s * (Math.PI / 4) : Math.PI / 4 + (x - s) / (1 - s) * (Math.PI / 4);
2597
+ origin.push({ time, db: Math.round(gainToDb(Math.cos(theta)) * 100) / 100 });
2598
+ target.push({ time, db: Math.round(gainToDb(Math.sin(theta)) * 100) / 100 });
2599
+ }
2600
+ return { origin, target };
2601
+ }
2580
2602
 
2581
2603
  // src/crossfade-inpaint.ts
2582
2604
  var PITCH_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
@@ -2825,6 +2847,7 @@ function CrossfadeModal({
2825
2847
  toSceneId,
2826
2848
  fromSceneName,
2827
2849
  toSceneName,
2850
+ excludeSourceDbIds,
2828
2851
  onClose,
2829
2852
  onCreate,
2830
2853
  testIdPrefix = "crossfade-modal"
@@ -2834,6 +2857,8 @@ function CrossfadeModal({
2834
2857
  const [targetDbId, setTargetDbId] = (0, import_react12.useState)("");
2835
2858
  const [isCreating, setIsCreating] = (0, import_react12.useState)(false);
2836
2859
  const [error, setError] = (0, import_react12.useState)(null);
2860
+ const [fromName, setFromName] = (0, import_react12.useState)(null);
2861
+ const [toName, setToName] = (0, import_react12.useState)(null);
2837
2862
  const cancelRef = (0, import_react12.useRef)(null);
2838
2863
  const refresh = (0, import_react12.useCallback)(async () => {
2839
2864
  if (!host.listSceneFamilyTracks) {
@@ -2842,12 +2867,15 @@ function CrossfadeModal({
2842
2867
  }
2843
2868
  setLoad({ status: "loading" });
2844
2869
  try {
2845
- const [origin, target] = await Promise.all([
2870
+ const [origin, target, fName, tName] = await Promise.all([
2846
2871
  host.listSceneFamilyTracks(fromSceneId),
2847
- host.listSceneFamilyTracks(toSceneId)
2872
+ host.listSceneFamilyTracks(toSceneId),
2873
+ host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
2874
+ host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null)
2848
2875
  ]);
2876
+ setFromName(fName);
2877
+ setToName(tName);
2849
2878
  setLoad({ status: "ready", origin, target });
2850
- setOriginDbId(origin[0]?.dbId ?? "");
2851
2879
  } catch (err) {
2852
2880
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
2853
2881
  }
@@ -2861,21 +2889,26 @@ function CrossfadeModal({
2861
2889
  void refresh();
2862
2890
  }
2863
2891
  }, [open, refresh]);
2864
- const originTrack = (0, import_react12.useMemo)(
2865
- () => load.status === "ready" ? load.origin.find((t) => t.dbId === originDbId) ?? null : null,
2866
- [load, originDbId]
2892
+ const excludeSet = (0, import_react12.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
2893
+ const originCandidates = (0, import_react12.useMemo)(
2894
+ () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
2895
+ [load, excludeSet]
2867
2896
  );
2868
- const originRole = originTrack?.role;
2869
- const targetCandidates = (0, import_react12.useMemo)(() => {
2870
- if (load.status !== "ready") return [];
2871
- if (!originRole) return load.target;
2872
- return load.target.filter((t) => t.role === originRole);
2873
- }, [load, originRole]);
2897
+ const targetCandidates = (0, import_react12.useMemo)(
2898
+ () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
2899
+ [load, excludeSet]
2900
+ );
2901
+ (0, import_react12.useEffect)(() => {
2902
+ if (!originCandidates.some((t) => t.dbId === originDbId)) {
2903
+ setOriginDbId(originCandidates[0]?.dbId ?? "");
2904
+ }
2905
+ }, [originCandidates, originDbId]);
2874
2906
  (0, import_react12.useEffect)(() => {
2875
2907
  if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
2876
2908
  setTargetDbId(targetCandidates[0]?.dbId ?? "");
2877
2909
  }
2878
2910
  }, [targetCandidates, targetDbId]);
2911
+ const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
2879
2912
  const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
2880
2913
  const canCreate = !isCreating && !!originTrack && !!targetTrack;
2881
2914
  const handleClose = (0, import_react12.useCallback)(() => {
@@ -2896,6 +2929,8 @@ function CrossfadeModal({
2896
2929
  setIsCreating(false);
2897
2930
  }
2898
2931
  }, [originTrack, targetTrack, onCreate, onClose]);
2932
+ const fromLabel = fromName ?? fromSceneName ?? null;
2933
+ const toLabel = toName ?? toSceneName ?? null;
2899
2934
  if (!open) return null;
2900
2935
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2901
2936
  "div",
@@ -2908,24 +2943,31 @@ function CrossfadeModal({
2908
2943
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
2909
2944
  "Bridge a track from",
2910
2945
  " ",
2911
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: fromSceneName ?? "the origin scene" }),
2946
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
2912
2947
  " into one from",
2913
2948
  " ",
2914
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: toSceneName ?? "the target scene" }),
2949
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
2915
2950
  ". Both layers share one generated part; each keeps its own preset."
2916
2951
  ] }),
2917
2952
  load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
2918
2953
  load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
2919
- load.status === "ready" && (load.origin.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2954
+ load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2920
2955
  "div",
2921
2956
  {
2922
2957
  className: "text-xs text-sas-muted py-4 text-center",
2923
2958
  "data-testid": `${testIdPrefix}-empty-origin`,
2924
- children: "No matching tracks in the origin scene. Add one there first."
2959
+ children: [
2960
+ "No available tracks in ",
2961
+ fromLabel ?? "the origin scene",
2962
+ ". Add one (or free one from another crossfade) first."
2963
+ ]
2925
2964
  }
2926
2965
  ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2927
2966
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "block", children: [
2928
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "Origin (top)" }),
2967
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
2968
+ "Origin ",
2969
+ fromLabel ? `(${fromLabel})` : "(top)"
2970
+ ] }),
2929
2971
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2930
2972
  "select",
2931
2973
  {
@@ -2934,7 +2976,7 @@ function CrossfadeModal({
2934
2976
  onChange: (e) => setOriginDbId(e.target.value),
2935
2977
  disabled: isCreating,
2936
2978
  className: "sas-input w-full mt-0.5 text-xs",
2937
- children: load.origin.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("option", { value: t.dbId, children: [
2979
+ children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("option", { value: t.dbId, children: [
2938
2980
  t.name,
2939
2981
  t.role ? ` \xB7 ${t.role}` : ""
2940
2982
  ] }, t.dbId))
@@ -2943,13 +2985,13 @@ function CrossfadeModal({
2943
2985
  ] }),
2944
2986
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "block", children: [
2945
2987
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
2946
- "Target (bottom)",
2947
- originRole ? ` \xB7 ${originRole}` : ""
2988
+ "Target ",
2989
+ toLabel ? `(${toLabel})` : "(bottom)"
2948
2990
  ] }),
2949
2991
  targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
2950
- "No ",
2951
- originRole ?? "matching",
2952
- " track in the target scene to crossfade into."
2992
+ "No available tracks in ",
2993
+ toLabel ?? "the target scene",
2994
+ " to crossfade into."
2953
2995
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2954
2996
  "select",
2955
2997
  {
@@ -3868,7 +3910,7 @@ function useTrackReorder({
3868
3910
  }
3869
3911
 
3870
3912
  // src/constants/sdk-version.ts
3871
- var PLUGIN_SDK_VERSION = "2.24.0";
3913
+ var PLUGIN_SDK_VERSION = "2.26.0";
3872
3914
 
3873
3915
  // src/utils/format-concurrent-tracks.ts
3874
3916
  function formatConcurrentTracks(ctx) {
@@ -4055,6 +4097,7 @@ function pickTopKWeighted(scored, options = {}) {
4055
4097
  analyzeWavPeak,
4056
4098
  asCrossfadeMeta,
4057
4099
  buildCrossfadeInpaintPrompt,
4100
+ buildCrossfadeVolumeCurves,
4058
4101
  calculateTimeBasedTarget,
4059
4102
  cellToPx,
4060
4103
  centerScrollTop,