@dawcore/components 0.0.13 → 0.0.15

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 CHANGED
@@ -13,6 +13,7 @@ Framework-agnostic Web Components for multi-track audio editing. Drop `<daw-edit
13
13
  - **File drop** — Drag audio files onto the editor to add tracks
14
14
  - **Recording** — Live mic recording with waveform preview, pause/resume, cancelable clip creation
15
15
  - **Pre-computed peaks** — Instant waveform rendering from `.dat` files before audio decodes
16
+ - **MIDI tracks** — Programmatic MIDI clips render as piano-roll via `<daw-piano-roll>`. Playback via Tone.js adapter (native MIDI synth deferred). See [MIDI Tracks](#midi-tracks).
16
17
  - **Track controls** — Volume, pan, mute, solo per track via `<daw-track-controls>`
17
18
  - **Transport access** — Tempo, metronome, count-in, meter, effects via `@dawcore/transport`
18
19
  - **CSS theming** — Dark mode by default, fully customizable via CSS custom properties
@@ -92,7 +93,7 @@ adapter.transport.setCountIn(true);
92
93
 
93
94
  ### Tone.js (effects, MIDI synths)
94
95
 
95
- Uses Tone.js for audio processing. Single tempo/meter only.
96
+ Uses Tone.js for audio processing. Single tempo/meter only. **Required for MIDI playback** — the native adapter has no MIDI synth yet, so MIDI clips render as piano-roll but are silent.
96
97
 
97
98
  ```bash
98
99
  npm install @waveform-playlist/playout tone
@@ -125,6 +126,50 @@ For multiple clips per track with independent positioning:
125
126
  </daw-editor>
126
127
  ```
127
128
 
129
+ ## MIDI Tracks
130
+
131
+ Programmatic MIDI clips render as piano-roll. Playback requires the Tone.js adapter (the native adapter has no MIDI synth yet). Use the `editor.addTrack({ midi })` sugar for the simplest path:
132
+
133
+ ```javascript
134
+ import { createToneAdapter } from '@waveform-playlist/playout';
135
+
136
+ const editor = document.querySelector('daw-editor');
137
+ editor.adapter = createToneAdapter({ ppqn: 960 });
138
+
139
+ await editor.addTrack({
140
+ name: 'Lead',
141
+ midi: {
142
+ notes: [
143
+ { midi: 60, name: 'C4', time: 0.0, duration: 0.5, velocity: 0.8 },
144
+ { midi: 64, name: 'E4', time: 0.5, duration: 0.5, velocity: 0.7 },
145
+ { midi: 67, name: 'G4', time: 1.0, duration: 0.5, velocity: 0.8 },
146
+ ],
147
+ channel: 0, // optional — 9 = GM percussion
148
+ program: 24, // optional — GM instrument 0-127 (used by SoundFontToneTrack)
149
+ },
150
+ });
151
+ ```
152
+
153
+ This expands to a `<daw-track render-mode="piano-roll">` containing a `<daw-clip>` whose `midiNotes` JS property is set to the notes array. Equivalent declarative form:
154
+
155
+ ```html
156
+ <daw-track render-mode="piano-roll" name="Lead">
157
+ <daw-clip midi-channel="0" midi-program="24"></daw-clip>
158
+ </daw-track>
159
+ <script>
160
+ document.querySelector('daw-clip').midiNotes = [
161
+ { midi: 60, name: 'C4', time: 0.0, duration: 0.5, velocity: 0.8 },
162
+ // ...
163
+ ];
164
+ </script>
165
+ ```
166
+
167
+ A clip is treated as MIDI iff `clip.midiNotes != null`. MIDI clips skip audio fetch + decode + peak generation. Trim handles and split-at-playhead are inert on MIDI clips for now (note slicing is a follow-up). Move drag works.
168
+
169
+ **Theming:** the piano-roll honors `--daw-piano-roll-note-color` (default `#2a7070`), `--daw-piano-roll-selected-note-color` (default `#3d9e9e`), and `--daw-piano-roll-background` (default `#1a1a2e`).
170
+
171
+ See `examples/dawcore-tone/midi.html` for a runnable demo (C major scale, PolySynth playback).
172
+
128
173
  ## Pre-Computed Peaks
129
174
 
130
175
  For instant waveform rendering before audio finishes decoding:
@@ -233,6 +278,9 @@ daw-editor {
233
278
  --daw-clip-header-text: #e0d4c8;
234
279
  --daw-controls-width: 180px;
235
280
  --daw-min-height: 200px;
281
+ --daw-piano-roll-note-color: #2a7070;
282
+ --daw-piano-roll-selected-note-color: #3d9e9e;
283
+ --daw-piano-roll-background: #1a1a2e;
236
284
  }
237
285
  ```
238
286
 
@@ -259,11 +307,15 @@ Methods: `loadFiles(fileList)`, `splitAtPlayhead()`.
259
307
 
260
308
  ### `<daw-track>`
261
309
 
262
- Declarative track data. Attributes: `src`, `name`, `volume`, `pan`, `muted`, `soloed`, `mono`.
310
+ Declarative track data. Attributes: `src`, `name`, `volume`, `pan`, `muted`, `soloed`, `mono`, `render-mode` (`'waveform' | 'piano-roll'`, default `'waveform'`).
263
311
 
264
312
  ### `<daw-clip>`
265
313
 
266
- Declarative clip data. Attributes: `src`, `peaks-src`, `start`, `duration`, `offset`, `gain`.
314
+ Declarative clip data. Attributes: `src`, `peaks-src`, `start`, `duration`, `offset`, `gain`, `midi-channel`, `midi-program`. JS-only property: `midiNotes: MidiNoteData[] | null` (note arrays are too large for attributes).
315
+
316
+ ### `<daw-piano-roll>`
317
+
318
+ Visual element for MIDI note rendering. Mounted automatically when the parent track has `render-mode="piano-roll"` — you don't usually instantiate it directly. See [MIDI Tracks](#midi-tracks).
267
319
 
268
320
  ### `<daw-transport for="editor-id">`
269
321
 
package/dist/index.d.mts CHANGED
@@ -18,6 +18,9 @@ declare class DawClipElement extends LitElement {
18
18
  fadeType: string;
19
19
  readonly clipId: `${string}-${string}-${string}-${string}-${string}`;
20
20
  createRenderRoot(): this;
21
+ connectedCallback(): void;
22
+ private _hasRendered;
23
+ updated(changed: PropertyValues): void;
21
24
  }
22
25
  declare global {
23
26
  interface HTMLElementTagNameMap {
@@ -201,7 +204,10 @@ interface TrackDescriptor {
201
204
  soloed: boolean;
202
205
  clips: ClipDescriptor[];
203
206
  }
204
- interface ClipDescriptor {
207
+ /**
208
+ * Common fields shared by all clip descriptors regardless of source.
209
+ */
210
+ interface BaseClipDescriptor {
205
211
  src: string;
206
212
  peaksSrc: string;
207
213
  start: number;
@@ -213,6 +219,62 @@ interface ClipDescriptor {
213
219
  fadeOut: number;
214
220
  fadeType: FadeType;
215
221
  }
222
+ /**
223
+ * A clip descriptor sourced from a `<daw-clip>` DOM element. `clipId` is
224
+ * always set — `<daw-clip>.clipId` is a `crypto.randomUUID()` generated at
225
+ * construction. Engine `clip.id` is aligned with this id in `_loadTrack`
226
+ * and `_loadAndAppendClip` so DOM and engine reference the same clip.
227
+ */
228
+ interface DomClipDescriptor extends BaseClipDescriptor {
229
+ kind: 'dom';
230
+ clipId: string;
231
+ }
232
+ /**
233
+ * A clip descriptor synthesized from a non-DOM source — file drops, the
234
+ * `<daw-track src>` shorthand fallback, or recording-clip insertion. No
235
+ * `clipId` because there's no DOM element to align with; the engine
236
+ * generates its own id at clip-creation time.
237
+ */
238
+ interface DropClipDescriptor extends BaseClipDescriptor {
239
+ kind: 'drop';
240
+ }
241
+ type ClipDescriptor = DomClipDescriptor | DropClipDescriptor;
242
+ /**
243
+ * Type predicate for the `'dom'` discriminator. Use to narrow a
244
+ * `ClipDescriptor` to `DomClipDescriptor` (which has `clipId`) without
245
+ * inline `c.kind === 'dom'` repetition.
246
+ */
247
+ declare function isDomClip(desc: ClipDescriptor): desc is DomClipDescriptor;
248
+ /**
249
+ * Public input shape for `editor.addTrack(config)`. All fields optional —
250
+ * defaults match the declarative `<daw-track>` defaults.
251
+ */
252
+ interface TrackConfig {
253
+ name?: string;
254
+ volume?: number;
255
+ pan?: number;
256
+ muted?: boolean;
257
+ soloed?: boolean;
258
+ clips?: ClipConfig[];
259
+ }
260
+ /**
261
+ * Public input shape for clips passed via `TrackConfig.clips` or
262
+ * `editor.addClip(trackId, config)`. `src` is required — every clip needs
263
+ * an audio source to load. Other fields default to the matching
264
+ * `<daw-clip>` attribute defaults.
265
+ */
266
+ interface ClipConfig {
267
+ src: string;
268
+ peaksSrc?: string;
269
+ start?: number;
270
+ duration?: number;
271
+ offset?: number;
272
+ gain?: number;
273
+ name?: string;
274
+ fadeIn?: number;
275
+ fadeOut?: number;
276
+ fadeType?: FadeType;
277
+ }
216
278
 
217
279
  /**
218
280
  * Peak generation pipeline: AudioBuffer → web worker → WaveformData → PeakData.
@@ -584,6 +646,23 @@ interface DawRecordingErrorDetail {
584
646
  trackId: string;
585
647
  error: unknown;
586
648
  }
649
+ interface DawClipConnectedDetail {
650
+ clipId: string;
651
+ element: DawClipElement;
652
+ }
653
+ interface DawClipIdDetail {
654
+ trackId: string;
655
+ clipId: string;
656
+ }
657
+ interface DawClipUpdateDetail {
658
+ trackId: string;
659
+ clipId: string;
660
+ }
661
+ interface DawClipErrorDetail {
662
+ trackId: string;
663
+ clipId: string;
664
+ error: unknown;
665
+ }
587
666
  interface DawClipMoveDetail {
588
667
  readonly trackId: string;
589
668
  readonly clipId: string;
@@ -623,6 +702,10 @@ interface DawEventMap {
623
702
  'daw-recording-start': CustomEvent<DawRecordingStartDetail>;
624
703
  'daw-recording-complete': CustomEvent<DawRecordingCompleteDetail>;
625
704
  'daw-recording-error': CustomEvent<DawRecordingErrorDetail>;
705
+ 'daw-clip-connected': CustomEvent<DawClipConnectedDetail>;
706
+ 'daw-clip-update': CustomEvent<DawClipUpdateDetail>;
707
+ 'daw-clip-ready': CustomEvent<DawClipIdDetail>;
708
+ 'daw-clip-error': CustomEvent<DawClipErrorDetail>;
626
709
  'daw-clip-move': CustomEvent<DawClipMoveDetail>;
627
710
  'daw-clip-trim': CustomEvent<DawClipTrimDetail>;
628
711
  'daw-clip-split': CustomEvent<DawClipSplitDetail>;
@@ -649,6 +732,13 @@ declare class DawEditorElement extends LitElement {
649
732
  clipHeaders: boolean;
650
733
  clipHeaderHeight: number;
651
734
  interactiveClips: boolean;
735
+ /**
736
+ * When true, the timeline fills the visible viewport even if total clip
737
+ * duration is less. Lets the ruler render before any audio is loaded —
738
+ * useful for empty editors and recording UIs. In beats mode the 32-bar
739
+ * floor already provides this; this attribute controls the temporal mode.
740
+ */
741
+ indefinitePlayback: boolean;
652
742
  scaleMode: 'temporal' | 'beats';
653
743
  get ticksPerPixel(): number;
654
744
  set ticksPerPixel(value: number);
@@ -710,10 +800,7 @@ declare class DawEditorElement extends LitElement {
710
800
  get engine(): PlaylistEngine | null;
711
801
  get renderSamplesPerPixel(): number;
712
802
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
713
- reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
714
- data: Peaks[];
715
- length: number;
716
- } | null;
803
+ reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): PeakData | null;
717
804
  private _pointer;
718
805
  private _viewport;
719
806
  static styles: lit.CSSResult[];
@@ -748,6 +835,42 @@ declare class DawEditorElement extends LitElement {
748
835
  private static _CONTROL_PROPS;
749
836
  private _onTrackControl;
750
837
  private _onTrackRemoveRequest;
838
+ private _onClipConnected;
839
+ private _onClipUpdate;
840
+ private _onClipRemovedFromDom;
841
+ private _loadAndAppendClip;
842
+ /**
843
+ * Resolve pre-computed peaks for a clip: fetch the .dat/.json, validate the
844
+ * sample rate matches the AudioContext, return the WaveformData or null.
845
+ * Warns on fetch failure and on sample-rate mismatch — never silent.
846
+ *
847
+ * Shared between `_loadTrack` (peaks-first preview path) and
848
+ * `_loadAndAppendClip` (incremental late-append).
849
+ */
850
+ private _resolvePeaks;
851
+ /**
852
+ * Construct an AudioClip from a decoded buffer (and optional WaveformData),
853
+ * align its id with the source `<daw-clip>.clipId` when present, populate
854
+ * `_clipBuffers` / `_clipOffsets`, generate peaks via the worker pipeline,
855
+ * and populate `_peaksData`. Returns the finished AudioClip.
856
+ *
857
+ * Shared between `_loadTrack`'s standard path and `_loadAndAppendClip`.
858
+ * Not used by `_loadTrack`'s peaks-first preview path because that path
859
+ * uses sync `extractPeaks` and inserts a preview track BEFORE audio decode.
860
+ */
861
+ private _finalizeAudioClip;
862
+ /** Remove a single clip from all per-clip caches. Used by error rollbacks. */
863
+ private _purgeClipCaches;
864
+ /**
865
+ * Recompute duration and forward an updated track to the engine. Single
866
+ * source of truth for the incremental-vs-full-rebuild policy used by every
867
+ * clip-level mutation (addClip, updateClip, removeClip, _applyClipUpdate).
868
+ * Use the engine's incremental updateTrack when available; otherwise fall
869
+ * back to full setTracks (legacy adapters).
870
+ */
871
+ private _commitTrackChange;
872
+ private _applyClipUpdate;
873
+ private _removeClipFromTrack;
751
874
  private _readTrackDescriptor;
752
875
  private _loadTrack;
753
876
  _fetchAndDecode(src: string): Promise<AudioBuffer>;
@@ -760,6 +883,67 @@ declare class DawEditorElement extends LitElement {
760
883
  private _onDragLeave;
761
884
  private _onDrop;
762
885
  loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
886
+ /**
887
+ * Build the engine if it hasn't been built yet. Lets consumers obtain a
888
+ * non-null `editor.engine` before any track has been loaded — useful for
889
+ * wiring analyzers, effects, or master taps before content arrives.
890
+ */
891
+ ready(): Promise<PlaylistEngine>;
892
+ /**
893
+ * Wait for either `readyEvent` or `errorEvent` to fire on this editor for
894
+ * the entity matching `matchesId`. Listeners are wired synchronously, then
895
+ * `setup` is called (typical: appendChild). Resolves with `resolveValue`
896
+ * on ready; rejects with a normalized Error on error. Used by addTrack and
897
+ * addClip to share their Promise-with-listener-cleanup machinery.
898
+ */
899
+ private _awaitId;
900
+ /**
901
+ * Append a `<daw-track>` element built from `config` and resolve once the
902
+ * track finishes loading (or reject on `daw-track-error`). Goes through
903
+ * the same `_loadTrack` pipeline as declarative tracks, so descriptors,
904
+ * peaks, and clip buffers are populated correctly.
905
+ */
906
+ addTrack(config?: TrackConfig): Promise<DawTrackElement>;
907
+ /**
908
+ * Remove a track by id. Equivalent to `trackElement.remove()` —
909
+ * the editor's MutationObserver handles engine and cache cleanup.
910
+ * No-op if no matching track exists.
911
+ */
912
+ removeTrack(trackId: string): void;
913
+ /**
914
+ * Update reflected attributes on a track. For DOM-element tracks the changes
915
+ * are written to the `<daw-track>` element (which fires `daw-track-update`);
916
+ * for tracks without a DOM element (file drops) the descriptor and engine
917
+ * state are updated in place.
918
+ */
919
+ updateTrack(trackId: string, partial: Partial<TrackConfig>): void;
920
+ /**
921
+ * Append a clip to an existing track. Builds a `<daw-clip>` from `config`
922
+ * and appends it to the track's DOM element when one exists; resolves with
923
+ * the new clip's id once the audio decode + peak generation finish.
924
+ */
925
+ addClip(trackId: string, config: ClipConfig): Promise<string>;
926
+ /**
927
+ * Remove a clip by id. Removes the matching `<daw-clip>` DOM element when
928
+ * present (MutationObserver handles cleanup); otherwise updates engine
929
+ * state directly. No-op if no matching clip exists.
930
+ */
931
+ removeClip(trackId: string, clipId: string): void;
932
+ /**
933
+ * Update a clip's position (start/duration/offset) or properties (gain/name).
934
+ * For DOM-element clips, writes properties on the `<daw-clip>` element which
935
+ * fires `daw-clip-update`; otherwise applies directly via `_applyClipUpdate`.
936
+ *
937
+ * Re-decoding (changing `src`) is not supported via this method — remove and
938
+ * re-add the clip instead.
939
+ *
940
+ * Note: `fadeIn` / `fadeOut` / `fadeType` on the partial are written to the
941
+ * `<daw-clip>` element (so they round-trip in the descriptor), but engine-side
942
+ * fade application from `<daw-clip>` properties is not yet implemented — see
943
+ * the broader fade-engine integration tracked separately.
944
+ */
945
+ updateClip(trackId: string, clipId: string, partial: Partial<ClipConfig>): void;
946
+ private _buildClipElement;
763
947
  play(startTime?: number): Promise<void>;
764
948
  pause(): void;
765
949
  stop(): void;
@@ -965,4 +1149,4 @@ interface SplitHost {
965
1149
  */
966
1150
  declare function splitAtPlayhead(host: SplitHost): boolean;
967
1151
 
968
- export { AudioResumeController, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, DawClipElement, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawGridElement, DawKeyboardShortcutsElement, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type KeyBinding, type LoadFilesResult, type PlaybackShortcutMap, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type SplittingShortcutMap, type TrackDescriptor, type UndoShortcutMap, type WaveformSegment, splitAtPlayhead };
1152
+ export { AudioResumeController, type ClipConfig, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, type DawClipConnectedDetail, DawClipElement, type DawClipErrorDetail, type DawClipIdDetail, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, type DawClipUpdateDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawGridElement, DawKeyboardShortcutsElement, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type DomClipDescriptor, type DropClipDescriptor, type KeyBinding, type LoadFilesResult, type PlaybackShortcutMap, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type SplittingShortcutMap, type TrackConfig, type TrackDescriptor, type UndoShortcutMap, type WaveformSegment, isDomClip, splitAtPlayhead };
package/dist/index.d.ts CHANGED
@@ -18,6 +18,9 @@ declare class DawClipElement extends LitElement {
18
18
  fadeType: string;
19
19
  readonly clipId: `${string}-${string}-${string}-${string}-${string}`;
20
20
  createRenderRoot(): this;
21
+ connectedCallback(): void;
22
+ private _hasRendered;
23
+ updated(changed: PropertyValues): void;
21
24
  }
22
25
  declare global {
23
26
  interface HTMLElementTagNameMap {
@@ -201,7 +204,10 @@ interface TrackDescriptor {
201
204
  soloed: boolean;
202
205
  clips: ClipDescriptor[];
203
206
  }
204
- interface ClipDescriptor {
207
+ /**
208
+ * Common fields shared by all clip descriptors regardless of source.
209
+ */
210
+ interface BaseClipDescriptor {
205
211
  src: string;
206
212
  peaksSrc: string;
207
213
  start: number;
@@ -213,6 +219,62 @@ interface ClipDescriptor {
213
219
  fadeOut: number;
214
220
  fadeType: FadeType;
215
221
  }
222
+ /**
223
+ * A clip descriptor sourced from a `<daw-clip>` DOM element. `clipId` is
224
+ * always set — `<daw-clip>.clipId` is a `crypto.randomUUID()` generated at
225
+ * construction. Engine `clip.id` is aligned with this id in `_loadTrack`
226
+ * and `_loadAndAppendClip` so DOM and engine reference the same clip.
227
+ */
228
+ interface DomClipDescriptor extends BaseClipDescriptor {
229
+ kind: 'dom';
230
+ clipId: string;
231
+ }
232
+ /**
233
+ * A clip descriptor synthesized from a non-DOM source — file drops, the
234
+ * `<daw-track src>` shorthand fallback, or recording-clip insertion. No
235
+ * `clipId` because there's no DOM element to align with; the engine
236
+ * generates its own id at clip-creation time.
237
+ */
238
+ interface DropClipDescriptor extends BaseClipDescriptor {
239
+ kind: 'drop';
240
+ }
241
+ type ClipDescriptor = DomClipDescriptor | DropClipDescriptor;
242
+ /**
243
+ * Type predicate for the `'dom'` discriminator. Use to narrow a
244
+ * `ClipDescriptor` to `DomClipDescriptor` (which has `clipId`) without
245
+ * inline `c.kind === 'dom'` repetition.
246
+ */
247
+ declare function isDomClip(desc: ClipDescriptor): desc is DomClipDescriptor;
248
+ /**
249
+ * Public input shape for `editor.addTrack(config)`. All fields optional —
250
+ * defaults match the declarative `<daw-track>` defaults.
251
+ */
252
+ interface TrackConfig {
253
+ name?: string;
254
+ volume?: number;
255
+ pan?: number;
256
+ muted?: boolean;
257
+ soloed?: boolean;
258
+ clips?: ClipConfig[];
259
+ }
260
+ /**
261
+ * Public input shape for clips passed via `TrackConfig.clips` or
262
+ * `editor.addClip(trackId, config)`. `src` is required — every clip needs
263
+ * an audio source to load. Other fields default to the matching
264
+ * `<daw-clip>` attribute defaults.
265
+ */
266
+ interface ClipConfig {
267
+ src: string;
268
+ peaksSrc?: string;
269
+ start?: number;
270
+ duration?: number;
271
+ offset?: number;
272
+ gain?: number;
273
+ name?: string;
274
+ fadeIn?: number;
275
+ fadeOut?: number;
276
+ fadeType?: FadeType;
277
+ }
216
278
 
217
279
  /**
218
280
  * Peak generation pipeline: AudioBuffer → web worker → WaveformData → PeakData.
@@ -584,6 +646,23 @@ interface DawRecordingErrorDetail {
584
646
  trackId: string;
585
647
  error: unknown;
586
648
  }
649
+ interface DawClipConnectedDetail {
650
+ clipId: string;
651
+ element: DawClipElement;
652
+ }
653
+ interface DawClipIdDetail {
654
+ trackId: string;
655
+ clipId: string;
656
+ }
657
+ interface DawClipUpdateDetail {
658
+ trackId: string;
659
+ clipId: string;
660
+ }
661
+ interface DawClipErrorDetail {
662
+ trackId: string;
663
+ clipId: string;
664
+ error: unknown;
665
+ }
587
666
  interface DawClipMoveDetail {
588
667
  readonly trackId: string;
589
668
  readonly clipId: string;
@@ -623,6 +702,10 @@ interface DawEventMap {
623
702
  'daw-recording-start': CustomEvent<DawRecordingStartDetail>;
624
703
  'daw-recording-complete': CustomEvent<DawRecordingCompleteDetail>;
625
704
  'daw-recording-error': CustomEvent<DawRecordingErrorDetail>;
705
+ 'daw-clip-connected': CustomEvent<DawClipConnectedDetail>;
706
+ 'daw-clip-update': CustomEvent<DawClipUpdateDetail>;
707
+ 'daw-clip-ready': CustomEvent<DawClipIdDetail>;
708
+ 'daw-clip-error': CustomEvent<DawClipErrorDetail>;
626
709
  'daw-clip-move': CustomEvent<DawClipMoveDetail>;
627
710
  'daw-clip-trim': CustomEvent<DawClipTrimDetail>;
628
711
  'daw-clip-split': CustomEvent<DawClipSplitDetail>;
@@ -649,6 +732,13 @@ declare class DawEditorElement extends LitElement {
649
732
  clipHeaders: boolean;
650
733
  clipHeaderHeight: number;
651
734
  interactiveClips: boolean;
735
+ /**
736
+ * When true, the timeline fills the visible viewport even if total clip
737
+ * duration is less. Lets the ruler render before any audio is loaded —
738
+ * useful for empty editors and recording UIs. In beats mode the 32-bar
739
+ * floor already provides this; this attribute controls the temporal mode.
740
+ */
741
+ indefinitePlayback: boolean;
652
742
  scaleMode: 'temporal' | 'beats';
653
743
  get ticksPerPixel(): number;
654
744
  set ticksPerPixel(value: number);
@@ -710,10 +800,7 @@ declare class DawEditorElement extends LitElement {
710
800
  get engine(): PlaylistEngine | null;
711
801
  get renderSamplesPerPixel(): number;
712
802
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
713
- reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
714
- data: Peaks[];
715
- length: number;
716
- } | null;
803
+ reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): PeakData | null;
717
804
  private _pointer;
718
805
  private _viewport;
719
806
  static styles: lit.CSSResult[];
@@ -748,6 +835,42 @@ declare class DawEditorElement extends LitElement {
748
835
  private static _CONTROL_PROPS;
749
836
  private _onTrackControl;
750
837
  private _onTrackRemoveRequest;
838
+ private _onClipConnected;
839
+ private _onClipUpdate;
840
+ private _onClipRemovedFromDom;
841
+ private _loadAndAppendClip;
842
+ /**
843
+ * Resolve pre-computed peaks for a clip: fetch the .dat/.json, validate the
844
+ * sample rate matches the AudioContext, return the WaveformData or null.
845
+ * Warns on fetch failure and on sample-rate mismatch — never silent.
846
+ *
847
+ * Shared between `_loadTrack` (peaks-first preview path) and
848
+ * `_loadAndAppendClip` (incremental late-append).
849
+ */
850
+ private _resolvePeaks;
851
+ /**
852
+ * Construct an AudioClip from a decoded buffer (and optional WaveformData),
853
+ * align its id with the source `<daw-clip>.clipId` when present, populate
854
+ * `_clipBuffers` / `_clipOffsets`, generate peaks via the worker pipeline,
855
+ * and populate `_peaksData`. Returns the finished AudioClip.
856
+ *
857
+ * Shared between `_loadTrack`'s standard path and `_loadAndAppendClip`.
858
+ * Not used by `_loadTrack`'s peaks-first preview path because that path
859
+ * uses sync `extractPeaks` and inserts a preview track BEFORE audio decode.
860
+ */
861
+ private _finalizeAudioClip;
862
+ /** Remove a single clip from all per-clip caches. Used by error rollbacks. */
863
+ private _purgeClipCaches;
864
+ /**
865
+ * Recompute duration and forward an updated track to the engine. Single
866
+ * source of truth for the incremental-vs-full-rebuild policy used by every
867
+ * clip-level mutation (addClip, updateClip, removeClip, _applyClipUpdate).
868
+ * Use the engine's incremental updateTrack when available; otherwise fall
869
+ * back to full setTracks (legacy adapters).
870
+ */
871
+ private _commitTrackChange;
872
+ private _applyClipUpdate;
873
+ private _removeClipFromTrack;
751
874
  private _readTrackDescriptor;
752
875
  private _loadTrack;
753
876
  _fetchAndDecode(src: string): Promise<AudioBuffer>;
@@ -760,6 +883,67 @@ declare class DawEditorElement extends LitElement {
760
883
  private _onDragLeave;
761
884
  private _onDrop;
762
885
  loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
886
+ /**
887
+ * Build the engine if it hasn't been built yet. Lets consumers obtain a
888
+ * non-null `editor.engine` before any track has been loaded — useful for
889
+ * wiring analyzers, effects, or master taps before content arrives.
890
+ */
891
+ ready(): Promise<PlaylistEngine>;
892
+ /**
893
+ * Wait for either `readyEvent` or `errorEvent` to fire on this editor for
894
+ * the entity matching `matchesId`. Listeners are wired synchronously, then
895
+ * `setup` is called (typical: appendChild). Resolves with `resolveValue`
896
+ * on ready; rejects with a normalized Error on error. Used by addTrack and
897
+ * addClip to share their Promise-with-listener-cleanup machinery.
898
+ */
899
+ private _awaitId;
900
+ /**
901
+ * Append a `<daw-track>` element built from `config` and resolve once the
902
+ * track finishes loading (or reject on `daw-track-error`). Goes through
903
+ * the same `_loadTrack` pipeline as declarative tracks, so descriptors,
904
+ * peaks, and clip buffers are populated correctly.
905
+ */
906
+ addTrack(config?: TrackConfig): Promise<DawTrackElement>;
907
+ /**
908
+ * Remove a track by id. Equivalent to `trackElement.remove()` —
909
+ * the editor's MutationObserver handles engine and cache cleanup.
910
+ * No-op if no matching track exists.
911
+ */
912
+ removeTrack(trackId: string): void;
913
+ /**
914
+ * Update reflected attributes on a track. For DOM-element tracks the changes
915
+ * are written to the `<daw-track>` element (which fires `daw-track-update`);
916
+ * for tracks without a DOM element (file drops) the descriptor and engine
917
+ * state are updated in place.
918
+ */
919
+ updateTrack(trackId: string, partial: Partial<TrackConfig>): void;
920
+ /**
921
+ * Append a clip to an existing track. Builds a `<daw-clip>` from `config`
922
+ * and appends it to the track's DOM element when one exists; resolves with
923
+ * the new clip's id once the audio decode + peak generation finish.
924
+ */
925
+ addClip(trackId: string, config: ClipConfig): Promise<string>;
926
+ /**
927
+ * Remove a clip by id. Removes the matching `<daw-clip>` DOM element when
928
+ * present (MutationObserver handles cleanup); otherwise updates engine
929
+ * state directly. No-op if no matching clip exists.
930
+ */
931
+ removeClip(trackId: string, clipId: string): void;
932
+ /**
933
+ * Update a clip's position (start/duration/offset) or properties (gain/name).
934
+ * For DOM-element clips, writes properties on the `<daw-clip>` element which
935
+ * fires `daw-clip-update`; otherwise applies directly via `_applyClipUpdate`.
936
+ *
937
+ * Re-decoding (changing `src`) is not supported via this method — remove and
938
+ * re-add the clip instead.
939
+ *
940
+ * Note: `fadeIn` / `fadeOut` / `fadeType` on the partial are written to the
941
+ * `<daw-clip>` element (so they round-trip in the descriptor), but engine-side
942
+ * fade application from `<daw-clip>` properties is not yet implemented — see
943
+ * the broader fade-engine integration tracked separately.
944
+ */
945
+ updateClip(trackId: string, clipId: string, partial: Partial<ClipConfig>): void;
946
+ private _buildClipElement;
763
947
  play(startTime?: number): Promise<void>;
764
948
  pause(): void;
765
949
  stop(): void;
@@ -965,4 +1149,4 @@ interface SplitHost {
965
1149
  */
966
1150
  declare function splitAtPlayhead(host: SplitHost): boolean;
967
1151
 
968
- export { AudioResumeController, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, DawClipElement, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawGridElement, DawKeyboardShortcutsElement, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type KeyBinding, type LoadFilesResult, type PlaybackShortcutMap, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type SplittingShortcutMap, type TrackDescriptor, type UndoShortcutMap, type WaveformSegment, splitAtPlayhead };
1152
+ export { AudioResumeController, type ClipConfig, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, type DawClipConnectedDetail, DawClipElement, type DawClipErrorDetail, type DawClipIdDetail, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, type DawClipUpdateDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawGridElement, DawKeyboardShortcutsElement, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type DomClipDescriptor, type DropClipDescriptor, type KeyBinding, type LoadFilesResult, type PlaybackShortcutMap, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type SplittingShortcutMap, type TrackConfig, type TrackDescriptor, type UndoShortcutMap, type WaveformSegment, isDomClip, splitAtPlayhead };