@dawcore/components 0.0.1 → 0.0.2

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
@@ -130,6 +130,12 @@ declare class DawTransportButton extends LitElement {
130
130
  }
131
131
 
132
132
  declare class DawPlayButtonElement extends DawTransportButton {
133
+ private _isRecording;
134
+ private _targetRef;
135
+ private _onRecStart;
136
+ private _onRecEnd;
137
+ connectedCallback(): void;
138
+ disconnectedCallback(): void;
133
139
  render(): lit.TemplateResult<1>;
134
140
  private _onClick;
135
141
  }
@@ -140,6 +146,14 @@ declare global {
140
146
  }
141
147
 
142
148
  declare class DawPauseButtonElement extends DawTransportButton {
149
+ private _isPaused;
150
+ private _isRecording;
151
+ private _targetRef;
152
+ private _onRecStart;
153
+ private _onRecEnd;
154
+ static styles: lit.CSSResultGroup[];
155
+ connectedCallback(): void;
156
+ disconnectedCallback(): void;
143
157
  render(): lit.TemplateResult<1>;
144
158
  private _onClick;
145
159
  }
@@ -196,19 +210,25 @@ declare class PeakPipeline {
196
210
  private _worker;
197
211
  private _cache;
198
212
  private _inflight;
213
+ private _baseScale;
214
+ private _bits;
215
+ constructor(baseScale?: number, bits?: 8 | 16);
199
216
  /**
200
217
  * Generate PeakData for a clip from its AudioBuffer.
201
218
  * Uses cached WaveformData when available; otherwise generates via worker.
202
- * The worker generates at `scale` (= samplesPerPixel) for exact rendering.
219
+ * Worker generates at baseScale (default 128); extractPeaks resamples to the requested zoom.
203
220
  */
204
- generatePeaks(audioBuffer: AudioBuffer, samplesPerPixel: number, isMono: boolean): Promise<PeakData>;
221
+ generatePeaks(audioBuffer: AudioBuffer, samplesPerPixel: number, isMono: boolean, offsetSamples?: number, durationSamples?: number): Promise<PeakData>;
205
222
  /**
206
223
  * Re-extract peaks for all clips at a new zoom level using cached WaveformData.
207
224
  * Only works for zoom levels coarser than (or equal to) the cached base scale.
208
225
  * Returns a new Map of clipId → PeakData. Clips without cached data or where
209
226
  * the target scale is finer than the cached base are skipped.
210
227
  */
211
- reextractPeaks(clipBuffers: ReadonlyMap<string, AudioBuffer>, samplesPerPixel: number, isMono: boolean): Map<string, PeakData>;
228
+ reextractPeaks(clipBuffers: ReadonlyMap<string, AudioBuffer>, samplesPerPixel: number, isMono: boolean, clipOffsets?: ReadonlyMap<string, {
229
+ offsetSamples: number;
230
+ durationSamples: number;
231
+ }>): Map<string, PeakData>;
212
232
  terminate(): void;
213
233
  private _getWaveformData;
214
234
  }
@@ -240,6 +260,8 @@ interface RecordingOptions {
240
260
  trackId?: string;
241
261
  bits?: 8 | 16;
242
262
  startSample?: number;
263
+ /** Start playback during recording so user hears existing tracks. */
264
+ overdub?: boolean;
243
265
  }
244
266
  interface RecordingSession {
245
267
  readonly trackId: string;
@@ -259,12 +281,16 @@ interface RecordingSession {
259
281
  readonly channelCount: number;
260
282
  readonly bits: Bits;
261
283
  isFirstMessage: boolean;
284
+ /** Latency samples to skip in live preview (outputLatency + lookAhead). */
285
+ readonly latencySamples: number;
286
+ readonly wasOverdub: boolean;
262
287
  /** Stored so it can be removed on stop/cleanup — not just when stream ends. */
263
288
  readonly _onTrackEnded: (() => void) | null;
264
289
  readonly _audioTrack: MediaStreamTrack | null;
265
290
  }
266
291
  /** Readonly view of a recording session for external consumers. */
267
- type ReadonlyRecordingSession = Readonly<Omit<RecordingSession, 'chunks' | 'peaks' | '_onTrackEnded' | '_audioTrack'>> & {
292
+ type ReadonlyRecordingSession = Readonly<Omit<RecordingSession, 'chunks' | 'peaks' | '_onTrackEnded' | '_audioTrack' | 'latencySamples'>> & {
293
+ readonly latencySamples: number;
268
294
  readonly chunks: ReadonlyArray<ReadonlyArray<Float32Array>>;
269
295
  readonly peaks: ReadonlyArray<Int8Array | Int16Array>;
270
296
  };
@@ -276,7 +302,9 @@ interface RecordingHost extends ReactiveControllerHost {
276
302
  readonly _currentTime: number;
277
303
  readonly shadowRoot: ShadowRoot | null;
278
304
  resolveAudioContextSampleRate(rate: number): void;
279
- _addRecordedClip?(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number): void;
305
+ _addRecordedClip?(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
306
+ play?(startTime?: number): Promise<void>;
307
+ stop?(): void;
280
308
  dispatchEvent(event: Event): boolean;
281
309
  }
282
310
  declare class RecordingController implements ReactiveController {
@@ -289,6 +317,8 @@ declare class RecordingController implements ReactiveController {
289
317
  get isRecording(): boolean;
290
318
  getSession(trackId: string): ReadonlyRecordingSession | undefined;
291
319
  startRecording(stream: MediaStream, options?: RecordingOptions): Promise<void>;
320
+ pauseRecording(trackId?: string): void;
321
+ resumeRecording(trackId?: string): void;
292
322
  stopRecording(trackId?: string): void;
293
323
  private _onWorkletMessage;
294
324
  private _createClipFromRecording;
@@ -296,6 +326,85 @@ declare class RecordingController implements ReactiveController {
296
326
  private _cleanupSession;
297
327
  }
298
328
 
329
+ /** Snapshot of a clip's bounds for trim constraint computation. */
330
+ interface ClipBounds {
331
+ readonly offsetSamples: number;
332
+ readonly durationSamples: number;
333
+ readonly startSample: number;
334
+ readonly sourceDurationSamples: number;
335
+ }
336
+ /** Narrow engine contract for clip move/trim interactions. */
337
+ interface ClipEngineContract {
338
+ moveClip(trackId: string, clipId: string, deltaSamples: number, skipAdapter?: boolean): void;
339
+ trimClip(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number, skipAdapter?: boolean): void;
340
+ updateTrack(trackId: string): void;
341
+ /** Get a clip's full bounds for trim constraint computation. */
342
+ getClipBounds(trackId: string, clipId: string): ClipBounds | null;
343
+ /** Constrain a trim delta using the engine's collision/bounds logic. */
344
+ constrainTrimDelta(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number): number;
345
+ }
346
+ /** Peak data returned by reextractClipPeaks for imperative waveform updates. */
347
+ interface ClipPeakSlice {
348
+ data: ArrayLike<number>[];
349
+ length: number;
350
+ }
351
+ /** Host interface required by ClipPointerHandler. */
352
+ interface ClipPointerHost {
353
+ readonly samplesPerPixel: number;
354
+ readonly effectiveSampleRate: number;
355
+ readonly interactiveClips: boolean;
356
+ readonly engine: ClipEngineContract | null;
357
+ readonly shadowRoot: ShadowRoot | null;
358
+ dispatchEvent(event: Event): boolean;
359
+ /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
360
+ reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): ClipPeakSlice | null;
361
+ }
362
+ /**
363
+ * Handles pointer interactions for clip move and trim drag operations.
364
+ * Converts pixel deltas to sample deltas and delegates to the engine.
365
+ *
366
+ * Move: sends incremental deltas per-frame with skipAdapter=true (engine shifts
367
+ * startSample additively without touching audio adapter). Adapter synced once
368
+ * via updateTrack() at drag end.
369
+ * Trim: updates clip container CSS imperatively during drag for visual feedback,
370
+ * then applies cumulative delta to engine once at drag end.
371
+ */
372
+ declare class ClipPointerHandler {
373
+ private _host;
374
+ private _mode;
375
+ private _clipId;
376
+ private _trackId;
377
+ private _startPx;
378
+ private _isDragging;
379
+ private _lastDeltaPx;
380
+ private _cumulativeDeltaSamples;
381
+ private _clipContainer;
382
+ private _boundaryEl;
383
+ private _originalLeft;
384
+ private _originalWidth;
385
+ private _originalOffsetSamples;
386
+ private _originalDurationSamples;
387
+ constructor(host: ClipPointerHost);
388
+ /** Returns true if a drag interaction is currently in progress. */
389
+ get isActive(): boolean;
390
+ /**
391
+ * Attempts to handle a pointerdown event on the given target element.
392
+ * Returns true if the target is a recognized clip interaction element.
393
+ */
394
+ tryHandle(target: Element, e: PointerEvent): boolean;
395
+ private _beginDrag;
396
+ /** Processes pointermove events during an active drag. */
397
+ onPointerMove(e: PointerEvent): void;
398
+ /** Processes pointerup events to finalize and dispatch result events. */
399
+ onPointerUp(_e: PointerEvent): void;
400
+ /** Re-extract peaks from cache and set on waveform elements during trim drag.
401
+ * Returns true if peaks were successfully updated. */
402
+ private _updateWaveformPeaks;
403
+ /** Restore clip container CSS to original values after trim visual preview. */
404
+ private _restoreTrimVisual;
405
+ private _reset;
406
+ }
407
+
299
408
  interface DawSelectionDetail {
300
409
  start: number;
301
410
  end: number;
@@ -342,11 +451,33 @@ interface DawRecordingCompleteDetail {
342
451
  audioBuffer: AudioBuffer;
343
452
  startSample: number;
344
453
  durationSamples: number;
454
+ offsetSamples: number;
345
455
  }
346
456
  interface DawRecordingErrorDetail {
347
457
  trackId: string;
348
458
  error: unknown;
349
459
  }
460
+ interface DawClipMoveDetail {
461
+ readonly trackId: string;
462
+ readonly clipId: string;
463
+ /** Requested cumulative delta. May exceed actual applied movement due to
464
+ * collision constraints. Query engine state for actual clip positions. */
465
+ readonly deltaSamples: number;
466
+ }
467
+ interface DawClipTrimDetail {
468
+ readonly trackId: string;
469
+ readonly clipId: string;
470
+ readonly boundary: 'left' | 'right';
471
+ /** Constrained cumulative delta applied by the engine. Already clamped
472
+ * by collision and boundary constraints during drag. */
473
+ readonly deltaSamples: number;
474
+ }
475
+ interface DawClipSplitDetail {
476
+ readonly trackId: string;
477
+ readonly originalClipId: string;
478
+ readonly leftClipId: string;
479
+ readonly rightClipId: string;
480
+ }
350
481
  interface DawEventMap {
351
482
  'daw-selection': CustomEvent<DawSelectionDetail>;
352
483
  'daw-seek': CustomEvent<DawSeekDetail>;
@@ -365,6 +496,9 @@ interface DawEventMap {
365
496
  'daw-recording-start': CustomEvent<DawRecordingStartDetail>;
366
497
  'daw-recording-complete': CustomEvent<DawRecordingCompleteDetail>;
367
498
  'daw-recording-error': CustomEvent<DawRecordingErrorDetail>;
499
+ 'daw-clip-move': CustomEvent<DawClipMoveDetail>;
500
+ 'daw-clip-trim': CustomEvent<DawClipTrimDetail>;
501
+ 'daw-clip-split': CustomEvent<DawClipSplitDetail>;
368
502
  }
369
503
  type DawEvent<K extends keyof DawEventMap> = DawEventMap[K];
370
504
  interface LoadFilesResult {
@@ -383,6 +517,9 @@ declare class DawEditorElement extends LitElement {
383
517
  barWidth: number;
384
518
  barGap: number;
385
519
  fileDrop: boolean;
520
+ clipHeaders: boolean;
521
+ clipHeaderHeight: number;
522
+ interactiveClips: boolean;
386
523
  /** Initial sample rate hint. Overridden by decoded audio buffer's actual rate. */
387
524
  sampleRate: number;
388
525
  /** Resolved sample rate — falls back to sampleRate property until first audio decode. */
@@ -399,15 +536,26 @@ declare class DawEditorElement extends LitElement {
399
536
  _currentTime: number;
400
537
  _engine: PlaylistEngine | null;
401
538
  private _enginePromise;
402
- private _audioInitialized;
403
539
  _audioCache: Map<string, Promise<AudioBuffer>>;
404
540
  _clipBuffers: Map<string, AudioBuffer>;
541
+ _clipOffsets: Map<string, {
542
+ offsetSamples: number;
543
+ durationSamples: number;
544
+ }>;
405
545
  _peakPipeline: PeakPipeline;
406
546
  private _trackElements;
407
547
  private _childObserver;
408
548
  private _audioResume;
409
549
  eagerResume?: string;
410
550
  private _recordingController;
551
+ private _clipPointer;
552
+ get _clipHandler(): ClipPointerHandler | null;
553
+ get engine(): PlaylistEngine | null;
554
+ /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
555
+ reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
556
+ data: Peaks[];
557
+ length: number;
558
+ } | null;
411
559
  private _pointer;
412
560
  private _viewport;
413
561
  static styles: lit.CSSResult[];
@@ -442,14 +590,20 @@ declare class DawEditorElement extends LitElement {
442
590
  private _onDragLeave;
443
591
  private _onDrop;
444
592
  loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
445
- play(): Promise<void>;
593
+ play(startTime?: number): Promise<void>;
446
594
  pause(): void;
447
595
  stop(): void;
448
596
  seekTo(time: number): void;
597
+ /** Split the clip under the playhead on the selected track. */
598
+ splitAtPlayhead(): boolean;
599
+ private _onKeyDown;
449
600
  recordingStream: MediaStream | null;
601
+ get currentTime(): number;
450
602
  get isRecording(): boolean;
603
+ pauseRecording(): void;
604
+ resumeRecording(): void;
451
605
  stopRecording(): void;
452
- _addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number): void;
606
+ _addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
453
607
  startRecording(stream?: MediaStream, options?: RecordingOptions): Promise<void>;
454
608
  private _renderRecordingPreview;
455
609
  _startPlayhead(): void;
@@ -538,4 +692,27 @@ interface PointerEngineContract {
538
692
  selectTrack(trackId: string | null): void;
539
693
  }
540
694
 
541
- export { AudioResumeController, type ClipDescriptor, DawClipElement, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, 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 LoadFilesResult, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type TrackDescriptor };
695
+ /** Narrow engine contract for split operations. */
696
+ interface SplitEngineContract {
697
+ getState(): {
698
+ selectedTrackId: string | null;
699
+ tracks: ClipTrack[];
700
+ };
701
+ splitClip(trackId: string, clipId: string, atSample: number): void;
702
+ }
703
+ /** Host interface for splitAtPlayhead. */
704
+ interface SplitHost {
705
+ readonly effectiveSampleRate: number;
706
+ readonly currentTime: number;
707
+ readonly engine: SplitEngineContract | null;
708
+ dispatchEvent(event: Event): boolean;
709
+ }
710
+ /**
711
+ * Splits the clip under the playhead on the selected track.
712
+ *
713
+ * Returns true if the split occurred and dispatched a daw-clip-split event.
714
+ * Returns false for any guard failure or engine no-op.
715
+ */
716
+ declare function splitAtPlayhead(host: SplitHost): boolean;
717
+
718
+ 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, 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 LoadFilesResult, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type TrackDescriptor, splitAtPlayhead };
package/dist/index.d.ts CHANGED
@@ -130,6 +130,12 @@ declare class DawTransportButton extends LitElement {
130
130
  }
131
131
 
132
132
  declare class DawPlayButtonElement extends DawTransportButton {
133
+ private _isRecording;
134
+ private _targetRef;
135
+ private _onRecStart;
136
+ private _onRecEnd;
137
+ connectedCallback(): void;
138
+ disconnectedCallback(): void;
133
139
  render(): lit.TemplateResult<1>;
134
140
  private _onClick;
135
141
  }
@@ -140,6 +146,14 @@ declare global {
140
146
  }
141
147
 
142
148
  declare class DawPauseButtonElement extends DawTransportButton {
149
+ private _isPaused;
150
+ private _isRecording;
151
+ private _targetRef;
152
+ private _onRecStart;
153
+ private _onRecEnd;
154
+ static styles: lit.CSSResultGroup[];
155
+ connectedCallback(): void;
156
+ disconnectedCallback(): void;
143
157
  render(): lit.TemplateResult<1>;
144
158
  private _onClick;
145
159
  }
@@ -196,19 +210,25 @@ declare class PeakPipeline {
196
210
  private _worker;
197
211
  private _cache;
198
212
  private _inflight;
213
+ private _baseScale;
214
+ private _bits;
215
+ constructor(baseScale?: number, bits?: 8 | 16);
199
216
  /**
200
217
  * Generate PeakData for a clip from its AudioBuffer.
201
218
  * Uses cached WaveformData when available; otherwise generates via worker.
202
- * The worker generates at `scale` (= samplesPerPixel) for exact rendering.
219
+ * Worker generates at baseScale (default 128); extractPeaks resamples to the requested zoom.
203
220
  */
204
- generatePeaks(audioBuffer: AudioBuffer, samplesPerPixel: number, isMono: boolean): Promise<PeakData>;
221
+ generatePeaks(audioBuffer: AudioBuffer, samplesPerPixel: number, isMono: boolean, offsetSamples?: number, durationSamples?: number): Promise<PeakData>;
205
222
  /**
206
223
  * Re-extract peaks for all clips at a new zoom level using cached WaveformData.
207
224
  * Only works for zoom levels coarser than (or equal to) the cached base scale.
208
225
  * Returns a new Map of clipId → PeakData. Clips without cached data or where
209
226
  * the target scale is finer than the cached base are skipped.
210
227
  */
211
- reextractPeaks(clipBuffers: ReadonlyMap<string, AudioBuffer>, samplesPerPixel: number, isMono: boolean): Map<string, PeakData>;
228
+ reextractPeaks(clipBuffers: ReadonlyMap<string, AudioBuffer>, samplesPerPixel: number, isMono: boolean, clipOffsets?: ReadonlyMap<string, {
229
+ offsetSamples: number;
230
+ durationSamples: number;
231
+ }>): Map<string, PeakData>;
212
232
  terminate(): void;
213
233
  private _getWaveformData;
214
234
  }
@@ -240,6 +260,8 @@ interface RecordingOptions {
240
260
  trackId?: string;
241
261
  bits?: 8 | 16;
242
262
  startSample?: number;
263
+ /** Start playback during recording so user hears existing tracks. */
264
+ overdub?: boolean;
243
265
  }
244
266
  interface RecordingSession {
245
267
  readonly trackId: string;
@@ -259,12 +281,16 @@ interface RecordingSession {
259
281
  readonly channelCount: number;
260
282
  readonly bits: Bits;
261
283
  isFirstMessage: boolean;
284
+ /** Latency samples to skip in live preview (outputLatency + lookAhead). */
285
+ readonly latencySamples: number;
286
+ readonly wasOverdub: boolean;
262
287
  /** Stored so it can be removed on stop/cleanup — not just when stream ends. */
263
288
  readonly _onTrackEnded: (() => void) | null;
264
289
  readonly _audioTrack: MediaStreamTrack | null;
265
290
  }
266
291
  /** Readonly view of a recording session for external consumers. */
267
- type ReadonlyRecordingSession = Readonly<Omit<RecordingSession, 'chunks' | 'peaks' | '_onTrackEnded' | '_audioTrack'>> & {
292
+ type ReadonlyRecordingSession = Readonly<Omit<RecordingSession, 'chunks' | 'peaks' | '_onTrackEnded' | '_audioTrack' | 'latencySamples'>> & {
293
+ readonly latencySamples: number;
268
294
  readonly chunks: ReadonlyArray<ReadonlyArray<Float32Array>>;
269
295
  readonly peaks: ReadonlyArray<Int8Array | Int16Array>;
270
296
  };
@@ -276,7 +302,9 @@ interface RecordingHost extends ReactiveControllerHost {
276
302
  readonly _currentTime: number;
277
303
  readonly shadowRoot: ShadowRoot | null;
278
304
  resolveAudioContextSampleRate(rate: number): void;
279
- _addRecordedClip?(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number): void;
305
+ _addRecordedClip?(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
306
+ play?(startTime?: number): Promise<void>;
307
+ stop?(): void;
280
308
  dispatchEvent(event: Event): boolean;
281
309
  }
282
310
  declare class RecordingController implements ReactiveController {
@@ -289,6 +317,8 @@ declare class RecordingController implements ReactiveController {
289
317
  get isRecording(): boolean;
290
318
  getSession(trackId: string): ReadonlyRecordingSession | undefined;
291
319
  startRecording(stream: MediaStream, options?: RecordingOptions): Promise<void>;
320
+ pauseRecording(trackId?: string): void;
321
+ resumeRecording(trackId?: string): void;
292
322
  stopRecording(trackId?: string): void;
293
323
  private _onWorkletMessage;
294
324
  private _createClipFromRecording;
@@ -296,6 +326,85 @@ declare class RecordingController implements ReactiveController {
296
326
  private _cleanupSession;
297
327
  }
298
328
 
329
+ /** Snapshot of a clip's bounds for trim constraint computation. */
330
+ interface ClipBounds {
331
+ readonly offsetSamples: number;
332
+ readonly durationSamples: number;
333
+ readonly startSample: number;
334
+ readonly sourceDurationSamples: number;
335
+ }
336
+ /** Narrow engine contract for clip move/trim interactions. */
337
+ interface ClipEngineContract {
338
+ moveClip(trackId: string, clipId: string, deltaSamples: number, skipAdapter?: boolean): void;
339
+ trimClip(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number, skipAdapter?: boolean): void;
340
+ updateTrack(trackId: string): void;
341
+ /** Get a clip's full bounds for trim constraint computation. */
342
+ getClipBounds(trackId: string, clipId: string): ClipBounds | null;
343
+ /** Constrain a trim delta using the engine's collision/bounds logic. */
344
+ constrainTrimDelta(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number): number;
345
+ }
346
+ /** Peak data returned by reextractClipPeaks for imperative waveform updates. */
347
+ interface ClipPeakSlice {
348
+ data: ArrayLike<number>[];
349
+ length: number;
350
+ }
351
+ /** Host interface required by ClipPointerHandler. */
352
+ interface ClipPointerHost {
353
+ readonly samplesPerPixel: number;
354
+ readonly effectiveSampleRate: number;
355
+ readonly interactiveClips: boolean;
356
+ readonly engine: ClipEngineContract | null;
357
+ readonly shadowRoot: ShadowRoot | null;
358
+ dispatchEvent(event: Event): boolean;
359
+ /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
360
+ reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): ClipPeakSlice | null;
361
+ }
362
+ /**
363
+ * Handles pointer interactions for clip move and trim drag operations.
364
+ * Converts pixel deltas to sample deltas and delegates to the engine.
365
+ *
366
+ * Move: sends incremental deltas per-frame with skipAdapter=true (engine shifts
367
+ * startSample additively without touching audio adapter). Adapter synced once
368
+ * via updateTrack() at drag end.
369
+ * Trim: updates clip container CSS imperatively during drag for visual feedback,
370
+ * then applies cumulative delta to engine once at drag end.
371
+ */
372
+ declare class ClipPointerHandler {
373
+ private _host;
374
+ private _mode;
375
+ private _clipId;
376
+ private _trackId;
377
+ private _startPx;
378
+ private _isDragging;
379
+ private _lastDeltaPx;
380
+ private _cumulativeDeltaSamples;
381
+ private _clipContainer;
382
+ private _boundaryEl;
383
+ private _originalLeft;
384
+ private _originalWidth;
385
+ private _originalOffsetSamples;
386
+ private _originalDurationSamples;
387
+ constructor(host: ClipPointerHost);
388
+ /** Returns true if a drag interaction is currently in progress. */
389
+ get isActive(): boolean;
390
+ /**
391
+ * Attempts to handle a pointerdown event on the given target element.
392
+ * Returns true if the target is a recognized clip interaction element.
393
+ */
394
+ tryHandle(target: Element, e: PointerEvent): boolean;
395
+ private _beginDrag;
396
+ /** Processes pointermove events during an active drag. */
397
+ onPointerMove(e: PointerEvent): void;
398
+ /** Processes pointerup events to finalize and dispatch result events. */
399
+ onPointerUp(_e: PointerEvent): void;
400
+ /** Re-extract peaks from cache and set on waveform elements during trim drag.
401
+ * Returns true if peaks were successfully updated. */
402
+ private _updateWaveformPeaks;
403
+ /** Restore clip container CSS to original values after trim visual preview. */
404
+ private _restoreTrimVisual;
405
+ private _reset;
406
+ }
407
+
299
408
  interface DawSelectionDetail {
300
409
  start: number;
301
410
  end: number;
@@ -342,11 +451,33 @@ interface DawRecordingCompleteDetail {
342
451
  audioBuffer: AudioBuffer;
343
452
  startSample: number;
344
453
  durationSamples: number;
454
+ offsetSamples: number;
345
455
  }
346
456
  interface DawRecordingErrorDetail {
347
457
  trackId: string;
348
458
  error: unknown;
349
459
  }
460
+ interface DawClipMoveDetail {
461
+ readonly trackId: string;
462
+ readonly clipId: string;
463
+ /** Requested cumulative delta. May exceed actual applied movement due to
464
+ * collision constraints. Query engine state for actual clip positions. */
465
+ readonly deltaSamples: number;
466
+ }
467
+ interface DawClipTrimDetail {
468
+ readonly trackId: string;
469
+ readonly clipId: string;
470
+ readonly boundary: 'left' | 'right';
471
+ /** Constrained cumulative delta applied by the engine. Already clamped
472
+ * by collision and boundary constraints during drag. */
473
+ readonly deltaSamples: number;
474
+ }
475
+ interface DawClipSplitDetail {
476
+ readonly trackId: string;
477
+ readonly originalClipId: string;
478
+ readonly leftClipId: string;
479
+ readonly rightClipId: string;
480
+ }
350
481
  interface DawEventMap {
351
482
  'daw-selection': CustomEvent<DawSelectionDetail>;
352
483
  'daw-seek': CustomEvent<DawSeekDetail>;
@@ -365,6 +496,9 @@ interface DawEventMap {
365
496
  'daw-recording-start': CustomEvent<DawRecordingStartDetail>;
366
497
  'daw-recording-complete': CustomEvent<DawRecordingCompleteDetail>;
367
498
  'daw-recording-error': CustomEvent<DawRecordingErrorDetail>;
499
+ 'daw-clip-move': CustomEvent<DawClipMoveDetail>;
500
+ 'daw-clip-trim': CustomEvent<DawClipTrimDetail>;
501
+ 'daw-clip-split': CustomEvent<DawClipSplitDetail>;
368
502
  }
369
503
  type DawEvent<K extends keyof DawEventMap> = DawEventMap[K];
370
504
  interface LoadFilesResult {
@@ -383,6 +517,9 @@ declare class DawEditorElement extends LitElement {
383
517
  barWidth: number;
384
518
  barGap: number;
385
519
  fileDrop: boolean;
520
+ clipHeaders: boolean;
521
+ clipHeaderHeight: number;
522
+ interactiveClips: boolean;
386
523
  /** Initial sample rate hint. Overridden by decoded audio buffer's actual rate. */
387
524
  sampleRate: number;
388
525
  /** Resolved sample rate — falls back to sampleRate property until first audio decode. */
@@ -399,15 +536,26 @@ declare class DawEditorElement extends LitElement {
399
536
  _currentTime: number;
400
537
  _engine: PlaylistEngine | null;
401
538
  private _enginePromise;
402
- private _audioInitialized;
403
539
  _audioCache: Map<string, Promise<AudioBuffer>>;
404
540
  _clipBuffers: Map<string, AudioBuffer>;
541
+ _clipOffsets: Map<string, {
542
+ offsetSamples: number;
543
+ durationSamples: number;
544
+ }>;
405
545
  _peakPipeline: PeakPipeline;
406
546
  private _trackElements;
407
547
  private _childObserver;
408
548
  private _audioResume;
409
549
  eagerResume?: string;
410
550
  private _recordingController;
551
+ private _clipPointer;
552
+ get _clipHandler(): ClipPointerHandler | null;
553
+ get engine(): PlaylistEngine | null;
554
+ /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
555
+ reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
556
+ data: Peaks[];
557
+ length: number;
558
+ } | null;
411
559
  private _pointer;
412
560
  private _viewport;
413
561
  static styles: lit.CSSResult[];
@@ -442,14 +590,20 @@ declare class DawEditorElement extends LitElement {
442
590
  private _onDragLeave;
443
591
  private _onDrop;
444
592
  loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
445
- play(): Promise<void>;
593
+ play(startTime?: number): Promise<void>;
446
594
  pause(): void;
447
595
  stop(): void;
448
596
  seekTo(time: number): void;
597
+ /** Split the clip under the playhead on the selected track. */
598
+ splitAtPlayhead(): boolean;
599
+ private _onKeyDown;
449
600
  recordingStream: MediaStream | null;
601
+ get currentTime(): number;
450
602
  get isRecording(): boolean;
603
+ pauseRecording(): void;
604
+ resumeRecording(): void;
451
605
  stopRecording(): void;
452
- _addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number): void;
606
+ _addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
453
607
  startRecording(stream?: MediaStream, options?: RecordingOptions): Promise<void>;
454
608
  private _renderRecordingPreview;
455
609
  _startPlayhead(): void;
@@ -538,4 +692,27 @@ interface PointerEngineContract {
538
692
  selectTrack(trackId: string | null): void;
539
693
  }
540
694
 
541
- export { AudioResumeController, type ClipDescriptor, DawClipElement, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, 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 LoadFilesResult, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type TrackDescriptor };
695
+ /** Narrow engine contract for split operations. */
696
+ interface SplitEngineContract {
697
+ getState(): {
698
+ selectedTrackId: string | null;
699
+ tracks: ClipTrack[];
700
+ };
701
+ splitClip(trackId: string, clipId: string, atSample: number): void;
702
+ }
703
+ /** Host interface for splitAtPlayhead. */
704
+ interface SplitHost {
705
+ readonly effectiveSampleRate: number;
706
+ readonly currentTime: number;
707
+ readonly engine: SplitEngineContract | null;
708
+ dispatchEvent(event: Event): boolean;
709
+ }
710
+ /**
711
+ * Splits the clip under the playhead on the selected track.
712
+ *
713
+ * Returns true if the split occurred and dispatched a daw-clip-split event.
714
+ * Returns false for any guard failure or engine no-op.
715
+ */
716
+ declare function splitAtPlayhead(host: SplitHost): boolean;
717
+
718
+ 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, 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 LoadFilesResult, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type TrackDescriptor, splitAtPlayhead };