@signalsandsorcery/plugin-sdk 2.0.2 → 2.3.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
@@ -412,6 +412,37 @@ interface PluginHost {
412
412
  setAudioOffsetSamples(trackId: string, offsetSamples: number): Promise<void>;
413
413
  /** Read the current manual offset (0 if never set). */
414
414
  getAudioOffsetSamples(trackId: string): Promise<number>;
415
+ /**
416
+ * Read raw bytes of an audio file written by the host. The path may be
417
+ * `~app/`-relative or project-relative — the host resolves it using the
418
+ * same logic as `writeAudioClip`. Throws FILE_NOT_FOUND if the path
419
+ * can't be resolved or doesn't exist on disk.
420
+ */
421
+ getAudioFileBytes(filePath: string): Promise<ArrayBuffer>;
422
+ /** Persist the original (raw, un-trimmed) audio file path for a track. */
423
+ setRawAudioFilePath(trackId: string, filePath: string | null): Promise<void>;
424
+ /** Read the raw audio file path persisted via `setRawAudioFilePath`. */
425
+ getRawAudioFilePath(trackId: string): Promise<string | null>;
426
+ /**
427
+ * Persist the cue-points detected in the raw (un-trimmed) audio file.
428
+ * Sample positions are in input-file coordinates.
429
+ */
430
+ setRawCuePoints(trackId: string, cues: PluginCuePoints | null): Promise<void>;
431
+ /** Read raw-domain cue points persisted via `setRawCuePoints`. */
432
+ getRawCuePoints(trackId: string): Promise<PluginCuePoints | null>;
433
+ /** Persist the current trim window inside the raw audio file. */
434
+ setTrimWindow(trackId: string, window: PluginTrimWindow | null): Promise<void>;
435
+ /** Read the current trim window persisted via `setTrimWindow`. */
436
+ getTrimWindow(trackId: string): Promise<PluginTrimWindow | null>;
437
+ /**
438
+ * Re-trim the raw audio file at the given sample offset and replace the
439
+ * track's audio clip with the new slice. Persists updated trimmed-domain
440
+ * cue points and the new trim window.
441
+ */
442
+ commitTrimWindow(trackId: string, startSample: number, durationSamples: number): Promise<{
443
+ filePath: string;
444
+ cuePoints: PluginCuePoints | null;
445
+ }>;
415
446
  /** Trigger bulk composition for the active scene (LLM plans arrangement, creates tracks, generates MIDI). */
416
447
  composeScene(options: ComposeSceneOptions): Promise<ComposeSceneResult>;
417
448
  /** Subscribe to composition progress events (planning, generating, complete, error). */
@@ -434,6 +465,108 @@ interface PluginHost {
434
465
  splitStems(trackId: string): Promise<PluginStemSplitResult>;
435
466
  /** Check if the stem splitter binary is available. */
436
467
  isStemSplitterAvailable(): Promise<boolean>;
468
+ /**
469
+ * Enumerate audio input devices visible to the engine. Empty list means
470
+ * no input device is available (or the OS denied permission). Requires
471
+ * `audioCapture` capability.
472
+ * @since SDK 2.1.0
473
+ */
474
+ getAudioInputDevices(): Promise<AudioInputDevice[]>;
475
+ /**
476
+ * Snapshot of engine state needed to start a recording session. Reads
477
+ * the engine sample rate, the active scene id, the transition-render
478
+ * lock state, and current BPM/bars. Requires `audioCapture`.
479
+ * @since SDK 2.1.0
480
+ */
481
+ getRecordingTargetInfo(): Promise<RecordingTargetInfo>;
482
+ /**
483
+ * Begin a recording session. Engine writes integer-PCM WAV chunks to
484
+ * disk; one chunk per call to `markRecordingChunkBoundary`. Each
485
+ * finalized chunk fires a `RecordingChunkFinalizedEvent` to
486
+ * subscribers of `onRecordingChunkFinalized`. Throws
487
+ * AUDIO_CAPTURE_DENIED on permission failure or if no device is
488
+ * available.
489
+ *
490
+ * Pass `deviceId` to override the platform-configured input (rare —
491
+ * only useful for tests or workflows that need a specific device).
492
+ * Omit it to use the platform's selected input from
493
+ * `AudioRoutingConfig.inputDeviceId` — this is the recommended path
494
+ * for plugins post-SDK-2.2.0.
495
+ *
496
+ * @since SDK 2.1.0 (deviceId required) — 2.2.0 made it optional.
497
+ */
498
+ startTrackRecording(deviceId?: string): Promise<void>;
499
+ /**
500
+ * Mark the boundary between two recording chunks. The engine closes the
501
+ * currently-open WAV writer and opens a new one; the closed file fires
502
+ * a `RecordingChunkFinalizedEvent` once flush completes. No-op if no
503
+ * recording session is active.
504
+ *
505
+ * Pass `boundaryHostTimeNs` from `DeckBoundaryEvent.boundaryHostTimeNs`
506
+ * for sample-perfect take alignment (Path 2). The engine then splits
507
+ * the chunk at the EXACT recorder-sample that corresponds to that
508
+ * host-time, eliminating the ~5–50 ms of jitter introduced by the
509
+ * legacy "split wherever the writer is" path. Required for any
510
+ * workflow that overlays multiple takes (vocalist comping, layered
511
+ * dubs); optional for single-take captures.
512
+ *
513
+ * @since SDK 2.1.0 — 2.4.0 added optional boundaryHostTimeNs.
514
+ */
515
+ markRecordingChunkBoundary(boundaryHostTimeNs?: number): Promise<void>;
516
+ /**
517
+ * Stop the active recording session. The final chunk is closed and
518
+ * finalized; its `RecordingChunkFinalizedEvent` fires before this
519
+ * promise resolves. Returns the path of the final chunk (also delivered
520
+ * via the event for symmetry).
521
+ * @since SDK 2.1.0
522
+ */
523
+ stopTrackRecording(): Promise<{
524
+ finalChunkPath: string;
525
+ durationMs: number;
526
+ }>;
527
+ /**
528
+ * Subscribe to chunk-finalized events for this plugin's active recording
529
+ * session. Auto-unsubscribed on `deactivate`. Returns unsubscribe fn.
530
+ * @since SDK 2.1.0
531
+ */
532
+ onRecordingChunkFinalized(listener: (event: RecordingChunkFinalizedEvent) => void): UnsubscribeFn;
533
+ /**
534
+ * Get the platform-configured audio input device, or null when no
535
+ * device is set. Read-only; configured via the assistant's
536
+ * AudioRoutingPanel. Plugins use this to display the current input
537
+ * to the user without exposing their own picker.
538
+ *
539
+ * @since SDK 2.2.0
540
+ */
541
+ getCurrentInputDevice(): Promise<AudioInputDevice | null>;
542
+ /**
543
+ * Get the platform's mic-to-output round-trip latency offset in
544
+ * samples. 0 = uncalibrated. Plugins recording audio apply this via
545
+ * `setAudioOffsetSamples` so takes line up with the source loop.
546
+ *
547
+ * @since SDK 2.2.0
548
+ */
549
+ getRecordingLatencyOffsetSamples(): Promise<number>;
550
+ /**
551
+ * Snapshot of the input level for the most recent audio block.
552
+ * Renderer polls at ~30Hz to drive a level meter / scrolling
553
+ * waveform without an event-channel subscription.
554
+ *
555
+ * @since SDK 2.3.0
556
+ */
557
+ getRecordingInputLevel(): Promise<{
558
+ peakDb: number;
559
+ peakLinear: number;
560
+ clipped: boolean;
561
+ active: boolean;
562
+ }>;
563
+ /**
564
+ * Reset the latched clip indicator. Safe regardless of whether
565
+ * monitoring or recording is active.
566
+ *
567
+ * @since SDK 2.3.0
568
+ */
569
+ clearRecordingInputClipIndicator(): Promise<void>;
437
570
  }
438
571
  /** Stem type identifiers */
439
572
  type StemType = 'vocals' | 'drums' | 'bass' | 'other';
@@ -716,6 +849,20 @@ interface DeckBoundaryEvent {
716
849
  bar: number;
717
850
  beat: number;
718
851
  loopCount: number;
852
+ /**
853
+ * Stream-time sample index at which the loop wrap was detected in the
854
+ * audio thread (engine's AudioBoundaryProbe). Undefined when the
855
+ * audio-thread anchor was unavailable. @since SDK 2.4.0
856
+ */
857
+ boundaryAudioSamplePosition?: number;
858
+ /**
859
+ * Monotonic host-time (nanoseconds) at the audio block in which the
860
+ * loop wrap was detected. Same clock as
861
+ * `juce::AudioIODeviceCallbackContext::hostTimeNs`. Pair with
862
+ * `markRecordingChunkBoundary(boundaryHostTimeNs)` for sample-perfect
863
+ * take alignment. @since SDK 2.4.0
864
+ */
865
+ boundaryHostTimeNs?: number;
719
866
  }
720
867
  interface PluginTransportState {
721
868
  isPlaying: boolean;
@@ -826,7 +973,7 @@ interface PluginSettingsStore {
826
973
  /** Subscribe to settings changes. Returns unsubscribe fn. */
827
974
  onChange(listener: (key: string, value: unknown) => void): UnsubscribeFn;
828
975
  }
829
- type PluginErrorCode = 'NOT_OWNED' | 'TRACK_NOT_FOUND' | 'TRACK_LIMIT_EXCEEDED' | 'NO_ACTIVE_SCENE' | 'ENGINE_ERROR' | 'INVALID_MIDI' | 'FILE_NOT_FOUND' | 'INVALID_FORMAT' | 'PLUGIN_NOT_FOUND' | 'LLM_BUDGET_EXCEEDED' | 'LLM_UNAVAILABLE' | 'NOT_AUTHENTICATED' | 'TIMEOUT' | 'CANCELLED' | 'INCOMPATIBLE' | 'CAPABILITY_DENIED' | 'SECRET_NOT_FOUND' | 'VALIDATION_ERROR';
976
+ type PluginErrorCode = 'NOT_OWNED' | 'TRACK_NOT_FOUND' | 'TRACK_LIMIT_EXCEEDED' | 'NO_ACTIVE_SCENE' | 'ENGINE_ERROR' | 'INVALID_MIDI' | 'FILE_NOT_FOUND' | 'INVALID_FORMAT' | 'PLUGIN_NOT_FOUND' | 'LLM_BUDGET_EXCEEDED' | 'LLM_UNAVAILABLE' | 'NOT_AUTHENTICATED' | 'TIMEOUT' | 'CANCELLED' | 'INCOMPATIBLE' | 'CAPABILITY_DENIED' | 'SECRET_NOT_FOUND' | 'VALIDATION_ERROR' | 'AUDIO_CAPTURE_DENIED';
830
977
  declare class PluginError extends Error {
831
978
  readonly code: PluginErrorCode;
832
979
  readonly details?: Record<string, unknown>;
@@ -859,6 +1006,82 @@ interface PluginCapabilities {
859
1006
  };
860
1007
  /** Plugin needs native file dialog access */
861
1008
  fileDialog?: boolean;
1009
+ /**
1010
+ * Plugin needs microphone / line-in capture. Gates the recording host
1011
+ * methods (getAudioInputDevices, startTrackRecording, etc).
1012
+ * @since SDK 2.1.0
1013
+ */
1014
+ audioCapture?: boolean;
1015
+ }
1016
+ /**
1017
+ * Audio input device exposed by the audio engine. The `deviceId` is the
1018
+ * stable identifier returned by JUCE's AudioDeviceManager and accepted as
1019
+ * the device argument to `startTrackRecording`.
1020
+ * @since SDK 2.1.0
1021
+ */
1022
+ interface AudioInputDevice {
1023
+ /** Stable device identifier — passed back to startTrackRecording. */
1024
+ deviceId: string;
1025
+ /** Human-readable device name (e.g., "MacBook Pro Microphone", "USB Mic"). */
1026
+ label: string;
1027
+ /** True if this is the system default input device. */
1028
+ isDefault: boolean;
1029
+ /** Number of input channels the device supports (1 = mono, 2 = stereo). */
1030
+ channelCount: number;
1031
+ }
1032
+ /**
1033
+ * Engine state snapshot that an audio-recording plugin needs before
1034
+ * starting a session.
1035
+ * @since SDK 2.1.0
1036
+ */
1037
+ interface RecordingTargetInfo {
1038
+ /** Engine device sample rate, e.g. 44100 or 48000. */
1039
+ engineSampleRate: number;
1040
+ /** Active scene id, or null when no scene is selected. */
1041
+ sceneId: string | null;
1042
+ /** True when a transition render lock is held — recorder must refuse. */
1043
+ isRenderLocked: boolean;
1044
+ /** Current project BPM. */
1045
+ bpm: number;
1046
+ /** Active scene length in bars (4/4 assumed), or null when no scene. */
1047
+ bars: number | null;
1048
+ /**
1049
+ * Sample-perfect-recording compatibility (Path 2 gate). When false,
1050
+ * the recorder must refuse to start a session and surface
1051
+ * `recordingCompatibilityReason` to the user — input + output
1052
+ * devices cannot be sample-aligned.
1053
+ * @since SDK 2.4.0
1054
+ */
1055
+ canRecordSamplePerfect?: boolean;
1056
+ recordingCompatibilityReason?: string;
1057
+ }
1058
+ /**
1059
+ * Event payload fired when the engine finalizes a recording chunk WAV
1060
+ * file (either at a boundary mark or at session stop).
1061
+ * @since SDK 2.1.0
1062
+ */
1063
+ interface RecordingChunkFinalizedEvent {
1064
+ /** Absolute path to the finalized WAV file on disk. */
1065
+ filePath: string;
1066
+ /** Zero-based chunk index within the active session. */
1067
+ chunkIndex: number;
1068
+ /** Duration of this chunk in milliseconds. */
1069
+ durationMs: number;
1070
+ /** WAV sample rate. */
1071
+ sampleRate: number;
1072
+ /** WAV channel count. */
1073
+ channels: number;
1074
+ /**
1075
+ * Sample-perfect-recording metadata (Path 2). When the chunk was
1076
+ * closed via a host-time-anchored `markRecordingChunkBoundary` call,
1077
+ * carries recorder-local sample positions plus the host-time at
1078
+ * which the boundary fired. Undefined / -1 means the boundary
1079
+ * lacked a host-time anchor (legacy or stop-driven finalize).
1080
+ * @since SDK 2.4.0
1081
+ */
1082
+ recorderSampleStart?: number;
1083
+ recorderSampleEnd?: number;
1084
+ boundaryHostTimeNs?: number;
862
1085
  }
863
1086
  interface PluginFileDialogOptions {
864
1087
  title?: string;
@@ -946,6 +1169,19 @@ interface PluginAudioTextureResult {
946
1169
  * clip is written so the OffsetScrubber UI can read them later.
947
1170
  */
948
1171
  cuePoints: PluginCuePoints | null;
1172
+ /**
1173
+ * Path to the un-trimmed (raw) Lyria output. Used by the audio-texture
1174
+ * trim editor to draw the full waveform. Persist via
1175
+ * `host.setRawAudioFilePath`. Null when no raw file is available.
1176
+ */
1177
+ rawFilePath?: string | null;
1178
+ /** Same beats as `cuePoints` in raw-file sample coordinates. */
1179
+ rawCuePoints?: PluginCuePoints | null;
1180
+ /**
1181
+ * Auto-detected start of the trim window inside the raw file (sample
1182
+ * offset). Null when detection was skipped.
1183
+ */
1184
+ inputStartSample?: number | null;
949
1185
  }
950
1186
  /**
951
1187
  * Cue-points sidecar surfaced by the audio-processor `trim` command —
@@ -967,6 +1203,15 @@ interface PluginCuePoints {
967
1203
  /** ISO-8601 timestamp of when detection ran. */
968
1204
  detected_at: string;
969
1205
  }
1206
+ /**
1207
+ * A trim window inside a raw (un-trimmed) audio file. `start_sample` is
1208
+ * the offset from the start of the raw file; `duration_samples` is the
1209
+ * length of the trimmed slice. Both are in raw-file sample coordinates.
1210
+ */
1211
+ interface PluginTrimWindow {
1212
+ start_sample: number;
1213
+ duration_samples: number;
1214
+ }
970
1215
  /** Options for composing a full scene arrangement via LLM. */
971
1216
  interface ComposeSceneOptions {
972
1217
  /** The contract prompt / musical direction for the arrangement. */
@@ -1409,7 +1654,7 @@ declare function useSceneState<T>(activeSceneId: string | null, initialValue: T)
1409
1654
  * Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
1410
1655
  * during activation and marks incompatible plugins accordingly.
1411
1656
  */
1412
- declare const PLUGIN_SDK_VERSION = "2.0.0";
1657
+ declare const PLUGIN_SDK_VERSION = "2.3.0";
1413
1658
 
1414
1659
  /**
1415
1660
  * FX Preset Definitions
@@ -1463,4 +1708,4 @@ declare function sliderToDb(slider: number): number;
1463
1708
  */
1464
1709
  declare function dbToSlider(db: number): number;
1465
1710
 
1466
- export { type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type 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, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type 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 PluginTrackRuntimeState, type PluginTransportState, type PluginUIProps, type PostProcessOptions, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type StemType, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
1711
+ export { type AudioInputDevice, type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type 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, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type 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 PluginTrackRuntimeState, type PluginTransportState, type PluginTrimWindow, type PluginUIProps, type PostProcessOptions, type RecordingChunkFinalizedEvent, type RecordingTargetInfo, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type StemType, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
package/dist/index.d.ts CHANGED
@@ -412,6 +412,37 @@ interface PluginHost {
412
412
  setAudioOffsetSamples(trackId: string, offsetSamples: number): Promise<void>;
413
413
  /** Read the current manual offset (0 if never set). */
414
414
  getAudioOffsetSamples(trackId: string): Promise<number>;
415
+ /**
416
+ * Read raw bytes of an audio file written by the host. The path may be
417
+ * `~app/`-relative or project-relative — the host resolves it using the
418
+ * same logic as `writeAudioClip`. Throws FILE_NOT_FOUND if the path
419
+ * can't be resolved or doesn't exist on disk.
420
+ */
421
+ getAudioFileBytes(filePath: string): Promise<ArrayBuffer>;
422
+ /** Persist the original (raw, un-trimmed) audio file path for a track. */
423
+ setRawAudioFilePath(trackId: string, filePath: string | null): Promise<void>;
424
+ /** Read the raw audio file path persisted via `setRawAudioFilePath`. */
425
+ getRawAudioFilePath(trackId: string): Promise<string | null>;
426
+ /**
427
+ * Persist the cue-points detected in the raw (un-trimmed) audio file.
428
+ * Sample positions are in input-file coordinates.
429
+ */
430
+ setRawCuePoints(trackId: string, cues: PluginCuePoints | null): Promise<void>;
431
+ /** Read raw-domain cue points persisted via `setRawCuePoints`. */
432
+ getRawCuePoints(trackId: string): Promise<PluginCuePoints | null>;
433
+ /** Persist the current trim window inside the raw audio file. */
434
+ setTrimWindow(trackId: string, window: PluginTrimWindow | null): Promise<void>;
435
+ /** Read the current trim window persisted via `setTrimWindow`. */
436
+ getTrimWindow(trackId: string): Promise<PluginTrimWindow | null>;
437
+ /**
438
+ * Re-trim the raw audio file at the given sample offset and replace the
439
+ * track's audio clip with the new slice. Persists updated trimmed-domain
440
+ * cue points and the new trim window.
441
+ */
442
+ commitTrimWindow(trackId: string, startSample: number, durationSamples: number): Promise<{
443
+ filePath: string;
444
+ cuePoints: PluginCuePoints | null;
445
+ }>;
415
446
  /** Trigger bulk composition for the active scene (LLM plans arrangement, creates tracks, generates MIDI). */
416
447
  composeScene(options: ComposeSceneOptions): Promise<ComposeSceneResult>;
417
448
  /** Subscribe to composition progress events (planning, generating, complete, error). */
@@ -434,6 +465,108 @@ interface PluginHost {
434
465
  splitStems(trackId: string): Promise<PluginStemSplitResult>;
435
466
  /** Check if the stem splitter binary is available. */
436
467
  isStemSplitterAvailable(): Promise<boolean>;
468
+ /**
469
+ * Enumerate audio input devices visible to the engine. Empty list means
470
+ * no input device is available (or the OS denied permission). Requires
471
+ * `audioCapture` capability.
472
+ * @since SDK 2.1.0
473
+ */
474
+ getAudioInputDevices(): Promise<AudioInputDevice[]>;
475
+ /**
476
+ * Snapshot of engine state needed to start a recording session. Reads
477
+ * the engine sample rate, the active scene id, the transition-render
478
+ * lock state, and current BPM/bars. Requires `audioCapture`.
479
+ * @since SDK 2.1.0
480
+ */
481
+ getRecordingTargetInfo(): Promise<RecordingTargetInfo>;
482
+ /**
483
+ * Begin a recording session. Engine writes integer-PCM WAV chunks to
484
+ * disk; one chunk per call to `markRecordingChunkBoundary`. Each
485
+ * finalized chunk fires a `RecordingChunkFinalizedEvent` to
486
+ * subscribers of `onRecordingChunkFinalized`. Throws
487
+ * AUDIO_CAPTURE_DENIED on permission failure or if no device is
488
+ * available.
489
+ *
490
+ * Pass `deviceId` to override the platform-configured input (rare —
491
+ * only useful for tests or workflows that need a specific device).
492
+ * Omit it to use the platform's selected input from
493
+ * `AudioRoutingConfig.inputDeviceId` — this is the recommended path
494
+ * for plugins post-SDK-2.2.0.
495
+ *
496
+ * @since SDK 2.1.0 (deviceId required) — 2.2.0 made it optional.
497
+ */
498
+ startTrackRecording(deviceId?: string): Promise<void>;
499
+ /**
500
+ * Mark the boundary between two recording chunks. The engine closes the
501
+ * currently-open WAV writer and opens a new one; the closed file fires
502
+ * a `RecordingChunkFinalizedEvent` once flush completes. No-op if no
503
+ * recording session is active.
504
+ *
505
+ * Pass `boundaryHostTimeNs` from `DeckBoundaryEvent.boundaryHostTimeNs`
506
+ * for sample-perfect take alignment (Path 2). The engine then splits
507
+ * the chunk at the EXACT recorder-sample that corresponds to that
508
+ * host-time, eliminating the ~5–50 ms of jitter introduced by the
509
+ * legacy "split wherever the writer is" path. Required for any
510
+ * workflow that overlays multiple takes (vocalist comping, layered
511
+ * dubs); optional for single-take captures.
512
+ *
513
+ * @since SDK 2.1.0 — 2.4.0 added optional boundaryHostTimeNs.
514
+ */
515
+ markRecordingChunkBoundary(boundaryHostTimeNs?: number): Promise<void>;
516
+ /**
517
+ * Stop the active recording session. The final chunk is closed and
518
+ * finalized; its `RecordingChunkFinalizedEvent` fires before this
519
+ * promise resolves. Returns the path of the final chunk (also delivered
520
+ * via the event for symmetry).
521
+ * @since SDK 2.1.0
522
+ */
523
+ stopTrackRecording(): Promise<{
524
+ finalChunkPath: string;
525
+ durationMs: number;
526
+ }>;
527
+ /**
528
+ * Subscribe to chunk-finalized events for this plugin's active recording
529
+ * session. Auto-unsubscribed on `deactivate`. Returns unsubscribe fn.
530
+ * @since SDK 2.1.0
531
+ */
532
+ onRecordingChunkFinalized(listener: (event: RecordingChunkFinalizedEvent) => void): UnsubscribeFn;
533
+ /**
534
+ * Get the platform-configured audio input device, or null when no
535
+ * device is set. Read-only; configured via the assistant's
536
+ * AudioRoutingPanel. Plugins use this to display the current input
537
+ * to the user without exposing their own picker.
538
+ *
539
+ * @since SDK 2.2.0
540
+ */
541
+ getCurrentInputDevice(): Promise<AudioInputDevice | null>;
542
+ /**
543
+ * Get the platform's mic-to-output round-trip latency offset in
544
+ * samples. 0 = uncalibrated. Plugins recording audio apply this via
545
+ * `setAudioOffsetSamples` so takes line up with the source loop.
546
+ *
547
+ * @since SDK 2.2.0
548
+ */
549
+ getRecordingLatencyOffsetSamples(): Promise<number>;
550
+ /**
551
+ * Snapshot of the input level for the most recent audio block.
552
+ * Renderer polls at ~30Hz to drive a level meter / scrolling
553
+ * waveform without an event-channel subscription.
554
+ *
555
+ * @since SDK 2.3.0
556
+ */
557
+ getRecordingInputLevel(): Promise<{
558
+ peakDb: number;
559
+ peakLinear: number;
560
+ clipped: boolean;
561
+ active: boolean;
562
+ }>;
563
+ /**
564
+ * Reset the latched clip indicator. Safe regardless of whether
565
+ * monitoring or recording is active.
566
+ *
567
+ * @since SDK 2.3.0
568
+ */
569
+ clearRecordingInputClipIndicator(): Promise<void>;
437
570
  }
438
571
  /** Stem type identifiers */
439
572
  type StemType = 'vocals' | 'drums' | 'bass' | 'other';
@@ -716,6 +849,20 @@ interface DeckBoundaryEvent {
716
849
  bar: number;
717
850
  beat: number;
718
851
  loopCount: number;
852
+ /**
853
+ * Stream-time sample index at which the loop wrap was detected in the
854
+ * audio thread (engine's AudioBoundaryProbe). Undefined when the
855
+ * audio-thread anchor was unavailable. @since SDK 2.4.0
856
+ */
857
+ boundaryAudioSamplePosition?: number;
858
+ /**
859
+ * Monotonic host-time (nanoseconds) at the audio block in which the
860
+ * loop wrap was detected. Same clock as
861
+ * `juce::AudioIODeviceCallbackContext::hostTimeNs`. Pair with
862
+ * `markRecordingChunkBoundary(boundaryHostTimeNs)` for sample-perfect
863
+ * take alignment. @since SDK 2.4.0
864
+ */
865
+ boundaryHostTimeNs?: number;
719
866
  }
720
867
  interface PluginTransportState {
721
868
  isPlaying: boolean;
@@ -826,7 +973,7 @@ interface PluginSettingsStore {
826
973
  /** Subscribe to settings changes. Returns unsubscribe fn. */
827
974
  onChange(listener: (key: string, value: unknown) => void): UnsubscribeFn;
828
975
  }
829
- type PluginErrorCode = 'NOT_OWNED' | 'TRACK_NOT_FOUND' | 'TRACK_LIMIT_EXCEEDED' | 'NO_ACTIVE_SCENE' | 'ENGINE_ERROR' | 'INVALID_MIDI' | 'FILE_NOT_FOUND' | 'INVALID_FORMAT' | 'PLUGIN_NOT_FOUND' | 'LLM_BUDGET_EXCEEDED' | 'LLM_UNAVAILABLE' | 'NOT_AUTHENTICATED' | 'TIMEOUT' | 'CANCELLED' | 'INCOMPATIBLE' | 'CAPABILITY_DENIED' | 'SECRET_NOT_FOUND' | 'VALIDATION_ERROR';
976
+ type PluginErrorCode = 'NOT_OWNED' | 'TRACK_NOT_FOUND' | 'TRACK_LIMIT_EXCEEDED' | 'NO_ACTIVE_SCENE' | 'ENGINE_ERROR' | 'INVALID_MIDI' | 'FILE_NOT_FOUND' | 'INVALID_FORMAT' | 'PLUGIN_NOT_FOUND' | 'LLM_BUDGET_EXCEEDED' | 'LLM_UNAVAILABLE' | 'NOT_AUTHENTICATED' | 'TIMEOUT' | 'CANCELLED' | 'INCOMPATIBLE' | 'CAPABILITY_DENIED' | 'SECRET_NOT_FOUND' | 'VALIDATION_ERROR' | 'AUDIO_CAPTURE_DENIED';
830
977
  declare class PluginError extends Error {
831
978
  readonly code: PluginErrorCode;
832
979
  readonly details?: Record<string, unknown>;
@@ -859,6 +1006,82 @@ interface PluginCapabilities {
859
1006
  };
860
1007
  /** Plugin needs native file dialog access */
861
1008
  fileDialog?: boolean;
1009
+ /**
1010
+ * Plugin needs microphone / line-in capture. Gates the recording host
1011
+ * methods (getAudioInputDevices, startTrackRecording, etc).
1012
+ * @since SDK 2.1.0
1013
+ */
1014
+ audioCapture?: boolean;
1015
+ }
1016
+ /**
1017
+ * Audio input device exposed by the audio engine. The `deviceId` is the
1018
+ * stable identifier returned by JUCE's AudioDeviceManager and accepted as
1019
+ * the device argument to `startTrackRecording`.
1020
+ * @since SDK 2.1.0
1021
+ */
1022
+ interface AudioInputDevice {
1023
+ /** Stable device identifier — passed back to startTrackRecording. */
1024
+ deviceId: string;
1025
+ /** Human-readable device name (e.g., "MacBook Pro Microphone", "USB Mic"). */
1026
+ label: string;
1027
+ /** True if this is the system default input device. */
1028
+ isDefault: boolean;
1029
+ /** Number of input channels the device supports (1 = mono, 2 = stereo). */
1030
+ channelCount: number;
1031
+ }
1032
+ /**
1033
+ * Engine state snapshot that an audio-recording plugin needs before
1034
+ * starting a session.
1035
+ * @since SDK 2.1.0
1036
+ */
1037
+ interface RecordingTargetInfo {
1038
+ /** Engine device sample rate, e.g. 44100 or 48000. */
1039
+ engineSampleRate: number;
1040
+ /** Active scene id, or null when no scene is selected. */
1041
+ sceneId: string | null;
1042
+ /** True when a transition render lock is held — recorder must refuse. */
1043
+ isRenderLocked: boolean;
1044
+ /** Current project BPM. */
1045
+ bpm: number;
1046
+ /** Active scene length in bars (4/4 assumed), or null when no scene. */
1047
+ bars: number | null;
1048
+ /**
1049
+ * Sample-perfect-recording compatibility (Path 2 gate). When false,
1050
+ * the recorder must refuse to start a session and surface
1051
+ * `recordingCompatibilityReason` to the user — input + output
1052
+ * devices cannot be sample-aligned.
1053
+ * @since SDK 2.4.0
1054
+ */
1055
+ canRecordSamplePerfect?: boolean;
1056
+ recordingCompatibilityReason?: string;
1057
+ }
1058
+ /**
1059
+ * Event payload fired when the engine finalizes a recording chunk WAV
1060
+ * file (either at a boundary mark or at session stop).
1061
+ * @since SDK 2.1.0
1062
+ */
1063
+ interface RecordingChunkFinalizedEvent {
1064
+ /** Absolute path to the finalized WAV file on disk. */
1065
+ filePath: string;
1066
+ /** Zero-based chunk index within the active session. */
1067
+ chunkIndex: number;
1068
+ /** Duration of this chunk in milliseconds. */
1069
+ durationMs: number;
1070
+ /** WAV sample rate. */
1071
+ sampleRate: number;
1072
+ /** WAV channel count. */
1073
+ channels: number;
1074
+ /**
1075
+ * Sample-perfect-recording metadata (Path 2). When the chunk was
1076
+ * closed via a host-time-anchored `markRecordingChunkBoundary` call,
1077
+ * carries recorder-local sample positions plus the host-time at
1078
+ * which the boundary fired. Undefined / -1 means the boundary
1079
+ * lacked a host-time anchor (legacy or stop-driven finalize).
1080
+ * @since SDK 2.4.0
1081
+ */
1082
+ recorderSampleStart?: number;
1083
+ recorderSampleEnd?: number;
1084
+ boundaryHostTimeNs?: number;
862
1085
  }
863
1086
  interface PluginFileDialogOptions {
864
1087
  title?: string;
@@ -946,6 +1169,19 @@ interface PluginAudioTextureResult {
946
1169
  * clip is written so the OffsetScrubber UI can read them later.
947
1170
  */
948
1171
  cuePoints: PluginCuePoints | null;
1172
+ /**
1173
+ * Path to the un-trimmed (raw) Lyria output. Used by the audio-texture
1174
+ * trim editor to draw the full waveform. Persist via
1175
+ * `host.setRawAudioFilePath`. Null when no raw file is available.
1176
+ */
1177
+ rawFilePath?: string | null;
1178
+ /** Same beats as `cuePoints` in raw-file sample coordinates. */
1179
+ rawCuePoints?: PluginCuePoints | null;
1180
+ /**
1181
+ * Auto-detected start of the trim window inside the raw file (sample
1182
+ * offset). Null when detection was skipped.
1183
+ */
1184
+ inputStartSample?: number | null;
949
1185
  }
950
1186
  /**
951
1187
  * Cue-points sidecar surfaced by the audio-processor `trim` command —
@@ -967,6 +1203,15 @@ interface PluginCuePoints {
967
1203
  /** ISO-8601 timestamp of when detection ran. */
968
1204
  detected_at: string;
969
1205
  }
1206
+ /**
1207
+ * A trim window inside a raw (un-trimmed) audio file. `start_sample` is
1208
+ * the offset from the start of the raw file; `duration_samples` is the
1209
+ * length of the trimmed slice. Both are in raw-file sample coordinates.
1210
+ */
1211
+ interface PluginTrimWindow {
1212
+ start_sample: number;
1213
+ duration_samples: number;
1214
+ }
970
1215
  /** Options for composing a full scene arrangement via LLM. */
971
1216
  interface ComposeSceneOptions {
972
1217
  /** The contract prompt / musical direction for the arrangement. */
@@ -1409,7 +1654,7 @@ declare function useSceneState<T>(activeSceneId: string | null, initialValue: T)
1409
1654
  * Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
1410
1655
  * during activation and marks incompatible plugins accordingly.
1411
1656
  */
1412
- declare const PLUGIN_SDK_VERSION = "2.0.0";
1657
+ declare const PLUGIN_SDK_VERSION = "2.3.0";
1413
1658
 
1414
1659
  /**
1415
1660
  * FX Preset Definitions
@@ -1463,4 +1708,4 @@ declare function sliderToDb(slider: number): number;
1463
1708
  */
1464
1709
  declare function dbToSlider(db: number): number;
1465
1710
 
1466
- export { type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type 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, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type 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 PluginTrackRuntimeState, type PluginTransportState, type PluginUIProps, type PostProcessOptions, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type StemType, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
1711
+ export { type AudioInputDevice, type BulkAddPlaceholderTrack, type ComposeProgressEvent, type ComposeProgressListener, type ComposeSceneOptions, type ComposeSceneResult, type CreateTrackOptions, DB_MAX, DB_MIN, DEFAULT_FX_CATEGORY_DETAIL, DEFAULT_FX_DRY_WET, type DeckBoundaryEvent, type DeckBoundaryListener, EMPTY_FX_DETAIL_STATE, EMPTY_FX_STATE, type 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, type GeneratorPlugin, type GeneratorType, type InstrumentDescriptor, InstrumentDrawer, type InstrumentDrawerProps, type LLMGenerationRequest, type LLMGenerationResult, type MidiClipData, type MidiWriteResult, type MixInterpolation, type MusicalContext, PLUGIN_SDK_VERSION, PanSlider, type 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 PluginTrackRuntimeState, type PluginTransportState, type PluginTrimWindow, type PluginUIProps, type PostProcessOptions, type RecordingChunkFinalizedEvent, type RecordingTargetInfo, type SDKTrackRowProps, SLIDER_UNITY, type SavePluginPresetOptions, type SceneChangeListener, type SettingDefinition, type ShufflePresetResult, SorceryProgressBar, type StemType, type TrackFxDetailState, type TrackFxState, TrackRow, type TrackStateChangeListener, type TransportEvent, type TransportEventListener, type UnsubscribeFn, VolumeSlider, calculateTimeBasedTarget, dbToSlider, sliderToDb, useSceneState };
package/dist/index.js CHANGED
@@ -1293,7 +1293,7 @@ function useSceneState(activeSceneId, initialValue) {
1293
1293
  }
1294
1294
 
1295
1295
  // src/constants/sdk-version.ts
1296
- var PLUGIN_SDK_VERSION = "2.0.0";
1296
+ var PLUGIN_SDK_VERSION = "2.3.0";
1297
1297
  // Annotate the CommonJS export names for ESM import in node:
1298
1298
  0 && (module.exports = {
1299
1299
  DB_MAX,