@dawcore/components 0.0.9 → 0.0.11

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
@@ -31,7 +31,7 @@ npm install @waveform-playlist/core @waveform-playlist/engine @dawcore/transport
31
31
 
32
32
  Optional (for recording):
33
33
  ```bash
34
- npm install @waveform-playlist/recording @waveform-playlist/worklets
34
+ npm install @waveform-playlist/worklets
35
35
  ```
36
36
 
37
37
  ## Quick Start
@@ -97,30 +97,28 @@ Access the native transport for tempo, metronome, count-in, meter, and effects:
97
97
  ```javascript
98
98
  const editor = document.getElementById('editor');
99
99
 
100
- // Transport is available after first track loads
101
- editor.addEventListener('daw-play', () => {
102
- const transport = editor.engine?.adapter?.transport;
103
- if (!transport) return;
100
+ // Build engine eagerly so transport is available immediately
101
+ await editor._ensureEngine();
102
+ const transport = editor.transport;
104
103
 
105
- // Tempo & meter
106
- transport.setTempo(140);
107
- transport.setMeter(3, 4);
104
+ // Tempo & meter
105
+ transport.setTempo(140);
106
+ transport.setMeter(3, 4);
108
107
 
109
- // Metronome (default click sounds built in)
110
- transport.setMetronomeEnabled(true);
108
+ // Metronome (default click sounds built in)
109
+ transport.setMetronomeEnabled(true);
111
110
 
112
- // Count-in
113
- transport.setCountIn(true);
114
- transport.setCountInBars(1);
115
- transport.setCountInMode('always');
111
+ // Count-in
112
+ transport.setCountIn(true);
113
+ transport.setCountInBars(1);
114
+ transport.setCountInMode('always');
116
115
 
117
- transport.on('countIn', ({ beat, totalBeats }) => {
118
- console.log(beat + '/' + totalBeats);
119
- });
120
-
121
- // Effects hook — insert any AudioNode chain
122
- transport.connectTrackOutput('track-id', reverbNode);
116
+ transport.on('countIn', ({ beat, totalBeats }) => {
117
+ console.log(beat + '/' + totalBeats);
123
118
  });
119
+
120
+ // Effects hook — insert any AudioNode chain
121
+ transport.connectTrackOutput('track-id', reverbNode);
124
122
  ```
125
123
 
126
124
  ## Programmatic File Loading
package/dist/index.d.mts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as lit from 'lit';
2
2
  import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
3
- import { Peaks, Bits, FadeType, PeakData, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
3
+ import { Peaks, Bits, FadeType, PeakData, MeterEntry, SnapTo, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
4
4
  import WaveformData from 'waveform-data';
5
5
  import { PlaylistEngine } from '@waveform-playlist/engine';
6
+ import { Transport } from '@dawcore/transport';
6
7
 
7
8
  declare class DawClipElement extends LitElement {
8
9
  src: string;
@@ -44,6 +45,17 @@ declare global {
44
45
  }
45
46
  }
46
47
 
48
+ /** A segment mapping a peak-index range to a pixel range within the waveform. */
49
+ interface WaveformSegment {
50
+ /** Start position in the peaks array (fractional index). */
51
+ peakStart: number;
52
+ /** End position in the peaks array (fractional index). */
53
+ peakEnd: number;
54
+ /** Start pixel position within the waveform. */
55
+ pixelStart: number;
56
+ /** End pixel position within the waveform. */
57
+ pixelEnd: number;
58
+ }
47
59
  declare class DawWaveformElement extends LitElement {
48
60
  private _peaks;
49
61
  private _dirtyPixels;
@@ -70,6 +82,8 @@ declare class DawWaveformElement extends LitElement {
70
82
  visibleEnd: number;
71
83
  /** This element's left offset on the timeline (for viewport intersection). */
72
84
  originX: number;
85
+ /** When set, draws per-segment with independent samples-per-pixel ratios. */
86
+ segments?: WaveformSegment[];
73
87
  static styles: lit.CSSResult;
74
88
  private _getVisibleChunkIndices;
75
89
  /**
@@ -82,6 +96,7 @@ declare class DawWaveformElement extends LitElement {
82
96
  private _scheduleDraw;
83
97
  private _drawDirty;
84
98
  private _drawChunk;
99
+ private _drawSegments;
85
100
  connectedCallback(): void;
86
101
  disconnectedCallback(): void;
87
102
  render(): lit.TemplateResult<1>;
@@ -103,6 +118,10 @@ declare class DawPlayheadElement extends LitElement {
103
118
  firstUpdated(): void;
104
119
  startAnimation(getTime: () => number, sampleRate: number, samplesPerPixel: number): void;
105
120
  stopAnimation(time: number, sampleRate: number, samplesPerPixel: number): void;
121
+ startBeatsAnimation(getTime: () => number, bpm: number, ppqn: number, ticksPerPixel: number): void;
122
+ stopBeatsAnimation(time: number, bpm: number, ppqn: number, ticksPerPixel: number): void;
123
+ startBeatsAnimationWithMap(getTime: () => number, secondsToTicks: (s: number) => number, ticksPerPixel: number): void;
124
+ stopBeatsAnimationWithMap(time: number, secondsToTicks: (s: number) => number, ticksPerPixel: number): void;
106
125
  }
107
126
  declare global {
108
127
  interface HTMLElementTagNameMap {
@@ -243,6 +262,15 @@ declare class PeakPipeline {
243
262
  * (default); reextractPeaks passes false and logs a single summary instead.
244
263
  */
245
264
  private _clampScale;
265
+ /**
266
+ * Extract peaks at the base scale from cached WaveformData.
267
+ * Returns null if no cached data exists for this buffer.
268
+ * Used by variable-tempo segments which handle stretching themselves.
269
+ */
270
+ getBaseScalePeaks(audioBuffer: AudioBuffer, isMono: boolean, offsetSamples?: number, durationSamples?: number): {
271
+ peaks: PeakData;
272
+ scale: number;
273
+ } | null;
246
274
  /**
247
275
  * Return the coarsest (largest) scale among cached WaveformData entries
248
276
  * that correspond to the given clip buffers. Returns 0 if none are cached.
@@ -275,6 +303,43 @@ declare global {
275
303
  }
276
304
  }
277
305
 
306
+ /**
307
+ * `<daw-grid>` renders a musical grid overlay behind waveforms using the
308
+ * Audacity three-tier model:
309
+ *
310
+ * - **Zebra stripes**: Alternating bar backgrounds at 2% white opacity.
311
+ * - **Major lines** (bars): 10% white opacity, full height.
312
+ * - **Minor lines** (beats): 6% white opacity, full height.
313
+ * - **MinorMinor** (subdivisions): Ruler ticks only — no grid lines.
314
+ *
315
+ * Uses chunked 1000px canvases with virtual scrolling (same as daw-waveform).
316
+ *
317
+ * CSS custom properties:
318
+ * --daw-grid-bar-highlight Alternating bar fill (default: rgba(255,255,255,0.02))
319
+ * --daw-grid-major-line Bar line color (default: rgba(255,255,255,0.1))
320
+ * --daw-grid-minor-line Beat line color (default: rgba(255,255,255,0.06))
321
+ */
322
+ declare class DawGridElement extends LitElement {
323
+ ticksPerPixel: number;
324
+ meterEntries: MeterEntry[];
325
+ ppqn: number;
326
+ visibleStart: number;
327
+ visibleEnd: number;
328
+ length: number;
329
+ height: number;
330
+ private _tickData;
331
+ static styles: lit.CSSResult;
332
+ willUpdate(): void;
333
+ render(): lit.TemplateResult<1>;
334
+ updated(): void;
335
+ private _drawGrid;
336
+ }
337
+ declare global {
338
+ interface HTMLElementTagNameMap {
339
+ 'daw-grid': DawGridElement;
340
+ }
341
+ }
342
+
278
343
  interface RecordingOptions {
279
344
  trackId?: string;
280
345
  bits?: 8 | 16;
@@ -379,10 +444,20 @@ interface ClipPeakSlice {
379
444
  /** Host interface required by ClipPointerHandler. */
380
445
  interface ClipPointerHost {
381
446
  readonly samplesPerPixel: number;
447
+ /** In beats mode, the tick-derived SPP used for rendering. */
448
+ readonly renderSamplesPerPixel: number;
382
449
  readonly effectiveSampleRate: number;
383
450
  readonly interactiveClips: boolean;
384
451
  readonly engine: ClipEngineContract | null;
385
452
  readonly shadowRoot: ShadowRoot | null;
453
+ readonly scaleMode: 'temporal' | 'beats';
454
+ readonly ticksPerPixel: number;
455
+ readonly bpm: number;
456
+ readonly ppqn: number;
457
+ readonly _meterEntries: MeterEntry[];
458
+ readonly snapTo: SnapTo;
459
+ readonly _secondsToTicks: (seconds: number) => number;
460
+ readonly _ticksToSeconds: (ticks: number) => number;
386
461
  dispatchEvent(event: Event): boolean;
387
462
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
388
463
  reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): ClipPeakSlice | null;
@@ -404,7 +479,6 @@ declare class ClipPointerHandler {
404
479
  private _trackId;
405
480
  private _startPx;
406
481
  private _isDragging;
407
- private _lastDeltaPx;
408
482
  private _cumulativeDeltaSamples;
409
483
  private _clipContainer;
410
484
  private _boundaryEl;
@@ -412,7 +486,17 @@ declare class ClipPointerHandler {
412
486
  private _originalWidth;
413
487
  private _originalOffsetSamples;
414
488
  private _originalDurationSamples;
489
+ private _originalStartSample;
415
490
  constructor(host: ClipPointerHost);
491
+ /**
492
+ * Convert a pixel delta to samples, snapping in tick space when in beats mode.
493
+ *
494
+ * The anchor is the absolute sample position being moved (e.g., clip start
495
+ * for move/left-trim, clip end for right-trim). Snapping the absolute
496
+ * position — not just the delta — ensures clips land exactly on grid lines
497
+ * even if they started off-grid.
498
+ */
499
+ private _snapDeltaToSamples;
416
500
  /** Returns true if a drag interaction is currently in progress. */
417
501
  get isActive(): boolean;
418
502
  /**
@@ -550,6 +634,25 @@ declare class DawEditorElement extends LitElement {
550
634
  clipHeaders: boolean;
551
635
  clipHeaderHeight: number;
552
636
  interactiveClips: boolean;
637
+ scaleMode: 'temporal' | 'beats';
638
+ get ticksPerPixel(): number;
639
+ set ticksPerPixel(value: number);
640
+ private _ticksPerPixel;
641
+ get bpm(): number;
642
+ set bpm(value: number);
643
+ private _bpm;
644
+ timeSignature: [number, number];
645
+ meterEntries?: MeterEntry[];
646
+ /** MeterEntries for grid/ruler: explicit meterEntries if set, otherwise derived from timeSignature. */
647
+ get _meterEntries(): MeterEntry[];
648
+ get ppqn(): number;
649
+ set ppqn(value: number);
650
+ private _ppqn;
651
+ snapTo: SnapTo;
652
+ /** Optional tempo-aware conversion: seconds → PPQN ticks. When provided, enables variable tempo. */
653
+ secondsToTicks?: (seconds: number) => number;
654
+ /** Optional tempo-aware conversion: PPQN ticks → seconds. Required alongside secondsToTicks. */
655
+ ticksToSeconds?: (ticks: number) => number;
553
656
  /** Desired sample rate. Creates a cross-browser AudioContext at this rate.
554
657
  * Pre-computed .dat peaks render instantly when they match. */
555
658
  sampleRate: number;
@@ -572,6 +675,9 @@ declare class DawEditorElement extends LitElement {
572
675
  set audioContext(ctx: AudioContext | null);
573
676
  get audioContext(): AudioContext;
574
677
  _engine: PlaylistEngine | null;
678
+ private _adapter;
679
+ private _warnedMissingTicksToSeconds;
680
+ private _warnedMissingSecondsToTicks;
575
681
  private _enginePromise;
576
682
  _audioCache: Map<string, Promise<AudioBuffer>>;
577
683
  private _peaksCache;
@@ -591,6 +697,9 @@ declare class DawEditorElement extends LitElement {
591
697
  private _clipPointer;
592
698
  get _clipHandler(): ClipPointerHandler | null;
593
699
  get engine(): PlaylistEngine | null;
700
+ /** The adapter's Transport — use for tempo, metronome, and effects. */
701
+ get transport(): Transport | null;
702
+ get renderSamplesPerPixel(): number;
594
703
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
595
704
  reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
596
705
  data: Peaks[];
@@ -601,7 +710,18 @@ declare class DawEditorElement extends LitElement {
601
710
  static styles: lit.CSSResult[];
602
711
  get effectiveSampleRate(): number;
603
712
  resolveAudioContextSampleRate(rate: number): void;
713
+ /**
714
+ * In beats mode, derive samplesPerPixel from ticksPerPixel so that
715
+ * clip positions, waveforms, and the tick-space grid all align.
716
+ */
717
+ private get _renderSpp();
718
+ /** Convert seconds to ticks — uses callback if provided, otherwise single-BPM fallback. */
719
+ _secondsToTicks(seconds: number): number;
720
+ /** Convert ticks to seconds — uses callback if provided, otherwise single-BPM fallback. */
721
+ _ticksToSeconds(ticks: number): number;
604
722
  private get _totalWidth();
723
+ /** Grid height when no tracks exist — matches scroll area's rendered height. */
724
+ private get _emptyGridHeight();
605
725
  _setSelectedTrackId(trackId: string | null): void;
606
726
  get tracks(): TrackDescriptor[];
607
727
  get selectedTrackId(): string | null;
@@ -673,7 +793,13 @@ declare class DawRulerElement extends LitElement {
673
793
  sampleRate: number;
674
794
  duration: number;
675
795
  rulerHeight: number;
796
+ scaleMode: 'temporal' | 'beats';
797
+ ticksPerPixel: number;
798
+ meterEntries: MeterEntry[];
799
+ ppqn: number;
800
+ totalWidth: number;
676
801
  private _tickData;
802
+ private _musicalTickData;
677
803
  static styles: lit.CSSResult;
678
804
  willUpdate(): void;
679
805
  render(): lit.TemplateResult<1>;
@@ -827,4 +953,4 @@ interface SplitHost {
827
953
  */
828
954
  declare function splitAtPlayhead(host: SplitHost): boolean;
829
955
 
830
- 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, 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, splitAtPlayhead };
956
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as lit from 'lit';
2
2
  import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
3
- import { Peaks, Bits, FadeType, PeakData, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
3
+ import { Peaks, Bits, FadeType, PeakData, MeterEntry, SnapTo, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
4
4
  import WaveformData from 'waveform-data';
5
5
  import { PlaylistEngine } from '@waveform-playlist/engine';
6
+ import { Transport } from '@dawcore/transport';
6
7
 
7
8
  declare class DawClipElement extends LitElement {
8
9
  src: string;
@@ -44,6 +45,17 @@ declare global {
44
45
  }
45
46
  }
46
47
 
48
+ /** A segment mapping a peak-index range to a pixel range within the waveform. */
49
+ interface WaveformSegment {
50
+ /** Start position in the peaks array (fractional index). */
51
+ peakStart: number;
52
+ /** End position in the peaks array (fractional index). */
53
+ peakEnd: number;
54
+ /** Start pixel position within the waveform. */
55
+ pixelStart: number;
56
+ /** End pixel position within the waveform. */
57
+ pixelEnd: number;
58
+ }
47
59
  declare class DawWaveformElement extends LitElement {
48
60
  private _peaks;
49
61
  private _dirtyPixels;
@@ -70,6 +82,8 @@ declare class DawWaveformElement extends LitElement {
70
82
  visibleEnd: number;
71
83
  /** This element's left offset on the timeline (for viewport intersection). */
72
84
  originX: number;
85
+ /** When set, draws per-segment with independent samples-per-pixel ratios. */
86
+ segments?: WaveformSegment[];
73
87
  static styles: lit.CSSResult;
74
88
  private _getVisibleChunkIndices;
75
89
  /**
@@ -82,6 +96,7 @@ declare class DawWaveformElement extends LitElement {
82
96
  private _scheduleDraw;
83
97
  private _drawDirty;
84
98
  private _drawChunk;
99
+ private _drawSegments;
85
100
  connectedCallback(): void;
86
101
  disconnectedCallback(): void;
87
102
  render(): lit.TemplateResult<1>;
@@ -103,6 +118,10 @@ declare class DawPlayheadElement extends LitElement {
103
118
  firstUpdated(): void;
104
119
  startAnimation(getTime: () => number, sampleRate: number, samplesPerPixel: number): void;
105
120
  stopAnimation(time: number, sampleRate: number, samplesPerPixel: number): void;
121
+ startBeatsAnimation(getTime: () => number, bpm: number, ppqn: number, ticksPerPixel: number): void;
122
+ stopBeatsAnimation(time: number, bpm: number, ppqn: number, ticksPerPixel: number): void;
123
+ startBeatsAnimationWithMap(getTime: () => number, secondsToTicks: (s: number) => number, ticksPerPixel: number): void;
124
+ stopBeatsAnimationWithMap(time: number, secondsToTicks: (s: number) => number, ticksPerPixel: number): void;
106
125
  }
107
126
  declare global {
108
127
  interface HTMLElementTagNameMap {
@@ -243,6 +262,15 @@ declare class PeakPipeline {
243
262
  * (default); reextractPeaks passes false and logs a single summary instead.
244
263
  */
245
264
  private _clampScale;
265
+ /**
266
+ * Extract peaks at the base scale from cached WaveformData.
267
+ * Returns null if no cached data exists for this buffer.
268
+ * Used by variable-tempo segments which handle stretching themselves.
269
+ */
270
+ getBaseScalePeaks(audioBuffer: AudioBuffer, isMono: boolean, offsetSamples?: number, durationSamples?: number): {
271
+ peaks: PeakData;
272
+ scale: number;
273
+ } | null;
246
274
  /**
247
275
  * Return the coarsest (largest) scale among cached WaveformData entries
248
276
  * that correspond to the given clip buffers. Returns 0 if none are cached.
@@ -275,6 +303,43 @@ declare global {
275
303
  }
276
304
  }
277
305
 
306
+ /**
307
+ * `<daw-grid>` renders a musical grid overlay behind waveforms using the
308
+ * Audacity three-tier model:
309
+ *
310
+ * - **Zebra stripes**: Alternating bar backgrounds at 2% white opacity.
311
+ * - **Major lines** (bars): 10% white opacity, full height.
312
+ * - **Minor lines** (beats): 6% white opacity, full height.
313
+ * - **MinorMinor** (subdivisions): Ruler ticks only — no grid lines.
314
+ *
315
+ * Uses chunked 1000px canvases with virtual scrolling (same as daw-waveform).
316
+ *
317
+ * CSS custom properties:
318
+ * --daw-grid-bar-highlight Alternating bar fill (default: rgba(255,255,255,0.02))
319
+ * --daw-grid-major-line Bar line color (default: rgba(255,255,255,0.1))
320
+ * --daw-grid-minor-line Beat line color (default: rgba(255,255,255,0.06))
321
+ */
322
+ declare class DawGridElement extends LitElement {
323
+ ticksPerPixel: number;
324
+ meterEntries: MeterEntry[];
325
+ ppqn: number;
326
+ visibleStart: number;
327
+ visibleEnd: number;
328
+ length: number;
329
+ height: number;
330
+ private _tickData;
331
+ static styles: lit.CSSResult;
332
+ willUpdate(): void;
333
+ render(): lit.TemplateResult<1>;
334
+ updated(): void;
335
+ private _drawGrid;
336
+ }
337
+ declare global {
338
+ interface HTMLElementTagNameMap {
339
+ 'daw-grid': DawGridElement;
340
+ }
341
+ }
342
+
278
343
  interface RecordingOptions {
279
344
  trackId?: string;
280
345
  bits?: 8 | 16;
@@ -379,10 +444,20 @@ interface ClipPeakSlice {
379
444
  /** Host interface required by ClipPointerHandler. */
380
445
  interface ClipPointerHost {
381
446
  readonly samplesPerPixel: number;
447
+ /** In beats mode, the tick-derived SPP used for rendering. */
448
+ readonly renderSamplesPerPixel: number;
382
449
  readonly effectiveSampleRate: number;
383
450
  readonly interactiveClips: boolean;
384
451
  readonly engine: ClipEngineContract | null;
385
452
  readonly shadowRoot: ShadowRoot | null;
453
+ readonly scaleMode: 'temporal' | 'beats';
454
+ readonly ticksPerPixel: number;
455
+ readonly bpm: number;
456
+ readonly ppqn: number;
457
+ readonly _meterEntries: MeterEntry[];
458
+ readonly snapTo: SnapTo;
459
+ readonly _secondsToTicks: (seconds: number) => number;
460
+ readonly _ticksToSeconds: (ticks: number) => number;
386
461
  dispatchEvent(event: Event): boolean;
387
462
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
388
463
  reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): ClipPeakSlice | null;
@@ -404,7 +479,6 @@ declare class ClipPointerHandler {
404
479
  private _trackId;
405
480
  private _startPx;
406
481
  private _isDragging;
407
- private _lastDeltaPx;
408
482
  private _cumulativeDeltaSamples;
409
483
  private _clipContainer;
410
484
  private _boundaryEl;
@@ -412,7 +486,17 @@ declare class ClipPointerHandler {
412
486
  private _originalWidth;
413
487
  private _originalOffsetSamples;
414
488
  private _originalDurationSamples;
489
+ private _originalStartSample;
415
490
  constructor(host: ClipPointerHost);
491
+ /**
492
+ * Convert a pixel delta to samples, snapping in tick space when in beats mode.
493
+ *
494
+ * The anchor is the absolute sample position being moved (e.g., clip start
495
+ * for move/left-trim, clip end for right-trim). Snapping the absolute
496
+ * position — not just the delta — ensures clips land exactly on grid lines
497
+ * even if they started off-grid.
498
+ */
499
+ private _snapDeltaToSamples;
416
500
  /** Returns true if a drag interaction is currently in progress. */
417
501
  get isActive(): boolean;
418
502
  /**
@@ -550,6 +634,25 @@ declare class DawEditorElement extends LitElement {
550
634
  clipHeaders: boolean;
551
635
  clipHeaderHeight: number;
552
636
  interactiveClips: boolean;
637
+ scaleMode: 'temporal' | 'beats';
638
+ get ticksPerPixel(): number;
639
+ set ticksPerPixel(value: number);
640
+ private _ticksPerPixel;
641
+ get bpm(): number;
642
+ set bpm(value: number);
643
+ private _bpm;
644
+ timeSignature: [number, number];
645
+ meterEntries?: MeterEntry[];
646
+ /** MeterEntries for grid/ruler: explicit meterEntries if set, otherwise derived from timeSignature. */
647
+ get _meterEntries(): MeterEntry[];
648
+ get ppqn(): number;
649
+ set ppqn(value: number);
650
+ private _ppqn;
651
+ snapTo: SnapTo;
652
+ /** Optional tempo-aware conversion: seconds → PPQN ticks. When provided, enables variable tempo. */
653
+ secondsToTicks?: (seconds: number) => number;
654
+ /** Optional tempo-aware conversion: PPQN ticks → seconds. Required alongside secondsToTicks. */
655
+ ticksToSeconds?: (ticks: number) => number;
553
656
  /** Desired sample rate. Creates a cross-browser AudioContext at this rate.
554
657
  * Pre-computed .dat peaks render instantly when they match. */
555
658
  sampleRate: number;
@@ -572,6 +675,9 @@ declare class DawEditorElement extends LitElement {
572
675
  set audioContext(ctx: AudioContext | null);
573
676
  get audioContext(): AudioContext;
574
677
  _engine: PlaylistEngine | null;
678
+ private _adapter;
679
+ private _warnedMissingTicksToSeconds;
680
+ private _warnedMissingSecondsToTicks;
575
681
  private _enginePromise;
576
682
  _audioCache: Map<string, Promise<AudioBuffer>>;
577
683
  private _peaksCache;
@@ -591,6 +697,9 @@ declare class DawEditorElement extends LitElement {
591
697
  private _clipPointer;
592
698
  get _clipHandler(): ClipPointerHandler | null;
593
699
  get engine(): PlaylistEngine | null;
700
+ /** The adapter's Transport — use for tempo, metronome, and effects. */
701
+ get transport(): Transport | null;
702
+ get renderSamplesPerPixel(): number;
594
703
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
595
704
  reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
596
705
  data: Peaks[];
@@ -601,7 +710,18 @@ declare class DawEditorElement extends LitElement {
601
710
  static styles: lit.CSSResult[];
602
711
  get effectiveSampleRate(): number;
603
712
  resolveAudioContextSampleRate(rate: number): void;
713
+ /**
714
+ * In beats mode, derive samplesPerPixel from ticksPerPixel so that
715
+ * clip positions, waveforms, and the tick-space grid all align.
716
+ */
717
+ private get _renderSpp();
718
+ /** Convert seconds to ticks — uses callback if provided, otherwise single-BPM fallback. */
719
+ _secondsToTicks(seconds: number): number;
720
+ /** Convert ticks to seconds — uses callback if provided, otherwise single-BPM fallback. */
721
+ _ticksToSeconds(ticks: number): number;
604
722
  private get _totalWidth();
723
+ /** Grid height when no tracks exist — matches scroll area's rendered height. */
724
+ private get _emptyGridHeight();
605
725
  _setSelectedTrackId(trackId: string | null): void;
606
726
  get tracks(): TrackDescriptor[];
607
727
  get selectedTrackId(): string | null;
@@ -673,7 +793,13 @@ declare class DawRulerElement extends LitElement {
673
793
  sampleRate: number;
674
794
  duration: number;
675
795
  rulerHeight: number;
796
+ scaleMode: 'temporal' | 'beats';
797
+ ticksPerPixel: number;
798
+ meterEntries: MeterEntry[];
799
+ ppqn: number;
800
+ totalWidth: number;
676
801
  private _tickData;
802
+ private _musicalTickData;
677
803
  static styles: lit.CSSResult;
678
804
  willUpdate(): void;
679
805
  render(): lit.TemplateResult<1>;
@@ -827,4 +953,4 @@ interface SplitHost {
827
953
  */
828
954
  declare function splitAtPlayhead(host: SplitHost): boolean;
829
955
 
830
- 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, 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, splitAtPlayhead };
956
+ 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 };