@dawcore/components 0.0.16 → 0.0.17

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
@@ -175,6 +175,7 @@ var DawTrackElement = class extends LitElement2 {
175
175
  this.muted = false;
176
176
  this.soloed = false;
177
177
  this.renderMode = "waveform";
178
+ this.spectrogramConfig = null;
178
179
  this.trackId = crypto.randomUUID();
179
180
  // Track removal is detected by the editor's MutationObserver,
180
181
  // not by dispatching from disconnectedCallback (detached elements
@@ -202,7 +203,16 @@ var DawTrackElement = class extends LitElement2 {
202
203
  this._hasRendered = true;
203
204
  return;
204
205
  }
205
- const trackProps = ["volume", "pan", "muted", "soloed", "src", "name", "renderMode"];
206
+ const trackProps = [
207
+ "volume",
208
+ "pan",
209
+ "muted",
210
+ "soloed",
211
+ "src",
212
+ "name",
213
+ "renderMode",
214
+ "spectrogramConfig"
215
+ ];
206
216
  const hasTrackChange = trackProps.some((p) => changed.has(p));
207
217
  if (hasTrackChange) {
208
218
  this.dispatchEvent(
@@ -236,6 +246,9 @@ __decorateClass([
236
246
  __decorateClass([
237
247
  property2({ attribute: "render-mode" })
238
248
  ], DawTrackElement.prototype, "renderMode", 2);
249
+ __decorateClass([
250
+ property2({ attribute: false })
251
+ ], DawTrackElement.prototype, "spectrogramConfig", 2);
239
252
  DawTrackElement = __decorateClass([
240
253
  customElement2("daw-track")
241
254
  ], DawTrackElement);
@@ -2708,6 +2721,123 @@ var RecordingController = class {
2708
2721
  }
2709
2722
  };
2710
2723
 
2724
+ // src/controllers/spectrogram-controller.ts
2725
+ import {
2726
+ SpectrogramOrchestrator
2727
+ } 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";
2737
+ var SpectrogramController = class {
2738
+ constructor(host, workerFactory) {
2739
+ this.orchestrator = null;
2740
+ this.editorConfig = null;
2741
+ this.editorColorMap = null;
2742
+ this.trackConfigs = /* @__PURE__ */ new Map();
2743
+ this.trackColorMaps = /* @__PURE__ */ new Map();
2744
+ this.host = host;
2745
+ this.workerFactory = workerFactory;
2746
+ this.host.addController(this);
2747
+ }
2748
+ hostConnected() {
2749
+ }
2750
+ hostDisconnected() {
2751
+ this.dispose();
2752
+ }
2753
+ setEditorConfig(config) {
2754
+ this.editorConfig = config;
2755
+ this.reapply();
2756
+ }
2757
+ setEditorColorMap(colorMap) {
2758
+ this.editorColorMap = colorMap;
2759
+ this.reapply();
2760
+ }
2761
+ setTrackConfig(trackId, config) {
2762
+ if (config === null) {
2763
+ this.trackConfigs.delete(trackId);
2764
+ } else {
2765
+ this.trackConfigs.set(trackId, config);
2766
+ }
2767
+ this.reapply();
2768
+ }
2769
+ setTrackColorMap(trackId, colorMap) {
2770
+ if (colorMap === null) {
2771
+ this.trackColorMaps.delete(trackId);
2772
+ } else {
2773
+ this.trackColorMaps.set(trackId, colorMap);
2774
+ }
2775
+ this.reapply();
2776
+ }
2777
+ registerClipAudio(reg) {
2778
+ this.ensureOrchestrator().registerClip(reg);
2779
+ }
2780
+ unregisterClipAudio(clipId) {
2781
+ this.orchestrator?.unregisterClip(clipId);
2782
+ }
2783
+ registerCanvas(reg) {
2784
+ this.ensureOrchestrator().registerCanvas(reg);
2785
+ }
2786
+ unregisterCanvas(canvasId) {
2787
+ this.orchestrator?.unregisterCanvas(canvasId);
2788
+ }
2789
+ setViewport(state5) {
2790
+ this.orchestrator?.setViewport(state5);
2791
+ }
2792
+ dispose() {
2793
+ if (this.orchestrator) {
2794
+ this.orchestrator.dispose();
2795
+ this.orchestrator = null;
2796
+ }
2797
+ }
2798
+ ensureOrchestrator() {
2799
+ if (!this.orchestrator) {
2800
+ this.orchestrator = new SpectrogramOrchestrator({
2801
+ workerFactory: this.workerFactory,
2802
+ workerPoolSize: 2,
2803
+ config: this.mergedConfig(),
2804
+ colorMap: this.mergedColorMap()
2805
+ });
2806
+ this.orchestrator.addEventListener("viewport-ready", (e) => {
2807
+ const detail = e.detail;
2808
+ this.host.dispatchEvent(
2809
+ new CustomEvent("daw-spectrogram-ready", {
2810
+ detail,
2811
+ bubbles: true,
2812
+ composed: true
2813
+ })
2814
+ );
2815
+ });
2816
+ this.reapply();
2817
+ }
2818
+ return this.orchestrator;
2819
+ }
2820
+ reapply() {
2821
+ if (!this.orchestrator) return;
2822
+ this.orchestrator.setConfig(this.mergedConfig());
2823
+ this.orchestrator.setColorMap(this.mergedColorMap());
2824
+ }
2825
+ mergedConfig() {
2826
+ let track = null;
2827
+ for (const c of this.trackConfigs.values()) {
2828
+ track = c;
2829
+ break;
2830
+ }
2831
+ return { ...LIBRARY_DEFAULTS, ...this.editorConfig ?? {}, ...track ?? {} };
2832
+ }
2833
+ mergedColorMap() {
2834
+ for (const c of this.trackColorMaps.values()) {
2835
+ return c ?? LIBRARY_DEFAULT_COLOR_MAP;
2836
+ }
2837
+ return this.editorColorMap ?? LIBRARY_DEFAULT_COLOR_MAP;
2838
+ }
2839
+ };
2840
+
2711
2841
  // src/interactions/pointer-handler.ts
2712
2842
  import { pixelsToSeconds, snapTickToGrid } from "@waveform-playlist/core";
2713
2843
 
@@ -3603,6 +3733,8 @@ var DawEditorElement = class extends LitElement10 {
3603
3733
  this.clipHeaderHeight = 20;
3604
3734
  this.interactiveClips = false;
3605
3735
  this.indefinitePlayback = false;
3736
+ this._spectrogramConfig = null;
3737
+ this._spectrogramColorMap = null;
3606
3738
  this.scaleMode = "temporal";
3607
3739
  this._ticksPerPixel = 24;
3608
3740
  this._bpm = 120;
@@ -3638,6 +3770,7 @@ var DawEditorElement = class extends LitElement10 {
3638
3770
  this._childObserver = null;
3639
3771
  this._audioResume = new AudioResumeController(this);
3640
3772
  this._recordingController = new RecordingController(this);
3773
+ this._spectrogramController = null;
3641
3774
  this._clipPointer = new ClipPointerHandler(this);
3642
3775
  this._pointer = new PointerHandler(this);
3643
3776
  this._viewport = (() => {
@@ -3678,6 +3811,26 @@ var DawEditorElement = class extends LitElement10 {
3678
3811
  if (oldDescriptor?.src !== descriptor.src) {
3679
3812
  this._loadTrack(trackId, descriptor);
3680
3813
  }
3814
+ if (descriptor.renderMode === "spectrogram" && oldDescriptor?.renderMode !== "spectrogram") {
3815
+ const engineTrack = this._engineTracks.get(trackId);
3816
+ if (engineTrack) {
3817
+ for (const clip of engineTrack.clips) {
3818
+ this._maybeRegisterSpectrogramClipAudio(trackId, clip);
3819
+ }
3820
+ }
3821
+ }
3822
+ if (descriptor.renderMode !== "spectrogram" && oldDescriptor?.renderMode === "spectrogram") {
3823
+ const engineTrack = this._engineTracks.get(trackId);
3824
+ if (engineTrack && this._spectrogramController) {
3825
+ for (const clip of engineTrack.clips) {
3826
+ this._spectrogramController.unregisterClipAudio(clip.id);
3827
+ }
3828
+ }
3829
+ this._disposeSpectrogramControllerIfEmpty();
3830
+ }
3831
+ if (descriptor.spectrogramConfig !== oldDescriptor?.spectrogramConfig) {
3832
+ this._spectrogramController?.setTrackConfig(trackId, descriptor.spectrogramConfig ?? null);
3833
+ }
3681
3834
  };
3682
3835
  this._onTrackControl = (e) => {
3683
3836
  const { trackId, prop, value } = e.detail ?? {};
@@ -3821,6 +3974,72 @@ var DawEditorElement = class extends LitElement10 {
3821
3974
  this._samplesPerPixel = clamped;
3822
3975
  this.requestUpdate("samplesPerPixel", old);
3823
3976
  }
3977
+ get spectrogramConfig() {
3978
+ return this._spectrogramConfig;
3979
+ }
3980
+ set spectrogramConfig(value) {
3981
+ const old = this._spectrogramConfig;
3982
+ this._spectrogramConfig = value;
3983
+ this._spectrogramController?.setEditorConfig(value);
3984
+ this.requestUpdate("spectrogramConfig", old);
3985
+ }
3986
+ get spectrogramColorMap() {
3987
+ return this._spectrogramColorMap;
3988
+ }
3989
+ set spectrogramColorMap(value) {
3990
+ const old = this._spectrogramColorMap;
3991
+ this._spectrogramColorMap = value;
3992
+ this._spectrogramController?.setEditorColorMap(value);
3993
+ this.requestUpdate("spectrogramColorMap", old);
3994
+ }
3995
+ _ensureSpectrogramController() {
3996
+ if (!this._spectrogramController) {
3997
+ this._spectrogramController = new SpectrogramController(
3998
+ this,
3999
+ () => new Worker(new URL("@dawcore/spectrogram/worker/spectrogram.worker", import.meta.url), {
4000
+ type: "module"
4001
+ })
4002
+ );
4003
+ if (this._spectrogramConfig) {
4004
+ this._spectrogramController.setEditorConfig(this._spectrogramConfig);
4005
+ }
4006
+ if (this._spectrogramColorMap) {
4007
+ this._spectrogramController.setEditorColorMap(this._spectrogramColorMap);
4008
+ }
4009
+ }
4010
+ return this._spectrogramController;
4011
+ }
4012
+ /** Called by <daw-spectrogram> after transferControlToOffscreen. */
4013
+ _spectrogramRegisterCanvas(reg) {
4014
+ this._ensureSpectrogramController().registerCanvas(reg);
4015
+ }
4016
+ /** Called by <daw-spectrogram> on chunk unmount / element disconnect. */
4017
+ _spectrogramUnregisterCanvas(canvasId) {
4018
+ this._spectrogramController?.unregisterCanvas(canvasId);
4019
+ }
4020
+ /**
4021
+ * Push a clip's decoded audio into the spectrogram controller. No-op
4022
+ * unless the track is in spectrogram render-mode and the controller
4023
+ * already exists (it bootstraps from canvas registration).
4024
+ */
4025
+ _maybeRegisterSpectrogramClipAudio(trackId, clip) {
4026
+ const descriptor = this._tracks.get(trackId);
4027
+ if (descriptor?.renderMode !== "spectrogram") return;
4028
+ const buffer = clip.audioBuffer ?? this._clipBuffers.get(clip.id);
4029
+ if (!buffer) return;
4030
+ const channelData = [];
4031
+ for (let i = 0; i < buffer.numberOfChannels; i++) {
4032
+ channelData.push(buffer.getChannelData(i));
4033
+ }
4034
+ this._ensureSpectrogramController().registerClipAudio({
4035
+ clipId: clip.id,
4036
+ trackId,
4037
+ channelData,
4038
+ sampleRate: buffer.sampleRate,
4039
+ durationSamples: clip.durationSamples,
4040
+ offsetSamples: clip.offsetSamples
4041
+ });
4042
+ }
3824
4043
  get ticksPerPixel() {
3825
4044
  return this._ticksPerPixel;
3826
4045
  }
@@ -4061,6 +4280,8 @@ var DawEditorElement = class extends LitElement10 {
4061
4280
  this._clipOffsets.clear();
4062
4281
  this._peakPipeline.terminate();
4063
4282
  this._minSamplesPerPixel = 0;
4283
+ this._spectrogramController?.dispose();
4284
+ this._spectrogramController = null;
4064
4285
  try {
4065
4286
  this._disposeEngine();
4066
4287
  } catch (err) {
@@ -4089,6 +4310,23 @@ var DawEditorElement = class extends LitElement10 {
4089
4310
  }
4090
4311
  }
4091
4312
  }
4313
+ updated(_changed) {
4314
+ if (this._spectrogramController) {
4315
+ const vs = this._viewport.visibleStart;
4316
+ const ve = this._viewport.visibleEnd;
4317
+ if (Number.isFinite(vs) && Number.isFinite(ve)) {
4318
+ const span = ve - vs;
4319
+ const bufferPad = span * 0.25;
4320
+ this._spectrogramController.setViewport({
4321
+ visibleStartPx: vs,
4322
+ visibleEndPx: ve,
4323
+ bufferStartPx: Math.max(0, vs - bufferPad),
4324
+ bufferEndPx: ve + bufferPad,
4325
+ samplesPerPixel: this._renderSpp
4326
+ });
4327
+ }
4328
+ }
4329
+ }
4092
4330
  _onTrackRemoved(trackId) {
4093
4331
  this._trackElements.delete(trackId);
4094
4332
  const removedTrack = this._engineTracks.get(trackId);
@@ -4098,6 +4336,7 @@ var DawEditorElement = class extends LitElement10 {
4098
4336
  this._clipBuffers.delete(clip.id);
4099
4337
  this._clipOffsets.delete(clip.id);
4100
4338
  nextPeaks.delete(clip.id);
4339
+ this._spectrogramController?.unregisterClipAudio(clip.id);
4101
4340
  }
4102
4341
  this._peaksData = nextPeaks;
4103
4342
  }
@@ -4112,11 +4351,23 @@ var DawEditorElement = class extends LitElement10 {
4112
4351
  this._engine.removeTrack(trackId);
4113
4352
  }
4114
4353
  this._minSamplesPerPixel = this._peakPipeline.getMaxCachedScale(this._clipBuffers);
4354
+ this._disposeSpectrogramControllerIfEmpty();
4115
4355
  if (nextEngine.size === 0) {
4116
4356
  this._currentTime = 0;
4117
4357
  this._stopPlayhead();
4118
4358
  }
4119
4359
  }
4360
+ /** Drop the controller when no spectrogram tracks remain. */
4361
+ _disposeSpectrogramControllerIfEmpty() {
4362
+ if (!this._spectrogramController) return;
4363
+ const stillNeeded = Array.from(this._tracks.values()).some(
4364
+ (d) => d.renderMode === "spectrogram"
4365
+ );
4366
+ if (!stillNeeded) {
4367
+ this._spectrogramController.dispose();
4368
+ this._spectrogramController = null;
4369
+ }
4370
+ }
4120
4371
  _onClipRemovedFromDom(clipEl) {
4121
4372
  const clipId = clipEl.clipId;
4122
4373
  for (const [trackId, t] of this._engineTracks.entries()) {
@@ -4158,6 +4409,7 @@ var DawEditorElement = class extends LitElement10 {
4158
4409
  });
4159
4410
  }
4160
4411
  this._commitTrackChange(trackId, updatedTrack);
4412
+ this._maybeRegisterSpectrogramClipAudio(trackId, clip);
4161
4413
  this.dispatchEvent(
4162
4414
  new CustomEvent("daw-clip-ready", {
4163
4415
  bubbles: true,
@@ -4332,6 +4584,7 @@ var DawEditorElement = class extends LitElement10 {
4332
4584
  nextPeaks.delete(clipId);
4333
4585
  this._peaksData = nextPeaks;
4334
4586
  this._clipOffsets.delete(clipId);
4587
+ this._spectrogramController?.unregisterClipAudio(clipId);
4335
4588
  }
4336
4589
  /**
4337
4590
  * Recompute duration and forward an updated track to the engine. Single
@@ -4614,6 +4867,9 @@ var DawEditorElement = class extends LitElement10 {
4614
4867
  track.id = trackId;
4615
4868
  this._engineTracks = new Map(this._engineTracks).set(trackId, track);
4616
4869
  this._recomputeDuration();
4870
+ for (const c of clips) {
4871
+ this._maybeRegisterSpectrogramClipAudio(trackId, c);
4872
+ }
4617
4873
  const engine = await this._ensureEngine();
4618
4874
  engine.setTracks([...this._engineTracks.values()]);
4619
4875
  this.dispatchEvent(
@@ -5425,19 +5681,34 @@ var DawEditorElement = class extends LitElement10 {
5425
5681
  .visibleEnd=${this._viewport.visibleEnd}
5426
5682
  .originX=${clipLeft}
5427
5683
  ?selected=${t.trackId === this._selectedTrackId}
5428
- ></daw-piano-roll>` : channels.map(
5684
+ ></daw-piano-roll>` : t.descriptor?.renderMode === "spectrogram" ? channels.map(
5685
+ (_chPeaks, chIdx) => html9`<daw-spectrogram
5686
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;height:${chH}px;width:${peakData?.length ?? width}px;"
5687
+ .clipId=${clip.id}
5688
+ .trackId=${t.trackId}
5689
+ .channelIndex=${chIdx}
5690
+ .length=${peakData?.length ?? width}
5691
+ .waveHeight=${chH}
5692
+ .samplesPerPixel=${this._renderSpp}
5693
+ .sampleRate=${this.effectiveSampleRate}
5694
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
5695
+ .visibleStart=${this._viewport.visibleStart}
5696
+ .visibleEnd=${this._viewport.visibleEnd}
5697
+ .originX=${clipLeft}
5698
+ ></daw-spectrogram>`
5699
+ ) : channels.map(
5429
5700
  (chPeaks, chIdx) => html9` <daw-waveform
5430
- style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5431
- .peaks=${chPeaks}
5432
- .length=${peakData?.length ?? width}
5433
- .waveHeight=${chH}
5434
- .barWidth=${this.barWidth}
5435
- .barGap=${this.barGap}
5436
- .visibleStart=${this._viewport.visibleStart}
5437
- .visibleEnd=${this._viewport.visibleEnd}
5438
- .originX=${clipLeft}
5439
- .segments=${clipSegments}
5440
- ></daw-waveform>`
5701
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5702
+ .peaks=${chPeaks}
5703
+ .length=${peakData?.length ?? width}
5704
+ .waveHeight=${chH}
5705
+ .barWidth=${this.barWidth}
5706
+ .barGap=${this.barGap}
5707
+ .visibleStart=${this._viewport.visibleStart}
5708
+ .visibleEnd=${this._viewport.visibleEnd}
5709
+ .originX=${clipLeft}
5710
+ .segments=${clipSegments}
5711
+ ></daw-waveform>`
5441
5712
  )}
5442
5713
  ${this.interactiveClips ? html9` <div
5443
5714
  class="clip-boundary"
@@ -5545,6 +5816,12 @@ __decorateClass([
5545
5816
  __decorateClass([
5546
5817
  property8({ type: Boolean, attribute: "indefinite-playback" })
5547
5818
  ], DawEditorElement.prototype, "indefinitePlayback", 2);
5819
+ __decorateClass([
5820
+ property8({ attribute: false, noAccessor: true })
5821
+ ], DawEditorElement.prototype, "spectrogramConfig", 1);
5822
+ __decorateClass([
5823
+ property8({ attribute: false, noAccessor: true })
5824
+ ], DawEditorElement.prototype, "spectrogramColorMap", 1);
5548
5825
  __decorateClass([
5549
5826
  property8({ type: String, attribute: "scale-mode" })
5550
5827
  ], DawEditorElement.prototype, "scaleMode", 2);
@@ -6123,6 +6400,187 @@ __decorateClass([
6123
6400
  DawKeyboardShortcutsElement = __decorateClass([
6124
6401
  customElement16("daw-keyboard-shortcuts")
6125
6402
  ], DawKeyboardShortcutsElement);
6403
+
6404
+ // src/elements/daw-spectrogram.ts
6405
+ import { LitElement as LitElement14, html as html13, css as css13 } from "lit";
6406
+ import { customElement as customElement17, property as property12 } from "lit/decorators.js";
6407
+ var MAX_CANVAS_WIDTH5 = 1e3;
6408
+ var DawSpectrogramElement = class extends LitElement14 {
6409
+ constructor() {
6410
+ super(...arguments);
6411
+ this.clipId = "";
6412
+ this.trackId = "";
6413
+ this.channelIndex = 0;
6414
+ this.length = 0;
6415
+ this.waveHeight = 128;
6416
+ this._samplesPerPixel = 1024;
6417
+ this._sampleRate = 44100;
6418
+ this.clipOffsetSeconds = 0;
6419
+ this.visibleStart = -Infinity;
6420
+ this.visibleEnd = Infinity;
6421
+ this.originX = 0;
6422
+ this._canvases = [];
6423
+ this._registeredCanvasIds = [];
6424
+ }
6425
+ get samplesPerPixel() {
6426
+ return this._samplesPerPixel;
6427
+ }
6428
+ set samplesPerPixel(value) {
6429
+ if (!Number.isFinite(value) || value <= 0) {
6430
+ console.warn("[dawcore] daw-spectrogram samplesPerPixel " + value + " is invalid \u2014 ignored");
6431
+ return;
6432
+ }
6433
+ const old = this._samplesPerPixel;
6434
+ this._samplesPerPixel = value;
6435
+ this.requestUpdate("samplesPerPixel", old);
6436
+ }
6437
+ get sampleRate() {
6438
+ return this._sampleRate;
6439
+ }
6440
+ set sampleRate(value) {
6441
+ if (!Number.isFinite(value) || value <= 0) {
6442
+ console.warn("[dawcore] daw-spectrogram sampleRate " + value + " is invalid \u2014 ignored");
6443
+ return;
6444
+ }
6445
+ const old = this._sampleRate;
6446
+ this._sampleRate = value;
6447
+ this.requestUpdate("sampleRate", old);
6448
+ }
6449
+ /**
6450
+ * Walk up to the editor host. `closest('daw-editor')` does NOT cross
6451
+ * shadow boundaries — and this element lives inside the editor's shadow
6452
+ * DOM — so use getRootNode().host to step out.
6453
+ */
6454
+ _findHostEditor() {
6455
+ const root = this.getRootNode();
6456
+ const host = root instanceof ShadowRoot ? root.host : null;
6457
+ if (!host) return null;
6458
+ if (host.tagName === "DAW-EDITOR") return host;
6459
+ return host.closest("daw-editor");
6460
+ }
6461
+ willUpdate(changed) {
6462
+ const layoutChanged = changed.has("length") || changed.has("waveHeight") || changed.has("samplesPerPixel") || changed.has("clipId") || changed.has("channelIndex");
6463
+ if (layoutChanged) {
6464
+ this._rebuildChunks();
6465
+ }
6466
+ }
6467
+ _rebuildChunks() {
6468
+ this._unregisterAllCanvases();
6469
+ this._canvases = [];
6470
+ if (this.length <= 0) return;
6471
+ const chunkCount = Math.ceil(this.length / MAX_CANVAS_WIDTH5);
6472
+ for (let i = 0; i < chunkCount; i++) {
6473
+ const widthPx = Math.min(MAX_CANVAS_WIDTH5, this.length - i * MAX_CANVAS_WIDTH5);
6474
+ const canvas = document.createElement("canvas");
6475
+ canvas.style.left = i * MAX_CANVAS_WIDTH5 + "px";
6476
+ canvas.style.width = widthPx + "px";
6477
+ const dpr = window.devicePixelRatio || 1;
6478
+ canvas.width = widthPx * dpr;
6479
+ canvas.height = this.waveHeight * dpr;
6480
+ this._canvases.push(canvas);
6481
+ }
6482
+ }
6483
+ updated(_changed) {
6484
+ if (this._registeredCanvasIds.length === 0 && this._canvases.length > 0) {
6485
+ requestAnimationFrame(() => this._registerCanvases());
6486
+ }
6487
+ }
6488
+ _registerCanvases() {
6489
+ const editor = this._findHostEditor();
6490
+ if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") return;
6491
+ for (let i = 0; i < this._canvases.length; i++) {
6492
+ const canvas = this._canvases[i];
6493
+ const canvasId = this.clipId + "-ch" + this.channelIndex + "-chunk" + i;
6494
+ let offscreen;
6495
+ try {
6496
+ offscreen = canvas.transferControlToOffscreen();
6497
+ } catch (err) {
6498
+ console.warn(
6499
+ "[dawcore] daw-spectrogram transferControlToOffscreen failed for " + canvasId + ": " + (err instanceof Error ? err.message : String(err))
6500
+ );
6501
+ continue;
6502
+ }
6503
+ editor._spectrogramRegisterCanvas({
6504
+ canvasId,
6505
+ canvas: offscreen,
6506
+ clipId: this.clipId,
6507
+ trackId: this.trackId,
6508
+ channelIndex: this.channelIndex,
6509
+ chunkIndex: i,
6510
+ globalPixelOffset: this.originX + i * MAX_CANVAS_WIDTH5,
6511
+ widthPx: parseFloat(canvas.style.width),
6512
+ heightPx: this.waveHeight
6513
+ });
6514
+ this._registeredCanvasIds.push(canvasId);
6515
+ }
6516
+ }
6517
+ _unregisterAllCanvases() {
6518
+ const editor = this._findHostEditor();
6519
+ if (editor && typeof editor._spectrogramUnregisterCanvas === "function") {
6520
+ for (const id of this._registeredCanvasIds) {
6521
+ editor._spectrogramUnregisterCanvas(id);
6522
+ }
6523
+ }
6524
+ this._registeredCanvasIds = [];
6525
+ }
6526
+ disconnectedCallback() {
6527
+ super.disconnectedCallback();
6528
+ this._unregisterAllCanvases();
6529
+ }
6530
+ render() {
6531
+ return html13`${this._canvases.map((c) => c)}`;
6532
+ }
6533
+ };
6534
+ DawSpectrogramElement.styles = css13`
6535
+ :host {
6536
+ display: block;
6537
+ position: relative;
6538
+ background: var(--daw-spectrogram-background, #000);
6539
+ }
6540
+ canvas {
6541
+ position: absolute;
6542
+ top: 0;
6543
+ left: 0;
6544
+ height: 100%;
6545
+ pointer-events: none;
6546
+ }
6547
+ `;
6548
+ __decorateClass([
6549
+ property12({ attribute: false })
6550
+ ], DawSpectrogramElement.prototype, "clipId", 2);
6551
+ __decorateClass([
6552
+ property12({ attribute: false })
6553
+ ], DawSpectrogramElement.prototype, "trackId", 2);
6554
+ __decorateClass([
6555
+ property12({ type: Number, attribute: false })
6556
+ ], DawSpectrogramElement.prototype, "channelIndex", 2);
6557
+ __decorateClass([
6558
+ property12({ type: Number, attribute: false })
6559
+ ], DawSpectrogramElement.prototype, "length", 2);
6560
+ __decorateClass([
6561
+ property12({ type: Number, attribute: false })
6562
+ ], DawSpectrogramElement.prototype, "waveHeight", 2);
6563
+ __decorateClass([
6564
+ property12({ type: Number, attribute: false, noAccessor: true })
6565
+ ], DawSpectrogramElement.prototype, "samplesPerPixel", 1);
6566
+ __decorateClass([
6567
+ property12({ type: Number, attribute: false, noAccessor: true })
6568
+ ], DawSpectrogramElement.prototype, "sampleRate", 1);
6569
+ __decorateClass([
6570
+ property12({ type: Number, attribute: false })
6571
+ ], DawSpectrogramElement.prototype, "clipOffsetSeconds", 2);
6572
+ __decorateClass([
6573
+ property12({ type: Number, attribute: false })
6574
+ ], DawSpectrogramElement.prototype, "visibleStart", 2);
6575
+ __decorateClass([
6576
+ property12({ type: Number, attribute: false })
6577
+ ], DawSpectrogramElement.prototype, "visibleEnd", 2);
6578
+ __decorateClass([
6579
+ property12({ type: Number, attribute: false })
6580
+ ], DawSpectrogramElement.prototype, "originX", 2);
6581
+ DawSpectrogramElement = __decorateClass([
6582
+ customElement17("daw-spectrogram")
6583
+ ], DawSpectrogramElement);
6126
6584
  export {
6127
6585
  AudioResumeController,
6128
6586
  ClipPointerHandler,
@@ -6137,6 +6595,7 @@ export {
6137
6595
  DawRecordButtonElement,
6138
6596
  DawRulerElement,
6139
6597
  DawSelectionElement,
6598
+ DawSpectrogramElement,
6140
6599
  DawStopButtonElement,
6141
6600
  DawTrackControlsElement,
6142
6601
  DawTrackElement,
@@ -6144,6 +6603,7 @@ export {
6144
6603
  DawTransportElement,
6145
6604
  DawWaveformElement,
6146
6605
  RecordingController,
6606
+ SpectrogramController,
6147
6607
  isDomClip,
6148
6608
  splitAtPlayhead
6149
6609
  };