@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.mjs CHANGED
@@ -174,7 +174,7 @@ var DawTrackElement = class extends LitElement2 {
174
174
  this.pan = 0;
175
175
  this.muted = false;
176
176
  this.soloed = false;
177
- this.renderMode = "waveform";
177
+ this._renderMode = "waveform";
178
178
  this.spectrogramConfig = null;
179
179
  this.trackId = crypto.randomUUID();
180
180
  // Track removal is detected by the editor's MutationObserver,
@@ -182,6 +182,21 @@ var DawTrackElement = class extends LitElement2 {
182
182
  // cannot bubble events to ancestors).
183
183
  this._hasRendered = false;
184
184
  }
185
+ get renderMode() {
186
+ return this._renderMode;
187
+ }
188
+ set renderMode(value) {
189
+ const old = this._renderMode;
190
+ let next = value;
191
+ if (next === "both") {
192
+ console.warn(
193
+ `[dawcore] <daw-track render-mode="both"> is not yet supported; falling back to 'spectrogram'`
194
+ );
195
+ next = "spectrogram";
196
+ }
197
+ this._renderMode = next;
198
+ this.requestUpdate("renderMode", old);
199
+ }
185
200
  // Light DOM so <daw-clip> children are queryable.
186
201
  createRenderRoot() {
187
202
  return this;
@@ -244,8 +259,8 @@ __decorateClass([
244
259
  property2({ type: Boolean })
245
260
  ], DawTrackElement.prototype, "soloed", 2);
246
261
  __decorateClass([
247
- property2({ attribute: "render-mode" })
248
- ], DawTrackElement.prototype, "renderMode", 2);
262
+ property2({ attribute: "render-mode", noAccessor: true })
263
+ ], DawTrackElement.prototype, "renderMode", 1);
249
264
  __decorateClass([
250
265
  property2({ attribute: false })
251
266
  ], DawTrackElement.prototype, "spectrogramConfig", 2);
@@ -2725,15 +2740,10 @@ var RecordingController = class {
2725
2740
  import {
2726
2741
  SpectrogramOrchestrator
2727
2742
  } from "@dawcore/spectrogram";
2728
- var LIBRARY_DEFAULTS = {
2729
- fftSize: 2048,
2730
- windowFunction: "hann",
2731
- frequencyScale: "mel",
2732
- minFrequency: 0,
2733
- gainDb: 20,
2734
- rangeDb: 80
2735
- };
2736
- var LIBRARY_DEFAULT_COLOR_MAP = "viridis";
2743
+ import {
2744
+ SPECTROGRAM_DEFAULTS as LIBRARY_DEFAULTS,
2745
+ DEFAULT_SPECTROGRAM_COLOR_MAP as LIBRARY_DEFAULT_COLOR_MAP
2746
+ } from "@waveform-playlist/core";
2737
2747
  var SpectrogramController = class {
2738
2748
  constructor(host, workerFactory) {
2739
2749
  this.orchestrator = null;
@@ -2807,7 +2817,21 @@ var SpectrogramController = class {
2807
2817
  const detail = e.detail;
2808
2818
  this.host.dispatchEvent(
2809
2819
  new CustomEvent("daw-spectrogram-ready", {
2810
- detail,
2820
+ detail: { trackId: detail.trackId, generation: detail.generation },
2821
+ bubbles: true,
2822
+ composed: true
2823
+ })
2824
+ );
2825
+ });
2826
+ this.orchestrator.addEventListener("viewport-error", (e) => {
2827
+ const detail = e.detail;
2828
+ this.host.dispatchEvent(
2829
+ new CustomEvent("daw-spectrogram-error", {
2830
+ detail: {
2831
+ trackId: detail.trackId,
2832
+ generation: detail.generation,
2833
+ error: detail.error
2834
+ },
2811
2835
  bubbles: true,
2812
2836
  composed: true
2813
2837
  })
@@ -3446,6 +3470,108 @@ async function loadFiles(host, files) {
3446
3470
  return { loaded, failed };
3447
3471
  }
3448
3472
 
3473
+ // src/interactions/midi-loader.ts
3474
+ var INSTALL_HINT = "@dawcore/midi is required for loadMidi(). Install with: npm install @dawcore/midi";
3475
+ async function loadMidiImpl(host, source, options = {}) {
3476
+ const startTime = options.startTime ?? 0;
3477
+ if (!Number.isFinite(startTime) || startTime < 0) {
3478
+ throw new RangeError(
3479
+ "loadMidi: startTime must be a non-negative finite number (got " + String(options.startTime) + ")"
3480
+ );
3481
+ }
3482
+ let midiModule;
3483
+ try {
3484
+ midiModule = await import("@dawcore/midi");
3485
+ } catch (originalErr) {
3486
+ console.warn("[dawcore] @dawcore/midi dynamic import failed: " + String(originalErr));
3487
+ throw new Error(INSTALL_HINT);
3488
+ }
3489
+ const { parseMidiUrl, parseMidiFile } = midiModule;
3490
+ let parsed;
3491
+ if (typeof source === "string") {
3492
+ parsed = await parseMidiUrl(source, void 0, options.signal);
3493
+ } else {
3494
+ let buffer;
3495
+ try {
3496
+ buffer = await source.arrayBuffer();
3497
+ } catch (err) {
3498
+ throw new Error(
3499
+ 'loadMidi: failed to read File "' + source.name + '" (' + source.size + " bytes): " + String(err)
3500
+ );
3501
+ }
3502
+ parsed = parseMidiFile(buffer);
3503
+ }
3504
+ const childrenBefore = new Set(host.querySelectorAll("daw-track"));
3505
+ const settlements = await Promise.allSettled(
3506
+ parsed.tracks.map(
3507
+ (t) => host.addTrack({
3508
+ name: t.name,
3509
+ renderMode: "piano-roll",
3510
+ clips: [
3511
+ {
3512
+ midiNotes: t.notes,
3513
+ midiChannel: t.channel,
3514
+ midiProgram: t.programNumber,
3515
+ start: startTime
3516
+ }
3517
+ ]
3518
+ })
3519
+ )
3520
+ );
3521
+ const succeeded = [];
3522
+ const rejections = [];
3523
+ for (const s of settlements) {
3524
+ if (s.status === "fulfilled") {
3525
+ succeeded.push(s.value);
3526
+ } else {
3527
+ rejections.push(s.reason);
3528
+ }
3529
+ }
3530
+ if (rejections.length > 0) {
3531
+ const appendedTracks = Array.from(host.querySelectorAll("daw-track")).filter(
3532
+ (el) => !childrenBefore.has(el)
3533
+ );
3534
+ for (const el of appendedTracks) {
3535
+ try {
3536
+ el.remove();
3537
+ } catch (cleanupErr) {
3538
+ console.warn("[dawcore] loadMidi cleanup failed for a track: " + String(cleanupErr));
3539
+ }
3540
+ }
3541
+ await Promise.resolve();
3542
+ for (let i = 1; i < rejections.length; i++) {
3543
+ console.warn(
3544
+ "[dawcore] loadMidi: additional track failure (" + i + "): " + stringifyReason(rejections[i])
3545
+ );
3546
+ }
3547
+ const first = rejections[0];
3548
+ if (rejections.length > 1) {
3549
+ const message = "loadMidi: " + rejections.length + " of " + settlements.length + " tracks failed; first: " + (first instanceof Error ? first.message : stringifyReason(first));
3550
+ throw new Error(message);
3551
+ }
3552
+ throw first instanceof Error ? first : new Error(stringifyReason(first));
3553
+ }
3554
+ return {
3555
+ trackIds: succeeded.map((el) => el.trackId),
3556
+ bpm: parsed.bpm,
3557
+ timeSignature: parsed.timeSignature,
3558
+ duration: parsed.duration,
3559
+ name: parsed.name
3560
+ };
3561
+ }
3562
+ function stringifyReason(reason) {
3563
+ if (reason === null) return "null";
3564
+ if (reason === void 0) return "undefined";
3565
+ if (typeof reason === "object") {
3566
+ try {
3567
+ return JSON.stringify(reason);
3568
+ } catch {
3569
+ return Object.prototype.toString.call(reason);
3570
+ }
3571
+ }
3572
+ return String(reason);
3573
+ }
3574
+
3449
3575
  // src/interactions/recording-clip.ts
3450
3576
  import { createClip as createClip2 } from "@waveform-playlist/core";
3451
3577
  function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamples = 0) {
@@ -3784,7 +3910,10 @@ var DawEditorElement = class extends LitElement10 {
3784
3910
  * `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
3785
3911
  * viewport. Skip the cross-controller call when nothing changed.
3786
3912
  *
3787
- * The orchestrator dedupes too, but this avoids the call entirely.
3913
+ * The orchestrator dedupes identical viewports too, so removing this cache
3914
+ * wouldn't change observable behavior — but it would push a fresh
3915
+ * `setViewport` call (with object allocation) into every Lit reactive
3916
+ * update for properties unrelated to the viewport.
3788
3917
  */
3789
3918
  this._lastSpectrogramViewport = null;
3790
3919
  // --- Track Events ---
@@ -4027,9 +4156,10 @@ var DawEditorElement = class extends LitElement10 {
4027
4156
  this._spectrogramController?.unregisterCanvas(canvasId);
4028
4157
  }
4029
4158
  /**
4030
- * Push a clip's decoded audio into the spectrogram controller. No-op
4031
- * unless the track is in spectrogram render-mode and the controller
4032
- * already exists (it bootstraps from canvas registration).
4159
+ * Forward a clip's AudioBuffer to the spectrogram controller if the parent
4160
+ * track is in spectrogram render-mode. Eagerly creates the controller via
4161
+ * `_ensureSpectrogramController` so the audio data is queued for the first
4162
+ * render — even if no canvases have been registered yet.
4033
4163
  */
4034
4164
  _maybeRegisterSpectrogramClipAudio(trackId, clip) {
4035
4165
  const descriptor = this._tracks.get(trackId);
@@ -5017,6 +5147,22 @@ var DawEditorElement = class extends LitElement10 {
5017
5147
  async loadFiles(files) {
5018
5148
  return loadFiles(this, files);
5019
5149
  }
5150
+ /**
5151
+ * Imperatively load a `.mid` file (URL or File) and create N `<daw-track>`
5152
+ * elements — one per note-bearing MIDI track. On any per-track failure,
5153
+ * every `<daw-track>` appended during the call is removed (both successful
5154
+ * and failed) so the editor returns to its pre-call state.
5155
+ *
5156
+ * `options.signal` is forwarded to `fetch()` only for URL sources; aborting
5157
+ * after parsing does not cancel in-flight `addTrack` calls.
5158
+ *
5159
+ * Requires the optional `@dawcore/midi` peer dep — throws with an install
5160
+ * hint (and `console.warn`s the original error) when the dynamic import
5161
+ * fails for any reason.
5162
+ */
5163
+ async loadMidi(source, options) {
5164
+ return loadMidiImpl(this, source, options);
5165
+ }
5020
5166
  // --- Programmatic Track API ---
5021
5167
  /**
5022
5168
  * Build the engine if it hasn't been built yet. Lets consumers obtain a
@@ -5130,6 +5276,13 @@ var DawEditorElement = class extends LitElement10 {
5130
5276
  }
5131
5277
  const oldDesc = this._tracks.get(trackId);
5132
5278
  if (!oldDesc) return;
5279
+ let normalizedRenderMode = partial.renderMode;
5280
+ if (normalizedRenderMode === "both") {
5281
+ console.warn(
5282
+ `[dawcore] render-mode="both" is not yet supported; falling back to 'spectrogram'`
5283
+ );
5284
+ normalizedRenderMode = "spectrogram";
5285
+ }
5133
5286
  const newDesc = {
5134
5287
  ...oldDesc,
5135
5288
  ...partial.name !== void 0 && { name: partial.name },
@@ -5137,7 +5290,7 @@ var DawEditorElement = class extends LitElement10 {
5137
5290
  ...partial.pan !== void 0 && { pan: partial.pan },
5138
5291
  ...partial.muted !== void 0 && { muted: partial.muted },
5139
5292
  ...partial.soloed !== void 0 && { soloed: partial.soloed },
5140
- ...partial.renderMode !== void 0 && { renderMode: partial.renderMode }
5293
+ ...normalizedRenderMode !== void 0 && { renderMode: normalizedRenderMode }
5141
5294
  };
5142
5295
  this._tracks = new Map(this._tracks).set(trackId, newDesc);
5143
5296
  if (this._engine) {
@@ -6434,6 +6587,7 @@ var DawSpectrogramElement = class extends LitElement14 {
6434
6587
  this.originX = 0;
6435
6588
  this._canvases = [];
6436
6589
  this._registeredCanvasIds = [];
6590
+ this._warnedNoHost = false;
6437
6591
  }
6438
6592
  get samplesPerPixel() {
6439
6593
  return this._samplesPerPixel;
@@ -6500,7 +6654,15 @@ var DawSpectrogramElement = class extends LitElement14 {
6500
6654
  }
6501
6655
  _registerCanvases() {
6502
6656
  const editor = this._findHostEditor();
6503
- if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") return;
6657
+ if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") {
6658
+ if (!this._warnedNoHost) {
6659
+ this._warnedNoHost = true;
6660
+ console.warn(
6661
+ "[dawcore] <daw-spectrogram> (clip " + this.clipId + ") could not find host <daw-editor>. Canvases will not render. Ensure the element is mounted inside a <daw-editor>."
6662
+ );
6663
+ }
6664
+ return;
6665
+ }
6504
6666
  for (let i = 0; i < this._canvases.length; i++) {
6505
6667
  const canvas = this._canvases[i];
6506
6668
  const canvasId = this.clipId + "-ch" + this.channelIndex + "-chunk" + i;