@dawcore/components 0.0.15 → 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.d.mts +353 -85
- package/dist/index.d.ts +353 -85
- package/dist/index.js +1202 -199
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1190 -189
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -26,11 +26,48 @@ var DawClipElement = class extends LitElement {
|
|
|
26
26
|
this.fadeIn = 0;
|
|
27
27
|
this.fadeOut = 0;
|
|
28
28
|
this.fadeType = "linear";
|
|
29
|
+
this.midiNotes = null;
|
|
30
|
+
this._midiChannel = null;
|
|
31
|
+
this._midiProgram = null;
|
|
29
32
|
this.clipId = crypto.randomUUID();
|
|
30
33
|
// Removal is detected by the editor's MutationObserver — detached elements
|
|
31
34
|
// cannot bubble events to ancestors.
|
|
32
35
|
this._hasRendered = false;
|
|
33
36
|
}
|
|
37
|
+
get midiChannel() {
|
|
38
|
+
return this._midiChannel;
|
|
39
|
+
}
|
|
40
|
+
set midiChannel(value) {
|
|
41
|
+
const old = this._midiChannel;
|
|
42
|
+
if (value === null) {
|
|
43
|
+
this._midiChannel = null;
|
|
44
|
+
this.requestUpdate("midiChannel", old);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0 || value > 15) {
|
|
48
|
+
console.warn("[dawcore] daw-clip midi-channel " + value + " is out of range 0-15 \u2014 ignored");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this._midiChannel = value;
|
|
52
|
+
this.requestUpdate("midiChannel", old);
|
|
53
|
+
}
|
|
54
|
+
get midiProgram() {
|
|
55
|
+
return this._midiProgram;
|
|
56
|
+
}
|
|
57
|
+
set midiProgram(value) {
|
|
58
|
+
const old = this._midiProgram;
|
|
59
|
+
if (value === null) {
|
|
60
|
+
this._midiProgram = null;
|
|
61
|
+
this.requestUpdate("midiProgram", old);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0 || value > 127) {
|
|
65
|
+
console.warn("[dawcore] daw-clip midi-program " + value + " is out of range 0-127 \u2014 ignored");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this._midiProgram = value;
|
|
69
|
+
this.requestUpdate("midiProgram", old);
|
|
70
|
+
}
|
|
34
71
|
// Light DOM — no visual rendering, just a data container
|
|
35
72
|
createRenderRoot() {
|
|
36
73
|
return this;
|
|
@@ -62,7 +99,10 @@ var DawClipElement = class extends LitElement {
|
|
|
62
99
|
"name",
|
|
63
100
|
"fadeIn",
|
|
64
101
|
"fadeOut",
|
|
65
|
-
"fadeType"
|
|
102
|
+
"fadeType",
|
|
103
|
+
"midiNotes",
|
|
104
|
+
"midiChannel",
|
|
105
|
+
"midiProgram"
|
|
66
106
|
];
|
|
67
107
|
if (clipProps.some((p) => changed.has(p))) {
|
|
68
108
|
const trackEl = this.closest("daw-track");
|
|
@@ -109,6 +149,15 @@ __decorateClass([
|
|
|
109
149
|
__decorateClass([
|
|
110
150
|
property({ attribute: "fade-type" })
|
|
111
151
|
], DawClipElement.prototype, "fadeType", 2);
|
|
152
|
+
__decorateClass([
|
|
153
|
+
property({ attribute: false })
|
|
154
|
+
], DawClipElement.prototype, "midiNotes", 2);
|
|
155
|
+
__decorateClass([
|
|
156
|
+
property({ type: Number, attribute: "midi-channel", noAccessor: true })
|
|
157
|
+
], DawClipElement.prototype, "midiChannel", 1);
|
|
158
|
+
__decorateClass([
|
|
159
|
+
property({ type: Number, attribute: "midi-program", noAccessor: true })
|
|
160
|
+
], DawClipElement.prototype, "midiProgram", 1);
|
|
112
161
|
DawClipElement = __decorateClass([
|
|
113
162
|
customElement("daw-clip")
|
|
114
163
|
], DawClipElement);
|
|
@@ -125,6 +174,8 @@ var DawTrackElement = class extends LitElement2 {
|
|
|
125
174
|
this.pan = 0;
|
|
126
175
|
this.muted = false;
|
|
127
176
|
this.soloed = false;
|
|
177
|
+
this.renderMode = "waveform";
|
|
178
|
+
this.spectrogramConfig = null;
|
|
128
179
|
this.trackId = crypto.randomUUID();
|
|
129
180
|
// Track removal is detected by the editor's MutationObserver,
|
|
130
181
|
// not by dispatching from disconnectedCallback (detached elements
|
|
@@ -152,7 +203,16 @@ var DawTrackElement = class extends LitElement2 {
|
|
|
152
203
|
this._hasRendered = true;
|
|
153
204
|
return;
|
|
154
205
|
}
|
|
155
|
-
const trackProps = [
|
|
206
|
+
const trackProps = [
|
|
207
|
+
"volume",
|
|
208
|
+
"pan",
|
|
209
|
+
"muted",
|
|
210
|
+
"soloed",
|
|
211
|
+
"src",
|
|
212
|
+
"name",
|
|
213
|
+
"renderMode",
|
|
214
|
+
"spectrogramConfig"
|
|
215
|
+
];
|
|
156
216
|
const hasTrackChange = trackProps.some((p) => changed.has(p));
|
|
157
217
|
if (hasTrackChange) {
|
|
158
218
|
this.dispatchEvent(
|
|
@@ -183,6 +243,12 @@ __decorateClass([
|
|
|
183
243
|
__decorateClass([
|
|
184
244
|
property2({ type: Boolean })
|
|
185
245
|
], DawTrackElement.prototype, "soloed", 2);
|
|
246
|
+
__decorateClass([
|
|
247
|
+
property2({ attribute: "render-mode" })
|
|
248
|
+
], DawTrackElement.prototype, "renderMode", 2);
|
|
249
|
+
__decorateClass([
|
|
250
|
+
property2({ attribute: false })
|
|
251
|
+
], DawTrackElement.prototype, "spectrogramConfig", 2);
|
|
186
252
|
DawTrackElement = __decorateClass([
|
|
187
253
|
customElement2("daw-track")
|
|
188
254
|
], DawTrackElement);
|
|
@@ -532,9 +598,229 @@ DawWaveformElement = __decorateClass([
|
|
|
532
598
|
customElement3("daw-waveform")
|
|
533
599
|
], DawWaveformElement);
|
|
534
600
|
|
|
535
|
-
// src/elements/daw-
|
|
601
|
+
// src/elements/daw-piano-roll.ts
|
|
536
602
|
import { LitElement as LitElement4, html as html2, css as css2 } from "lit";
|
|
537
|
-
import { customElement as customElement4 } from "lit/decorators.js";
|
|
603
|
+
import { customElement as customElement4, property as property4 } from "lit/decorators.js";
|
|
604
|
+
var MAX_CANVAS_WIDTH2 = 1e3;
|
|
605
|
+
var LAYOUT_PROPS2 = /* @__PURE__ */ new Set([
|
|
606
|
+
"length",
|
|
607
|
+
"waveHeight",
|
|
608
|
+
"samplesPerPixel",
|
|
609
|
+
"sampleRate",
|
|
610
|
+
"clipOffsetSeconds",
|
|
611
|
+
"midiNotes",
|
|
612
|
+
"selected"
|
|
613
|
+
]);
|
|
614
|
+
var DawPianoRollElement = class extends LitElement4 {
|
|
615
|
+
constructor() {
|
|
616
|
+
super(...arguments);
|
|
617
|
+
this.midiNotes = [];
|
|
618
|
+
this.length = 0;
|
|
619
|
+
this.waveHeight = 128;
|
|
620
|
+
this._samplesPerPixel = 1024;
|
|
621
|
+
this._sampleRate = 48e3;
|
|
622
|
+
this.clipOffsetSeconds = 0;
|
|
623
|
+
this.visibleStart = -Infinity;
|
|
624
|
+
this.visibleEnd = Infinity;
|
|
625
|
+
this.originX = 0;
|
|
626
|
+
this.selected = false;
|
|
627
|
+
this._rafHandle = null;
|
|
628
|
+
}
|
|
629
|
+
get samplesPerPixel() {
|
|
630
|
+
return this._samplesPerPixel;
|
|
631
|
+
}
|
|
632
|
+
set samplesPerPixel(value) {
|
|
633
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
634
|
+
console.warn("[dawcore] daw-piano-roll samplesPerPixel " + value + " is invalid \u2014 ignored");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const old = this._samplesPerPixel;
|
|
638
|
+
this._samplesPerPixel = value;
|
|
639
|
+
this.requestUpdate("samplesPerPixel", old);
|
|
640
|
+
}
|
|
641
|
+
get sampleRate() {
|
|
642
|
+
return this._sampleRate;
|
|
643
|
+
}
|
|
644
|
+
set sampleRate(value) {
|
|
645
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
646
|
+
console.warn("[dawcore] daw-piano-roll sampleRate " + value + " is invalid \u2014 ignored");
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const old = this._sampleRate;
|
|
650
|
+
this._sampleRate = value;
|
|
651
|
+
this.requestUpdate("sampleRate", old);
|
|
652
|
+
}
|
|
653
|
+
_scheduleDraw() {
|
|
654
|
+
if (this._rafHandle !== null) return;
|
|
655
|
+
this._rafHandle = requestAnimationFrame(() => {
|
|
656
|
+
this._rafHandle = null;
|
|
657
|
+
this._draw();
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
willUpdate(_changed) {
|
|
661
|
+
this._scheduleDraw();
|
|
662
|
+
}
|
|
663
|
+
updated(changedProperties) {
|
|
664
|
+
const needsFullDraw = [...changedProperties.keys()].some((key) => LAYOUT_PROPS2.has(key));
|
|
665
|
+
if (needsFullDraw) return;
|
|
666
|
+
if (changedProperties.has("visibleStart") || changedProperties.has("visibleEnd") || changedProperties.has("originX")) {
|
|
667
|
+
this._scheduleDraw();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
_getPitchRange() {
|
|
671
|
+
if (this.midiNotes.length === 0) return { minMidi: 0, maxMidi: 127 };
|
|
672
|
+
let min = 127;
|
|
673
|
+
let max = 0;
|
|
674
|
+
for (const note of this.midiNotes) {
|
|
675
|
+
if (note.midi < min) min = note.midi;
|
|
676
|
+
if (note.midi > max) max = note.midi;
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
minMidi: Math.max(0, min - 1),
|
|
680
|
+
maxMidi: Math.min(127, max + 1)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
_getNoteColor() {
|
|
684
|
+
const cs = getComputedStyle(this);
|
|
685
|
+
const note = cs.getPropertyValue("--daw-piano-roll-note-color").trim() || "#2a7070";
|
|
686
|
+
const selectedColor = cs.getPropertyValue("--daw-piano-roll-selected-note-color").trim() || "#3d9e9e";
|
|
687
|
+
return this.selected ? selectedColor : note;
|
|
688
|
+
}
|
|
689
|
+
_draw() {
|
|
690
|
+
if (!this.shadowRoot) return;
|
|
691
|
+
const canvases = this.shadowRoot.querySelectorAll("canvas");
|
|
692
|
+
if (canvases.length === 0) return;
|
|
693
|
+
const { minMidi, maxMidi } = this._getPitchRange();
|
|
694
|
+
const noteRange = maxMidi - minMidi + 1;
|
|
695
|
+
const noteHeight = Math.max(2, this.waveHeight / noteRange);
|
|
696
|
+
const pixelsPerSecond = this.sampleRate / this.samplesPerPixel;
|
|
697
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
698
|
+
const color = this._getNoteColor();
|
|
699
|
+
for (const canvas of canvases) {
|
|
700
|
+
const chunkIdx = Number(canvas.dataset.index);
|
|
701
|
+
const chunkPixelStart = chunkIdx * MAX_CANVAS_WIDTH2;
|
|
702
|
+
const canvasWidth = canvas.width / dpr;
|
|
703
|
+
const ctx = canvas.getContext("2d");
|
|
704
|
+
if (!ctx) continue;
|
|
705
|
+
ctx.resetTransform();
|
|
706
|
+
ctx.clearRect(
|
|
707
|
+
0,
|
|
708
|
+
0,
|
|
709
|
+
canvas.width,
|
|
710
|
+
canvas.height
|
|
711
|
+
);
|
|
712
|
+
ctx.imageSmoothingEnabled = false;
|
|
713
|
+
ctx.scale(dpr, dpr);
|
|
714
|
+
const chunkStartTime = chunkPixelStart * this.samplesPerPixel / this.sampleRate;
|
|
715
|
+
const chunkEndTime = (chunkPixelStart + canvasWidth) * this.samplesPerPixel / this.sampleRate;
|
|
716
|
+
for (const note of this.midiNotes) {
|
|
717
|
+
const noteStart = note.time - this.clipOffsetSeconds;
|
|
718
|
+
const noteEnd = noteStart + note.duration;
|
|
719
|
+
if (noteEnd <= chunkStartTime || noteStart >= chunkEndTime) continue;
|
|
720
|
+
const x = noteStart * pixelsPerSecond - chunkPixelStart;
|
|
721
|
+
const w = Math.max(2, note.duration * pixelsPerSecond);
|
|
722
|
+
const y = (maxMidi - note.midi) / noteRange * this.waveHeight;
|
|
723
|
+
const alpha = 0.3 + note.velocity * 0.7;
|
|
724
|
+
ctx.fillStyle = color;
|
|
725
|
+
ctx.globalAlpha = alpha;
|
|
726
|
+
ctx.beginPath();
|
|
727
|
+
ctx.roundRect(x, y, w, noteHeight, 1);
|
|
728
|
+
ctx.fill();
|
|
729
|
+
}
|
|
730
|
+
ctx.globalAlpha = 1;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
connectedCallback() {
|
|
734
|
+
super.connectedCallback();
|
|
735
|
+
this._scheduleDraw();
|
|
736
|
+
}
|
|
737
|
+
disconnectedCallback() {
|
|
738
|
+
super.disconnectedCallback();
|
|
739
|
+
if (this._rafHandle !== null) {
|
|
740
|
+
cancelAnimationFrame(this._rafHandle);
|
|
741
|
+
this._rafHandle = null;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
render() {
|
|
745
|
+
if (this.length <= 0)
|
|
746
|
+
return html2`<div class="container" style="width: 0; height: ${this.waveHeight}px;"></div>`;
|
|
747
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
748
|
+
const visibleIndices = getVisibleChunkIndices(
|
|
749
|
+
this.length,
|
|
750
|
+
MAX_CANVAS_WIDTH2,
|
|
751
|
+
this.visibleStart,
|
|
752
|
+
this.visibleEnd,
|
|
753
|
+
this.originX
|
|
754
|
+
);
|
|
755
|
+
return html2`
|
|
756
|
+
<div class="container" style="width: ${this.length}px; height: ${this.waveHeight}px;">
|
|
757
|
+
${visibleIndices.map((i) => {
|
|
758
|
+
const chunkLeft = i * MAX_CANVAS_WIDTH2;
|
|
759
|
+
const chunkWidth = Math.min(this.length - chunkLeft, MAX_CANVAS_WIDTH2);
|
|
760
|
+
return html2`<canvas
|
|
761
|
+
data-index=${i}
|
|
762
|
+
width=${chunkWidth * dpr}
|
|
763
|
+
height=${this.waveHeight * dpr}
|
|
764
|
+
style="left: ${chunkLeft}px; width: ${chunkWidth}px; height: ${this.waveHeight}px;"
|
|
765
|
+
></canvas>`;
|
|
766
|
+
})}
|
|
767
|
+
</div>
|
|
768
|
+
`;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
DawPianoRollElement.styles = css2`
|
|
772
|
+
:host {
|
|
773
|
+
display: block;
|
|
774
|
+
position: relative;
|
|
775
|
+
}
|
|
776
|
+
.container {
|
|
777
|
+
position: relative;
|
|
778
|
+
background: var(--daw-piano-roll-background, #1a1a2e);
|
|
779
|
+
}
|
|
780
|
+
canvas {
|
|
781
|
+
position: absolute;
|
|
782
|
+
top: 0;
|
|
783
|
+
image-rendering: pixelated;
|
|
784
|
+
image-rendering: crisp-edges;
|
|
785
|
+
}
|
|
786
|
+
`;
|
|
787
|
+
__decorateClass([
|
|
788
|
+
property4({ attribute: false })
|
|
789
|
+
], DawPianoRollElement.prototype, "midiNotes", 2);
|
|
790
|
+
__decorateClass([
|
|
791
|
+
property4({ type: Number, attribute: false })
|
|
792
|
+
], DawPianoRollElement.prototype, "length", 2);
|
|
793
|
+
__decorateClass([
|
|
794
|
+
property4({ type: Number, attribute: false })
|
|
795
|
+
], DawPianoRollElement.prototype, "waveHeight", 2);
|
|
796
|
+
__decorateClass([
|
|
797
|
+
property4({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
|
|
798
|
+
], DawPianoRollElement.prototype, "samplesPerPixel", 1);
|
|
799
|
+
__decorateClass([
|
|
800
|
+
property4({ type: Number, attribute: "sample-rate", noAccessor: true })
|
|
801
|
+
], DawPianoRollElement.prototype, "sampleRate", 1);
|
|
802
|
+
__decorateClass([
|
|
803
|
+
property4({ type: Number, attribute: false })
|
|
804
|
+
], DawPianoRollElement.prototype, "clipOffsetSeconds", 2);
|
|
805
|
+
__decorateClass([
|
|
806
|
+
property4({ type: Number, attribute: false })
|
|
807
|
+
], DawPianoRollElement.prototype, "visibleStart", 2);
|
|
808
|
+
__decorateClass([
|
|
809
|
+
property4({ type: Number, attribute: false })
|
|
810
|
+
], DawPianoRollElement.prototype, "visibleEnd", 2);
|
|
811
|
+
__decorateClass([
|
|
812
|
+
property4({ type: Number, attribute: false })
|
|
813
|
+
], DawPianoRollElement.prototype, "originX", 2);
|
|
814
|
+
__decorateClass([
|
|
815
|
+
property4({ type: Boolean, reflect: true })
|
|
816
|
+
], DawPianoRollElement.prototype, "selected", 2);
|
|
817
|
+
DawPianoRollElement = __decorateClass([
|
|
818
|
+
customElement4("daw-piano-roll")
|
|
819
|
+
], DawPianoRollElement);
|
|
820
|
+
|
|
821
|
+
// src/elements/daw-playhead.ts
|
|
822
|
+
import { LitElement as LitElement5, html as html3, css as css3 } from "lit";
|
|
823
|
+
import { customElement as customElement5 } from "lit/decorators.js";
|
|
538
824
|
|
|
539
825
|
// src/controllers/animation-controller.ts
|
|
540
826
|
var AnimationController = class {
|
|
@@ -567,14 +853,14 @@ var AnimationController = class {
|
|
|
567
853
|
};
|
|
568
854
|
|
|
569
855
|
// src/elements/daw-playhead.ts
|
|
570
|
-
var DawPlayheadElement = class extends
|
|
856
|
+
var DawPlayheadElement = class extends LitElement5 {
|
|
571
857
|
constructor() {
|
|
572
858
|
super(...arguments);
|
|
573
859
|
this._animation = new AnimationController(this);
|
|
574
860
|
this._line = null;
|
|
575
861
|
}
|
|
576
862
|
render() {
|
|
577
|
-
return
|
|
863
|
+
return html3`<div></div>`;
|
|
578
864
|
}
|
|
579
865
|
firstUpdated() {
|
|
580
866
|
this._line = this.shadowRoot.querySelector("div");
|
|
@@ -630,7 +916,7 @@ var DawPlayheadElement = class extends LitElement4 {
|
|
|
630
916
|
}
|
|
631
917
|
}
|
|
632
918
|
};
|
|
633
|
-
DawPlayheadElement.styles =
|
|
919
|
+
DawPlayheadElement.styles = css3`
|
|
634
920
|
:host {
|
|
635
921
|
position: absolute;
|
|
636
922
|
top: 0;
|
|
@@ -649,13 +935,13 @@ DawPlayheadElement.styles = css2`
|
|
|
649
935
|
}
|
|
650
936
|
`;
|
|
651
937
|
DawPlayheadElement = __decorateClass([
|
|
652
|
-
|
|
938
|
+
customElement5("daw-playhead")
|
|
653
939
|
], DawPlayheadElement);
|
|
654
940
|
|
|
655
941
|
// src/elements/daw-transport.ts
|
|
656
|
-
import { LitElement as
|
|
657
|
-
import { customElement as
|
|
658
|
-
var DawTransportElement = class extends
|
|
942
|
+
import { LitElement as LitElement6 } from "lit";
|
|
943
|
+
import { customElement as customElement6, property as property5 } from "lit/decorators.js";
|
|
944
|
+
var DawTransportElement = class extends LitElement6 {
|
|
659
945
|
constructor() {
|
|
660
946
|
super(...arguments);
|
|
661
947
|
this.for = "";
|
|
@@ -670,25 +956,25 @@ var DawTransportElement = class extends LitElement5 {
|
|
|
670
956
|
}
|
|
671
957
|
};
|
|
672
958
|
__decorateClass([
|
|
673
|
-
|
|
959
|
+
property5()
|
|
674
960
|
], DawTransportElement.prototype, "for", 2);
|
|
675
961
|
DawTransportElement = __decorateClass([
|
|
676
|
-
|
|
962
|
+
customElement6("daw-transport")
|
|
677
963
|
], DawTransportElement);
|
|
678
964
|
|
|
679
965
|
// src/elements/daw-play-button.ts
|
|
680
|
-
import { html as
|
|
681
|
-
import { customElement as
|
|
966
|
+
import { html as html4 } from "lit";
|
|
967
|
+
import { customElement as customElement7, state } from "lit/decorators.js";
|
|
682
968
|
|
|
683
969
|
// src/elements/daw-transport-button.ts
|
|
684
|
-
import { LitElement as
|
|
685
|
-
var DawTransportButton = class extends
|
|
970
|
+
import { LitElement as LitElement7, css as css4 } from "lit";
|
|
971
|
+
var DawTransportButton = class extends LitElement7 {
|
|
686
972
|
get target() {
|
|
687
973
|
const transport = this.closest("daw-transport");
|
|
688
974
|
return transport?.target ?? null;
|
|
689
975
|
}
|
|
690
976
|
};
|
|
691
|
-
DawTransportButton.styles =
|
|
977
|
+
DawTransportButton.styles = css4`
|
|
692
978
|
button {
|
|
693
979
|
cursor: pointer;
|
|
694
980
|
background: var(--daw-controls-background, #1a1a2e);
|
|
@@ -740,7 +1026,7 @@ var DawPlayButtonElement = class extends DawTransportButton {
|
|
|
740
1026
|
}
|
|
741
1027
|
}
|
|
742
1028
|
render() {
|
|
743
|
-
return
|
|
1029
|
+
return html4`
|
|
744
1030
|
<button part="button" ?disabled=${this._isRecording} @click=${this._onClick}>
|
|
745
1031
|
<slot>Play</slot>
|
|
746
1032
|
</button>
|
|
@@ -761,12 +1047,12 @@ __decorateClass([
|
|
|
761
1047
|
state()
|
|
762
1048
|
], DawPlayButtonElement.prototype, "_isRecording", 2);
|
|
763
1049
|
DawPlayButtonElement = __decorateClass([
|
|
764
|
-
|
|
1050
|
+
customElement7("daw-play-button")
|
|
765
1051
|
], DawPlayButtonElement);
|
|
766
1052
|
|
|
767
1053
|
// src/elements/daw-pause-button.ts
|
|
768
|
-
import { html as
|
|
769
|
-
import { customElement as
|
|
1054
|
+
import { html as html5, css as css5 } from "lit";
|
|
1055
|
+
import { customElement as customElement8, state as state2 } from "lit/decorators.js";
|
|
770
1056
|
var DawPauseButtonElement = class extends DawTransportButton {
|
|
771
1057
|
constructor() {
|
|
772
1058
|
super(...arguments);
|
|
@@ -780,6 +1066,12 @@ var DawPauseButtonElement = class extends DawTransportButton {
|
|
|
780
1066
|
this._isRecording = false;
|
|
781
1067
|
this._isPaused = false;
|
|
782
1068
|
};
|
|
1069
|
+
this._onRecPause = () => {
|
|
1070
|
+
this._isPaused = true;
|
|
1071
|
+
};
|
|
1072
|
+
this._onRecResume = () => {
|
|
1073
|
+
this._isPaused = false;
|
|
1074
|
+
};
|
|
783
1075
|
}
|
|
784
1076
|
connectedCallback() {
|
|
785
1077
|
super.connectedCallback();
|
|
@@ -790,6 +1082,8 @@ var DawPauseButtonElement = class extends DawTransportButton {
|
|
|
790
1082
|
target.addEventListener("daw-recording-start", this._onRecStart);
|
|
791
1083
|
target.addEventListener("daw-recording-complete", this._onRecEnd);
|
|
792
1084
|
target.addEventListener("daw-recording-error", this._onRecEnd);
|
|
1085
|
+
target.addEventListener("daw-recording-pause", this._onRecPause);
|
|
1086
|
+
target.addEventListener("daw-recording-resume", this._onRecResume);
|
|
793
1087
|
});
|
|
794
1088
|
}
|
|
795
1089
|
disconnectedCallback() {
|
|
@@ -798,11 +1092,13 @@ var DawPauseButtonElement = class extends DawTransportButton {
|
|
|
798
1092
|
this._targetRef.removeEventListener("daw-recording-start", this._onRecStart);
|
|
799
1093
|
this._targetRef.removeEventListener("daw-recording-complete", this._onRecEnd);
|
|
800
1094
|
this._targetRef.removeEventListener("daw-recording-error", this._onRecEnd);
|
|
1095
|
+
this._targetRef.removeEventListener("daw-recording-pause", this._onRecPause);
|
|
1096
|
+
this._targetRef.removeEventListener("daw-recording-resume", this._onRecResume);
|
|
801
1097
|
this._targetRef = null;
|
|
802
1098
|
}
|
|
803
1099
|
}
|
|
804
1100
|
render() {
|
|
805
|
-
return
|
|
1101
|
+
return html5`
|
|
806
1102
|
<button part="button" ?data-paused=${this._isPaused} @click=${this._onClick}>
|
|
807
1103
|
<slot>Pause</slot>
|
|
808
1104
|
</button>
|
|
@@ -817,15 +1113,7 @@ var DawPauseButtonElement = class extends DawTransportButton {
|
|
|
817
1113
|
return;
|
|
818
1114
|
}
|
|
819
1115
|
if (this._isRecording) {
|
|
820
|
-
|
|
821
|
-
target.resumeRecording();
|
|
822
|
-
target.play(target.currentTime);
|
|
823
|
-
this._isPaused = false;
|
|
824
|
-
} else {
|
|
825
|
-
target.pauseRecording();
|
|
826
|
-
target.pause();
|
|
827
|
-
this._isPaused = true;
|
|
828
|
-
}
|
|
1116
|
+
target.togglePauseRecording();
|
|
829
1117
|
} else {
|
|
830
1118
|
target.pause();
|
|
831
1119
|
}
|
|
@@ -833,7 +1121,7 @@ var DawPauseButtonElement = class extends DawTransportButton {
|
|
|
833
1121
|
};
|
|
834
1122
|
DawPauseButtonElement.styles = [
|
|
835
1123
|
DawTransportButton.styles,
|
|
836
|
-
|
|
1124
|
+
css5`
|
|
837
1125
|
button[data-paused] {
|
|
838
1126
|
background: rgba(255, 255, 255, 0.1);
|
|
839
1127
|
border-color: var(--daw-controls-text, #e0d4c8);
|
|
@@ -847,15 +1135,15 @@ __decorateClass([
|
|
|
847
1135
|
state2()
|
|
848
1136
|
], DawPauseButtonElement.prototype, "_isRecording", 2);
|
|
849
1137
|
DawPauseButtonElement = __decorateClass([
|
|
850
|
-
|
|
1138
|
+
customElement8("daw-pause-button")
|
|
851
1139
|
], DawPauseButtonElement);
|
|
852
1140
|
|
|
853
1141
|
// src/elements/daw-stop-button.ts
|
|
854
|
-
import { html as
|
|
855
|
-
import { customElement as
|
|
1142
|
+
import { html as html6 } from "lit";
|
|
1143
|
+
import { customElement as customElement9 } from "lit/decorators.js";
|
|
856
1144
|
var DawStopButtonElement = class extends DawTransportButton {
|
|
857
1145
|
render() {
|
|
858
|
-
return
|
|
1146
|
+
return html6`
|
|
859
1147
|
<button part="button" @click=${this._onClick}>
|
|
860
1148
|
<slot>Stop</slot>
|
|
861
1149
|
</button>
|
|
@@ -870,18 +1158,31 @@ var DawStopButtonElement = class extends DawTransportButton {
|
|
|
870
1158
|
return;
|
|
871
1159
|
}
|
|
872
1160
|
if (target.isRecording) {
|
|
873
|
-
target.stopRecording()
|
|
1161
|
+
target.stopRecording().catch((err) => {
|
|
1162
|
+
console.warn("[dawcore] stopRecording failed: " + String(err));
|
|
1163
|
+
}).then(() => {
|
|
1164
|
+
try {
|
|
1165
|
+
target.stop();
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
console.warn("[dawcore] stop after stopRecording failed: " + String(err));
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
} else {
|
|
1171
|
+
try {
|
|
1172
|
+
target.stop();
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
console.warn("[dawcore] stop failed: " + String(err));
|
|
1175
|
+
}
|
|
874
1176
|
}
|
|
875
|
-
target.stop();
|
|
876
1177
|
}
|
|
877
1178
|
};
|
|
878
1179
|
DawStopButtonElement = __decorateClass([
|
|
879
|
-
|
|
1180
|
+
customElement9("daw-stop-button")
|
|
880
1181
|
], DawStopButtonElement);
|
|
881
1182
|
|
|
882
1183
|
// src/elements/daw-editor.ts
|
|
883
|
-
import { LitElement as
|
|
884
|
-
import { customElement as
|
|
1184
|
+
import { LitElement as LitElement10, html as html9, css as css9 } from "lit";
|
|
1185
|
+
import { customElement as customElement12, property as property8, state as state3 } from "lit/decorators.js";
|
|
885
1186
|
|
|
886
1187
|
// src/types.ts
|
|
887
1188
|
function isDomClip(desc) {
|
|
@@ -1366,9 +1667,9 @@ var PeakPipeline = class {
|
|
|
1366
1667
|
};
|
|
1367
1668
|
|
|
1368
1669
|
// src/elements/daw-track-controls.ts
|
|
1369
|
-
import { LitElement as
|
|
1370
|
-
import { customElement as
|
|
1371
|
-
var DawTrackControlsElement = class extends
|
|
1670
|
+
import { LitElement as LitElement8, html as html7, css as css6 } from "lit";
|
|
1671
|
+
import { customElement as customElement10, property as property6 } from "lit/decorators.js";
|
|
1672
|
+
var DawTrackControlsElement = class extends LitElement8 {
|
|
1372
1673
|
constructor() {
|
|
1373
1674
|
super(...arguments);
|
|
1374
1675
|
this.trackId = null;
|
|
@@ -1416,7 +1717,7 @@ var DawTrackControlsElement = class extends LitElement7 {
|
|
|
1416
1717
|
const volPercent = Math.round(this.volume * 100);
|
|
1417
1718
|
const panPercent = Math.round(Math.abs(this.pan) * 100);
|
|
1418
1719
|
const panDisplay = this.pan === 0 ? "C" : (this.pan > 0 ? "R" : "L") + panPercent;
|
|
1419
|
-
return
|
|
1720
|
+
return html7`
|
|
1420
1721
|
<div class="header">
|
|
1421
1722
|
<span class="name" title=${this.trackName}>${this.trackName || "Untitled"}</span>
|
|
1422
1723
|
<button class="remove-btn" @click=${this._onRemoveClick} title="Remove track">
|
|
@@ -1466,7 +1767,7 @@ var DawTrackControlsElement = class extends LitElement7 {
|
|
|
1466
1767
|
`;
|
|
1467
1768
|
}
|
|
1468
1769
|
};
|
|
1469
|
-
DawTrackControlsElement.styles =
|
|
1770
|
+
DawTrackControlsElement.styles = css6`
|
|
1470
1771
|
:host {
|
|
1471
1772
|
display: flex;
|
|
1472
1773
|
flex-direction: column;
|
|
@@ -1600,30 +1901,30 @@ DawTrackControlsElement.styles = css5`
|
|
|
1600
1901
|
}
|
|
1601
1902
|
`;
|
|
1602
1903
|
__decorateClass([
|
|
1603
|
-
|
|
1904
|
+
property6({ attribute: false })
|
|
1604
1905
|
], DawTrackControlsElement.prototype, "trackId", 2);
|
|
1605
1906
|
__decorateClass([
|
|
1606
|
-
|
|
1907
|
+
property6({ attribute: false })
|
|
1607
1908
|
], DawTrackControlsElement.prototype, "trackName", 2);
|
|
1608
1909
|
__decorateClass([
|
|
1609
|
-
|
|
1910
|
+
property6({ type: Number, attribute: false })
|
|
1610
1911
|
], DawTrackControlsElement.prototype, "volume", 2);
|
|
1611
1912
|
__decorateClass([
|
|
1612
|
-
|
|
1913
|
+
property6({ type: Number, attribute: false })
|
|
1613
1914
|
], DawTrackControlsElement.prototype, "pan", 2);
|
|
1614
1915
|
__decorateClass([
|
|
1615
|
-
|
|
1916
|
+
property6({ type: Boolean, attribute: false })
|
|
1616
1917
|
], DawTrackControlsElement.prototype, "muted", 2);
|
|
1617
1918
|
__decorateClass([
|
|
1618
|
-
|
|
1919
|
+
property6({ type: Boolean, attribute: false })
|
|
1619
1920
|
], DawTrackControlsElement.prototype, "soloed", 2);
|
|
1620
1921
|
DawTrackControlsElement = __decorateClass([
|
|
1621
|
-
|
|
1922
|
+
customElement10("daw-track-controls")
|
|
1622
1923
|
], DawTrackControlsElement);
|
|
1623
1924
|
|
|
1624
1925
|
// src/elements/daw-grid.ts
|
|
1625
|
-
import { LitElement as
|
|
1626
|
-
import { customElement as
|
|
1926
|
+
import { LitElement as LitElement9, html as html8, css as css7 } from "lit";
|
|
1927
|
+
import { customElement as customElement11, property as property7 } from "lit/decorators.js";
|
|
1627
1928
|
import { MIN_PIXELS_PER_UNIT } from "@waveform-playlist/core";
|
|
1628
1929
|
|
|
1629
1930
|
// src/utils/musical-tick-cache.ts
|
|
@@ -1654,8 +1955,8 @@ function getCachedMusicalTicks(params) {
|
|
|
1654
1955
|
}
|
|
1655
1956
|
|
|
1656
1957
|
// src/elements/daw-grid.ts
|
|
1657
|
-
var
|
|
1658
|
-
var DawGridElement = class extends
|
|
1958
|
+
var MAX_CANVAS_WIDTH3 = 1e3;
|
|
1959
|
+
var DawGridElement = class extends LitElement9 {
|
|
1659
1960
|
constructor() {
|
|
1660
1961
|
super(...arguments);
|
|
1661
1962
|
this.ticksPerPixel = 24;
|
|
@@ -1683,25 +1984,25 @@ var DawGridElement = class extends LitElement8 {
|
|
|
1683
1984
|
}
|
|
1684
1985
|
}
|
|
1685
1986
|
render() {
|
|
1686
|
-
if (!this._tickData) return
|
|
1987
|
+
if (!this._tickData) return html8``;
|
|
1687
1988
|
const totalWidth = this.length;
|
|
1688
1989
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1689
1990
|
const indices = getVisibleChunkIndices(
|
|
1690
1991
|
totalWidth,
|
|
1691
|
-
|
|
1992
|
+
MAX_CANVAS_WIDTH3,
|
|
1692
1993
|
this.visibleStart,
|
|
1693
1994
|
this.visibleEnd
|
|
1694
1995
|
);
|
|
1695
|
-
return
|
|
1996
|
+
return html8`
|
|
1696
1997
|
<div class="container" style="width: ${totalWidth}px; height: ${this.height}px;">
|
|
1697
1998
|
${indices.map((i) => {
|
|
1698
|
-
const width = Math.min(
|
|
1699
|
-
return
|
|
1999
|
+
const width = Math.min(MAX_CANVAS_WIDTH3, totalWidth - i * MAX_CANVAS_WIDTH3);
|
|
2000
|
+
return html8`
|
|
1700
2001
|
<canvas
|
|
1701
2002
|
data-index=${i}
|
|
1702
2003
|
width=${width * dpr}
|
|
1703
2004
|
height=${this.height * dpr}
|
|
1704
|
-
style="left: ${i *
|
|
2005
|
+
style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.height}px;"
|
|
1705
2006
|
></canvas>
|
|
1706
2007
|
`;
|
|
1707
2008
|
})}
|
|
@@ -1725,8 +2026,8 @@ var DawGridElement = class extends LitElement8 {
|
|
|
1725
2026
|
const idx = Number(canvas.dataset.index);
|
|
1726
2027
|
const ctx = canvas.getContext("2d");
|
|
1727
2028
|
if (!ctx) continue;
|
|
1728
|
-
const chunkLeft = idx *
|
|
1729
|
-
const canvasWidth = Math.min(
|
|
2029
|
+
const chunkLeft = idx * MAX_CANVAS_WIDTH3;
|
|
2030
|
+
const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, this.length - chunkLeft);
|
|
1730
2031
|
ctx.resetTransform();
|
|
1731
2032
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1732
2033
|
ctx.scale(dpr, dpr);
|
|
@@ -1757,7 +2058,7 @@ var DawGridElement = class extends LitElement8 {
|
|
|
1757
2058
|
}
|
|
1758
2059
|
}
|
|
1759
2060
|
};
|
|
1760
|
-
DawGridElement.styles =
|
|
2061
|
+
DawGridElement.styles = css7`
|
|
1761
2062
|
:host {
|
|
1762
2063
|
display: block;
|
|
1763
2064
|
position: absolute;
|
|
@@ -1775,33 +2076,33 @@ DawGridElement.styles = css6`
|
|
|
1775
2076
|
}
|
|
1776
2077
|
`;
|
|
1777
2078
|
__decorateClass([
|
|
1778
|
-
|
|
2079
|
+
property7({ type: Number, attribute: false })
|
|
1779
2080
|
], DawGridElement.prototype, "ticksPerPixel", 2);
|
|
1780
2081
|
__decorateClass([
|
|
1781
|
-
|
|
2082
|
+
property7({ attribute: false })
|
|
1782
2083
|
], DawGridElement.prototype, "meterEntries", 2);
|
|
1783
2084
|
__decorateClass([
|
|
1784
|
-
|
|
2085
|
+
property7({ type: Number, attribute: false })
|
|
1785
2086
|
], DawGridElement.prototype, "ppqn", 2);
|
|
1786
2087
|
__decorateClass([
|
|
1787
|
-
|
|
2088
|
+
property7({ type: Number, attribute: false })
|
|
1788
2089
|
], DawGridElement.prototype, "visibleStart", 2);
|
|
1789
2090
|
__decorateClass([
|
|
1790
|
-
|
|
2091
|
+
property7({ type: Number, attribute: false })
|
|
1791
2092
|
], DawGridElement.prototype, "visibleEnd", 2);
|
|
1792
2093
|
__decorateClass([
|
|
1793
|
-
|
|
2094
|
+
property7({ type: Number, attribute: false })
|
|
1794
2095
|
], DawGridElement.prototype, "length", 2);
|
|
1795
2096
|
__decorateClass([
|
|
1796
|
-
|
|
2097
|
+
property7({ type: Number, attribute: false })
|
|
1797
2098
|
], DawGridElement.prototype, "height", 2);
|
|
1798
2099
|
DawGridElement = __decorateClass([
|
|
1799
|
-
|
|
2100
|
+
customElement11("daw-grid")
|
|
1800
2101
|
], DawGridElement);
|
|
1801
2102
|
|
|
1802
2103
|
// src/styles/theme.ts
|
|
1803
|
-
import { css as
|
|
1804
|
-
var hostStyles =
|
|
2104
|
+
import { css as css8 } from "lit";
|
|
2105
|
+
var hostStyles = css8`
|
|
1805
2106
|
:host {
|
|
1806
2107
|
--daw-wave-color: #c49a6c;
|
|
1807
2108
|
--daw-progress-color: #63c75f;
|
|
@@ -1817,7 +2118,7 @@ var hostStyles = css7`
|
|
|
1817
2118
|
--daw-clip-header-text: #e0d4c8;
|
|
1818
2119
|
}
|
|
1819
2120
|
`;
|
|
1820
|
-
var clipStyles =
|
|
2121
|
+
var clipStyles = css8`
|
|
1821
2122
|
.clip-container {
|
|
1822
2123
|
position: absolute;
|
|
1823
2124
|
overflow: hidden;
|
|
@@ -2062,6 +2363,9 @@ var RecordingController = class {
|
|
|
2062
2363
|
constructor(host) {
|
|
2063
2364
|
this._sessions = /* @__PURE__ */ new Map();
|
|
2064
2365
|
this._workletLoadedCtx = null;
|
|
2366
|
+
/** Tracks worklet pause state explicitly so external consumers (editor,
|
|
2367
|
+
* pause button, spacebar) can share one source of truth. */
|
|
2368
|
+
this._isPaused = false;
|
|
2065
2369
|
this._host = host;
|
|
2066
2370
|
host.addController(this);
|
|
2067
2371
|
}
|
|
@@ -2076,6 +2380,9 @@ var RecordingController = class {
|
|
|
2076
2380
|
get isRecording() {
|
|
2077
2381
|
return this._sessions.size > 0;
|
|
2078
2382
|
}
|
|
2383
|
+
get isPaused() {
|
|
2384
|
+
return this._isPaused && this._sessions.size > 0;
|
|
2385
|
+
}
|
|
2079
2386
|
getSession(trackId) {
|
|
2080
2387
|
return this._sessions.get(trackId);
|
|
2081
2388
|
}
|
|
@@ -2148,7 +2455,9 @@ var RecordingController = class {
|
|
|
2148
2455
|
latencySamples,
|
|
2149
2456
|
wasOverdub: options.overdub ?? false,
|
|
2150
2457
|
_onTrackEnded: onTrackEnded,
|
|
2151
|
-
_audioTrack: audioTrack
|
|
2458
|
+
_audioTrack: audioTrack,
|
|
2459
|
+
stopAckResolve: null,
|
|
2460
|
+
stopping: false
|
|
2152
2461
|
};
|
|
2153
2462
|
this._sessions.set(trackId, session);
|
|
2154
2463
|
workletNode.port.onmessage = (e) => {
|
|
@@ -2186,25 +2495,69 @@ var RecordingController = class {
|
|
|
2186
2495
|
const id = trackId ?? [...this._sessions.keys()][0];
|
|
2187
2496
|
if (!id) return;
|
|
2188
2497
|
const session = this._sessions.get(id);
|
|
2189
|
-
if (!session) return;
|
|
2498
|
+
if (!session || session.stopping) return;
|
|
2190
2499
|
session.workletNode.port.postMessage({ command: "pause" });
|
|
2500
|
+
this._isPaused = true;
|
|
2501
|
+
this._host.dispatchEvent(
|
|
2502
|
+
new CustomEvent("daw-recording-pause", {
|
|
2503
|
+
bubbles: true,
|
|
2504
|
+
composed: true,
|
|
2505
|
+
detail: { trackId: id }
|
|
2506
|
+
})
|
|
2507
|
+
);
|
|
2191
2508
|
}
|
|
2192
2509
|
resumeRecording(trackId) {
|
|
2193
2510
|
const id = trackId ?? [...this._sessions.keys()][0];
|
|
2194
2511
|
if (!id) return;
|
|
2195
2512
|
const session = this._sessions.get(id);
|
|
2196
|
-
if (!session) return;
|
|
2513
|
+
if (!session || session.stopping) return;
|
|
2197
2514
|
session.workletNode.port.postMessage({ command: "resume" });
|
|
2515
|
+
this._isPaused = false;
|
|
2516
|
+
this._host.dispatchEvent(
|
|
2517
|
+
new CustomEvent("daw-recording-resume", {
|
|
2518
|
+
bubbles: true,
|
|
2519
|
+
composed: true,
|
|
2520
|
+
detail: { trackId: id }
|
|
2521
|
+
})
|
|
2522
|
+
);
|
|
2198
2523
|
}
|
|
2199
|
-
stopRecording(trackId) {
|
|
2524
|
+
async stopRecording(trackId) {
|
|
2200
2525
|
const id = trackId ?? [...this._sessions.keys()][0];
|
|
2201
2526
|
if (!id) return;
|
|
2202
2527
|
const session = this._sessions.get(id);
|
|
2203
2528
|
if (!session) return;
|
|
2529
|
+
const wasPaused = this._isPaused;
|
|
2530
|
+
this._isPaused = false;
|
|
2531
|
+
session.stopping = true;
|
|
2204
2532
|
if (session.wasOverdub && typeof this._host.stop === "function") {
|
|
2205
2533
|
this._host.stop();
|
|
2206
2534
|
}
|
|
2207
|
-
|
|
2535
|
+
if (wasPaused) {
|
|
2536
|
+
session.workletNode.port.postMessage({ command: "stop" });
|
|
2537
|
+
} else {
|
|
2538
|
+
const stopAck = new Promise((resolve) => {
|
|
2539
|
+
session.stopAckResolve = resolve;
|
|
2540
|
+
});
|
|
2541
|
+
let timeoutId;
|
|
2542
|
+
const timeout = new Promise((resolve) => {
|
|
2543
|
+
timeoutId = setTimeout(resolve, 1e3);
|
|
2544
|
+
});
|
|
2545
|
+
session.workletNode.port.postMessage({ command: "stop" });
|
|
2546
|
+
await Promise.race([stopAck, timeout]);
|
|
2547
|
+
clearTimeout(timeoutId);
|
|
2548
|
+
session.stopAckResolve = null;
|
|
2549
|
+
let lastSamples = -1;
|
|
2550
|
+
let stable = 0;
|
|
2551
|
+
for (let i = 0; i < 50; i++) {
|
|
2552
|
+
if (session.totalSamples === lastSamples) {
|
|
2553
|
+
if (++stable >= 3) break;
|
|
2554
|
+
} else {
|
|
2555
|
+
stable = 0;
|
|
2556
|
+
lastSamples = session.totalSamples;
|
|
2557
|
+
}
|
|
2558
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2208
2561
|
session.source.disconnect();
|
|
2209
2562
|
session.workletNode.disconnect();
|
|
2210
2563
|
this._removeTrackEndedListener(session);
|
|
@@ -2276,8 +2629,20 @@ var RecordingController = class {
|
|
|
2276
2629
|
_onWorkletMessage(trackId, data) {
|
|
2277
2630
|
const session = this._sessions.get(trackId);
|
|
2278
2631
|
if (!session) return;
|
|
2279
|
-
const { channels } = data;
|
|
2280
|
-
|
|
2632
|
+
const { channels, done } = data;
|
|
2633
|
+
try {
|
|
2634
|
+
const hasSamples = !!(channels && channels.length > 0 && channels[0] && channels[0].length > 0);
|
|
2635
|
+
if (!hasSamples) return;
|
|
2636
|
+
this._processWorkletSamples(trackId, session, channels);
|
|
2637
|
+
} finally {
|
|
2638
|
+
if (done && session.stopAckResolve) {
|
|
2639
|
+
const resolve = session.stopAckResolve;
|
|
2640
|
+
session.stopAckResolve = null;
|
|
2641
|
+
resolve();
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
_processWorkletSamples(trackId, session, channels) {
|
|
2281
2646
|
const samplesProcessedBefore = session.totalSamples;
|
|
2282
2647
|
for (let ch = 0; ch < session.channelCount; ch++) {
|
|
2283
2648
|
if (channels[ch]) {
|
|
@@ -2285,6 +2650,7 @@ var RecordingController = class {
|
|
|
2285
2650
|
}
|
|
2286
2651
|
}
|
|
2287
2652
|
session.totalSamples += channels[0].length;
|
|
2653
|
+
if (session.stopAckResolve !== null) return;
|
|
2288
2654
|
for (let ch = 0; ch < session.channelCount; ch++) {
|
|
2289
2655
|
if (!channels[ch]) continue;
|
|
2290
2656
|
const oldPeakCount = Math.floor(session.peaks[ch].length / 2);
|
|
@@ -2355,6 +2721,123 @@ var RecordingController = class {
|
|
|
2355
2721
|
}
|
|
2356
2722
|
};
|
|
2357
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
|
+
|
|
2358
2841
|
// src/interactions/pointer-handler.ts
|
|
2359
2842
|
import { pixelsToSeconds, snapTickToGrid } from "@waveform-playlist/core";
|
|
2360
2843
|
|
|
@@ -2619,6 +3102,7 @@ var ClipPointerHandler = class {
|
|
|
2619
3102
|
const trackId = boundary.dataset.trackId;
|
|
2620
3103
|
const edge = boundary.dataset.boundaryEdge;
|
|
2621
3104
|
if (!clipId || !trackId || edge !== "left" && edge !== "right") return false;
|
|
3105
|
+
if (this._host.isMidiClip(trackId, clipId)) return true;
|
|
2622
3106
|
this._beginDrag(edge === "left" ? "trim-left" : "trim-right", clipId, trackId, e);
|
|
2623
3107
|
this._boundaryEl = boundary;
|
|
2624
3108
|
return true;
|
|
@@ -2912,6 +3396,7 @@ async function loadFiles(host, files) {
|
|
|
2912
3396
|
pan: 0,
|
|
2913
3397
|
muted: false,
|
|
2914
3398
|
soloed: false,
|
|
3399
|
+
renderMode: "waveform",
|
|
2915
3400
|
clips: [
|
|
2916
3401
|
{
|
|
2917
3402
|
kind: "drop",
|
|
@@ -2924,7 +3409,10 @@ async function loadFiles(host, files) {
|
|
|
2924
3409
|
name,
|
|
2925
3410
|
fadeIn: 0,
|
|
2926
3411
|
fadeOut: 0,
|
|
2927
|
-
fadeType: "linear"
|
|
3412
|
+
fadeType: "linear",
|
|
3413
|
+
midiNotes: null,
|
|
3414
|
+
midiChannel: null,
|
|
3415
|
+
midiProgram: null
|
|
2928
3416
|
}
|
|
2929
3417
|
]
|
|
2930
3418
|
});
|
|
@@ -3011,7 +3499,10 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamp
|
|
|
3011
3499
|
name: "Recording",
|
|
3012
3500
|
fadeIn: 0,
|
|
3013
3501
|
fadeOut: 0,
|
|
3014
|
-
fadeType: "linear"
|
|
3502
|
+
fadeType: "linear",
|
|
3503
|
+
midiNotes: null,
|
|
3504
|
+
midiChannel: null,
|
|
3505
|
+
midiProgram: null
|
|
3015
3506
|
};
|
|
3016
3507
|
host._tracks = new Map(host._tracks).set(trackId, {
|
|
3017
3508
|
...desc,
|
|
@@ -3070,7 +3561,10 @@ function canSplitAtTime(host, time) {
|
|
|
3070
3561
|
const track = state5.tracks.find((t) => t.id === state5.selectedTrackId);
|
|
3071
3562
|
if (!track) return false;
|
|
3072
3563
|
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
3073
|
-
|
|
3564
|
+
const clip = findClipAtSample(track.clips, atSample);
|
|
3565
|
+
if (!clip) return false;
|
|
3566
|
+
if (clip.midiNotes != null) return false;
|
|
3567
|
+
return true;
|
|
3074
3568
|
}
|
|
3075
3569
|
function performSplit(host, time) {
|
|
3076
3570
|
const { engine } = host;
|
|
@@ -3225,7 +3719,7 @@ async function loadWaveformDataFromUrl(src) {
|
|
|
3225
3719
|
|
|
3226
3720
|
// src/elements/daw-editor.ts
|
|
3227
3721
|
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();";
|
|
3228
|
-
var DawEditorElement = class extends
|
|
3722
|
+
var DawEditorElement = class extends LitElement10 {
|
|
3229
3723
|
constructor() {
|
|
3230
3724
|
super(...arguments);
|
|
3231
3725
|
this._samplesPerPixel = 1024;
|
|
@@ -3239,6 +3733,8 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3239
3733
|
this.clipHeaderHeight = 20;
|
|
3240
3734
|
this.interactiveClips = false;
|
|
3241
3735
|
this.indefinitePlayback = false;
|
|
3736
|
+
this._spectrogramConfig = null;
|
|
3737
|
+
this._spectrogramColorMap = null;
|
|
3242
3738
|
this.scaleMode = "temporal";
|
|
3243
3739
|
this._ticksPerPixel = 24;
|
|
3244
3740
|
this._bpm = 120;
|
|
@@ -3274,6 +3770,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3274
3770
|
this._childObserver = null;
|
|
3275
3771
|
this._audioResume = new AudioResumeController(this);
|
|
3276
3772
|
this._recordingController = new RecordingController(this);
|
|
3773
|
+
this._spectrogramController = null;
|
|
3277
3774
|
this._clipPointer = new ClipPointerHandler(this);
|
|
3278
3775
|
this._pointer = new PointerHandler(this);
|
|
3279
3776
|
this._viewport = (() => {
|
|
@@ -3314,6 +3811,26 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3314
3811
|
if (oldDescriptor?.src !== descriptor.src) {
|
|
3315
3812
|
this._loadTrack(trackId, descriptor);
|
|
3316
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
|
+
}
|
|
3317
3834
|
};
|
|
3318
3835
|
this._onTrackControl = (e) => {
|
|
3319
3836
|
const { trackId, prop, value } = e.detail ?? {};
|
|
@@ -3384,7 +3901,10 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3384
3901
|
name: clipEl.name,
|
|
3385
3902
|
fadeIn: clipEl.fadeIn,
|
|
3386
3903
|
fadeOut: clipEl.fadeOut,
|
|
3387
|
-
fadeType: clipEl.fadeType
|
|
3904
|
+
fadeType: clipEl.fadeType,
|
|
3905
|
+
midiNotes: clipEl.midiNotes,
|
|
3906
|
+
midiChannel: clipEl.midiChannel,
|
|
3907
|
+
midiProgram: clipEl.midiProgram
|
|
3388
3908
|
};
|
|
3389
3909
|
this._loadAndAppendClip(trackId, clipDesc);
|
|
3390
3910
|
};
|
|
@@ -3435,6 +3955,9 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3435
3955
|
};
|
|
3436
3956
|
// --- Recording ---
|
|
3437
3957
|
this.recordingStream = null;
|
|
3958
|
+
/** Set in togglePauseRecording when Transport is paused alongside the
|
|
3959
|
+
* worklet, so resume can restart it. Cleared on resume and on stop. */
|
|
3960
|
+
this._wasPlayingDuringRecording = false;
|
|
3438
3961
|
}
|
|
3439
3962
|
get samplesPerPixel() {
|
|
3440
3963
|
return this._samplesPerPixel;
|
|
@@ -3451,6 +3974,72 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3451
3974
|
this._samplesPerPixel = clamped;
|
|
3452
3975
|
this.requestUpdate("samplesPerPixel", old);
|
|
3453
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
|
+
}
|
|
3454
4043
|
get ticksPerPixel() {
|
|
3455
4044
|
return this._ticksPerPixel;
|
|
3456
4045
|
}
|
|
@@ -3534,6 +4123,17 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3534
4123
|
);
|
|
3535
4124
|
return result.get(clipId) ?? null;
|
|
3536
4125
|
}
|
|
4126
|
+
/**
|
|
4127
|
+
* Returns true if the clip is a MIDI clip (has midiNotes).
|
|
4128
|
+
* Used by ClipPointerHandler to make trim handles inert for MIDI clips.
|
|
4129
|
+
* Returns false for unknown track/clip IDs (defensive).
|
|
4130
|
+
*/
|
|
4131
|
+
isMidiClip(trackId, clipId) {
|
|
4132
|
+
const track = this._engineTracks.get(trackId);
|
|
4133
|
+
if (!track) return false;
|
|
4134
|
+
const clip = track.clips.find((c) => c.id === clipId);
|
|
4135
|
+
return clip?.midiNotes != null;
|
|
4136
|
+
}
|
|
3537
4137
|
get effectiveSampleRate() {
|
|
3538
4138
|
return this._resolvedSampleRate ?? this.sampleRate;
|
|
3539
4139
|
}
|
|
@@ -3680,6 +4280,8 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3680
4280
|
this._clipOffsets.clear();
|
|
3681
4281
|
this._peakPipeline.terminate();
|
|
3682
4282
|
this._minSamplesPerPixel = 0;
|
|
4283
|
+
this._spectrogramController?.dispose();
|
|
4284
|
+
this._spectrogramController = null;
|
|
3683
4285
|
try {
|
|
3684
4286
|
this._disposeEngine();
|
|
3685
4287
|
} catch (err) {
|
|
@@ -3708,6 +4310,23 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3708
4310
|
}
|
|
3709
4311
|
}
|
|
3710
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
|
+
}
|
|
3711
4330
|
_onTrackRemoved(trackId) {
|
|
3712
4331
|
this._trackElements.delete(trackId);
|
|
3713
4332
|
const removedTrack = this._engineTracks.get(trackId);
|
|
@@ -3717,6 +4336,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3717
4336
|
this._clipBuffers.delete(clip.id);
|
|
3718
4337
|
this._clipOffsets.delete(clip.id);
|
|
3719
4338
|
nextPeaks.delete(clip.id);
|
|
4339
|
+
this._spectrogramController?.unregisterClipAudio(clip.id);
|
|
3720
4340
|
}
|
|
3721
4341
|
this._peaksData = nextPeaks;
|
|
3722
4342
|
}
|
|
@@ -3731,11 +4351,23 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3731
4351
|
this._engine.removeTrack(trackId);
|
|
3732
4352
|
}
|
|
3733
4353
|
this._minSamplesPerPixel = this._peakPipeline.getMaxCachedScale(this._clipBuffers);
|
|
4354
|
+
this._disposeSpectrogramControllerIfEmpty();
|
|
3734
4355
|
if (nextEngine.size === 0) {
|
|
3735
4356
|
this._currentTime = 0;
|
|
3736
4357
|
this._stopPlayhead();
|
|
3737
4358
|
}
|
|
3738
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
|
+
}
|
|
3739
4371
|
_onClipRemovedFromDom(clipEl) {
|
|
3740
4372
|
const clipId = clipEl.clipId;
|
|
3741
4373
|
for (const [trackId, t] of this._engineTracks.entries()) {
|
|
@@ -3777,6 +4409,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3777
4409
|
});
|
|
3778
4410
|
}
|
|
3779
4411
|
this._commitTrackChange(trackId, updatedTrack);
|
|
4412
|
+
this._maybeRegisterSpectrogramClipAudio(trackId, clip);
|
|
3780
4413
|
this.dispatchEvent(
|
|
3781
4414
|
new CustomEvent("daw-clip-ready", {
|
|
3782
4415
|
bubbles: true,
|
|
@@ -3883,6 +4516,65 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3883
4516
|
}
|
|
3884
4517
|
return clip;
|
|
3885
4518
|
}
|
|
4519
|
+
/**
|
|
4520
|
+
* Filter MIDI notes to only those with finite, in-range fields. Logs a
|
|
4521
|
+
* warning for each dropped note. Used by _buildMidiClip and the
|
|
4522
|
+
* _applyClipUpdate MIDI branch to prevent NaN propagation through the
|
|
4523
|
+
* timeline.
|
|
4524
|
+
*/
|
|
4525
|
+
_validMidiNotes(notes) {
|
|
4526
|
+
return notes.filter((n) => {
|
|
4527
|
+
const ok = Number.isFinite(n.time) && n.time >= 0 && Number.isFinite(n.duration) && n.duration > 0 && Number.isInteger(n.midi) && n.midi >= 0 && n.midi <= 127 && Number.isFinite(n.velocity) && n.velocity >= 0 && n.velocity <= 1;
|
|
4528
|
+
if (!ok) {
|
|
4529
|
+
console.warn("[dawcore] dropping malformed MIDI note: " + JSON.stringify(n));
|
|
4530
|
+
}
|
|
4531
|
+
return ok;
|
|
4532
|
+
});
|
|
4533
|
+
}
|
|
4534
|
+
/**
|
|
4535
|
+
* A clip descriptor is treated as MIDI when it has no audio src.
|
|
4536
|
+
* Includes placeholder MIDI clips (no notes, no duration yet — registered
|
|
4537
|
+
* with a 1s default span; notes upgrade via _applyClipUpdate). Warns when
|
|
4538
|
+
* a clip ambiguously has both src and midiNotes — the audio path runs
|
|
4539
|
+
* and notes would be silently ignored.
|
|
4540
|
+
*/
|
|
4541
|
+
_isMidiDescriptor(clipDesc) {
|
|
4542
|
+
if (clipDesc.src) {
|
|
4543
|
+
if (clipDesc.midiNotes != null) {
|
|
4544
|
+
console.warn(
|
|
4545
|
+
'[dawcore] clip "' + (clipDesc.name || (isDomClip(clipDesc) ? clipDesc.clipId : "?")) + '" has both src and midiNotes \u2014 treating as audio (notes will be ignored)'
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
return false;
|
|
4549
|
+
}
|
|
4550
|
+
return true;
|
|
4551
|
+
}
|
|
4552
|
+
/**
|
|
4553
|
+
* Build an engine clip from a MIDI clip descriptor. Always returns a clip
|
|
4554
|
+
* — empty notes / no declared duration get a 1-second placeholder span so
|
|
4555
|
+
* the clip is reachable via `engine.updateTrack` once notes arrive.
|
|
4556
|
+
*/
|
|
4557
|
+
_buildMidiClip(clipDesc) {
|
|
4558
|
+
const sr = this.effectiveSampleRate;
|
|
4559
|
+
const notes = this._validMidiNotes(clipDesc.midiNotes ?? []);
|
|
4560
|
+
const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
|
|
4561
|
+
const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipDesc.duration, 1) * sr);
|
|
4562
|
+
const requestedDurationSamples = clipDesc.duration > 0 ? Math.round(clipDesc.duration * sr) : sourceDurationSamples;
|
|
4563
|
+
const clip = createClip3({
|
|
4564
|
+
startSample: Math.round(clipDesc.start * sr),
|
|
4565
|
+
durationSamples: requestedDurationSamples,
|
|
4566
|
+
offsetSamples: Math.round(clipDesc.offset * sr),
|
|
4567
|
+
sampleRate: sr,
|
|
4568
|
+
sourceDurationSamples,
|
|
4569
|
+
gain: clipDesc.gain,
|
|
4570
|
+
name: clipDesc.name,
|
|
4571
|
+
midiNotes: notes,
|
|
4572
|
+
midiChannel: clipDesc.midiChannel ?? void 0,
|
|
4573
|
+
midiProgram: clipDesc.midiProgram ?? void 0
|
|
4574
|
+
});
|
|
4575
|
+
if (isDomClip(clipDesc)) clip.id = clipDesc.clipId;
|
|
4576
|
+
return clip;
|
|
4577
|
+
}
|
|
3886
4578
|
/** Remove a single clip from all per-clip caches. Used by error rollbacks. */
|
|
3887
4579
|
_purgeClipCaches(clipId) {
|
|
3888
4580
|
const nextBuffers = new Map(this._clipBuffers);
|
|
@@ -3892,6 +4584,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3892
4584
|
nextPeaks.delete(clipId);
|
|
3893
4585
|
this._peaksData = nextPeaks;
|
|
3894
4586
|
this._clipOffsets.delete(clipId);
|
|
4587
|
+
this._spectrogramController?.unregisterClipAudio(clipId);
|
|
3895
4588
|
}
|
|
3896
4589
|
/**
|
|
3897
4590
|
* Recompute duration and forward an updated track to the engine. Single
|
|
@@ -3920,6 +4613,34 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3920
4613
|
}
|
|
3921
4614
|
const oldClip = t.clips[idx];
|
|
3922
4615
|
const sr = oldClip.sampleRate ?? this.effectiveSampleRate;
|
|
4616
|
+
const isMidiNow = clipEl.midiNotes != null;
|
|
4617
|
+
const wasMidi = oldClip.midiNotes != null;
|
|
4618
|
+
if (isMidiNow || wasMidi) {
|
|
4619
|
+
const notes = this._validMidiNotes(clipEl.midiNotes ?? []);
|
|
4620
|
+
const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
|
|
4621
|
+
const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipEl.duration, 1) * sr);
|
|
4622
|
+
const requestedDurationSamples = clipEl.duration > 0 ? Math.round(clipEl.duration * sr) : sourceDurationSamples;
|
|
4623
|
+
const updatedClip2 = {
|
|
4624
|
+
...oldClip,
|
|
4625
|
+
audioBuffer: void 0,
|
|
4626
|
+
startSample: Math.round(clipEl.start * sr),
|
|
4627
|
+
offsetSamples: Math.round(clipEl.offset * sr),
|
|
4628
|
+
durationSamples: requestedDurationSamples,
|
|
4629
|
+
sourceDurationSamples,
|
|
4630
|
+
gain: clipEl.gain,
|
|
4631
|
+
name: clipEl.name || oldClip.name,
|
|
4632
|
+
midiNotes: notes,
|
|
4633
|
+
midiChannel: clipEl.midiChannel ?? void 0,
|
|
4634
|
+
midiProgram: clipEl.midiProgram ?? void 0
|
|
4635
|
+
};
|
|
4636
|
+
const updatedClips2 = [...t.clips];
|
|
4637
|
+
updatedClips2[idx] = updatedClip2;
|
|
4638
|
+
const updatedTrack2 = { ...t, clips: updatedClips2 };
|
|
4639
|
+
this._engineTracks = new Map(this._engineTracks).set(trackId, updatedTrack2);
|
|
4640
|
+
this._purgeClipCaches(clipId);
|
|
4641
|
+
this._commitTrackChange(trackId, updatedTrack2);
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
3923
4644
|
const newStartSample = Math.round(clipEl.start * sr);
|
|
3924
4645
|
const newDurationSamples = clipEl.duration > 0 ? Math.round(clipEl.duration * sr) : oldClip.durationSamples;
|
|
3925
4646
|
const newOffsetSamples = Math.round(clipEl.offset * sr);
|
|
@@ -3996,7 +4717,10 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
3996
4717
|
name: trackEl.name || "",
|
|
3997
4718
|
fadeIn: 0,
|
|
3998
4719
|
fadeOut: 0,
|
|
3999
|
-
fadeType: "linear"
|
|
4720
|
+
fadeType: "linear",
|
|
4721
|
+
midiNotes: null,
|
|
4722
|
+
midiChannel: null,
|
|
4723
|
+
midiProgram: null
|
|
4000
4724
|
});
|
|
4001
4725
|
} else {
|
|
4002
4726
|
for (const clipEl of clipEls) {
|
|
@@ -4012,7 +4736,10 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4012
4736
|
name: clipEl.name,
|
|
4013
4737
|
fadeIn: clipEl.fadeIn,
|
|
4014
4738
|
fadeOut: clipEl.fadeOut,
|
|
4015
|
-
fadeType: clipEl.fadeType
|
|
4739
|
+
fadeType: clipEl.fadeType,
|
|
4740
|
+
midiNotes: clipEl.midiNotes,
|
|
4741
|
+
midiChannel: clipEl.midiChannel,
|
|
4742
|
+
midiProgram: clipEl.midiProgram
|
|
4016
4743
|
});
|
|
4017
4744
|
}
|
|
4018
4745
|
}
|
|
@@ -4023,6 +4750,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4023
4750
|
pan: trackEl.pan,
|
|
4024
4751
|
muted: trackEl.muted,
|
|
4025
4752
|
soloed: trackEl.soloed,
|
|
4753
|
+
renderMode: trackEl.renderMode,
|
|
4026
4754
|
clips
|
|
4027
4755
|
};
|
|
4028
4756
|
}
|
|
@@ -4031,7 +4759,10 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4031
4759
|
try {
|
|
4032
4760
|
const clips = [];
|
|
4033
4761
|
for (const clipDesc of descriptor.clips) {
|
|
4034
|
-
if (
|
|
4762
|
+
if (this._isMidiDescriptor(clipDesc)) {
|
|
4763
|
+
clips.push(this._buildMidiClip(clipDesc));
|
|
4764
|
+
continue;
|
|
4765
|
+
}
|
|
4035
4766
|
try {
|
|
4036
4767
|
const waveformDataPromise = clipDesc.peaksSrc ? this._resolvePeaks(clipDesc.peaksSrc) : Promise.resolve(null);
|
|
4037
4768
|
const audioPromise = this._fetchAndDecode(clipDesc.src);
|
|
@@ -4136,6 +4867,9 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4136
4867
|
track.id = trackId;
|
|
4137
4868
|
this._engineTracks = new Map(this._engineTracks).set(trackId, track);
|
|
4138
4869
|
this._recomputeDuration();
|
|
4870
|
+
for (const c of clips) {
|
|
4871
|
+
this._maybeRegisterSpectrogramClipAudio(trackId, c);
|
|
4872
|
+
}
|
|
4139
4873
|
const engine = await this._ensureEngine();
|
|
4140
4874
|
engine.setTracks([...this._engineTracks.values()]);
|
|
4141
4875
|
this.dispatchEvent(
|
|
@@ -4243,7 +4977,11 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4243
4977
|
nextTracks.set(track.id, track);
|
|
4244
4978
|
}
|
|
4245
4979
|
this._engineTracks = nextTracks;
|
|
4246
|
-
|
|
4980
|
+
const audioTracks = engineState.tracks.filter((t) => {
|
|
4981
|
+
const desc = this._tracks.get(t.id);
|
|
4982
|
+
return desc?.renderMode !== "piano-roll";
|
|
4983
|
+
});
|
|
4984
|
+
syncPeaksForChangedClips(this, audioTracks);
|
|
4247
4985
|
}
|
|
4248
4986
|
});
|
|
4249
4987
|
engine.on("pause", () => {
|
|
@@ -4318,7 +5056,17 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4318
5056
|
if (config.pan !== void 0) trackEl.pan = config.pan;
|
|
4319
5057
|
if (config.muted) trackEl.setAttribute("muted", "");
|
|
4320
5058
|
if (config.soloed) trackEl.setAttribute("soloed", "");
|
|
4321
|
-
|
|
5059
|
+
const renderMode = config.renderMode ?? (config.midi ? "piano-roll" : void 0);
|
|
5060
|
+
if (renderMode !== void 0) trackEl.setAttribute("render-mode", renderMode);
|
|
5061
|
+
const clipConfigs = [...config.clips ?? []];
|
|
5062
|
+
if (config.midi) {
|
|
5063
|
+
clipConfigs.push({
|
|
5064
|
+
midiNotes: config.midi.notes,
|
|
5065
|
+
midiChannel: config.midi.channel,
|
|
5066
|
+
midiProgram: config.midi.program
|
|
5067
|
+
});
|
|
5068
|
+
}
|
|
5069
|
+
for (const clipConfig of clipConfigs) {
|
|
4322
5070
|
trackEl.appendChild(this._buildClipElement(clipConfig));
|
|
4323
5071
|
}
|
|
4324
5072
|
return this._awaitId(
|
|
@@ -4364,6 +5112,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4364
5112
|
if (partial.soloed) trackEl.setAttribute("soloed", "");
|
|
4365
5113
|
else trackEl.removeAttribute("soloed");
|
|
4366
5114
|
}
|
|
5115
|
+
if (partial.renderMode !== void 0) trackEl.setAttribute("render-mode", partial.renderMode);
|
|
4367
5116
|
return;
|
|
4368
5117
|
}
|
|
4369
5118
|
const oldDesc = this._tracks.get(trackId);
|
|
@@ -4374,7 +5123,8 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4374
5123
|
...partial.volume !== void 0 && { volume: partial.volume },
|
|
4375
5124
|
...partial.pan !== void 0 && { pan: partial.pan },
|
|
4376
5125
|
...partial.muted !== void 0 && { muted: partial.muted },
|
|
4377
|
-
...partial.soloed !== void 0 && { soloed: partial.soloed }
|
|
5126
|
+
...partial.soloed !== void 0 && { soloed: partial.soloed },
|
|
5127
|
+
...partial.renderMode !== void 0 && { renderMode: partial.renderMode }
|
|
4378
5128
|
};
|
|
4379
5129
|
this._tracks = new Map(this._tracks).set(trackId, newDesc);
|
|
4380
5130
|
if (this._engine) {
|
|
@@ -4509,6 +5259,11 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4509
5259
|
if (config.fadeIn !== void 0) clipEl.fadeIn = config.fadeIn;
|
|
4510
5260
|
if (config.fadeOut !== void 0) clipEl.fadeOut = config.fadeOut;
|
|
4511
5261
|
if (config.fadeType !== void 0) clipEl.setAttribute("fade-type", config.fadeType);
|
|
5262
|
+
if (config.midiNotes !== void 0) clipEl.midiNotes = config.midiNotes;
|
|
5263
|
+
if (config.midiChannel !== void 0)
|
|
5264
|
+
clipEl.setAttribute("midi-channel", String(config.midiChannel));
|
|
5265
|
+
if (config.midiProgram !== void 0)
|
|
5266
|
+
clipEl.setAttribute("midi-program", String(config.midiProgram));
|
|
4512
5267
|
return clipEl;
|
|
4513
5268
|
}
|
|
4514
5269
|
// --- Playback ---
|
|
@@ -4544,7 +5299,9 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4544
5299
|
}
|
|
4545
5300
|
/** Toggle between play and pause. */
|
|
4546
5301
|
togglePlayPause() {
|
|
4547
|
-
if (this.
|
|
5302
|
+
if (this.isRecording) {
|
|
5303
|
+
this.togglePauseRecording();
|
|
5304
|
+
} else if (this._isPlaying) {
|
|
4548
5305
|
this.pause();
|
|
4549
5306
|
} else {
|
|
4550
5307
|
this.play();
|
|
@@ -4618,14 +5375,41 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4618
5375
|
get isRecording() {
|
|
4619
5376
|
return this._recordingController.isRecording;
|
|
4620
5377
|
}
|
|
5378
|
+
get isRecordingPaused() {
|
|
5379
|
+
return this._recordingController.isPaused;
|
|
5380
|
+
}
|
|
4621
5381
|
pauseRecording() {
|
|
4622
5382
|
this._recordingController.pauseRecording();
|
|
4623
5383
|
}
|
|
4624
5384
|
resumeRecording() {
|
|
4625
5385
|
this._recordingController.resumeRecording();
|
|
5386
|
+
this._wasPlayingDuringRecording = false;
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* Audacity-style pause toggle for active recordings: pauses both the
|
|
5390
|
+
* worklet capture and (if running) the playback Transport. On resume,
|
|
5391
|
+
* Transport restarts only if it was running before — non-overdub
|
|
5392
|
+
* recordings stay silent on resume.
|
|
5393
|
+
*/
|
|
5394
|
+
togglePauseRecording() {
|
|
5395
|
+
if (!this.isRecording) return;
|
|
5396
|
+
if (this.isRecordingPaused) {
|
|
5397
|
+
const wasPlaying = this._wasPlayingDuringRecording;
|
|
5398
|
+
this.resumeRecording();
|
|
5399
|
+
if (wasPlaying) {
|
|
5400
|
+
void this.play(this.currentTime);
|
|
5401
|
+
}
|
|
5402
|
+
} else {
|
|
5403
|
+
this.pauseRecording();
|
|
5404
|
+
if (this._isPlaying) {
|
|
5405
|
+
this._wasPlayingDuringRecording = true;
|
|
5406
|
+
this.pause();
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
4626
5409
|
}
|
|
4627
5410
|
stopRecording() {
|
|
4628
|
-
this.
|
|
5411
|
+
this._wasPlayingDuringRecording = false;
|
|
5412
|
+
return this._recordingController.stopRecording();
|
|
4629
5413
|
}
|
|
4630
5414
|
_addRecordedClip(trackId, buf, startSample, durSamples, offsetSamples = 0) {
|
|
4631
5415
|
addRecordedClip(this, trackId, buf, startSample, durSamples, offsetSamples);
|
|
@@ -4661,7 +5445,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4661
5445
|
const w = Math.floor(audibleSamples / renderSpp);
|
|
4662
5446
|
return rs.peaks.map((chPeaks, ch) => {
|
|
4663
5447
|
const slicedPeaks = latencyPixels > 0 ? chPeaks.slice(latencyPixels * 2) : chPeaks;
|
|
4664
|
-
return
|
|
5448
|
+
return html9`
|
|
4665
5449
|
<daw-waveform
|
|
4666
5450
|
data-recording-track=${trackId}
|
|
4667
5451
|
data-recording-channel=${ch}
|
|
@@ -4758,11 +5542,11 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4758
5542
|
trackHeight: this.waveHeight * numChannels + (this.clipHeaders ? this.clipHeaderHeight : 0)
|
|
4759
5543
|
};
|
|
4760
5544
|
});
|
|
4761
|
-
return
|
|
4762
|
-
${orderedTracks.length > 0 || this.indefinitePlayback ?
|
|
4763
|
-
${this.timescale ?
|
|
5545
|
+
return html9`
|
|
5546
|
+
${orderedTracks.length > 0 || this.indefinitePlayback ? html9`<div class="controls-column">
|
|
5547
|
+
${this.timescale ? html9`<div style="height: 30px;"></div>` : ""}
|
|
4764
5548
|
${orderedTracks.map(
|
|
4765
|
-
(t) =>
|
|
5549
|
+
(t) => html9`
|
|
4766
5550
|
<daw-track-controls
|
|
4767
5551
|
style="height: ${t.trackHeight}px;"
|
|
4768
5552
|
.trackId=${t.trackId}
|
|
@@ -4785,7 +5569,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4785
5569
|
@dragleave=${this._onDragLeave}
|
|
4786
5570
|
@drop=${this._onDrop}
|
|
4787
5571
|
>
|
|
4788
|
-
${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ?
|
|
5572
|
+
${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ? html9`<daw-ruler
|
|
4789
5573
|
.samplesPerPixel=${spp}
|
|
4790
5574
|
.sampleRate=${this.effectiveSampleRate}
|
|
4791
5575
|
.duration=${this._duration}
|
|
@@ -4795,7 +5579,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4795
5579
|
.ppqn=${this.ppqn}
|
|
4796
5580
|
.totalWidth=${this._totalWidth}
|
|
4797
5581
|
></daw-ruler>` : ""}
|
|
4798
|
-
${this.scaleMode === "beats" ?
|
|
5582
|
+
${this.scaleMode === "beats" ? html9`<daw-grid
|
|
4799
5583
|
style="top: ${this.timescale ? 30 : 0}px;"
|
|
4800
5584
|
.ticksPerPixel=${this.ticksPerPixel}
|
|
4801
5585
|
.meterEntries=${this._meterEntries}
|
|
@@ -4805,11 +5589,11 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4805
5589
|
.length=${this._totalWidth}
|
|
4806
5590
|
.height=${orderedTracks.length > 0 ? orderedTracks.reduce((sum, t) => sum + t.trackHeight + 1, 0) : this._emptyGridHeight}
|
|
4807
5591
|
></daw-grid>` : ""}
|
|
4808
|
-
${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ?
|
|
5592
|
+
${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? html9`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
|
|
4809
5593
|
<daw-playhead></daw-playhead>` : ""}
|
|
4810
5594
|
${orderedTracks.map((t) => {
|
|
4811
5595
|
const channelHeight = this.waveHeight;
|
|
4812
|
-
return
|
|
5596
|
+
return html9`
|
|
4813
5597
|
<div
|
|
4814
5598
|
class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
|
|
4815
5599
|
style="height: ${t.trackHeight}px;"
|
|
@@ -4872,12 +5656,12 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4872
5656
|
const channels = segmentChannels ?? peakData?.data ?? [new Int16Array(0)];
|
|
4873
5657
|
const hdrH = this.clipHeaders ? this.clipHeaderHeight : 0;
|
|
4874
5658
|
const chH = this.waveHeight;
|
|
4875
|
-
return
|
|
5659
|
+
return html9` <div
|
|
4876
5660
|
class="clip-container"
|
|
4877
5661
|
style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
|
|
4878
5662
|
data-clip-id=${clip.id}
|
|
4879
5663
|
>
|
|
4880
|
-
${hdrH > 0 ?
|
|
5664
|
+
${hdrH > 0 ? html9`<div
|
|
4881
5665
|
class="clip-header"
|
|
4882
5666
|
data-clip-id=${clip.id}
|
|
4883
5667
|
data-track-id=${t.trackId}
|
|
@@ -4885,21 +5669,48 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4885
5669
|
>
|
|
4886
5670
|
<span>${clip.name || t.descriptor?.name || ""}</span>
|
|
4887
5671
|
</div>` : ""}
|
|
4888
|
-
${
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
.peaks=${chPeaks}
|
|
5672
|
+
${t.descriptor?.renderMode === "piano-roll" ? html9`<daw-piano-roll
|
|
5673
|
+
style="position:absolute;left:0;top:${hdrH}px;"
|
|
5674
|
+
.midiNotes=${clip.midiNotes ?? []}
|
|
4892
5675
|
.length=${peakData?.length ?? width}
|
|
4893
|
-
.waveHeight=${chH}
|
|
4894
|
-
.
|
|
4895
|
-
.
|
|
5676
|
+
.waveHeight=${chH * channels.length}
|
|
5677
|
+
.samplesPerPixel=${this._renderSpp}
|
|
5678
|
+
.sampleRate=${this.effectiveSampleRate}
|
|
5679
|
+
.clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
|
|
4896
5680
|
.visibleStart=${this._viewport.visibleStart}
|
|
4897
5681
|
.visibleEnd=${this._viewport.visibleEnd}
|
|
4898
5682
|
.originX=${clipLeft}
|
|
4899
|
-
|
|
4900
|
-
></daw-
|
|
5683
|
+
?selected=${t.trackId === this._selectedTrackId}
|
|
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(
|
|
5700
|
+
(chPeaks, chIdx) => html9` <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>`
|
|
4901
5712
|
)}
|
|
4902
|
-
${this.interactiveClips ?
|
|
5713
|
+
${this.interactiveClips ? html9` <div
|
|
4903
5714
|
class="clip-boundary"
|
|
4904
5715
|
data-boundary-edge="left"
|
|
4905
5716
|
data-clip-id=${clip.id}
|
|
@@ -4925,7 +5736,7 @@ var DawEditorElement = class extends LitElement9 {
|
|
|
4925
5736
|
};
|
|
4926
5737
|
DawEditorElement.styles = [
|
|
4927
5738
|
hostStyles,
|
|
4928
|
-
|
|
5739
|
+
css9`
|
|
4929
5740
|
:host {
|
|
4930
5741
|
display: flex;
|
|
4931
5742
|
position: relative;
|
|
@@ -4973,64 +5784,70 @@ DawEditorElement.styles = [
|
|
|
4973
5784
|
];
|
|
4974
5785
|
DawEditorElement._CONTROL_PROPS = /* @__PURE__ */ new Set(["volume", "pan", "muted", "soloed"]);
|
|
4975
5786
|
__decorateClass([
|
|
4976
|
-
|
|
5787
|
+
property8({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
|
|
4977
5788
|
], DawEditorElement.prototype, "samplesPerPixel", 1);
|
|
4978
5789
|
__decorateClass([
|
|
4979
|
-
|
|
5790
|
+
property8({ type: Number, attribute: "wave-height" })
|
|
4980
5791
|
], DawEditorElement.prototype, "waveHeight", 2);
|
|
4981
5792
|
__decorateClass([
|
|
4982
|
-
|
|
5793
|
+
property8({ type: Boolean })
|
|
4983
5794
|
], DawEditorElement.prototype, "timescale", 2);
|
|
4984
5795
|
__decorateClass([
|
|
4985
|
-
|
|
5796
|
+
property8({ type: Boolean })
|
|
4986
5797
|
], DawEditorElement.prototype, "mono", 2);
|
|
4987
5798
|
__decorateClass([
|
|
4988
|
-
|
|
5799
|
+
property8({ type: Number, attribute: "bar-width" })
|
|
4989
5800
|
], DawEditorElement.prototype, "barWidth", 2);
|
|
4990
5801
|
__decorateClass([
|
|
4991
|
-
|
|
5802
|
+
property8({ type: Number, attribute: "bar-gap" })
|
|
4992
5803
|
], DawEditorElement.prototype, "barGap", 2);
|
|
4993
5804
|
__decorateClass([
|
|
4994
|
-
|
|
5805
|
+
property8({ type: Boolean, attribute: "file-drop" })
|
|
4995
5806
|
], DawEditorElement.prototype, "fileDrop", 2);
|
|
4996
5807
|
__decorateClass([
|
|
4997
|
-
|
|
5808
|
+
property8({ type: Boolean, attribute: "clip-headers" })
|
|
4998
5809
|
], DawEditorElement.prototype, "clipHeaders", 2);
|
|
4999
5810
|
__decorateClass([
|
|
5000
|
-
|
|
5811
|
+
property8({ type: Number, attribute: "clip-header-height" })
|
|
5001
5812
|
], DawEditorElement.prototype, "clipHeaderHeight", 2);
|
|
5002
5813
|
__decorateClass([
|
|
5003
|
-
|
|
5814
|
+
property8({ type: Boolean, attribute: "interactive-clips" })
|
|
5004
5815
|
], DawEditorElement.prototype, "interactiveClips", 2);
|
|
5005
5816
|
__decorateClass([
|
|
5006
|
-
|
|
5817
|
+
property8({ type: Boolean, attribute: "indefinite-playback" })
|
|
5007
5818
|
], DawEditorElement.prototype, "indefinitePlayback", 2);
|
|
5008
5819
|
__decorateClass([
|
|
5009
|
-
|
|
5820
|
+
property8({ attribute: false, noAccessor: true })
|
|
5821
|
+
], DawEditorElement.prototype, "spectrogramConfig", 1);
|
|
5822
|
+
__decorateClass([
|
|
5823
|
+
property8({ attribute: false, noAccessor: true })
|
|
5824
|
+
], DawEditorElement.prototype, "spectrogramColorMap", 1);
|
|
5825
|
+
__decorateClass([
|
|
5826
|
+
property8({ type: String, attribute: "scale-mode" })
|
|
5010
5827
|
], DawEditorElement.prototype, "scaleMode", 2);
|
|
5011
5828
|
__decorateClass([
|
|
5012
|
-
|
|
5829
|
+
property8({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
|
|
5013
5830
|
], DawEditorElement.prototype, "ticksPerPixel", 1);
|
|
5014
5831
|
__decorateClass([
|
|
5015
|
-
|
|
5832
|
+
property8({ type: Number, noAccessor: true })
|
|
5016
5833
|
], DawEditorElement.prototype, "bpm", 1);
|
|
5017
5834
|
__decorateClass([
|
|
5018
|
-
|
|
5835
|
+
property8({ attribute: false })
|
|
5019
5836
|
], DawEditorElement.prototype, "timeSignature", 2);
|
|
5020
5837
|
__decorateClass([
|
|
5021
|
-
|
|
5838
|
+
property8({ attribute: false })
|
|
5022
5839
|
], DawEditorElement.prototype, "meterEntries", 2);
|
|
5023
5840
|
__decorateClass([
|
|
5024
|
-
|
|
5841
|
+
property8({ type: Number, noAccessor: true })
|
|
5025
5842
|
], DawEditorElement.prototype, "ppqn", 1);
|
|
5026
5843
|
__decorateClass([
|
|
5027
|
-
|
|
5844
|
+
property8({ type: String, attribute: "snap-to" })
|
|
5028
5845
|
], DawEditorElement.prototype, "snapTo", 2);
|
|
5029
5846
|
__decorateClass([
|
|
5030
|
-
|
|
5847
|
+
property8({ attribute: false })
|
|
5031
5848
|
], DawEditorElement.prototype, "secondsToTicks", 2);
|
|
5032
5849
|
__decorateClass([
|
|
5033
|
-
|
|
5850
|
+
property8({ attribute: false })
|
|
5034
5851
|
], DawEditorElement.prototype, "ticksToSeconds", 2);
|
|
5035
5852
|
__decorateClass([
|
|
5036
5853
|
state3()
|
|
@@ -5054,18 +5871,18 @@ __decorateClass([
|
|
|
5054
5871
|
state3()
|
|
5055
5872
|
], DawEditorElement.prototype, "_dragOver", 2);
|
|
5056
5873
|
__decorateClass([
|
|
5057
|
-
|
|
5874
|
+
property8({ attribute: false })
|
|
5058
5875
|
], DawEditorElement.prototype, "adapter", 1);
|
|
5059
5876
|
__decorateClass([
|
|
5060
|
-
|
|
5877
|
+
property8({ attribute: "eager-resume" })
|
|
5061
5878
|
], DawEditorElement.prototype, "eagerResume", 2);
|
|
5062
5879
|
DawEditorElement = __decorateClass([
|
|
5063
|
-
|
|
5880
|
+
customElement12("daw-editor")
|
|
5064
5881
|
], DawEditorElement);
|
|
5065
5882
|
|
|
5066
5883
|
// src/elements/daw-ruler.ts
|
|
5067
|
-
import { LitElement as
|
|
5068
|
-
import { customElement as
|
|
5884
|
+
import { LitElement as LitElement11, html as html10, css as css10 } from "lit";
|
|
5885
|
+
import { customElement as customElement13, property as property9 } from "lit/decorators.js";
|
|
5069
5886
|
|
|
5070
5887
|
// src/utils/time-format.ts
|
|
5071
5888
|
function formatTime(milliseconds) {
|
|
@@ -5116,8 +5933,8 @@ function computeTemporalTicks(samplesPerPixel, sampleRate, duration, rulerHeight
|
|
|
5116
5933
|
}
|
|
5117
5934
|
|
|
5118
5935
|
// src/elements/daw-ruler.ts
|
|
5119
|
-
var
|
|
5120
|
-
var DawRulerElement = class extends
|
|
5936
|
+
var MAX_CANVAS_WIDTH4 = 1e3;
|
|
5937
|
+
var DawRulerElement = class extends LitElement11 {
|
|
5121
5938
|
constructor() {
|
|
5122
5939
|
super(...arguments);
|
|
5123
5940
|
this.samplesPerPixel = 1024;
|
|
@@ -5161,33 +5978,33 @@ var DawRulerElement = class extends LitElement10 {
|
|
|
5161
5978
|
}
|
|
5162
5979
|
render() {
|
|
5163
5980
|
const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
|
|
5164
|
-
if (widthX <= 0) return
|
|
5165
|
-
const totalChunks = Math.ceil(widthX /
|
|
5981
|
+
if (widthX <= 0) return html10``;
|
|
5982
|
+
const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH4);
|
|
5166
5983
|
const indices = Array.from({ length: totalChunks }, (_, i) => i);
|
|
5167
5984
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
5168
5985
|
const beatsLabels = this.scaleMode === "beats" ? this._musicalTickData?.ticks.filter((t) => t.label) ?? [] : [];
|
|
5169
5986
|
const temporalLabels = this.scaleMode !== "beats" ? this._tickData?.labels ?? [] : [];
|
|
5170
|
-
return
|
|
5987
|
+
return html10`
|
|
5171
5988
|
<div class="container" style="width: ${widthX}px; height: ${this.rulerHeight}px;">
|
|
5172
5989
|
${indices.map((i) => {
|
|
5173
|
-
const width = Math.min(
|
|
5174
|
-
return
|
|
5990
|
+
const width = Math.min(MAX_CANVAS_WIDTH4, widthX - i * MAX_CANVAS_WIDTH4);
|
|
5991
|
+
return html10`
|
|
5175
5992
|
<canvas
|
|
5176
5993
|
data-index=${i}
|
|
5177
5994
|
width=${width * dpr}
|
|
5178
5995
|
height=${this.rulerHeight * dpr}
|
|
5179
|
-
style="left: ${i *
|
|
5996
|
+
style="left: ${i * MAX_CANVAS_WIDTH4}px; width: ${width}px; height: ${this.rulerHeight}px;"
|
|
5180
5997
|
></canvas>
|
|
5181
5998
|
`;
|
|
5182
5999
|
})}
|
|
5183
6000
|
${this.scaleMode === "beats" ? beatsLabels.map(
|
|
5184
|
-
(t) =>
|
|
6001
|
+
(t) => html10`<span
|
|
5185
6002
|
class="label ${t.pixel > 0 ? "centered" : ""}"
|
|
5186
6003
|
style="left: ${t.pixel > 0 ? t.pixel : t.pixel + 4}px;"
|
|
5187
6004
|
>${t.label}</span
|
|
5188
6005
|
>`
|
|
5189
6006
|
) : temporalLabels.map(
|
|
5190
|
-
({ pix, text }) =>
|
|
6007
|
+
({ pix, text }) => html10`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
|
|
5191
6008
|
)}
|
|
5192
6009
|
</div>
|
|
5193
6010
|
`;
|
|
@@ -5205,8 +6022,8 @@ var DawRulerElement = class extends LitElement10 {
|
|
|
5205
6022
|
const idx = Number(canvas.dataset.index);
|
|
5206
6023
|
const ctx = canvas.getContext("2d");
|
|
5207
6024
|
if (!ctx) continue;
|
|
5208
|
-
const canvasWidth = Math.min(
|
|
5209
|
-
const globalOffset = idx *
|
|
6025
|
+
const canvasWidth = Math.min(MAX_CANVAS_WIDTH4, widthX - idx * MAX_CANVAS_WIDTH4);
|
|
6026
|
+
const globalOffset = idx * MAX_CANVAS_WIDTH4;
|
|
5210
6027
|
ctx.resetTransform();
|
|
5211
6028
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
5212
6029
|
ctx.scale(dpr, dpr);
|
|
@@ -5238,7 +6055,7 @@ var DawRulerElement = class extends LitElement10 {
|
|
|
5238
6055
|
}
|
|
5239
6056
|
}
|
|
5240
6057
|
};
|
|
5241
|
-
DawRulerElement.styles =
|
|
6058
|
+
DawRulerElement.styles = css10`
|
|
5242
6059
|
:host {
|
|
5243
6060
|
display: block;
|
|
5244
6061
|
position: relative;
|
|
@@ -5264,40 +6081,40 @@ DawRulerElement.styles = css9`
|
|
|
5264
6081
|
}
|
|
5265
6082
|
`;
|
|
5266
6083
|
__decorateClass([
|
|
5267
|
-
|
|
6084
|
+
property9({ type: Number, attribute: false })
|
|
5268
6085
|
], DawRulerElement.prototype, "samplesPerPixel", 2);
|
|
5269
6086
|
__decorateClass([
|
|
5270
|
-
|
|
6087
|
+
property9({ type: Number, attribute: false })
|
|
5271
6088
|
], DawRulerElement.prototype, "sampleRate", 2);
|
|
5272
6089
|
__decorateClass([
|
|
5273
|
-
|
|
6090
|
+
property9({ type: Number, attribute: false })
|
|
5274
6091
|
], DawRulerElement.prototype, "duration", 2);
|
|
5275
6092
|
__decorateClass([
|
|
5276
|
-
|
|
6093
|
+
property9({ type: Number, attribute: false })
|
|
5277
6094
|
], DawRulerElement.prototype, "rulerHeight", 2);
|
|
5278
6095
|
__decorateClass([
|
|
5279
|
-
|
|
6096
|
+
property9({ type: String, attribute: false })
|
|
5280
6097
|
], DawRulerElement.prototype, "scaleMode", 2);
|
|
5281
6098
|
__decorateClass([
|
|
5282
|
-
|
|
6099
|
+
property9({ type: Number, attribute: false })
|
|
5283
6100
|
], DawRulerElement.prototype, "ticksPerPixel", 2);
|
|
5284
6101
|
__decorateClass([
|
|
5285
|
-
|
|
6102
|
+
property9({ attribute: false })
|
|
5286
6103
|
], DawRulerElement.prototype, "meterEntries", 2);
|
|
5287
6104
|
__decorateClass([
|
|
5288
|
-
|
|
6105
|
+
property9({ type: Number, attribute: false })
|
|
5289
6106
|
], DawRulerElement.prototype, "ppqn", 2);
|
|
5290
6107
|
__decorateClass([
|
|
5291
|
-
|
|
6108
|
+
property9({ type: Number, attribute: false })
|
|
5292
6109
|
], DawRulerElement.prototype, "totalWidth", 2);
|
|
5293
6110
|
DawRulerElement = __decorateClass([
|
|
5294
|
-
|
|
6111
|
+
customElement13("daw-ruler")
|
|
5295
6112
|
], DawRulerElement);
|
|
5296
6113
|
|
|
5297
6114
|
// src/elements/daw-selection.ts
|
|
5298
|
-
import { LitElement as
|
|
5299
|
-
import { customElement as
|
|
5300
|
-
var DawSelectionElement = class extends
|
|
6115
|
+
import { LitElement as LitElement12, html as html11, css as css11 } from "lit";
|
|
6116
|
+
import { customElement as customElement14, property as property10 } from "lit/decorators.js";
|
|
6117
|
+
var DawSelectionElement = class extends LitElement12 {
|
|
5301
6118
|
constructor() {
|
|
5302
6119
|
super(...arguments);
|
|
5303
6120
|
this.startPx = 0;
|
|
@@ -5306,11 +6123,11 @@ var DawSelectionElement = class extends LitElement11 {
|
|
|
5306
6123
|
render() {
|
|
5307
6124
|
const left = Math.min(this.startPx, this.endPx);
|
|
5308
6125
|
const width = Math.abs(this.endPx - this.startPx);
|
|
5309
|
-
if (width === 0) return
|
|
5310
|
-
return
|
|
6126
|
+
if (width === 0) return html11``;
|
|
6127
|
+
return html11`<div style="left: ${left}px; width: ${width}px;"></div>`;
|
|
5311
6128
|
}
|
|
5312
6129
|
};
|
|
5313
|
-
DawSelectionElement.styles =
|
|
6130
|
+
DawSelectionElement.styles = css11`
|
|
5314
6131
|
:host {
|
|
5315
6132
|
position: absolute;
|
|
5316
6133
|
top: 0;
|
|
@@ -5327,18 +6144,18 @@ DawSelectionElement.styles = css10`
|
|
|
5327
6144
|
}
|
|
5328
6145
|
`;
|
|
5329
6146
|
__decorateClass([
|
|
5330
|
-
|
|
6147
|
+
property10({ type: Number, attribute: false })
|
|
5331
6148
|
], DawSelectionElement.prototype, "startPx", 2);
|
|
5332
6149
|
__decorateClass([
|
|
5333
|
-
|
|
6150
|
+
property10({ type: Number, attribute: false })
|
|
5334
6151
|
], DawSelectionElement.prototype, "endPx", 2);
|
|
5335
6152
|
DawSelectionElement = __decorateClass([
|
|
5336
|
-
|
|
6153
|
+
customElement14("daw-selection")
|
|
5337
6154
|
], DawSelectionElement);
|
|
5338
6155
|
|
|
5339
6156
|
// src/elements/daw-record-button.ts
|
|
5340
|
-
import { html as
|
|
5341
|
-
import { customElement as
|
|
6157
|
+
import { html as html12, css as css12 } from "lit";
|
|
6158
|
+
import { customElement as customElement15, state as state4 } from "lit/decorators.js";
|
|
5342
6159
|
var DawRecordButtonElement = class extends DawTransportButton {
|
|
5343
6160
|
constructor() {
|
|
5344
6161
|
super(...arguments);
|
|
@@ -5379,7 +6196,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
|
|
|
5379
6196
|
}
|
|
5380
6197
|
}
|
|
5381
6198
|
render() {
|
|
5382
|
-
return
|
|
6199
|
+
return html12`
|
|
5383
6200
|
<button part="button" ?data-recording=${this._isRecording} @click=${this._onClick}>
|
|
5384
6201
|
<slot>Record</slot>
|
|
5385
6202
|
</button>
|
|
@@ -5399,7 +6216,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
|
|
|
5399
6216
|
};
|
|
5400
6217
|
DawRecordButtonElement.styles = [
|
|
5401
6218
|
DawTransportButton.styles,
|
|
5402
|
-
|
|
6219
|
+
css12`
|
|
5403
6220
|
button[data-recording] {
|
|
5404
6221
|
color: #d08070;
|
|
5405
6222
|
border-color: #d08070;
|
|
@@ -5411,14 +6228,14 @@ __decorateClass([
|
|
|
5411
6228
|
state4()
|
|
5412
6229
|
], DawRecordButtonElement.prototype, "_isRecording", 2);
|
|
5413
6230
|
DawRecordButtonElement = __decorateClass([
|
|
5414
|
-
|
|
6231
|
+
customElement15("daw-record-button")
|
|
5415
6232
|
], DawRecordButtonElement);
|
|
5416
6233
|
|
|
5417
6234
|
// src/elements/daw-keyboard-shortcuts.ts
|
|
5418
|
-
import { LitElement as
|
|
5419
|
-
import { customElement as
|
|
6235
|
+
import { LitElement as LitElement13 } from "lit";
|
|
6236
|
+
import { customElement as customElement16, property as property11 } from "lit/decorators.js";
|
|
5420
6237
|
import { handleKeyboardEvent } from "@waveform-playlist/core";
|
|
5421
|
-
var DawKeyboardShortcutsElement = class extends
|
|
6238
|
+
var DawKeyboardShortcutsElement = class extends LitElement13 {
|
|
5422
6239
|
constructor() {
|
|
5423
6240
|
super(...arguments);
|
|
5424
6241
|
this.playback = false;
|
|
@@ -5572,17 +6389,198 @@ var DawKeyboardShortcutsElement = class extends LitElement12 {
|
|
|
5572
6389
|
}
|
|
5573
6390
|
};
|
|
5574
6391
|
__decorateClass([
|
|
5575
|
-
|
|
6392
|
+
property11({ type: Boolean })
|
|
5576
6393
|
], DawKeyboardShortcutsElement.prototype, "playback", 2);
|
|
5577
6394
|
__decorateClass([
|
|
5578
|
-
|
|
6395
|
+
property11({ type: Boolean })
|
|
5579
6396
|
], DawKeyboardShortcutsElement.prototype, "splitting", 2);
|
|
5580
6397
|
__decorateClass([
|
|
5581
|
-
|
|
6398
|
+
property11({ type: Boolean })
|
|
5582
6399
|
], DawKeyboardShortcutsElement.prototype, "undo", 2);
|
|
5583
6400
|
DawKeyboardShortcutsElement = __decorateClass([
|
|
5584
|
-
|
|
6401
|
+
customElement16("daw-keyboard-shortcuts")
|
|
5585
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);
|
|
5586
6584
|
export {
|
|
5587
6585
|
AudioResumeController,
|
|
5588
6586
|
ClipPointerHandler,
|
|
@@ -5591,11 +6589,13 @@ export {
|
|
|
5591
6589
|
DawGridElement,
|
|
5592
6590
|
DawKeyboardShortcutsElement,
|
|
5593
6591
|
DawPauseButtonElement,
|
|
6592
|
+
DawPianoRollElement,
|
|
5594
6593
|
DawPlayButtonElement,
|
|
5595
6594
|
DawPlayheadElement,
|
|
5596
6595
|
DawRecordButtonElement,
|
|
5597
6596
|
DawRulerElement,
|
|
5598
6597
|
DawSelectionElement,
|
|
6598
|
+
DawSpectrogramElement,
|
|
5599
6599
|
DawStopButtonElement,
|
|
5600
6600
|
DawTrackControlsElement,
|
|
5601
6601
|
DawTrackElement,
|
|
@@ -5603,6 +6603,7 @@ export {
|
|
|
5603
6603
|
DawTransportElement,
|
|
5604
6604
|
DawWaveformElement,
|
|
5605
6605
|
RecordingController,
|
|
6606
|
+
SpectrogramController,
|
|
5606
6607
|
isDomClip,
|
|
5607
6608
|
splitAtPlayhead
|
|
5608
6609
|
};
|