@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.js CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  DawRecordButtonElement: () => DawRecordButtonElement,
52
52
  DawRulerElement: () => DawRulerElement,
53
53
  DawSelectionElement: () => DawSelectionElement,
54
+ DawSpectrogramElement: () => DawSpectrogramElement,
54
55
  DawStopButtonElement: () => DawStopButtonElement,
55
56
  DawTrackControlsElement: () => DawTrackControlsElement,
56
57
  DawTrackElement: () => DawTrackElement,
@@ -58,6 +59,7 @@ __export(index_exports, {
58
59
  DawTransportElement: () => DawTransportElement,
59
60
  DawWaveformElement: () => DawWaveformElement,
60
61
  RecordingController: () => RecordingController,
62
+ SpectrogramController: () => SpectrogramController,
61
63
  isDomClip: () => isDomClip,
62
64
  splitAtPlayhead: () => splitAtPlayhead
63
65
  });
@@ -229,6 +231,7 @@ var DawTrackElement = class extends import_lit2.LitElement {
229
231
  this.muted = false;
230
232
  this.soloed = false;
231
233
  this.renderMode = "waveform";
234
+ this.spectrogramConfig = null;
232
235
  this.trackId = crypto.randomUUID();
233
236
  // Track removal is detected by the editor's MutationObserver,
234
237
  // not by dispatching from disconnectedCallback (detached elements
@@ -256,7 +259,16 @@ var DawTrackElement = class extends import_lit2.LitElement {
256
259
  this._hasRendered = true;
257
260
  return;
258
261
  }
259
- const trackProps = ["volume", "pan", "muted", "soloed", "src", "name", "renderMode"];
262
+ const trackProps = [
263
+ "volume",
264
+ "pan",
265
+ "muted",
266
+ "soloed",
267
+ "src",
268
+ "name",
269
+ "renderMode",
270
+ "spectrogramConfig"
271
+ ];
260
272
  const hasTrackChange = trackProps.some((p) => changed.has(p));
261
273
  if (hasTrackChange) {
262
274
  this.dispatchEvent(
@@ -290,6 +302,9 @@ __decorateClass([
290
302
  __decorateClass([
291
303
  (0, import_decorators2.property)({ attribute: "render-mode" })
292
304
  ], DawTrackElement.prototype, "renderMode", 2);
305
+ __decorateClass([
306
+ (0, import_decorators2.property)({ attribute: false })
307
+ ], DawTrackElement.prototype, "spectrogramConfig", 2);
293
308
  DawTrackElement = __decorateClass([
294
309
  (0, import_decorators2.customElement)("daw-track")
295
310
  ], DawTrackElement);
@@ -2757,6 +2772,121 @@ var RecordingController = class {
2757
2772
  }
2758
2773
  };
2759
2774
 
2775
+ // src/controllers/spectrogram-controller.ts
2776
+ 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";
2786
+ var SpectrogramController = class {
2787
+ constructor(host, workerFactory) {
2788
+ this.orchestrator = null;
2789
+ this.editorConfig = null;
2790
+ this.editorColorMap = null;
2791
+ this.trackConfigs = /* @__PURE__ */ new Map();
2792
+ this.trackColorMaps = /* @__PURE__ */ new Map();
2793
+ this.host = host;
2794
+ this.workerFactory = workerFactory;
2795
+ this.host.addController(this);
2796
+ }
2797
+ hostConnected() {
2798
+ }
2799
+ hostDisconnected() {
2800
+ this.dispose();
2801
+ }
2802
+ setEditorConfig(config) {
2803
+ this.editorConfig = config;
2804
+ this.reapply();
2805
+ }
2806
+ setEditorColorMap(colorMap) {
2807
+ this.editorColorMap = colorMap;
2808
+ this.reapply();
2809
+ }
2810
+ setTrackConfig(trackId, config) {
2811
+ if (config === null) {
2812
+ this.trackConfigs.delete(trackId);
2813
+ } else {
2814
+ this.trackConfigs.set(trackId, config);
2815
+ }
2816
+ this.reapply();
2817
+ }
2818
+ setTrackColorMap(trackId, colorMap) {
2819
+ if (colorMap === null) {
2820
+ this.trackColorMaps.delete(trackId);
2821
+ } else {
2822
+ this.trackColorMaps.set(trackId, colorMap);
2823
+ }
2824
+ this.reapply();
2825
+ }
2826
+ registerClipAudio(reg) {
2827
+ this.ensureOrchestrator().registerClip(reg);
2828
+ }
2829
+ unregisterClipAudio(clipId) {
2830
+ this.orchestrator?.unregisterClip(clipId);
2831
+ }
2832
+ registerCanvas(reg) {
2833
+ this.ensureOrchestrator().registerCanvas(reg);
2834
+ }
2835
+ unregisterCanvas(canvasId) {
2836
+ this.orchestrator?.unregisterCanvas(canvasId);
2837
+ }
2838
+ setViewport(state5) {
2839
+ this.orchestrator?.setViewport(state5);
2840
+ }
2841
+ dispose() {
2842
+ if (this.orchestrator) {
2843
+ this.orchestrator.dispose();
2844
+ this.orchestrator = null;
2845
+ }
2846
+ }
2847
+ ensureOrchestrator() {
2848
+ if (!this.orchestrator) {
2849
+ this.orchestrator = new import_spectrogram.SpectrogramOrchestrator({
2850
+ workerFactory: this.workerFactory,
2851
+ workerPoolSize: 2,
2852
+ config: this.mergedConfig(),
2853
+ colorMap: this.mergedColorMap()
2854
+ });
2855
+ this.orchestrator.addEventListener("viewport-ready", (e) => {
2856
+ const detail = e.detail;
2857
+ this.host.dispatchEvent(
2858
+ new CustomEvent("daw-spectrogram-ready", {
2859
+ detail,
2860
+ bubbles: true,
2861
+ composed: true
2862
+ })
2863
+ );
2864
+ });
2865
+ this.reapply();
2866
+ }
2867
+ return this.orchestrator;
2868
+ }
2869
+ reapply() {
2870
+ if (!this.orchestrator) return;
2871
+ this.orchestrator.setConfig(this.mergedConfig());
2872
+ this.orchestrator.setColorMap(this.mergedColorMap());
2873
+ }
2874
+ mergedConfig() {
2875
+ let track = null;
2876
+ for (const c of this.trackConfigs.values()) {
2877
+ track = c;
2878
+ break;
2879
+ }
2880
+ return { ...LIBRARY_DEFAULTS, ...this.editorConfig ?? {}, ...track ?? {} };
2881
+ }
2882
+ mergedColorMap() {
2883
+ for (const c of this.trackColorMaps.values()) {
2884
+ return c ?? LIBRARY_DEFAULT_COLOR_MAP;
2885
+ }
2886
+ return this.editorColorMap ?? LIBRARY_DEFAULT_COLOR_MAP;
2887
+ }
2888
+ };
2889
+
2760
2890
  // src/interactions/pointer-handler.ts
2761
2891
  var import_core4 = require("@waveform-playlist/core");
2762
2892
 
@@ -3637,6 +3767,7 @@ async function loadWaveformDataFromUrl(src) {
3637
3767
  }
3638
3768
 
3639
3769
  // src/elements/daw-editor.ts
3770
+ var import_meta = {};
3640
3771
  var NO_ADAPTER_ERROR = "No PlayoutAdapter set on <daw-editor>. Set editor.adapter before use.\n\n // Option 1: Native Web Audio (no Tone.js)\n npm install @dawcore/transport\n import { NativePlayoutAdapter } from '@dawcore/transport';\n editor.adapter = new NativePlayoutAdapter(new AudioContext());\n\n // Option 2: Tone.js (effects, MIDI synths)\n npm install @waveform-playlist/playout\n import { createToneAdapter } from '@waveform-playlist/playout';\n editor.adapter = createToneAdapter();";
3641
3772
  var DawEditorElement = class extends import_lit14.LitElement {
3642
3773
  constructor() {
@@ -3652,6 +3783,8 @@ var DawEditorElement = class extends import_lit14.LitElement {
3652
3783
  this.clipHeaderHeight = 20;
3653
3784
  this.interactiveClips = false;
3654
3785
  this.indefinitePlayback = false;
3786
+ this._spectrogramConfig = null;
3787
+ this._spectrogramColorMap = null;
3655
3788
  this.scaleMode = "temporal";
3656
3789
  this._ticksPerPixel = 24;
3657
3790
  this._bpm = 120;
@@ -3687,6 +3820,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
3687
3820
  this._childObserver = null;
3688
3821
  this._audioResume = new AudioResumeController(this);
3689
3822
  this._recordingController = new RecordingController(this);
3823
+ this._spectrogramController = null;
3690
3824
  this._clipPointer = new ClipPointerHandler(this);
3691
3825
  this._pointer = new PointerHandler(this);
3692
3826
  this._viewport = (() => {
@@ -3727,6 +3861,26 @@ var DawEditorElement = class extends import_lit14.LitElement {
3727
3861
  if (oldDescriptor?.src !== descriptor.src) {
3728
3862
  this._loadTrack(trackId, descriptor);
3729
3863
  }
3864
+ if (descriptor.renderMode === "spectrogram" && oldDescriptor?.renderMode !== "spectrogram") {
3865
+ const engineTrack = this._engineTracks.get(trackId);
3866
+ if (engineTrack) {
3867
+ for (const clip of engineTrack.clips) {
3868
+ this._maybeRegisterSpectrogramClipAudio(trackId, clip);
3869
+ }
3870
+ }
3871
+ }
3872
+ if (descriptor.renderMode !== "spectrogram" && oldDescriptor?.renderMode === "spectrogram") {
3873
+ const engineTrack = this._engineTracks.get(trackId);
3874
+ if (engineTrack && this._spectrogramController) {
3875
+ for (const clip of engineTrack.clips) {
3876
+ this._spectrogramController.unregisterClipAudio(clip.id);
3877
+ }
3878
+ }
3879
+ this._disposeSpectrogramControllerIfEmpty();
3880
+ }
3881
+ if (descriptor.spectrogramConfig !== oldDescriptor?.spectrogramConfig) {
3882
+ this._spectrogramController?.setTrackConfig(trackId, descriptor.spectrogramConfig ?? null);
3883
+ }
3730
3884
  };
3731
3885
  this._onTrackControl = (e) => {
3732
3886
  const { trackId, prop, value } = e.detail ?? {};
@@ -3870,6 +4024,72 @@ var DawEditorElement = class extends import_lit14.LitElement {
3870
4024
  this._samplesPerPixel = clamped;
3871
4025
  this.requestUpdate("samplesPerPixel", old);
3872
4026
  }
4027
+ get spectrogramConfig() {
4028
+ return this._spectrogramConfig;
4029
+ }
4030
+ set spectrogramConfig(value) {
4031
+ const old = this._spectrogramConfig;
4032
+ this._spectrogramConfig = value;
4033
+ this._spectrogramController?.setEditorConfig(value);
4034
+ this.requestUpdate("spectrogramConfig", old);
4035
+ }
4036
+ get spectrogramColorMap() {
4037
+ return this._spectrogramColorMap;
4038
+ }
4039
+ set spectrogramColorMap(value) {
4040
+ const old = this._spectrogramColorMap;
4041
+ this._spectrogramColorMap = value;
4042
+ this._spectrogramController?.setEditorColorMap(value);
4043
+ this.requestUpdate("spectrogramColorMap", old);
4044
+ }
4045
+ _ensureSpectrogramController() {
4046
+ if (!this._spectrogramController) {
4047
+ this._spectrogramController = new SpectrogramController(
4048
+ this,
4049
+ () => new Worker(new URL("@dawcore/spectrogram/worker/spectrogram.worker", import_meta.url), {
4050
+ type: "module"
4051
+ })
4052
+ );
4053
+ if (this._spectrogramConfig) {
4054
+ this._spectrogramController.setEditorConfig(this._spectrogramConfig);
4055
+ }
4056
+ if (this._spectrogramColorMap) {
4057
+ this._spectrogramController.setEditorColorMap(this._spectrogramColorMap);
4058
+ }
4059
+ }
4060
+ return this._spectrogramController;
4061
+ }
4062
+ /** Called by <daw-spectrogram> after transferControlToOffscreen. */
4063
+ _spectrogramRegisterCanvas(reg) {
4064
+ this._ensureSpectrogramController().registerCanvas(reg);
4065
+ }
4066
+ /** Called by <daw-spectrogram> on chunk unmount / element disconnect. */
4067
+ _spectrogramUnregisterCanvas(canvasId) {
4068
+ this._spectrogramController?.unregisterCanvas(canvasId);
4069
+ }
4070
+ /**
4071
+ * Push a clip's decoded audio into the spectrogram controller. No-op
4072
+ * unless the track is in spectrogram render-mode and the controller
4073
+ * already exists (it bootstraps from canvas registration).
4074
+ */
4075
+ _maybeRegisterSpectrogramClipAudio(trackId, clip) {
4076
+ const descriptor = this._tracks.get(trackId);
4077
+ if (descriptor?.renderMode !== "spectrogram") return;
4078
+ const buffer = clip.audioBuffer ?? this._clipBuffers.get(clip.id);
4079
+ if (!buffer) return;
4080
+ const channelData = [];
4081
+ for (let i = 0; i < buffer.numberOfChannels; i++) {
4082
+ channelData.push(buffer.getChannelData(i));
4083
+ }
4084
+ this._ensureSpectrogramController().registerClipAudio({
4085
+ clipId: clip.id,
4086
+ trackId,
4087
+ channelData,
4088
+ sampleRate: buffer.sampleRate,
4089
+ durationSamples: clip.durationSamples,
4090
+ offsetSamples: clip.offsetSamples
4091
+ });
4092
+ }
3873
4093
  get ticksPerPixel() {
3874
4094
  return this._ticksPerPixel;
3875
4095
  }
@@ -4110,6 +4330,8 @@ var DawEditorElement = class extends import_lit14.LitElement {
4110
4330
  this._clipOffsets.clear();
4111
4331
  this._peakPipeline.terminate();
4112
4332
  this._minSamplesPerPixel = 0;
4333
+ this._spectrogramController?.dispose();
4334
+ this._spectrogramController = null;
4113
4335
  try {
4114
4336
  this._disposeEngine();
4115
4337
  } catch (err) {
@@ -4138,6 +4360,23 @@ var DawEditorElement = class extends import_lit14.LitElement {
4138
4360
  }
4139
4361
  }
4140
4362
  }
4363
+ updated(_changed) {
4364
+ if (this._spectrogramController) {
4365
+ const vs = this._viewport.visibleStart;
4366
+ const ve = this._viewport.visibleEnd;
4367
+ if (Number.isFinite(vs) && Number.isFinite(ve)) {
4368
+ const span = ve - vs;
4369
+ const bufferPad = span * 0.25;
4370
+ this._spectrogramController.setViewport({
4371
+ visibleStartPx: vs,
4372
+ visibleEndPx: ve,
4373
+ bufferStartPx: Math.max(0, vs - bufferPad),
4374
+ bufferEndPx: ve + bufferPad,
4375
+ samplesPerPixel: this._renderSpp
4376
+ });
4377
+ }
4378
+ }
4379
+ }
4141
4380
  _onTrackRemoved(trackId) {
4142
4381
  this._trackElements.delete(trackId);
4143
4382
  const removedTrack = this._engineTracks.get(trackId);
@@ -4147,6 +4386,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4147
4386
  this._clipBuffers.delete(clip.id);
4148
4387
  this._clipOffsets.delete(clip.id);
4149
4388
  nextPeaks.delete(clip.id);
4389
+ this._spectrogramController?.unregisterClipAudio(clip.id);
4150
4390
  }
4151
4391
  this._peaksData = nextPeaks;
4152
4392
  }
@@ -4161,11 +4401,23 @@ var DawEditorElement = class extends import_lit14.LitElement {
4161
4401
  this._engine.removeTrack(trackId);
4162
4402
  }
4163
4403
  this._minSamplesPerPixel = this._peakPipeline.getMaxCachedScale(this._clipBuffers);
4404
+ this._disposeSpectrogramControllerIfEmpty();
4164
4405
  if (nextEngine.size === 0) {
4165
4406
  this._currentTime = 0;
4166
4407
  this._stopPlayhead();
4167
4408
  }
4168
4409
  }
4410
+ /** Drop the controller when no spectrogram tracks remain. */
4411
+ _disposeSpectrogramControllerIfEmpty() {
4412
+ if (!this._spectrogramController) return;
4413
+ const stillNeeded = Array.from(this._tracks.values()).some(
4414
+ (d) => d.renderMode === "spectrogram"
4415
+ );
4416
+ if (!stillNeeded) {
4417
+ this._spectrogramController.dispose();
4418
+ this._spectrogramController = null;
4419
+ }
4420
+ }
4169
4421
  _onClipRemovedFromDom(clipEl) {
4170
4422
  const clipId = clipEl.clipId;
4171
4423
  for (const [trackId, t] of this._engineTracks.entries()) {
@@ -4207,6 +4459,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4207
4459
  });
4208
4460
  }
4209
4461
  this._commitTrackChange(trackId, updatedTrack);
4462
+ this._maybeRegisterSpectrogramClipAudio(trackId, clip);
4210
4463
  this.dispatchEvent(
4211
4464
  new CustomEvent("daw-clip-ready", {
4212
4465
  bubbles: true,
@@ -4381,6 +4634,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
4381
4634
  nextPeaks.delete(clipId);
4382
4635
  this._peaksData = nextPeaks;
4383
4636
  this._clipOffsets.delete(clipId);
4637
+ this._spectrogramController?.unregisterClipAudio(clipId);
4384
4638
  }
4385
4639
  /**
4386
4640
  * Recompute duration and forward an updated track to the engine. Single
@@ -4663,6 +4917,9 @@ var DawEditorElement = class extends import_lit14.LitElement {
4663
4917
  track.id = trackId;
4664
4918
  this._engineTracks = new Map(this._engineTracks).set(trackId, track);
4665
4919
  this._recomputeDuration();
4920
+ for (const c of clips) {
4921
+ this._maybeRegisterSpectrogramClipAudio(trackId, c);
4922
+ }
4666
4923
  const engine = await this._ensureEngine();
4667
4924
  engine.setTracks([...this._engineTracks.values()]);
4668
4925
  this.dispatchEvent(
@@ -5474,19 +5731,34 @@ var DawEditorElement = class extends import_lit14.LitElement {
5474
5731
  .visibleEnd=${this._viewport.visibleEnd}
5475
5732
  .originX=${clipLeft}
5476
5733
  ?selected=${t.trackId === this._selectedTrackId}
5477
- ></daw-piano-roll>` : channels.map(
5734
+ ></daw-piano-roll>` : t.descriptor?.renderMode === "spectrogram" ? channels.map(
5735
+ (_chPeaks, chIdx) => import_lit14.html`<daw-spectrogram
5736
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;height:${chH}px;width:${peakData?.length ?? width}px;"
5737
+ .clipId=${clip.id}
5738
+ .trackId=${t.trackId}
5739
+ .channelIndex=${chIdx}
5740
+ .length=${peakData?.length ?? width}
5741
+ .waveHeight=${chH}
5742
+ .samplesPerPixel=${this._renderSpp}
5743
+ .sampleRate=${this.effectiveSampleRate}
5744
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
5745
+ .visibleStart=${this._viewport.visibleStart}
5746
+ .visibleEnd=${this._viewport.visibleEnd}
5747
+ .originX=${clipLeft}
5748
+ ></daw-spectrogram>`
5749
+ ) : channels.map(
5478
5750
  (chPeaks, chIdx) => import_lit14.html` <daw-waveform
5479
- style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5480
- .peaks=${chPeaks}
5481
- .length=${peakData?.length ?? width}
5482
- .waveHeight=${chH}
5483
- .barWidth=${this.barWidth}
5484
- .barGap=${this.barGap}
5485
- .visibleStart=${this._viewport.visibleStart}
5486
- .visibleEnd=${this._viewport.visibleEnd}
5487
- .originX=${clipLeft}
5488
- .segments=${clipSegments}
5489
- ></daw-waveform>`
5751
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5752
+ .peaks=${chPeaks}
5753
+ .length=${peakData?.length ?? width}
5754
+ .waveHeight=${chH}
5755
+ .barWidth=${this.barWidth}
5756
+ .barGap=${this.barGap}
5757
+ .visibleStart=${this._viewport.visibleStart}
5758
+ .visibleEnd=${this._viewport.visibleEnd}
5759
+ .originX=${clipLeft}
5760
+ .segments=${clipSegments}
5761
+ ></daw-waveform>`
5490
5762
  )}
5491
5763
  ${this.interactiveClips ? import_lit14.html` <div
5492
5764
  class="clip-boundary"
@@ -5594,6 +5866,12 @@ __decorateClass([
5594
5866
  __decorateClass([
5595
5867
  (0, import_decorators12.property)({ type: Boolean, attribute: "indefinite-playback" })
5596
5868
  ], DawEditorElement.prototype, "indefinitePlayback", 2);
5869
+ __decorateClass([
5870
+ (0, import_decorators12.property)({ attribute: false, noAccessor: true })
5871
+ ], DawEditorElement.prototype, "spectrogramConfig", 1);
5872
+ __decorateClass([
5873
+ (0, import_decorators12.property)({ attribute: false, noAccessor: true })
5874
+ ], DawEditorElement.prototype, "spectrogramColorMap", 1);
5597
5875
  __decorateClass([
5598
5876
  (0, import_decorators12.property)({ type: String, attribute: "scale-mode" })
5599
5877
  ], DawEditorElement.prototype, "scaleMode", 2);
@@ -6172,6 +6450,187 @@ __decorateClass([
6172
6450
  DawKeyboardShortcutsElement = __decorateClass([
6173
6451
  (0, import_decorators16.customElement)("daw-keyboard-shortcuts")
6174
6452
  ], DawKeyboardShortcutsElement);
6453
+
6454
+ // src/elements/daw-spectrogram.ts
6455
+ var import_lit19 = require("lit");
6456
+ var import_decorators17 = require("lit/decorators.js");
6457
+ var MAX_CANVAS_WIDTH5 = 1e3;
6458
+ var DawSpectrogramElement = class extends import_lit19.LitElement {
6459
+ constructor() {
6460
+ super(...arguments);
6461
+ this.clipId = "";
6462
+ this.trackId = "";
6463
+ this.channelIndex = 0;
6464
+ this.length = 0;
6465
+ this.waveHeight = 128;
6466
+ this._samplesPerPixel = 1024;
6467
+ this._sampleRate = 44100;
6468
+ this.clipOffsetSeconds = 0;
6469
+ this.visibleStart = -Infinity;
6470
+ this.visibleEnd = Infinity;
6471
+ this.originX = 0;
6472
+ this._canvases = [];
6473
+ this._registeredCanvasIds = [];
6474
+ }
6475
+ get samplesPerPixel() {
6476
+ return this._samplesPerPixel;
6477
+ }
6478
+ set samplesPerPixel(value) {
6479
+ if (!Number.isFinite(value) || value <= 0) {
6480
+ console.warn("[dawcore] daw-spectrogram samplesPerPixel " + value + " is invalid \u2014 ignored");
6481
+ return;
6482
+ }
6483
+ const old = this._samplesPerPixel;
6484
+ this._samplesPerPixel = value;
6485
+ this.requestUpdate("samplesPerPixel", old);
6486
+ }
6487
+ get sampleRate() {
6488
+ return this._sampleRate;
6489
+ }
6490
+ set sampleRate(value) {
6491
+ if (!Number.isFinite(value) || value <= 0) {
6492
+ console.warn("[dawcore] daw-spectrogram sampleRate " + value + " is invalid \u2014 ignored");
6493
+ return;
6494
+ }
6495
+ const old = this._sampleRate;
6496
+ this._sampleRate = value;
6497
+ this.requestUpdate("sampleRate", old);
6498
+ }
6499
+ /**
6500
+ * Walk up to the editor host. `closest('daw-editor')` does NOT cross
6501
+ * shadow boundaries — and this element lives inside the editor's shadow
6502
+ * DOM — so use getRootNode().host to step out.
6503
+ */
6504
+ _findHostEditor() {
6505
+ const root = this.getRootNode();
6506
+ const host = root instanceof ShadowRoot ? root.host : null;
6507
+ if (!host) return null;
6508
+ if (host.tagName === "DAW-EDITOR") return host;
6509
+ return host.closest("daw-editor");
6510
+ }
6511
+ willUpdate(changed) {
6512
+ const layoutChanged = changed.has("length") || changed.has("waveHeight") || changed.has("samplesPerPixel") || changed.has("clipId") || changed.has("channelIndex");
6513
+ if (layoutChanged) {
6514
+ this._rebuildChunks();
6515
+ }
6516
+ }
6517
+ _rebuildChunks() {
6518
+ this._unregisterAllCanvases();
6519
+ this._canvases = [];
6520
+ if (this.length <= 0) return;
6521
+ const chunkCount = Math.ceil(this.length / MAX_CANVAS_WIDTH5);
6522
+ for (let i = 0; i < chunkCount; i++) {
6523
+ const widthPx = Math.min(MAX_CANVAS_WIDTH5, this.length - i * MAX_CANVAS_WIDTH5);
6524
+ const canvas = document.createElement("canvas");
6525
+ canvas.style.left = i * MAX_CANVAS_WIDTH5 + "px";
6526
+ canvas.style.width = widthPx + "px";
6527
+ const dpr = window.devicePixelRatio || 1;
6528
+ canvas.width = widthPx * dpr;
6529
+ canvas.height = this.waveHeight * dpr;
6530
+ this._canvases.push(canvas);
6531
+ }
6532
+ }
6533
+ updated(_changed) {
6534
+ if (this._registeredCanvasIds.length === 0 && this._canvases.length > 0) {
6535
+ requestAnimationFrame(() => this._registerCanvases());
6536
+ }
6537
+ }
6538
+ _registerCanvases() {
6539
+ const editor = this._findHostEditor();
6540
+ if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") return;
6541
+ for (let i = 0; i < this._canvases.length; i++) {
6542
+ const canvas = this._canvases[i];
6543
+ const canvasId = this.clipId + "-ch" + this.channelIndex + "-chunk" + i;
6544
+ let offscreen;
6545
+ try {
6546
+ offscreen = canvas.transferControlToOffscreen();
6547
+ } catch (err) {
6548
+ console.warn(
6549
+ "[dawcore] daw-spectrogram transferControlToOffscreen failed for " + canvasId + ": " + (err instanceof Error ? err.message : String(err))
6550
+ );
6551
+ continue;
6552
+ }
6553
+ editor._spectrogramRegisterCanvas({
6554
+ canvasId,
6555
+ canvas: offscreen,
6556
+ clipId: this.clipId,
6557
+ trackId: this.trackId,
6558
+ channelIndex: this.channelIndex,
6559
+ chunkIndex: i,
6560
+ globalPixelOffset: this.originX + i * MAX_CANVAS_WIDTH5,
6561
+ widthPx: parseFloat(canvas.style.width),
6562
+ heightPx: this.waveHeight
6563
+ });
6564
+ this._registeredCanvasIds.push(canvasId);
6565
+ }
6566
+ }
6567
+ _unregisterAllCanvases() {
6568
+ const editor = this._findHostEditor();
6569
+ if (editor && typeof editor._spectrogramUnregisterCanvas === "function") {
6570
+ for (const id of this._registeredCanvasIds) {
6571
+ editor._spectrogramUnregisterCanvas(id);
6572
+ }
6573
+ }
6574
+ this._registeredCanvasIds = [];
6575
+ }
6576
+ disconnectedCallback() {
6577
+ super.disconnectedCallback();
6578
+ this._unregisterAllCanvases();
6579
+ }
6580
+ render() {
6581
+ return import_lit19.html`${this._canvases.map((c) => c)}`;
6582
+ }
6583
+ };
6584
+ DawSpectrogramElement.styles = import_lit19.css`
6585
+ :host {
6586
+ display: block;
6587
+ position: relative;
6588
+ background: var(--daw-spectrogram-background, #000);
6589
+ }
6590
+ canvas {
6591
+ position: absolute;
6592
+ top: 0;
6593
+ left: 0;
6594
+ height: 100%;
6595
+ pointer-events: none;
6596
+ }
6597
+ `;
6598
+ __decorateClass([
6599
+ (0, import_decorators17.property)({ attribute: false })
6600
+ ], DawSpectrogramElement.prototype, "clipId", 2);
6601
+ __decorateClass([
6602
+ (0, import_decorators17.property)({ attribute: false })
6603
+ ], DawSpectrogramElement.prototype, "trackId", 2);
6604
+ __decorateClass([
6605
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6606
+ ], DawSpectrogramElement.prototype, "channelIndex", 2);
6607
+ __decorateClass([
6608
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6609
+ ], DawSpectrogramElement.prototype, "length", 2);
6610
+ __decorateClass([
6611
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6612
+ ], DawSpectrogramElement.prototype, "waveHeight", 2);
6613
+ __decorateClass([
6614
+ (0, import_decorators17.property)({ type: Number, attribute: false, noAccessor: true })
6615
+ ], DawSpectrogramElement.prototype, "samplesPerPixel", 1);
6616
+ __decorateClass([
6617
+ (0, import_decorators17.property)({ type: Number, attribute: false, noAccessor: true })
6618
+ ], DawSpectrogramElement.prototype, "sampleRate", 1);
6619
+ __decorateClass([
6620
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6621
+ ], DawSpectrogramElement.prototype, "clipOffsetSeconds", 2);
6622
+ __decorateClass([
6623
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6624
+ ], DawSpectrogramElement.prototype, "visibleStart", 2);
6625
+ __decorateClass([
6626
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6627
+ ], DawSpectrogramElement.prototype, "visibleEnd", 2);
6628
+ __decorateClass([
6629
+ (0, import_decorators17.property)({ type: Number, attribute: false })
6630
+ ], DawSpectrogramElement.prototype, "originX", 2);
6631
+ DawSpectrogramElement = __decorateClass([
6632
+ (0, import_decorators17.customElement)("daw-spectrogram")
6633
+ ], DawSpectrogramElement);
6175
6634
  // Annotate the CommonJS export names for ESM import in node:
6176
6635
  0 && (module.exports = {
6177
6636
  AudioResumeController,
@@ -6187,6 +6646,7 @@ DawKeyboardShortcutsElement = __decorateClass([
6187
6646
  DawRecordButtonElement,
6188
6647
  DawRulerElement,
6189
6648
  DawSelectionElement,
6649
+ DawSpectrogramElement,
6190
6650
  DawStopButtonElement,
6191
6651
  DawTrackControlsElement,
6192
6652
  DawTrackElement,
@@ -6194,6 +6654,7 @@ DawKeyboardShortcutsElement = __decorateClass([
6194
6654
  DawTransportElement,
6195
6655
  DawWaveformElement,
6196
6656
  RecordingController,
6657
+ SpectrogramController,
6197
6658
  isDomClip,
6198
6659
  splitAtPlayhead
6199
6660
  });