@dawcore/components 0.0.15 → 0.0.16

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
@@ -1,6 +1,6 @@
1
1
  import * as lit from 'lit';
2
2
  import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
3
- import { Peaks, Bits, FadeType, PeakData, MeterEntry, SnapTo, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
3
+ import { MidiNoteData, FadeType, Peaks, Bits, PeakData, MeterEntry, SnapTo, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
4
4
  import WaveformData from 'waveform-data';
5
5
  import { PlayoutAdapter, PlaylistEngine } from '@waveform-playlist/engine';
6
6
 
@@ -16,6 +16,16 @@ declare class DawClipElement extends LitElement {
16
16
  fadeIn: number;
17
17
  fadeOut: number;
18
18
  fadeType: string;
19
+ /** MIDI notes — JS property only, not reflected (note arrays are too large for attributes). */
20
+ midiNotes: MidiNoteData[] | null;
21
+ /** MIDI channel (0-indexed). Channel 9 = GM percussion. */
22
+ get midiChannel(): number | null;
23
+ set midiChannel(value: number | null);
24
+ private _midiChannel;
25
+ /** MIDI program (GM instrument 0-127). Used by SoundFontToneTrack. */
26
+ get midiProgram(): number | null;
27
+ set midiProgram(value: number | null);
28
+ private _midiProgram;
19
29
  readonly clipId: `${string}-${string}-${string}-${string}-${string}`;
20
30
  createRenderRoot(): this;
21
31
  connectedCallback(): void;
@@ -28,6 +38,111 @@ declare global {
28
38
  }
29
39
  }
30
40
 
41
+ type TrackRenderMode = 'waveform' | 'piano-roll';
42
+ interface TrackDescriptor {
43
+ name: string;
44
+ src: string;
45
+ volume: number;
46
+ pan: number;
47
+ muted: boolean;
48
+ soloed: boolean;
49
+ renderMode: TrackRenderMode;
50
+ clips: ClipDescriptor[];
51
+ }
52
+ /**
53
+ * Common fields shared by all clip descriptors regardless of source.
54
+ */
55
+ interface BaseClipDescriptor {
56
+ src: string;
57
+ peaksSrc: string;
58
+ start: number;
59
+ duration: number;
60
+ offset: number;
61
+ gain: number;
62
+ name: string;
63
+ fadeIn: number;
64
+ fadeOut: number;
65
+ fadeType: FadeType;
66
+ midiNotes: MidiNoteData[] | null;
67
+ midiChannel: number | null;
68
+ midiProgram: number | null;
69
+ }
70
+ /**
71
+ * A clip descriptor sourced from a `<daw-clip>` DOM element. `clipId` is
72
+ * always set — `<daw-clip>.clipId` is a `crypto.randomUUID()` generated at
73
+ * construction. Engine `clip.id` is aligned with this id in `_loadTrack`
74
+ * and `_loadAndAppendClip` so DOM and engine reference the same clip.
75
+ */
76
+ interface DomClipDescriptor extends BaseClipDescriptor {
77
+ kind: 'dom';
78
+ clipId: string;
79
+ }
80
+ /**
81
+ * A clip descriptor synthesized from a non-DOM source — file drops, the
82
+ * `<daw-track src>` shorthand fallback, or recording-clip insertion. No
83
+ * `clipId` because there's no DOM element to align with; the engine
84
+ * generates its own id at clip-creation time.
85
+ */
86
+ interface DropClipDescriptor extends BaseClipDescriptor {
87
+ kind: 'drop';
88
+ }
89
+ type ClipDescriptor = DomClipDescriptor | DropClipDescriptor;
90
+ /**
91
+ * Type predicate for the `'dom'` discriminator. Use to narrow a
92
+ * `ClipDescriptor` to `DomClipDescriptor` (which has `clipId`) without
93
+ * inline `c.kind === 'dom'` repetition.
94
+ */
95
+ declare function isDomClip(desc: ClipDescriptor): desc is DomClipDescriptor;
96
+ /**
97
+ * Public input shape for `editor.addTrack(config)`. All fields optional —
98
+ * defaults match the declarative `<daw-track>` defaults.
99
+ */
100
+ interface TrackConfig {
101
+ name?: string;
102
+ volume?: number;
103
+ pan?: number;
104
+ muted?: boolean;
105
+ soloed?: boolean;
106
+ renderMode?: TrackRenderMode;
107
+ clips?: ClipConfig[];
108
+ /**
109
+ * Convenience: creates a single piano-roll `<daw-clip>` child with these
110
+ * notes and sets `render-mode="piano-roll"` on the track. Equivalent to
111
+ * passing `{ renderMode: 'piano-roll', clips: [{ midiNotes, midiChannel, midiProgram }] }`.
112
+ * An explicit `renderMode` takes precedence over the inferred `'piano-roll'`.
113
+ *
114
+ * Creation-only — ignored by `updateTrack`. To modify notes after the
115
+ * track is built, use `editor.updateClip(trackId, clipId, { midiNotes })` or
116
+ * mutate the `<daw-clip>` element's `midiNotes` property directly.
117
+ */
118
+ midi?: {
119
+ notes: MidiNoteData[];
120
+ channel?: number;
121
+ program?: number;
122
+ };
123
+ }
124
+ /**
125
+ * Public input shape for clips passed via `TrackConfig.clips` or
126
+ * `editor.addClip(trackId, config)`. `src` is optional to support MIDI clips
127
+ * with no audio source. Other fields default to the matching `<daw-clip>`
128
+ * attribute defaults.
129
+ */
130
+ interface ClipConfig {
131
+ src?: string;
132
+ peaksSrc?: string;
133
+ start?: number;
134
+ duration?: number;
135
+ offset?: number;
136
+ gain?: number;
137
+ name?: string;
138
+ fadeIn?: number;
139
+ fadeOut?: number;
140
+ fadeType?: FadeType;
141
+ midiNotes?: MidiNoteData[];
142
+ midiChannel?: number;
143
+ midiProgram?: number;
144
+ }
145
+
31
146
  declare class DawTrackElement extends LitElement {
32
147
  src: string;
33
148
  name: string;
@@ -35,6 +150,7 @@ declare class DawTrackElement extends LitElement {
35
150
  pan: number;
36
151
  muted: boolean;
37
152
  soloed: boolean;
153
+ renderMode: TrackRenderMode;
38
154
  readonly trackId: `${string}-${string}-${string}-${string}-${string}`;
39
155
  createRenderRoot(): this;
40
156
  connectedCallback(): void;
@@ -112,6 +228,42 @@ declare global {
112
228
  }
113
229
  }
114
230
 
231
+ declare class DawPianoRollElement extends LitElement {
232
+ midiNotes: MidiNoteData[];
233
+ length: number;
234
+ waveHeight: number;
235
+ get samplesPerPixel(): number;
236
+ set samplesPerPixel(value: number);
237
+ private _samplesPerPixel;
238
+ get sampleRate(): number;
239
+ set sampleRate(value: number);
240
+ private _sampleRate;
241
+ clipOffsetSeconds: number;
242
+ /** Visible viewport start in pixels (relative to timeline origin). */
243
+ visibleStart: number;
244
+ /** Visible viewport end in pixels (relative to timeline origin). */
245
+ visibleEnd: number;
246
+ /** This element's left offset on the timeline (for viewport intersection). */
247
+ originX: number;
248
+ selected: boolean;
249
+ static styles: lit.CSSResult;
250
+ private _rafHandle;
251
+ private _scheduleDraw;
252
+ willUpdate(_changed: PropertyValues): void;
253
+ updated(changedProperties: Map<string, unknown>): void;
254
+ private _getPitchRange;
255
+ private _getNoteColor;
256
+ private _draw;
257
+ connectedCallback(): void;
258
+ disconnectedCallback(): void;
259
+ render(): lit.TemplateResult<1>;
260
+ }
261
+ declare global {
262
+ interface HTMLElementTagNameMap {
263
+ 'daw-piano-roll': DawPianoRollElement;
264
+ }
265
+ }
266
+
115
267
  declare class DawPlayheadElement extends LitElement {
116
268
  private _animation;
117
269
  private _line;
@@ -173,6 +325,8 @@ declare class DawPauseButtonElement extends DawTransportButton {
173
325
  private _targetRef;
174
326
  private _onRecStart;
175
327
  private _onRecEnd;
328
+ private _onRecPause;
329
+ private _onRecResume;
176
330
  static styles: lit.CSSResultGroup[];
177
331
  connectedCallback(): void;
178
332
  disconnectedCallback(): void;
@@ -195,87 +349,6 @@ declare global {
195
349
  }
196
350
  }
197
351
 
198
- interface TrackDescriptor {
199
- name: string;
200
- src: string;
201
- volume: number;
202
- pan: number;
203
- muted: boolean;
204
- soloed: boolean;
205
- clips: ClipDescriptor[];
206
- }
207
- /**
208
- * Common fields shared by all clip descriptors regardless of source.
209
- */
210
- interface BaseClipDescriptor {
211
- src: string;
212
- peaksSrc: string;
213
- start: number;
214
- duration: number;
215
- offset: number;
216
- gain: number;
217
- name: string;
218
- fadeIn: number;
219
- fadeOut: number;
220
- fadeType: FadeType;
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
- }
278
-
279
352
  /**
280
353
  * Peak generation pipeline: AudioBuffer → web worker → WaveformData → PeakData.
281
354
  *
@@ -434,6 +507,12 @@ interface RecordingSession {
434
507
  /** Stored so it can be removed on stop/cleanup — not just when stream ends. */
435
508
  readonly _onTrackEnded: (() => void) | null;
436
509
  readonly _audioTrack: MediaStreamTrack | null;
510
+ /** Set during stopRecording, cleared by the done message from the worklet. */
511
+ stopAckResolve: (() => void) | null;
512
+ /** True from the start of stopRecording until the session is deleted.
513
+ * Guards pauseRecording / resumeRecording so a mid-drain pause toggle
514
+ * can't dispatch events for a session that's already terminating. */
515
+ stopping: boolean;
437
516
  }
438
517
  /** Readonly view of a recording session for external consumers. */
439
518
  type ReadonlyRecordingSession = Readonly<Omit<RecordingSession, 'chunks' | 'peaks' | '_onTrackEnded' | '_audioTrack' | 'latencySamples'>> & {
@@ -475,16 +554,21 @@ declare class RecordingController implements ReactiveController {
475
554
  private _host;
476
555
  private _sessions;
477
556
  private _workletLoadedCtx;
557
+ /** Tracks worklet pause state explicitly so external consumers (editor,
558
+ * pause button, spacebar) can share one source of truth. */
559
+ private _isPaused;
478
560
  constructor(host: RecordingHost & HTMLElement);
479
561
  hostConnected(): void;
480
562
  hostDisconnected(): void;
481
563
  get isRecording(): boolean;
564
+ get isPaused(): boolean;
482
565
  getSession(trackId: string): ReadonlyRecordingSession | undefined;
483
566
  startRecording(stream: MediaStream, options?: RecordingOptions): Promise<void>;
484
567
  pauseRecording(trackId?: string): void;
485
568
  resumeRecording(trackId?: string): void;
486
- stopRecording(trackId?: string): void;
569
+ stopRecording(trackId?: string): Promise<void>;
487
570
  private _onWorkletMessage;
571
+ private _processWorkletSamples;
488
572
  private _createClipFromRecording;
489
573
  private _removeTrackEndedListener;
490
574
  private _cleanupSession;
@@ -538,6 +622,11 @@ interface ClipPointerHost {
538
622
  dispatchEvent(event: Event): boolean;
539
623
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
540
624
  reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): ClipPeakSlice | null;
625
+ /**
626
+ * Returns true if the clip is a MIDI clip (has midiNotes).
627
+ * Trim handles are inert for MIDI clips — note slicing is not yet implemented.
628
+ */
629
+ isMidiClip(trackId: string, clipId: string): boolean;
541
630
  }
542
631
  /**
543
632
  * Handles pointer interactions for clip move and trim drag operations.
@@ -646,6 +735,12 @@ interface DawRecordingErrorDetail {
646
735
  trackId: string;
647
736
  error: unknown;
648
737
  }
738
+ interface DawRecordingPauseDetail {
739
+ trackId: string;
740
+ }
741
+ interface DawRecordingResumeDetail {
742
+ trackId: string;
743
+ }
649
744
  interface DawClipConnectedDetail {
650
745
  clipId: string;
651
746
  element: DawClipElement;
@@ -702,6 +797,8 @@ interface DawEventMap {
702
797
  'daw-recording-start': CustomEvent<DawRecordingStartDetail>;
703
798
  'daw-recording-complete': CustomEvent<DawRecordingCompleteDetail>;
704
799
  'daw-recording-error': CustomEvent<DawRecordingErrorDetail>;
800
+ 'daw-recording-pause': CustomEvent<DawRecordingPauseDetail>;
801
+ 'daw-recording-resume': CustomEvent<DawRecordingResumeDetail>;
705
802
  'daw-clip-connected': CustomEvent<DawClipConnectedDetail>;
706
803
  'daw-clip-update': CustomEvent<DawClipUpdateDetail>;
707
804
  'daw-clip-ready': CustomEvent<DawClipIdDetail>;
@@ -801,6 +898,12 @@ declare class DawEditorElement extends LitElement {
801
898
  get renderSamplesPerPixel(): number;
802
899
  /** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
803
900
  reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): PeakData | null;
901
+ /**
902
+ * Returns true if the clip is a MIDI clip (has midiNotes).
903
+ * Used by ClipPointerHandler to make trim handles inert for MIDI clips.
904
+ * Returns false for unknown track/clip IDs (defensive).
905
+ */
906
+ isMidiClip(trackId: string, clipId: string): boolean;
804
907
  private _pointer;
805
908
  private _viewport;
806
909
  static styles: lit.CSSResult[];
@@ -859,6 +962,27 @@ declare class DawEditorElement extends LitElement {
859
962
  * uses sync `extractPeaks` and inserts a preview track BEFORE audio decode.
860
963
  */
861
964
  private _finalizeAudioClip;
965
+ /**
966
+ * Filter MIDI notes to only those with finite, in-range fields. Logs a
967
+ * warning for each dropped note. Used by _buildMidiClip and the
968
+ * _applyClipUpdate MIDI branch to prevent NaN propagation through the
969
+ * timeline.
970
+ */
971
+ private _validMidiNotes;
972
+ /**
973
+ * A clip descriptor is treated as MIDI when it has no audio src.
974
+ * Includes placeholder MIDI clips (no notes, no duration yet — registered
975
+ * with a 1s default span; notes upgrade via _applyClipUpdate). Warns when
976
+ * a clip ambiguously has both src and midiNotes — the audio path runs
977
+ * and notes would be silently ignored.
978
+ */
979
+ private _isMidiDescriptor;
980
+ /**
981
+ * Build an engine clip from a MIDI clip descriptor. Always returns a clip
982
+ * — empty notes / no declared duration get a 1-second placeholder span so
983
+ * the clip is reachable via `engine.updateTrack` once notes arrive.
984
+ */
985
+ private _buildMidiClip;
862
986
  /** Remove a single clip from all per-clip caches. Used by error rollbacks. */
863
987
  private _purgeClipCaches;
864
988
  /**
@@ -963,9 +1087,20 @@ declare class DawEditorElement extends LitElement {
963
1087
  recordingStream: MediaStream | null;
964
1088
  get currentTime(): number;
965
1089
  get isRecording(): boolean;
1090
+ get isRecordingPaused(): boolean;
966
1091
  pauseRecording(): void;
967
1092
  resumeRecording(): void;
968
- stopRecording(): void;
1093
+ /**
1094
+ * Audacity-style pause toggle for active recordings: pauses both the
1095
+ * worklet capture and (if running) the playback Transport. On resume,
1096
+ * Transport restarts only if it was running before — non-overdub
1097
+ * recordings stay silent on resume.
1098
+ */
1099
+ togglePauseRecording(): void;
1100
+ /** Set in togglePauseRecording when Transport is paused alongside the
1101
+ * worklet, so resume can restart it. Cleared on resume and on stop. */
1102
+ private _wasPlayingDuringRecording;
1103
+ stopRecording(): Promise<void>;
969
1104
  _addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
970
1105
  addWorkletModule(url: string): Promise<void>;
971
1106
  createAudioWorkletNode(name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode;
@@ -1149,4 +1284,4 @@ interface SplitHost {
1149
1284
  */
1150
1285
  declare function splitAtPlayhead(host: SplitHost): boolean;
1151
1286
 
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 };
1287
+ 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, DawPianoRollElement, 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 TrackRenderMode, type UndoShortcutMap, type WaveformSegment, isDomClip, splitAtPlayhead };