@dawcore/components 0.0.16 → 0.0.18
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 +145 -3
- package/dist/index.d.ts +145 -3
- package/dist/index.js +487 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +486 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
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 = [
|
|
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 = (() => {
|
|
@@ -3645,6 +3778,15 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
3645
3778
|
v.scrollSelector = ".scroll-area";
|
|
3646
3779
|
return v;
|
|
3647
3780
|
})();
|
|
3781
|
+
/**
|
|
3782
|
+
* Cache of the last ViewportState forwarded to the spectrogram controller.
|
|
3783
|
+
* Lit's `updated()` fires on every reactive state change (`_isPlaying`,
|
|
3784
|
+
* `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
|
|
3785
|
+
* viewport. Skip the cross-controller call when nothing changed.
|
|
3786
|
+
*
|
|
3787
|
+
* The orchestrator dedupes too, but this avoids the call entirely.
|
|
3788
|
+
*/
|
|
3789
|
+
this._lastSpectrogramViewport = null;
|
|
3648
3790
|
// --- Track Events ---
|
|
3649
3791
|
this._onTrackConnected = (e) => {
|
|
3650
3792
|
const trackId = e.detail?.trackId;
|
|
@@ -3678,6 +3820,26 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
3678
3820
|
if (oldDescriptor?.src !== descriptor.src) {
|
|
3679
3821
|
this._loadTrack(trackId, descriptor);
|
|
3680
3822
|
}
|
|
3823
|
+
if (descriptor.renderMode === "spectrogram" && oldDescriptor?.renderMode !== "spectrogram") {
|
|
3824
|
+
const engineTrack = this._engineTracks.get(trackId);
|
|
3825
|
+
if (engineTrack) {
|
|
3826
|
+
for (const clip of engineTrack.clips) {
|
|
3827
|
+
this._maybeRegisterSpectrogramClipAudio(trackId, clip);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
if (descriptor.renderMode !== "spectrogram" && oldDescriptor?.renderMode === "spectrogram") {
|
|
3832
|
+
const engineTrack = this._engineTracks.get(trackId);
|
|
3833
|
+
if (engineTrack && this._spectrogramController) {
|
|
3834
|
+
for (const clip of engineTrack.clips) {
|
|
3835
|
+
this._spectrogramController.unregisterClipAudio(clip.id);
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
this._disposeSpectrogramControllerIfEmpty();
|
|
3839
|
+
}
|
|
3840
|
+
if (descriptor.spectrogramConfig !== oldDescriptor?.spectrogramConfig) {
|
|
3841
|
+
this._spectrogramController?.setTrackConfig(trackId, descriptor.spectrogramConfig ?? null);
|
|
3842
|
+
}
|
|
3681
3843
|
};
|
|
3682
3844
|
this._onTrackControl = (e) => {
|
|
3683
3845
|
const { trackId, prop, value } = e.detail ?? {};
|
|
@@ -3821,6 +3983,72 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
3821
3983
|
this._samplesPerPixel = clamped;
|
|
3822
3984
|
this.requestUpdate("samplesPerPixel", old);
|
|
3823
3985
|
}
|
|
3986
|
+
get spectrogramConfig() {
|
|
3987
|
+
return this._spectrogramConfig;
|
|
3988
|
+
}
|
|
3989
|
+
set spectrogramConfig(value) {
|
|
3990
|
+
const old = this._spectrogramConfig;
|
|
3991
|
+
this._spectrogramConfig = value;
|
|
3992
|
+
this._spectrogramController?.setEditorConfig(value);
|
|
3993
|
+
this.requestUpdate("spectrogramConfig", old);
|
|
3994
|
+
}
|
|
3995
|
+
get spectrogramColorMap() {
|
|
3996
|
+
return this._spectrogramColorMap;
|
|
3997
|
+
}
|
|
3998
|
+
set spectrogramColorMap(value) {
|
|
3999
|
+
const old = this._spectrogramColorMap;
|
|
4000
|
+
this._spectrogramColorMap = value;
|
|
4001
|
+
this._spectrogramController?.setEditorColorMap(value);
|
|
4002
|
+
this.requestUpdate("spectrogramColorMap", old);
|
|
4003
|
+
}
|
|
4004
|
+
_ensureSpectrogramController() {
|
|
4005
|
+
if (!this._spectrogramController) {
|
|
4006
|
+
this._spectrogramController = new SpectrogramController(
|
|
4007
|
+
this,
|
|
4008
|
+
() => new Worker(new URL("@dawcore/spectrogram/worker/spectrogram.worker", import.meta.url), {
|
|
4009
|
+
type: "module"
|
|
4010
|
+
})
|
|
4011
|
+
);
|
|
4012
|
+
if (this._spectrogramConfig) {
|
|
4013
|
+
this._spectrogramController.setEditorConfig(this._spectrogramConfig);
|
|
4014
|
+
}
|
|
4015
|
+
if (this._spectrogramColorMap) {
|
|
4016
|
+
this._spectrogramController.setEditorColorMap(this._spectrogramColorMap);
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
return this._spectrogramController;
|
|
4020
|
+
}
|
|
4021
|
+
/** Called by <daw-spectrogram> after transferControlToOffscreen. */
|
|
4022
|
+
_spectrogramRegisterCanvas(reg) {
|
|
4023
|
+
this._ensureSpectrogramController().registerCanvas(reg);
|
|
4024
|
+
}
|
|
4025
|
+
/** Called by <daw-spectrogram> on chunk unmount / element disconnect. */
|
|
4026
|
+
_spectrogramUnregisterCanvas(canvasId) {
|
|
4027
|
+
this._spectrogramController?.unregisterCanvas(canvasId);
|
|
4028
|
+
}
|
|
4029
|
+
/**
|
|
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).
|
|
4033
|
+
*/
|
|
4034
|
+
_maybeRegisterSpectrogramClipAudio(trackId, clip) {
|
|
4035
|
+
const descriptor = this._tracks.get(trackId);
|
|
4036
|
+
if (descriptor?.renderMode !== "spectrogram") return;
|
|
4037
|
+
const buffer = clip.audioBuffer ?? this._clipBuffers.get(clip.id);
|
|
4038
|
+
if (!buffer) return;
|
|
4039
|
+
const channelData = [];
|
|
4040
|
+
for (let i = 0; i < buffer.numberOfChannels; i++) {
|
|
4041
|
+
channelData.push(buffer.getChannelData(i));
|
|
4042
|
+
}
|
|
4043
|
+
this._ensureSpectrogramController().registerClipAudio({
|
|
4044
|
+
clipId: clip.id,
|
|
4045
|
+
trackId,
|
|
4046
|
+
channelData,
|
|
4047
|
+
sampleRate: buffer.sampleRate,
|
|
4048
|
+
durationSamples: clip.durationSamples,
|
|
4049
|
+
offsetSamples: clip.offsetSamples
|
|
4050
|
+
});
|
|
4051
|
+
}
|
|
3824
4052
|
get ticksPerPixel() {
|
|
3825
4053
|
return this._ticksPerPixel;
|
|
3826
4054
|
}
|
|
@@ -4061,6 +4289,8 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4061
4289
|
this._clipOffsets.clear();
|
|
4062
4290
|
this._peakPipeline.terminate();
|
|
4063
4291
|
this._minSamplesPerPixel = 0;
|
|
4292
|
+
this._spectrogramController?.dispose();
|
|
4293
|
+
this._spectrogramController = null;
|
|
4064
4294
|
try {
|
|
4065
4295
|
this._disposeEngine();
|
|
4066
4296
|
} catch (err) {
|
|
@@ -4089,6 +4319,27 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4089
4319
|
}
|
|
4090
4320
|
}
|
|
4091
4321
|
}
|
|
4322
|
+
updated(_changed) {
|
|
4323
|
+
if (this._spectrogramController) {
|
|
4324
|
+
const vs = this._viewport.visibleStart;
|
|
4325
|
+
const ve = this._viewport.visibleEnd;
|
|
4326
|
+
const spp = this._renderSpp;
|
|
4327
|
+
if (Number.isFinite(vs) && Number.isFinite(ve)) {
|
|
4328
|
+
const prev = this._lastSpectrogramViewport;
|
|
4329
|
+
if (prev && prev.vs === vs && prev.ve === ve && prev.spp === spp) return;
|
|
4330
|
+
this._lastSpectrogramViewport = { vs, ve, spp };
|
|
4331
|
+
const span = ve - vs;
|
|
4332
|
+
const bufferPad = span * 0.25;
|
|
4333
|
+
this._spectrogramController.setViewport({
|
|
4334
|
+
visibleStartPx: vs,
|
|
4335
|
+
visibleEndPx: ve,
|
|
4336
|
+
bufferStartPx: Math.max(0, vs - bufferPad),
|
|
4337
|
+
bufferEndPx: ve + bufferPad,
|
|
4338
|
+
samplesPerPixel: spp
|
|
4339
|
+
});
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4092
4343
|
_onTrackRemoved(trackId) {
|
|
4093
4344
|
this._trackElements.delete(trackId);
|
|
4094
4345
|
const removedTrack = this._engineTracks.get(trackId);
|
|
@@ -4098,6 +4349,7 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4098
4349
|
this._clipBuffers.delete(clip.id);
|
|
4099
4350
|
this._clipOffsets.delete(clip.id);
|
|
4100
4351
|
nextPeaks.delete(clip.id);
|
|
4352
|
+
this._spectrogramController?.unregisterClipAudio(clip.id);
|
|
4101
4353
|
}
|
|
4102
4354
|
this._peaksData = nextPeaks;
|
|
4103
4355
|
}
|
|
@@ -4112,11 +4364,23 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4112
4364
|
this._engine.removeTrack(trackId);
|
|
4113
4365
|
}
|
|
4114
4366
|
this._minSamplesPerPixel = this._peakPipeline.getMaxCachedScale(this._clipBuffers);
|
|
4367
|
+
this._disposeSpectrogramControllerIfEmpty();
|
|
4115
4368
|
if (nextEngine.size === 0) {
|
|
4116
4369
|
this._currentTime = 0;
|
|
4117
4370
|
this._stopPlayhead();
|
|
4118
4371
|
}
|
|
4119
4372
|
}
|
|
4373
|
+
/** Drop the controller when no spectrogram tracks remain. */
|
|
4374
|
+
_disposeSpectrogramControllerIfEmpty() {
|
|
4375
|
+
if (!this._spectrogramController) return;
|
|
4376
|
+
const stillNeeded = Array.from(this._tracks.values()).some(
|
|
4377
|
+
(d) => d.renderMode === "spectrogram"
|
|
4378
|
+
);
|
|
4379
|
+
if (!stillNeeded) {
|
|
4380
|
+
this._spectrogramController.dispose();
|
|
4381
|
+
this._spectrogramController = null;
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4120
4384
|
_onClipRemovedFromDom(clipEl) {
|
|
4121
4385
|
const clipId = clipEl.clipId;
|
|
4122
4386
|
for (const [trackId, t] of this._engineTracks.entries()) {
|
|
@@ -4158,6 +4422,7 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4158
4422
|
});
|
|
4159
4423
|
}
|
|
4160
4424
|
this._commitTrackChange(trackId, updatedTrack);
|
|
4425
|
+
this._maybeRegisterSpectrogramClipAudio(trackId, clip);
|
|
4161
4426
|
this.dispatchEvent(
|
|
4162
4427
|
new CustomEvent("daw-clip-ready", {
|
|
4163
4428
|
bubbles: true,
|
|
@@ -4332,6 +4597,7 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4332
4597
|
nextPeaks.delete(clipId);
|
|
4333
4598
|
this._peaksData = nextPeaks;
|
|
4334
4599
|
this._clipOffsets.delete(clipId);
|
|
4600
|
+
this._spectrogramController?.unregisterClipAudio(clipId);
|
|
4335
4601
|
}
|
|
4336
4602
|
/**
|
|
4337
4603
|
* Recompute duration and forward an updated track to the engine. Single
|
|
@@ -4614,6 +4880,9 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
4614
4880
|
track.id = trackId;
|
|
4615
4881
|
this._engineTracks = new Map(this._engineTracks).set(trackId, track);
|
|
4616
4882
|
this._recomputeDuration();
|
|
4883
|
+
for (const c of clips) {
|
|
4884
|
+
this._maybeRegisterSpectrogramClipAudio(trackId, c);
|
|
4885
|
+
}
|
|
4617
4886
|
const engine = await this._ensureEngine();
|
|
4618
4887
|
engine.setTracks([...this._engineTracks.values()]);
|
|
4619
4888
|
this.dispatchEvent(
|
|
@@ -5425,19 +5694,34 @@ var DawEditorElement = class extends LitElement10 {
|
|
|
5425
5694
|
.visibleEnd=${this._viewport.visibleEnd}
|
|
5426
5695
|
.originX=${clipLeft}
|
|
5427
5696
|
?selected=${t.trackId === this._selectedTrackId}
|
|
5428
|
-
></daw-piano-roll>` : channels.map(
|
|
5697
|
+
></daw-piano-roll>` : t.descriptor?.renderMode === "spectrogram" ? channels.map(
|
|
5698
|
+
(_chPeaks, chIdx) => html9`<daw-spectrogram
|
|
5699
|
+
style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;height:${chH}px;width:${peakData?.length ?? width}px;"
|
|
5700
|
+
.clipId=${clip.id}
|
|
5701
|
+
.trackId=${t.trackId}
|
|
5702
|
+
.channelIndex=${chIdx}
|
|
5703
|
+
.length=${peakData?.length ?? width}
|
|
5704
|
+
.waveHeight=${chH}
|
|
5705
|
+
.samplesPerPixel=${this._renderSpp}
|
|
5706
|
+
.sampleRate=${this.effectiveSampleRate}
|
|
5707
|
+
.clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
|
|
5708
|
+
.visibleStart=${this._viewport.visibleStart}
|
|
5709
|
+
.visibleEnd=${this._viewport.visibleEnd}
|
|
5710
|
+
.originX=${clipLeft}
|
|
5711
|
+
></daw-spectrogram>`
|
|
5712
|
+
) : channels.map(
|
|
5429
5713
|
(chPeaks, chIdx) => html9` <daw-waveform
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5714
|
+
style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
|
|
5715
|
+
.peaks=${chPeaks}
|
|
5716
|
+
.length=${peakData?.length ?? width}
|
|
5717
|
+
.waveHeight=${chH}
|
|
5718
|
+
.barWidth=${this.barWidth}
|
|
5719
|
+
.barGap=${this.barGap}
|
|
5720
|
+
.visibleStart=${this._viewport.visibleStart}
|
|
5721
|
+
.visibleEnd=${this._viewport.visibleEnd}
|
|
5722
|
+
.originX=${clipLeft}
|
|
5723
|
+
.segments=${clipSegments}
|
|
5724
|
+
></daw-waveform>`
|
|
5441
5725
|
)}
|
|
5442
5726
|
${this.interactiveClips ? html9` <div
|
|
5443
5727
|
class="clip-boundary"
|
|
@@ -5545,6 +5829,12 @@ __decorateClass([
|
|
|
5545
5829
|
__decorateClass([
|
|
5546
5830
|
property8({ type: Boolean, attribute: "indefinite-playback" })
|
|
5547
5831
|
], DawEditorElement.prototype, "indefinitePlayback", 2);
|
|
5832
|
+
__decorateClass([
|
|
5833
|
+
property8({ attribute: false, noAccessor: true })
|
|
5834
|
+
], DawEditorElement.prototype, "spectrogramConfig", 1);
|
|
5835
|
+
__decorateClass([
|
|
5836
|
+
property8({ attribute: false, noAccessor: true })
|
|
5837
|
+
], DawEditorElement.prototype, "spectrogramColorMap", 1);
|
|
5548
5838
|
__decorateClass([
|
|
5549
5839
|
property8({ type: String, attribute: "scale-mode" })
|
|
5550
5840
|
], DawEditorElement.prototype, "scaleMode", 2);
|
|
@@ -6123,6 +6413,187 @@ __decorateClass([
|
|
|
6123
6413
|
DawKeyboardShortcutsElement = __decorateClass([
|
|
6124
6414
|
customElement16("daw-keyboard-shortcuts")
|
|
6125
6415
|
], DawKeyboardShortcutsElement);
|
|
6416
|
+
|
|
6417
|
+
// src/elements/daw-spectrogram.ts
|
|
6418
|
+
import { LitElement as LitElement14, html as html13, css as css13 } from "lit";
|
|
6419
|
+
import { customElement as customElement17, property as property12 } from "lit/decorators.js";
|
|
6420
|
+
var MAX_CANVAS_WIDTH5 = 1e3;
|
|
6421
|
+
var DawSpectrogramElement = class extends LitElement14 {
|
|
6422
|
+
constructor() {
|
|
6423
|
+
super(...arguments);
|
|
6424
|
+
this.clipId = "";
|
|
6425
|
+
this.trackId = "";
|
|
6426
|
+
this.channelIndex = 0;
|
|
6427
|
+
this.length = 0;
|
|
6428
|
+
this.waveHeight = 128;
|
|
6429
|
+
this._samplesPerPixel = 1024;
|
|
6430
|
+
this._sampleRate = 44100;
|
|
6431
|
+
this.clipOffsetSeconds = 0;
|
|
6432
|
+
this.visibleStart = -Infinity;
|
|
6433
|
+
this.visibleEnd = Infinity;
|
|
6434
|
+
this.originX = 0;
|
|
6435
|
+
this._canvases = [];
|
|
6436
|
+
this._registeredCanvasIds = [];
|
|
6437
|
+
}
|
|
6438
|
+
get samplesPerPixel() {
|
|
6439
|
+
return this._samplesPerPixel;
|
|
6440
|
+
}
|
|
6441
|
+
set samplesPerPixel(value) {
|
|
6442
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
6443
|
+
console.warn("[dawcore] daw-spectrogram samplesPerPixel " + value + " is invalid \u2014 ignored");
|
|
6444
|
+
return;
|
|
6445
|
+
}
|
|
6446
|
+
const old = this._samplesPerPixel;
|
|
6447
|
+
this._samplesPerPixel = value;
|
|
6448
|
+
this.requestUpdate("samplesPerPixel", old);
|
|
6449
|
+
}
|
|
6450
|
+
get sampleRate() {
|
|
6451
|
+
return this._sampleRate;
|
|
6452
|
+
}
|
|
6453
|
+
set sampleRate(value) {
|
|
6454
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
6455
|
+
console.warn("[dawcore] daw-spectrogram sampleRate " + value + " is invalid \u2014 ignored");
|
|
6456
|
+
return;
|
|
6457
|
+
}
|
|
6458
|
+
const old = this._sampleRate;
|
|
6459
|
+
this._sampleRate = value;
|
|
6460
|
+
this.requestUpdate("sampleRate", old);
|
|
6461
|
+
}
|
|
6462
|
+
/**
|
|
6463
|
+
* Walk up to the editor host. `closest('daw-editor')` does NOT cross
|
|
6464
|
+
* shadow boundaries — and this element lives inside the editor's shadow
|
|
6465
|
+
* DOM — so use getRootNode().host to step out.
|
|
6466
|
+
*/
|
|
6467
|
+
_findHostEditor() {
|
|
6468
|
+
const root = this.getRootNode();
|
|
6469
|
+
const host = root instanceof ShadowRoot ? root.host : null;
|
|
6470
|
+
if (!host) return null;
|
|
6471
|
+
if (host.tagName === "DAW-EDITOR") return host;
|
|
6472
|
+
return host.closest("daw-editor");
|
|
6473
|
+
}
|
|
6474
|
+
willUpdate(changed) {
|
|
6475
|
+
const layoutChanged = changed.has("length") || changed.has("waveHeight") || changed.has("samplesPerPixel") || changed.has("clipId") || changed.has("channelIndex");
|
|
6476
|
+
if (layoutChanged) {
|
|
6477
|
+
this._rebuildChunks();
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
_rebuildChunks() {
|
|
6481
|
+
this._unregisterAllCanvases();
|
|
6482
|
+
this._canvases = [];
|
|
6483
|
+
if (this.length <= 0) return;
|
|
6484
|
+
const chunkCount = Math.ceil(this.length / MAX_CANVAS_WIDTH5);
|
|
6485
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
6486
|
+
const widthPx = Math.min(MAX_CANVAS_WIDTH5, this.length - i * MAX_CANVAS_WIDTH5);
|
|
6487
|
+
const canvas = document.createElement("canvas");
|
|
6488
|
+
canvas.style.left = i * MAX_CANVAS_WIDTH5 + "px";
|
|
6489
|
+
canvas.style.width = widthPx + "px";
|
|
6490
|
+
const dpr = window.devicePixelRatio || 1;
|
|
6491
|
+
canvas.width = widthPx * dpr;
|
|
6492
|
+
canvas.height = this.waveHeight * dpr;
|
|
6493
|
+
this._canvases.push(canvas);
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
updated(_changed) {
|
|
6497
|
+
if (this._registeredCanvasIds.length === 0 && this._canvases.length > 0) {
|
|
6498
|
+
requestAnimationFrame(() => this._registerCanvases());
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
_registerCanvases() {
|
|
6502
|
+
const editor = this._findHostEditor();
|
|
6503
|
+
if (!editor || typeof editor._spectrogramRegisterCanvas !== "function") return;
|
|
6504
|
+
for (let i = 0; i < this._canvases.length; i++) {
|
|
6505
|
+
const canvas = this._canvases[i];
|
|
6506
|
+
const canvasId = this.clipId + "-ch" + this.channelIndex + "-chunk" + i;
|
|
6507
|
+
let offscreen;
|
|
6508
|
+
try {
|
|
6509
|
+
offscreen = canvas.transferControlToOffscreen();
|
|
6510
|
+
} catch (err) {
|
|
6511
|
+
console.warn(
|
|
6512
|
+
"[dawcore] daw-spectrogram transferControlToOffscreen failed for " + canvasId + ": " + (err instanceof Error ? err.message : String(err))
|
|
6513
|
+
);
|
|
6514
|
+
continue;
|
|
6515
|
+
}
|
|
6516
|
+
editor._spectrogramRegisterCanvas({
|
|
6517
|
+
canvasId,
|
|
6518
|
+
canvas: offscreen,
|
|
6519
|
+
clipId: this.clipId,
|
|
6520
|
+
trackId: this.trackId,
|
|
6521
|
+
channelIndex: this.channelIndex,
|
|
6522
|
+
chunkIndex: i,
|
|
6523
|
+
globalPixelOffset: this.originX + i * MAX_CANVAS_WIDTH5,
|
|
6524
|
+
widthPx: parseFloat(canvas.style.width),
|
|
6525
|
+
heightPx: this.waveHeight
|
|
6526
|
+
});
|
|
6527
|
+
this._registeredCanvasIds.push(canvasId);
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
_unregisterAllCanvases() {
|
|
6531
|
+
const editor = this._findHostEditor();
|
|
6532
|
+
if (editor && typeof editor._spectrogramUnregisterCanvas === "function") {
|
|
6533
|
+
for (const id of this._registeredCanvasIds) {
|
|
6534
|
+
editor._spectrogramUnregisterCanvas(id);
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
this._registeredCanvasIds = [];
|
|
6538
|
+
}
|
|
6539
|
+
disconnectedCallback() {
|
|
6540
|
+
super.disconnectedCallback();
|
|
6541
|
+
this._unregisterAllCanvases();
|
|
6542
|
+
}
|
|
6543
|
+
render() {
|
|
6544
|
+
return html13`${this._canvases.map((c) => c)}`;
|
|
6545
|
+
}
|
|
6546
|
+
};
|
|
6547
|
+
DawSpectrogramElement.styles = css13`
|
|
6548
|
+
:host {
|
|
6549
|
+
display: block;
|
|
6550
|
+
position: relative;
|
|
6551
|
+
background: var(--daw-spectrogram-background, #000);
|
|
6552
|
+
}
|
|
6553
|
+
canvas {
|
|
6554
|
+
position: absolute;
|
|
6555
|
+
top: 0;
|
|
6556
|
+
left: 0;
|
|
6557
|
+
height: 100%;
|
|
6558
|
+
pointer-events: none;
|
|
6559
|
+
}
|
|
6560
|
+
`;
|
|
6561
|
+
__decorateClass([
|
|
6562
|
+
property12({ attribute: false })
|
|
6563
|
+
], DawSpectrogramElement.prototype, "clipId", 2);
|
|
6564
|
+
__decorateClass([
|
|
6565
|
+
property12({ attribute: false })
|
|
6566
|
+
], DawSpectrogramElement.prototype, "trackId", 2);
|
|
6567
|
+
__decorateClass([
|
|
6568
|
+
property12({ type: Number, attribute: false })
|
|
6569
|
+
], DawSpectrogramElement.prototype, "channelIndex", 2);
|
|
6570
|
+
__decorateClass([
|
|
6571
|
+
property12({ type: Number, attribute: false })
|
|
6572
|
+
], DawSpectrogramElement.prototype, "length", 2);
|
|
6573
|
+
__decorateClass([
|
|
6574
|
+
property12({ type: Number, attribute: false })
|
|
6575
|
+
], DawSpectrogramElement.prototype, "waveHeight", 2);
|
|
6576
|
+
__decorateClass([
|
|
6577
|
+
property12({ type: Number, attribute: false, noAccessor: true })
|
|
6578
|
+
], DawSpectrogramElement.prototype, "samplesPerPixel", 1);
|
|
6579
|
+
__decorateClass([
|
|
6580
|
+
property12({ type: Number, attribute: false, noAccessor: true })
|
|
6581
|
+
], DawSpectrogramElement.prototype, "sampleRate", 1);
|
|
6582
|
+
__decorateClass([
|
|
6583
|
+
property12({ type: Number, attribute: false })
|
|
6584
|
+
], DawSpectrogramElement.prototype, "clipOffsetSeconds", 2);
|
|
6585
|
+
__decorateClass([
|
|
6586
|
+
property12({ type: Number, attribute: false })
|
|
6587
|
+
], DawSpectrogramElement.prototype, "visibleStart", 2);
|
|
6588
|
+
__decorateClass([
|
|
6589
|
+
property12({ type: Number, attribute: false })
|
|
6590
|
+
], DawSpectrogramElement.prototype, "visibleEnd", 2);
|
|
6591
|
+
__decorateClass([
|
|
6592
|
+
property12({ type: Number, attribute: false })
|
|
6593
|
+
], DawSpectrogramElement.prototype, "originX", 2);
|
|
6594
|
+
DawSpectrogramElement = __decorateClass([
|
|
6595
|
+
customElement17("daw-spectrogram")
|
|
6596
|
+
], DawSpectrogramElement);
|
|
6126
6597
|
export {
|
|
6127
6598
|
AudioResumeController,
|
|
6128
6599
|
ClipPointerHandler,
|
|
@@ -6137,6 +6608,7 @@ export {
|
|
|
6137
6608
|
DawRecordButtonElement,
|
|
6138
6609
|
DawRulerElement,
|
|
6139
6610
|
DawSelectionElement,
|
|
6611
|
+
DawSpectrogramElement,
|
|
6140
6612
|
DawStopButtonElement,
|
|
6141
6613
|
DawTrackControlsElement,
|
|
6142
6614
|
DawTrackElement,
|
|
@@ -6144,6 +6616,7 @@ export {
|
|
|
6144
6616
|
DawTransportElement,
|
|
6145
6617
|
DawWaveformElement,
|
|
6146
6618
|
RecordingController,
|
|
6619
|
+
SpectrogramController,
|
|
6147
6620
|
isDomClip,
|
|
6148
6621
|
splitAtPlayhead
|
|
6149
6622
|
};
|