@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.js CHANGED
@@ -230,7 +230,7 @@ var DawTrackElement = class extends import_lit2.LitElement {
230
230
  this.pan = 0;
231
231
  this.muted = false;
232
232
  this.soloed = false;
233
- this.renderMode = "waveform";
233
+ this._renderMode = "waveform";
234
234
  this.spectrogramConfig = null;
235
235
  this.trackId = crypto.randomUUID();
236
236
  // Track removal is detected by the editor's MutationObserver,
@@ -238,6 +238,21 @@ var DawTrackElement = class extends import_lit2.LitElement {
238
238
  // cannot bubble events to ancestors).
239
239
  this._hasRendered = false;
240
240
  }
241
+ get renderMode() {
242
+ return this._renderMode;
243
+ }
244
+ set renderMode(value) {
245
+ const old = this._renderMode;
246
+ let next = value;
247
+ if (next === "both") {
248
+ console.warn(
249
+ `[dawcore] <daw-track render-mode="both"> is not yet supported; falling back to 'spectrogram'`
250
+ );
251
+ next = "spectrogram";
252
+ }
253
+ this._renderMode = next;
254
+ this.requestUpdate("renderMode", old);
255
+ }
241
256
  // Light DOM so <daw-clip> children are queryable.
242
257
  createRenderRoot() {
243
258
  return this;
@@ -300,8 +315,8 @@ __decorateClass([
300
315
  (0, import_decorators2.property)({ type: Boolean })
301
316
  ], DawTrackElement.prototype, "soloed", 2);
302
317
  __decorateClass([
303
- (0, import_decorators2.property)({ attribute: "render-mode" })
304
- ], DawTrackElement.prototype, "renderMode", 2);
318
+ (0, import_decorators2.property)({ attribute: "render-mode", noAccessor: true })
319
+ ], DawTrackElement.prototype, "renderMode", 1);
305
320
  __decorateClass([
306
321
  (0, import_decorators2.property)({ attribute: false })
307
322
  ], DawTrackElement.prototype, "spectrogramConfig", 2);
@@ -1246,7 +1261,7 @@ function isDomClip(desc) {
1246
1261
  }
1247
1262
 
1248
1263
  // src/elements/daw-editor.ts
1249
- var import_core8 = require("@waveform-playlist/core");
1264
+ var import_core9 = require("@waveform-playlist/core");
1250
1265
 
1251
1266
  // src/workers/peaksWorker.ts
1252
1267
  var import_waveform_data = __toESM(require("waveform-data"));
@@ -2774,15 +2789,7 @@ var RecordingController = class {
2774
2789
 
2775
2790
  // src/controllers/spectrogram-controller.ts
2776
2791
  var import_spectrogram = require("@dawcore/spectrogram");
2777
- var LIBRARY_DEFAULTS = {
2778
- fftSize: 2048,
2779
- windowFunction: "hann",
2780
- frequencyScale: "mel",
2781
- minFrequency: 0,
2782
- gainDb: 20,
2783
- rangeDb: 80
2784
- };
2785
- var LIBRARY_DEFAULT_COLOR_MAP = "viridis";
2792
+ var import_core4 = require("@waveform-playlist/core");
2786
2793
  var SpectrogramController = class {
2787
2794
  constructor(host, workerFactory) {
2788
2795
  this.orchestrator = null;
@@ -2856,7 +2863,21 @@ var SpectrogramController = class {
2856
2863
  const detail = e.detail;
2857
2864
  this.host.dispatchEvent(
2858
2865
  new CustomEvent("daw-spectrogram-ready", {
2859
- detail,
2866
+ detail: { trackId: detail.trackId, generation: detail.generation },
2867
+ bubbles: true,
2868
+ composed: true
2869
+ })
2870
+ );
2871
+ });
2872
+ this.orchestrator.addEventListener("viewport-error", (e) => {
2873
+ const detail = e.detail;
2874
+ this.host.dispatchEvent(
2875
+ new CustomEvent("daw-spectrogram-error", {
2876
+ detail: {
2877
+ trackId: detail.trackId,
2878
+ generation: detail.generation,
2879
+ error: detail.error
2880
+ },
2860
2881
  bubbles: true,
2861
2882
  composed: true
2862
2883
  })
@@ -2877,18 +2898,18 @@ var SpectrogramController = class {
2877
2898
  track = c;
2878
2899
  break;
2879
2900
  }
2880
- return { ...LIBRARY_DEFAULTS, ...this.editorConfig ?? {}, ...track ?? {} };
2901
+ return { ...import_core4.SPECTROGRAM_DEFAULTS, ...this.editorConfig ?? {}, ...track ?? {} };
2881
2902
  }
2882
2903
  mergedColorMap() {
2883
2904
  for (const c of this.trackColorMaps.values()) {
2884
- return c ?? LIBRARY_DEFAULT_COLOR_MAP;
2905
+ return c ?? import_core4.DEFAULT_SPECTROGRAM_COLOR_MAP;
2885
2906
  }
2886
- return this.editorColorMap ?? LIBRARY_DEFAULT_COLOR_MAP;
2907
+ return this.editorColorMap ?? import_core4.DEFAULT_SPECTROGRAM_COLOR_MAP;
2887
2908
  }
2888
2909
  };
2889
2910
 
2890
2911
  // src/interactions/pointer-handler.ts
2891
- var import_core4 = require("@waveform-playlist/core");
2912
+ var import_core5 = require("@waveform-playlist/core");
2892
2913
 
2893
2914
  // src/interactions/constants.ts
2894
2915
  var DRAG_THRESHOLD = 3;
@@ -2998,10 +3019,10 @@ var PointerHandler = class {
2998
3019
  const h = this._host;
2999
3020
  if (h.scaleMode === "beats") {
3000
3021
  let tick = px * h.ticksPerPixel;
3001
- tick = (0, import_core4.snapTickToGrid)(tick, h.snapTo, h._meterEntries, h.ppqn);
3022
+ tick = (0, import_core5.snapTickToGrid)(tick, h.snapTo, h._meterEntries, h.ppqn);
3002
3023
  return h._ticksToSeconds(tick);
3003
3024
  }
3004
- return (0, import_core4.pixelsToSeconds)(px, h.samplesPerPixel, h.effectiveSampleRate);
3025
+ return (0, import_core5.pixelsToSeconds)(px, h.samplesPerPixel, h.effectiveSampleRate);
3005
3026
  }
3006
3027
  _timeToPx(time) {
3007
3028
  const h = this._host;
@@ -3093,7 +3114,7 @@ var PointerHandler = class {
3093
3114
  };
3094
3115
 
3095
3116
  // src/interactions/clip-pointer-handler.ts
3096
- var import_core5 = require("@waveform-playlist/core");
3117
+ var import_core6 = require("@waveform-playlist/core");
3097
3118
  var ClipPointerHandler = class {
3098
3119
  constructor(host) {
3099
3120
  this._mode = null;
@@ -3127,7 +3148,7 @@ var ClipPointerHandler = class {
3127
3148
  const anchorTick = h._secondsToTicks(anchorSeconds);
3128
3149
  const deltaTicks = totalDeltaPx * h.ticksPerPixel;
3129
3150
  const targetTick = anchorTick + deltaTicks;
3130
- const snappedTick = h.snapTo !== "off" ? (0, import_core5.snapTickToGrid)(targetTick, h.snapTo, h._meterEntries, h.ppqn) : targetTick;
3151
+ const snappedTick = h.snapTo !== "off" ? (0, import_core6.snapTickToGrid)(targetTick, h.snapTo, h._meterEntries, h.ppqn) : targetTick;
3131
3152
  const snappedSeconds = h._ticksToSeconds(snappedTick);
3132
3153
  const snappedSample = Math.round(snappedSeconds * h.effectiveSampleRate);
3133
3154
  return snappedSample - anchorSample;
@@ -3390,7 +3411,7 @@ var ClipPointerHandler = class {
3390
3411
  };
3391
3412
 
3392
3413
  // src/interactions/file-loader.ts
3393
- var import_core6 = require("@waveform-playlist/core");
3414
+ var import_core7 = require("@waveform-playlist/core");
3394
3415
  async function loadFiles(host, files) {
3395
3416
  if (!files) {
3396
3417
  console.warn("[dawcore] loadFiles called with null/undefined");
@@ -3412,7 +3433,7 @@ async function loadFiles(host, files) {
3412
3433
  host._audioCache.delete(blobUrl);
3413
3434
  host._resolvedSampleRate = audioBuffer.sampleRate;
3414
3435
  const name = file.name.replace(/\.\w+$/, "");
3415
- const clip = (0, import_core6.createClip)({
3436
+ const clip = (0, import_core7.createClip)({
3416
3437
  audioBuffer,
3417
3438
  startSample: 0,
3418
3439
  durationSamples: audioBuffer.length,
@@ -3436,7 +3457,7 @@ async function loadFiles(host, files) {
3436
3457
  );
3437
3458
  host._peaksData = new Map(host._peaksData).set(clip.id, peakData);
3438
3459
  const trackId = crypto.randomUUID();
3439
- const track = (0, import_core6.createTrack)({ name, clips: [clip] });
3460
+ const track = (0, import_core7.createTrack)({ name, clips: [clip] });
3440
3461
  track.id = trackId;
3441
3462
  host._tracks = new Map(host._tracks).set(trackId, {
3442
3463
  name,
@@ -3495,8 +3516,110 @@ async function loadFiles(host, files) {
3495
3516
  return { loaded, failed };
3496
3517
  }
3497
3518
 
3519
+ // src/interactions/midi-loader.ts
3520
+ var INSTALL_HINT = "@dawcore/midi is required for loadMidi(). Install with: npm install @dawcore/midi";
3521
+ async function loadMidiImpl(host, source, options = {}) {
3522
+ const startTime = options.startTime ?? 0;
3523
+ if (!Number.isFinite(startTime) || startTime < 0) {
3524
+ throw new RangeError(
3525
+ "loadMidi: startTime must be a non-negative finite number (got " + String(options.startTime) + ")"
3526
+ );
3527
+ }
3528
+ let midiModule;
3529
+ try {
3530
+ midiModule = await import("@dawcore/midi");
3531
+ } catch (originalErr) {
3532
+ console.warn("[dawcore] @dawcore/midi dynamic import failed: " + String(originalErr));
3533
+ throw new Error(INSTALL_HINT);
3534
+ }
3535
+ const { parseMidiUrl, parseMidiFile } = midiModule;
3536
+ let parsed;
3537
+ if (typeof source === "string") {
3538
+ parsed = await parseMidiUrl(source, void 0, options.signal);
3539
+ } else {
3540
+ let buffer;
3541
+ try {
3542
+ buffer = await source.arrayBuffer();
3543
+ } catch (err) {
3544
+ throw new Error(
3545
+ 'loadMidi: failed to read File "' + source.name + '" (' + source.size + " bytes): " + String(err)
3546
+ );
3547
+ }
3548
+ parsed = parseMidiFile(buffer);
3549
+ }
3550
+ const childrenBefore = new Set(host.querySelectorAll("daw-track"));
3551
+ const settlements = await Promise.allSettled(
3552
+ parsed.tracks.map(
3553
+ (t) => host.addTrack({
3554
+ name: t.name,
3555
+ renderMode: "piano-roll",
3556
+ clips: [
3557
+ {
3558
+ midiNotes: t.notes,
3559
+ midiChannel: t.channel,
3560
+ midiProgram: t.programNumber,
3561
+ start: startTime
3562
+ }
3563
+ ]
3564
+ })
3565
+ )
3566
+ );
3567
+ const succeeded = [];
3568
+ const rejections = [];
3569
+ for (const s of settlements) {
3570
+ if (s.status === "fulfilled") {
3571
+ succeeded.push(s.value);
3572
+ } else {
3573
+ rejections.push(s.reason);
3574
+ }
3575
+ }
3576
+ if (rejections.length > 0) {
3577
+ const appendedTracks = Array.from(host.querySelectorAll("daw-track")).filter(
3578
+ (el) => !childrenBefore.has(el)
3579
+ );
3580
+ for (const el of appendedTracks) {
3581
+ try {
3582
+ el.remove();
3583
+ } catch (cleanupErr) {
3584
+ console.warn("[dawcore] loadMidi cleanup failed for a track: " + String(cleanupErr));
3585
+ }
3586
+ }
3587
+ await Promise.resolve();
3588
+ for (let i = 1; i < rejections.length; i++) {
3589
+ console.warn(
3590
+ "[dawcore] loadMidi: additional track failure (" + i + "): " + stringifyReason(rejections[i])
3591
+ );
3592
+ }
3593
+ const first = rejections[0];
3594
+ if (rejections.length > 1) {
3595
+ const message = "loadMidi: " + rejections.length + " of " + settlements.length + " tracks failed; first: " + (first instanceof Error ? first.message : stringifyReason(first));
3596
+ throw new Error(message);
3597
+ }
3598
+ throw first instanceof Error ? first : new Error(stringifyReason(first));
3599
+ }
3600
+ return {
3601
+ trackIds: succeeded.map((el) => el.trackId),
3602
+ bpm: parsed.bpm,
3603
+ timeSignature: parsed.timeSignature,
3604
+ duration: parsed.duration,
3605
+ name: parsed.name
3606
+ };
3607
+ }
3608
+ function stringifyReason(reason) {
3609
+ if (reason === null) return "null";
3610
+ if (reason === void 0) return "undefined";
3611
+ if (typeof reason === "object") {
3612
+ try {
3613
+ return JSON.stringify(reason);
3614
+ } catch {
3615
+ return Object.prototype.toString.call(reason);
3616
+ }
3617
+ }
3618
+ return String(reason);
3619
+ }
3620
+
3498
3621
  // src/interactions/recording-clip.ts
3499
- var import_core7 = require("@waveform-playlist/core");
3622
+ var import_core8 = require("@waveform-playlist/core");
3500
3623
  function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamples = 0) {
3501
3624
  let trimmedBuf = buf;
3502
3625
  if (offsetSamples > 0 && offsetSamples < buf.length) {
@@ -3511,7 +3634,7 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamp
3511
3634
  }
3512
3635
  trimmedBuf = trimmed;
3513
3636
  }
3514
- const clip = (0, import_core7.createClip)({
3637
+ const clip = (0, import_core8.createClip)({
3515
3638
  audioBuffer: trimmedBuf,
3516
3639
  startSample,
3517
3640
  durationSamples: durSamples,
@@ -3834,7 +3957,10 @@ var DawEditorElement = class extends import_lit14.LitElement {
3834
3957
  * `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
3835
3958
  * viewport. Skip the cross-controller call when nothing changed.
3836
3959
  *
3837
- * The orchestrator dedupes too, but this avoids the call entirely.
3960
+ * The orchestrator dedupes identical viewports too, so removing this cache
3961
+ * wouldn't change observable behavior — but it would push a fresh
3962
+ * `setViewport` call (with object allocation) into every Lit reactive
3963
+ * update for properties unrelated to the viewport.
3838
3964
  */
3839
3965
  this._lastSpectrogramViewport = null;
3840
3966
  // --- Track Events ---
@@ -4077,9 +4203,10 @@ var DawEditorElement = class extends import_lit14.LitElement {
4077
4203
  this._spectrogramController?.unregisterCanvas(canvasId);
4078
4204
  }
4079
4205
  /**
4080
- * Push a clip's decoded audio into the spectrogram controller. No-op
4081
- * unless the track is in spectrogram render-mode and the controller
4082
- * already exists (it bootstraps from canvas registration).
4206
+ * Forward a clip's AudioBuffer to the spectrogram controller if the parent
4207
+ * track is in spectrogram render-mode. Eagerly creates the controller via
4208
+ * `_ensureSpectrogramController` so the audio data is queued for the first
4209
+ * render — even if no canvases have been registered yet.
4083
4210
  */
4084
4211
  _maybeRegisterSpectrogramClipAudio(trackId, clip) {
4085
4212
  const descriptor = this._tracks.get(trackId);
@@ -4530,7 +4657,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4530
4657
  let clip;
4531
4658
  if (waveformData) {
4532
4659
  const wdRate = waveformData.sample_rate;
4533
- clip = (0, import_core8.createClip)({
4660
+ clip = (0, import_core9.createClip)({
4534
4661
  audioBuffer,
4535
4662
  waveformData,
4536
4663
  startSample: Math.round(clipDesc.start * wdRate),
@@ -4543,7 +4670,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4543
4670
  });
4544
4671
  this._peakPipeline.cacheWaveformData(audioBuffer, waveformData);
4545
4672
  } else {
4546
- clip = (0, import_core8.createClipFromSeconds)({
4673
+ clip = (0, import_core9.createClipFromSeconds)({
4547
4674
  audioBuffer,
4548
4675
  startTime: clipDesc.start,
4549
4676
  duration: clipDesc.duration || audioBuffer.duration,
@@ -4623,7 +4750,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4623
4750
  const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
4624
4751
  const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipDesc.duration, 1) * sr);
4625
4752
  const requestedDurationSamples = clipDesc.duration > 0 ? Math.round(clipDesc.duration * sr) : sourceDurationSamples;
4626
- const clip = (0, import_core8.createClip)({
4753
+ const clip = (0, import_core9.createClip)({
4627
4754
  startSample: Math.round(clipDesc.start * sr),
4628
4755
  durationSamples: requestedDurationSamples,
4629
4756
  offsetSamples: Math.round(clipDesc.offset * sr),
@@ -4832,7 +4959,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4832
4959
  const waveformData = await waveformDataPromise;
4833
4960
  if (waveformData) {
4834
4961
  const wdRate = waveformData.sample_rate;
4835
- const clip2 = (0, import_core8.createClip)({
4962
+ const clip2 = (0, import_core9.createClip)({
4836
4963
  waveformData,
4837
4964
  startSample: Math.round(clipDesc.start * wdRate),
4838
4965
  durationSamples: Math.round((clipDesc.duration || waveformData.duration) * wdRate),
@@ -4857,7 +4984,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4857
4984
  });
4858
4985
  this._peaksData = new Map(this._peaksData).set(clip2.id, peakData);
4859
4986
  this._minSamplesPerPixel = Math.max(this._minSamplesPerPixel, waveformData.scale);
4860
- const previewTrack = (0, import_core8.createTrack)({
4987
+ const previewTrack = (0, import_core9.createTrack)({
4861
4988
  name: descriptor.name,
4862
4989
  clips: [clip2],
4863
4990
  volume: descriptor.volume,
@@ -4913,7 +5040,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4913
5040
  }
4914
5041
  }
4915
5042
  }
4916
- const track = (0, import_core8.createTrack)({
5043
+ const track = (0, import_core9.createTrack)({
4917
5044
  name: descriptor.name,
4918
5045
  clips,
4919
5046
  volume: descriptor.volume,
@@ -5067,6 +5194,22 @@ var DawEditorElement = class extends import_lit14.LitElement {
5067
5194
  async loadFiles(files) {
5068
5195
  return loadFiles(this, files);
5069
5196
  }
5197
+ /**
5198
+ * Imperatively load a `.mid` file (URL or File) and create N `<daw-track>`
5199
+ * elements — one per note-bearing MIDI track. On any per-track failure,
5200
+ * every `<daw-track>` appended during the call is removed (both successful
5201
+ * and failed) so the editor returns to its pre-call state.
5202
+ *
5203
+ * `options.signal` is forwarded to `fetch()` only for URL sources; aborting
5204
+ * after parsing does not cancel in-flight `addTrack` calls.
5205
+ *
5206
+ * Requires the optional `@dawcore/midi` peer dep — throws with an install
5207
+ * hint (and `console.warn`s the original error) when the dynamic import
5208
+ * fails for any reason.
5209
+ */
5210
+ async loadMidi(source, options) {
5211
+ return loadMidiImpl(this, source, options);
5212
+ }
5070
5213
  // --- Programmatic Track API ---
5071
5214
  /**
5072
5215
  * Build the engine if it hasn't been built yet. Lets consumers obtain a
@@ -5180,6 +5323,13 @@ var DawEditorElement = class extends import_lit14.LitElement {
5180
5323
  }
5181
5324
  const oldDesc = this._tracks.get(trackId);
5182
5325
  if (!oldDesc) return;
5326
+ let normalizedRenderMode = partial.renderMode;
5327
+ if (normalizedRenderMode === "both") {
5328
+ console.warn(
5329
+ `[dawcore] render-mode="both" is not yet supported; falling back to 'spectrogram'`
5330
+ );
5331
+ normalizedRenderMode = "spectrogram";
5332
+ }
5183
5333
  const newDesc = {
5184
5334
  ...oldDesc,
5185
5335
  ...partial.name !== void 0 && { name: partial.name },
@@ -5187,7 +5337,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
5187
5337
  ...partial.pan !== void 0 && { pan: partial.pan },
5188
5338
  ...partial.muted !== void 0 && { muted: partial.muted },
5189
5339
  ...partial.soloed !== void 0 && { soloed: partial.soloed },
5190
- ...partial.renderMode !== void 0 && { renderMode: partial.renderMode }
5340
+ ...normalizedRenderMode !== void 0 && { renderMode: normalizedRenderMode }
5191
5341
  };
5192
5342
  this._tracks = new Map(this._tracks).set(trackId, newDesc);
5193
5343
  if (this._engine) {
@@ -5675,7 +5825,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
5675
5825
  width = Math.round(endTick / this.ticksPerPixel) - clipLeft;
5676
5826
  } else {
5677
5827
  clipLeft = Math.floor(clip.startSample / spp);
5678
- width = (0, import_core8.clipPixelWidth)(clip.startSample, clip.durationSamples, spp);
5828
+ width = (0, import_core9.clipPixelWidth)(clip.startSample, clip.durationSamples, spp);
5679
5829
  }
5680
5830
  let clipSegments;
5681
5831
  let segmentChannels;
@@ -6297,7 +6447,7 @@ DawRecordButtonElement = __decorateClass([
6297
6447
  // src/elements/daw-keyboard-shortcuts.ts
6298
6448
  var import_lit18 = require("lit");
6299
6449
  var import_decorators16 = require("lit/decorators.js");
6300
- var import_core9 = require("@waveform-playlist/core");
6450
+ var import_core10 = require("@waveform-playlist/core");
6301
6451
  var DawKeyboardShortcutsElement = class extends import_lit18.LitElement {
6302
6452
  constructor() {
6303
6453
  super(...arguments);
@@ -6317,7 +6467,7 @@ var DawKeyboardShortcutsElement = class extends import_lit18.LitElement {
6317
6467
  const shortcuts = this.shortcuts;
6318
6468
  if (shortcuts.length === 0) return;
6319
6469
  try {
6320
- (0, import_core9.handleKeyboardEvent)(e, shortcuts, true);
6470
+ (0, import_core10.handleKeyboardEvent)(e, shortcuts, true);
6321
6471
  } catch (err) {
6322
6472
  console.warn("[dawcore] Keyboard shortcut failed (key=" + e.key + "): " + String(err));
6323
6473
  const target = this._editor ?? this;
@@ -6484,6 +6634,7 @@ var DawSpectrogramElement = class extends import_lit19.LitElement {
6484
6634
  this.originX = 0;
6485
6635
  this._canvases = [];
6486
6636
  this._registeredCanvasIds = [];
6637
+ this._warnedNoHost = false;
6487
6638
  }
6488
6639
  get samplesPerPixel() {
6489
6640
  return this._samplesPerPixel;
@@ -6550,7 +6701,15 @@ var DawSpectrogramElement = class extends import_lit19.LitElement {
6550
6701
  }
6551
6702
  _registerCanvases() {
6552
6703
  const editor = this._findHostEditor();
6553
- if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") return;
6704
+ if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") {
6705
+ if (!this._warnedNoHost) {
6706
+ this._warnedNoHost = true;
6707
+ console.warn(
6708
+ "[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>."
6709
+ );
6710
+ }
6711
+ return;
6712
+ }
6554
6713
  for (let i = 0; i < this._canvases.length; i++) {
6555
6714
  const canvas = this._canvases[i];
6556
6715
  const canvasId = this.clipId + "-ch" + this.channelIndex + "-chunk" + i;