@dawcore/components 0.0.18 → 0.0.20

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,13 +1,20 @@
1
1
  import * as lit from 'lit';
2
2
  import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
3
- import { MidiNoteData, SpectrogramConfig, FadeType, Peaks, Bits, PeakData, MeterEntry, SnapTo, ColorMapValue, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
3
+ import { MidiNoteData, RenderMode, SpectrogramConfig, Peaks, Bits, FadeType, PeakData, MeterEntry, SnapTo, ColorMapValue, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
4
4
  import WaveformData from 'waveform-data';
5
5
  import { PlayoutAdapter, PlaylistEngine } from '@waveform-playlist/engine';
6
+ import { MidiLoadOptions, MidiLoadResult } from '@dawcore/midi';
6
7
  import { ClipRegistration, CanvasRegistration, ViewportState } from '@dawcore/spectrogram';
7
8
 
8
9
  declare class DawClipElement extends LitElement {
9
10
  src: string;
10
11
  peaksSrc: string;
12
+ /**
13
+ * Timeline position in seconds. JS property only — NOT reflected to the
14
+ * `start` attribute (Lit's `reflect` defaults to false). Tests that need
15
+ * to assert clip position must read this property directly; reading
16
+ * `el.getAttribute('start')` returns `null` regardless of correctness.
17
+ */
11
18
  start: number;
12
19
  duration: number;
13
20
  offset: number;
@@ -39,113 +46,6 @@ declare global {
39
46
  }
40
47
  }
41
48
 
42
- type TrackRenderMode = 'waveform' | 'piano-roll' | 'spectrogram';
43
- interface TrackDescriptor {
44
- name: string;
45
- src: string;
46
- volume: number;
47
- pan: number;
48
- muted: boolean;
49
- soloed: boolean;
50
- renderMode: TrackRenderMode;
51
- spectrogramConfig?: SpectrogramConfig | null;
52
- clips: ClipDescriptor[];
53
- }
54
- /**
55
- * Common fields shared by all clip descriptors regardless of source.
56
- */
57
- interface BaseClipDescriptor {
58
- src: string;
59
- peaksSrc: string;
60
- start: number;
61
- duration: number;
62
- offset: number;
63
- gain: number;
64
- name: string;
65
- fadeIn: number;
66
- fadeOut: number;
67
- fadeType: FadeType;
68
- midiNotes: MidiNoteData[] | null;
69
- midiChannel: number | null;
70
- midiProgram: number | null;
71
- }
72
- /**
73
- * A clip descriptor sourced from a `<daw-clip>` DOM element. `clipId` is
74
- * always set — `<daw-clip>.clipId` is a `crypto.randomUUID()` generated at
75
- * construction. Engine `clip.id` is aligned with this id in `_loadTrack`
76
- * and `_loadAndAppendClip` so DOM and engine reference the same clip.
77
- */
78
- interface DomClipDescriptor extends BaseClipDescriptor {
79
- kind: 'dom';
80
- clipId: string;
81
- }
82
- /**
83
- * A clip descriptor synthesized from a non-DOM source — file drops, the
84
- * `<daw-track src>` shorthand fallback, or recording-clip insertion. No
85
- * `clipId` because there's no DOM element to align with; the engine
86
- * generates its own id at clip-creation time.
87
- */
88
- interface DropClipDescriptor extends BaseClipDescriptor {
89
- kind: 'drop';
90
- }
91
- type ClipDescriptor = DomClipDescriptor | DropClipDescriptor;
92
- /**
93
- * Type predicate for the `'dom'` discriminator. Use to narrow a
94
- * `ClipDescriptor` to `DomClipDescriptor` (which has `clipId`) without
95
- * inline `c.kind === 'dom'` repetition.
96
- */
97
- declare function isDomClip(desc: ClipDescriptor): desc is DomClipDescriptor;
98
- /**
99
- * Public input shape for `editor.addTrack(config)`. All fields optional —
100
- * defaults match the declarative `<daw-track>` defaults.
101
- */
102
- interface TrackConfig {
103
- name?: string;
104
- volume?: number;
105
- pan?: number;
106
- muted?: boolean;
107
- soloed?: boolean;
108
- renderMode?: TrackRenderMode;
109
- spectrogramConfig?: SpectrogramConfig | null;
110
- clips?: ClipConfig[];
111
- /**
112
- * Convenience: creates a single piano-roll `<daw-clip>` child with these
113
- * notes and sets `render-mode="piano-roll"` on the track. Equivalent to
114
- * passing `{ renderMode: 'piano-roll', clips: [{ midiNotes, midiChannel, midiProgram }] }`.
115
- * An explicit `renderMode` takes precedence over the inferred `'piano-roll'`.
116
- *
117
- * Creation-only — ignored by `updateTrack`. To modify notes after the
118
- * track is built, use `editor.updateClip(trackId, clipId, { midiNotes })` or
119
- * mutate the `<daw-clip>` element's `midiNotes` property directly.
120
- */
121
- midi?: {
122
- notes: MidiNoteData[];
123
- channel?: number;
124
- program?: number;
125
- };
126
- }
127
- /**
128
- * Public input shape for clips passed via `TrackConfig.clips` or
129
- * `editor.addClip(trackId, config)`. `src` is optional to support MIDI clips
130
- * with no audio source. Other fields default to the matching `<daw-clip>`
131
- * attribute defaults.
132
- */
133
- interface ClipConfig {
134
- src?: string;
135
- peaksSrc?: string;
136
- start?: number;
137
- duration?: number;
138
- offset?: number;
139
- gain?: number;
140
- name?: string;
141
- fadeIn?: number;
142
- fadeOut?: number;
143
- fadeType?: FadeType;
144
- midiNotes?: MidiNoteData[];
145
- midiChannel?: number;
146
- midiProgram?: number;
147
- }
148
-
149
49
  declare class DawTrackElement extends LitElement {
150
50
  src: string;
151
51
  name: string;
@@ -153,7 +53,9 @@ declare class DawTrackElement extends LitElement {
153
53
  pan: number;
154
54
  muted: boolean;
155
55
  soloed: boolean;
156
- renderMode: TrackRenderMode;
56
+ get renderMode(): RenderMode;
57
+ set renderMode(value: RenderMode);
58
+ private _renderMode;
157
59
  spectrogramConfig: SpectrogramConfig | null;
158
60
  readonly trackId: `${string}-${string}-${string}-${string}-${string}`;
159
61
  createRenderRoot(): this;
@@ -353,6 +255,113 @@ declare global {
353
255
  }
354
256
  }
355
257
 
258
+ type TrackRenderMode = RenderMode;
259
+ interface TrackDescriptor {
260
+ name: string;
261
+ src: string;
262
+ volume: number;
263
+ pan: number;
264
+ muted: boolean;
265
+ soloed: boolean;
266
+ renderMode: TrackRenderMode;
267
+ spectrogramConfig?: SpectrogramConfig | null;
268
+ clips: ClipDescriptor[];
269
+ }
270
+ /**
271
+ * Common fields shared by all clip descriptors regardless of source.
272
+ */
273
+ interface BaseClipDescriptor {
274
+ src: string;
275
+ peaksSrc: string;
276
+ start: number;
277
+ duration: number;
278
+ offset: number;
279
+ gain: number;
280
+ name: string;
281
+ fadeIn: number;
282
+ fadeOut: number;
283
+ fadeType: FadeType;
284
+ midiNotes: MidiNoteData[] | null;
285
+ midiChannel: number | null;
286
+ midiProgram: number | null;
287
+ }
288
+ /**
289
+ * A clip descriptor sourced from a `<daw-clip>` DOM element. `clipId` is
290
+ * always set — `<daw-clip>.clipId` is a `crypto.randomUUID()` generated at
291
+ * construction. Engine `clip.id` is aligned with this id in `_loadTrack`
292
+ * and `_loadAndAppendClip` so DOM and engine reference the same clip.
293
+ */
294
+ interface DomClipDescriptor extends BaseClipDescriptor {
295
+ kind: 'dom';
296
+ clipId: string;
297
+ }
298
+ /**
299
+ * A clip descriptor synthesized from a non-DOM source — file drops, the
300
+ * `<daw-track src>` shorthand fallback, or recording-clip insertion. No
301
+ * `clipId` because there's no DOM element to align with; the engine
302
+ * generates its own id at clip-creation time.
303
+ */
304
+ interface DropClipDescriptor extends BaseClipDescriptor {
305
+ kind: 'drop';
306
+ }
307
+ type ClipDescriptor = DomClipDescriptor | DropClipDescriptor;
308
+ /**
309
+ * Type predicate for the `'dom'` discriminator. Use to narrow a
310
+ * `ClipDescriptor` to `DomClipDescriptor` (which has `clipId`) without
311
+ * inline `c.kind === 'dom'` repetition.
312
+ */
313
+ declare function isDomClip(desc: ClipDescriptor): desc is DomClipDescriptor;
314
+ /**
315
+ * Public input shape for `editor.addTrack(config)`. All fields optional —
316
+ * defaults match the declarative `<daw-track>` defaults.
317
+ */
318
+ interface TrackConfig {
319
+ name?: string;
320
+ volume?: number;
321
+ pan?: number;
322
+ muted?: boolean;
323
+ soloed?: boolean;
324
+ renderMode?: TrackRenderMode;
325
+ spectrogramConfig?: SpectrogramConfig | null;
326
+ clips?: ClipConfig[];
327
+ /**
328
+ * Convenience: creates a single piano-roll `<daw-clip>` child with these
329
+ * notes and sets `render-mode="piano-roll"` on the track. Equivalent to
330
+ * passing `{ renderMode: 'piano-roll', clips: [{ midiNotes, midiChannel, midiProgram }] }`.
331
+ * An explicit `renderMode` takes precedence over the inferred `'piano-roll'`.
332
+ *
333
+ * Creation-only — ignored by `updateTrack`. To modify notes after the
334
+ * track is built, use `editor.updateClip(trackId, clipId, { midiNotes })` or
335
+ * mutate the `<daw-clip>` element's `midiNotes` property directly.
336
+ */
337
+ midi?: {
338
+ notes: MidiNoteData[];
339
+ channel?: number;
340
+ program?: number;
341
+ };
342
+ }
343
+ /**
344
+ * Public input shape for clips passed via `TrackConfig.clips` or
345
+ * `editor.addClip(trackId, config)`. `src` is optional to support MIDI clips
346
+ * with no audio source. Other fields default to the matching `<daw-clip>`
347
+ * attribute defaults.
348
+ */
349
+ interface ClipConfig {
350
+ src?: string;
351
+ peaksSrc?: string;
352
+ start?: number;
353
+ duration?: number;
354
+ offset?: number;
355
+ gain?: number;
356
+ name?: string;
357
+ fadeIn?: number;
358
+ fadeOut?: number;
359
+ fadeType?: FadeType;
360
+ midiNotes?: MidiNoteData[];
361
+ midiChannel?: number;
362
+ midiProgram?: number;
363
+ }
364
+
356
365
  /**
357
366
  * Peak generation pipeline: AudioBuffer → web worker → WaveformData → PeakData.
358
367
  *
@@ -785,6 +794,12 @@ interface DawClipSplitDetail {
785
794
  }
786
795
  interface DawSpectrogramReadyDetail {
787
796
  trackId: string;
797
+ generation: number;
798
+ }
799
+ interface DawSpectrogramErrorDetail {
800
+ trackId: string;
801
+ generation: number;
802
+ error: Error;
788
803
  }
789
804
  interface DawEventMap {
790
805
  'daw-selection': CustomEvent<DawSelectionDetail>;
@@ -814,6 +829,7 @@ interface DawEventMap {
814
829
  'daw-clip-trim': CustomEvent<DawClipTrimDetail>;
815
830
  'daw-clip-split': CustomEvent<DawClipSplitDetail>;
816
831
  'daw-spectrogram-ready': CustomEvent<DawSpectrogramReadyDetail>;
832
+ 'daw-spectrogram-error': CustomEvent<DawSpectrogramErrorDetail>;
817
833
  }
818
834
  type DawEvent<K extends keyof DawEventMap> = DawEventMap[K];
819
835
  interface LoadFilesResult {
@@ -824,7 +840,29 @@ interface LoadFilesResult {
824
840
  }>;
825
841
  }
826
842
 
827
- declare class DawEditorElement extends LitElement {
843
+ /**
844
+ * MIDI loading logic extracted from daw-editor. Operates on the editor via a
845
+ * narrow host interface (`addTrack` + `querySelectorAll`) — `<daw-editor>`
846
+ * satisfies it without any new public surface.
847
+ *
848
+ * Numbered steps below match the Data Flow diagram in
849
+ * `docs/specs/2026-05-23-dawcore-load-midi-design.md`.
850
+ */
851
+
852
+ /**
853
+ * Minimal host surface needed by `loadMidiImpl`. `<daw-editor>` satisfies this
854
+ * structurally. `querySelectorAll` is needed for cleanup-on-failure so the
855
+ * loader can identify `<daw-track>` elements appended during this call (both
856
+ * those whose `addTrack` resolved and those that rejected after `_loadTrack`
857
+ * fired `daw-track-error` — the latter aren't in the `addTrack` resolution
858
+ * value, so we need DOM observation to find them).
859
+ */
860
+ interface MidiLoaderHost {
861
+ addTrack(config: TrackConfig): Promise<DawTrackElement>;
862
+ querySelectorAll(selector: string): NodeListOf<Element>;
863
+ }
864
+
865
+ declare class DawEditorElement extends LitElement implements MidiLoaderHost {
828
866
  get samplesPerPixel(): number;
829
867
  set samplesPerPixel(value: number);
830
868
  private _samplesPerPixel;
@@ -877,9 +915,10 @@ declare class DawEditorElement extends LitElement {
877
915
  /** Called by <daw-spectrogram> on chunk unmount / element disconnect. */
878
916
  _spectrogramUnregisterCanvas(canvasId: string): void;
879
917
  /**
880
- * Push a clip's decoded audio into the spectrogram controller. No-op
881
- * unless the track is in spectrogram render-mode and the controller
882
- * already exists (it bootstraps from canvas registration).
918
+ * Forward a clip's AudioBuffer to the spectrogram controller if the parent
919
+ * track is in spectrogram render-mode. Eagerly creates the controller via
920
+ * `_ensureSpectrogramController` so the audio data is queued for the first
921
+ * render — even if no canvases have been registered yet.
883
922
  */
884
923
  private _maybeRegisterSpectrogramClipAudio;
885
924
  scaleMode: 'temporal' | 'beats';
@@ -985,7 +1024,10 @@ declare class DawEditorElement extends LitElement {
985
1024
  * `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
986
1025
  * viewport. Skip the cross-controller call when nothing changed.
987
1026
  *
988
- * The orchestrator dedupes too, but this avoids the call entirely.
1027
+ * The orchestrator dedupes identical viewports too, so removing this cache
1028
+ * wouldn't change observable behavior — but it would push a fresh
1029
+ * `setViewport` call (with object allocation) into every Lit reactive
1030
+ * update for properties unrelated to the viewport.
989
1031
  */
990
1032
  private _lastSpectrogramViewport;
991
1033
  protected updated(_changed: Map<string, unknown>): void;
@@ -1066,6 +1108,20 @@ declare class DawEditorElement extends LitElement {
1066
1108
  private _onDragLeave;
1067
1109
  private _onDrop;
1068
1110
  loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
1111
+ /**
1112
+ * Imperatively load a `.mid` file (URL or File) and create N `<daw-track>`
1113
+ * elements — one per note-bearing MIDI track. On any per-track failure,
1114
+ * every `<daw-track>` appended during the call is removed (both successful
1115
+ * and failed) so the editor returns to its pre-call state.
1116
+ *
1117
+ * `options.signal` is forwarded to `fetch()` only for URL sources; aborting
1118
+ * after parsing does not cancel in-flight `addTrack` calls.
1119
+ *
1120
+ * Requires the optional `@dawcore/midi` peer dep — throws with an install
1121
+ * hint (and `console.warn`s the original error) when the dynamic import
1122
+ * fails for any reason.
1123
+ */
1124
+ loadMidi(source: string | File, options?: MidiLoadOptions): Promise<MidiLoadResult>;
1069
1125
  /**
1070
1126
  * Build the engine if it hasn't been built yet. Lets consumers obtain a
1071
1127
  * non-null `editor.engine` before any track has been loaded — useful for
@@ -1306,6 +1362,7 @@ declare class DawSpectrogramElement extends LitElement {
1306
1362
  static styles: lit.CSSResult;
1307
1363
  private _canvases;
1308
1364
  private _registeredCanvasIds;
1365
+ private _warnedNoHost;
1309
1366
  /**
1310
1367
  * Walk up to the editor host. `closest('daw-editor')` does NOT cross
1311
1368
  * shadow boundaries — and this element lives inside the editor's shadow