@dawcore/components 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  DawGridElement: () => DawGridElement,
46
46
  DawKeyboardShortcutsElement: () => DawKeyboardShortcutsElement,
47
47
  DawPauseButtonElement: () => DawPauseButtonElement,
48
+ DawPianoRollElement: () => DawPianoRollElement,
48
49
  DawPlayButtonElement: () => DawPlayButtonElement,
49
50
  DawPlayheadElement: () => DawPlayheadElement,
50
51
  DawRecordButtonElement: () => DawRecordButtonElement,
@@ -79,11 +80,48 @@ var DawClipElement = class extends import_lit.LitElement {
79
80
  this.fadeIn = 0;
80
81
  this.fadeOut = 0;
81
82
  this.fadeType = "linear";
83
+ this.midiNotes = null;
84
+ this._midiChannel = null;
85
+ this._midiProgram = null;
82
86
  this.clipId = crypto.randomUUID();
83
87
  // Removal is detected by the editor's MutationObserver — detached elements
84
88
  // cannot bubble events to ancestors.
85
89
  this._hasRendered = false;
86
90
  }
91
+ get midiChannel() {
92
+ return this._midiChannel;
93
+ }
94
+ set midiChannel(value) {
95
+ const old = this._midiChannel;
96
+ if (value === null) {
97
+ this._midiChannel = null;
98
+ this.requestUpdate("midiChannel", old);
99
+ return;
100
+ }
101
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0 || value > 15) {
102
+ console.warn("[dawcore] daw-clip midi-channel " + value + " is out of range 0-15 \u2014 ignored");
103
+ return;
104
+ }
105
+ this._midiChannel = value;
106
+ this.requestUpdate("midiChannel", old);
107
+ }
108
+ get midiProgram() {
109
+ return this._midiProgram;
110
+ }
111
+ set midiProgram(value) {
112
+ const old = this._midiProgram;
113
+ if (value === null) {
114
+ this._midiProgram = null;
115
+ this.requestUpdate("midiProgram", old);
116
+ return;
117
+ }
118
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0 || value > 127) {
119
+ console.warn("[dawcore] daw-clip midi-program " + value + " is out of range 0-127 \u2014 ignored");
120
+ return;
121
+ }
122
+ this._midiProgram = value;
123
+ this.requestUpdate("midiProgram", old);
124
+ }
87
125
  // Light DOM — no visual rendering, just a data container
88
126
  createRenderRoot() {
89
127
  return this;
@@ -115,7 +153,10 @@ var DawClipElement = class extends import_lit.LitElement {
115
153
  "name",
116
154
  "fadeIn",
117
155
  "fadeOut",
118
- "fadeType"
156
+ "fadeType",
157
+ "midiNotes",
158
+ "midiChannel",
159
+ "midiProgram"
119
160
  ];
120
161
  if (clipProps.some((p) => changed.has(p))) {
121
162
  const trackEl = this.closest("daw-track");
@@ -162,6 +203,15 @@ __decorateClass([
162
203
  __decorateClass([
163
204
  (0, import_decorators.property)({ attribute: "fade-type" })
164
205
  ], DawClipElement.prototype, "fadeType", 2);
206
+ __decorateClass([
207
+ (0, import_decorators.property)({ attribute: false })
208
+ ], DawClipElement.prototype, "midiNotes", 2);
209
+ __decorateClass([
210
+ (0, import_decorators.property)({ type: Number, attribute: "midi-channel", noAccessor: true })
211
+ ], DawClipElement.prototype, "midiChannel", 1);
212
+ __decorateClass([
213
+ (0, import_decorators.property)({ type: Number, attribute: "midi-program", noAccessor: true })
214
+ ], DawClipElement.prototype, "midiProgram", 1);
165
215
  DawClipElement = __decorateClass([
166
216
  (0, import_decorators.customElement)("daw-clip")
167
217
  ], DawClipElement);
@@ -178,6 +228,7 @@ var DawTrackElement = class extends import_lit2.LitElement {
178
228
  this.pan = 0;
179
229
  this.muted = false;
180
230
  this.soloed = false;
231
+ this.renderMode = "waveform";
181
232
  this.trackId = crypto.randomUUID();
182
233
  // Track removal is detected by the editor's MutationObserver,
183
234
  // not by dispatching from disconnectedCallback (detached elements
@@ -205,7 +256,7 @@ var DawTrackElement = class extends import_lit2.LitElement {
205
256
  this._hasRendered = true;
206
257
  return;
207
258
  }
208
- const trackProps = ["volume", "pan", "muted", "soloed", "src", "name"];
259
+ const trackProps = ["volume", "pan", "muted", "soloed", "src", "name", "renderMode"];
209
260
  const hasTrackChange = trackProps.some((p) => changed.has(p));
210
261
  if (hasTrackChange) {
211
262
  this.dispatchEvent(
@@ -236,6 +287,9 @@ __decorateClass([
236
287
  __decorateClass([
237
288
  (0, import_decorators2.property)({ type: Boolean })
238
289
  ], DawTrackElement.prototype, "soloed", 2);
290
+ __decorateClass([
291
+ (0, import_decorators2.property)({ attribute: "render-mode" })
292
+ ], DawTrackElement.prototype, "renderMode", 2);
239
293
  DawTrackElement = __decorateClass([
240
294
  (0, import_decorators2.customElement)("daw-track")
241
295
  ], DawTrackElement);
@@ -585,9 +639,229 @@ DawWaveformElement = __decorateClass([
585
639
  (0, import_decorators3.customElement)("daw-waveform")
586
640
  ], DawWaveformElement);
587
641
 
588
- // src/elements/daw-playhead.ts
642
+ // src/elements/daw-piano-roll.ts
589
643
  var import_lit4 = require("lit");
590
644
  var import_decorators4 = require("lit/decorators.js");
645
+ var MAX_CANVAS_WIDTH2 = 1e3;
646
+ var LAYOUT_PROPS2 = /* @__PURE__ */ new Set([
647
+ "length",
648
+ "waveHeight",
649
+ "samplesPerPixel",
650
+ "sampleRate",
651
+ "clipOffsetSeconds",
652
+ "midiNotes",
653
+ "selected"
654
+ ]);
655
+ var DawPianoRollElement = class extends import_lit4.LitElement {
656
+ constructor() {
657
+ super(...arguments);
658
+ this.midiNotes = [];
659
+ this.length = 0;
660
+ this.waveHeight = 128;
661
+ this._samplesPerPixel = 1024;
662
+ this._sampleRate = 48e3;
663
+ this.clipOffsetSeconds = 0;
664
+ this.visibleStart = -Infinity;
665
+ this.visibleEnd = Infinity;
666
+ this.originX = 0;
667
+ this.selected = false;
668
+ this._rafHandle = null;
669
+ }
670
+ get samplesPerPixel() {
671
+ return this._samplesPerPixel;
672
+ }
673
+ set samplesPerPixel(value) {
674
+ if (!Number.isFinite(value) || value <= 0) {
675
+ console.warn("[dawcore] daw-piano-roll samplesPerPixel " + value + " is invalid \u2014 ignored");
676
+ return;
677
+ }
678
+ const old = this._samplesPerPixel;
679
+ this._samplesPerPixel = value;
680
+ this.requestUpdate("samplesPerPixel", old);
681
+ }
682
+ get sampleRate() {
683
+ return this._sampleRate;
684
+ }
685
+ set sampleRate(value) {
686
+ if (!Number.isFinite(value) || value <= 0) {
687
+ console.warn("[dawcore] daw-piano-roll sampleRate " + value + " is invalid \u2014 ignored");
688
+ return;
689
+ }
690
+ const old = this._sampleRate;
691
+ this._sampleRate = value;
692
+ this.requestUpdate("sampleRate", old);
693
+ }
694
+ _scheduleDraw() {
695
+ if (this._rafHandle !== null) return;
696
+ this._rafHandle = requestAnimationFrame(() => {
697
+ this._rafHandle = null;
698
+ this._draw();
699
+ });
700
+ }
701
+ willUpdate(_changed) {
702
+ this._scheduleDraw();
703
+ }
704
+ updated(changedProperties) {
705
+ const needsFullDraw = [...changedProperties.keys()].some((key) => LAYOUT_PROPS2.has(key));
706
+ if (needsFullDraw) return;
707
+ if (changedProperties.has("visibleStart") || changedProperties.has("visibleEnd") || changedProperties.has("originX")) {
708
+ this._scheduleDraw();
709
+ }
710
+ }
711
+ _getPitchRange() {
712
+ if (this.midiNotes.length === 0) return { minMidi: 0, maxMidi: 127 };
713
+ let min = 127;
714
+ let max = 0;
715
+ for (const note of this.midiNotes) {
716
+ if (note.midi < min) min = note.midi;
717
+ if (note.midi > max) max = note.midi;
718
+ }
719
+ return {
720
+ minMidi: Math.max(0, min - 1),
721
+ maxMidi: Math.min(127, max + 1)
722
+ };
723
+ }
724
+ _getNoteColor() {
725
+ const cs = getComputedStyle(this);
726
+ const note = cs.getPropertyValue("--daw-piano-roll-note-color").trim() || "#2a7070";
727
+ const selectedColor = cs.getPropertyValue("--daw-piano-roll-selected-note-color").trim() || "#3d9e9e";
728
+ return this.selected ? selectedColor : note;
729
+ }
730
+ _draw() {
731
+ if (!this.shadowRoot) return;
732
+ const canvases = this.shadowRoot.querySelectorAll("canvas");
733
+ if (canvases.length === 0) return;
734
+ const { minMidi, maxMidi } = this._getPitchRange();
735
+ const noteRange = maxMidi - minMidi + 1;
736
+ const noteHeight = Math.max(2, this.waveHeight / noteRange);
737
+ const pixelsPerSecond = this.sampleRate / this.samplesPerPixel;
738
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
739
+ const color = this._getNoteColor();
740
+ for (const canvas of canvases) {
741
+ const chunkIdx = Number(canvas.dataset.index);
742
+ const chunkPixelStart = chunkIdx * MAX_CANVAS_WIDTH2;
743
+ const canvasWidth = canvas.width / dpr;
744
+ const ctx = canvas.getContext("2d");
745
+ if (!ctx) continue;
746
+ ctx.resetTransform();
747
+ ctx.clearRect(
748
+ 0,
749
+ 0,
750
+ canvas.width,
751
+ canvas.height
752
+ );
753
+ ctx.imageSmoothingEnabled = false;
754
+ ctx.scale(dpr, dpr);
755
+ const chunkStartTime = chunkPixelStart * this.samplesPerPixel / this.sampleRate;
756
+ const chunkEndTime = (chunkPixelStart + canvasWidth) * this.samplesPerPixel / this.sampleRate;
757
+ for (const note of this.midiNotes) {
758
+ const noteStart = note.time - this.clipOffsetSeconds;
759
+ const noteEnd = noteStart + note.duration;
760
+ if (noteEnd <= chunkStartTime || noteStart >= chunkEndTime) continue;
761
+ const x = noteStart * pixelsPerSecond - chunkPixelStart;
762
+ const w = Math.max(2, note.duration * pixelsPerSecond);
763
+ const y = (maxMidi - note.midi) / noteRange * this.waveHeight;
764
+ const alpha = 0.3 + note.velocity * 0.7;
765
+ ctx.fillStyle = color;
766
+ ctx.globalAlpha = alpha;
767
+ ctx.beginPath();
768
+ ctx.roundRect(x, y, w, noteHeight, 1);
769
+ ctx.fill();
770
+ }
771
+ ctx.globalAlpha = 1;
772
+ }
773
+ }
774
+ connectedCallback() {
775
+ super.connectedCallback();
776
+ this._scheduleDraw();
777
+ }
778
+ disconnectedCallback() {
779
+ super.disconnectedCallback();
780
+ if (this._rafHandle !== null) {
781
+ cancelAnimationFrame(this._rafHandle);
782
+ this._rafHandle = null;
783
+ }
784
+ }
785
+ render() {
786
+ if (this.length <= 0)
787
+ return import_lit4.html`<div class="container" style="width: 0; height: ${this.waveHeight}px;"></div>`;
788
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
789
+ const visibleIndices = getVisibleChunkIndices(
790
+ this.length,
791
+ MAX_CANVAS_WIDTH2,
792
+ this.visibleStart,
793
+ this.visibleEnd,
794
+ this.originX
795
+ );
796
+ return import_lit4.html`
797
+ <div class="container" style="width: ${this.length}px; height: ${this.waveHeight}px;">
798
+ ${visibleIndices.map((i) => {
799
+ const chunkLeft = i * MAX_CANVAS_WIDTH2;
800
+ const chunkWidth = Math.min(this.length - chunkLeft, MAX_CANVAS_WIDTH2);
801
+ return import_lit4.html`<canvas
802
+ data-index=${i}
803
+ width=${chunkWidth * dpr}
804
+ height=${this.waveHeight * dpr}
805
+ style="left: ${chunkLeft}px; width: ${chunkWidth}px; height: ${this.waveHeight}px;"
806
+ ></canvas>`;
807
+ })}
808
+ </div>
809
+ `;
810
+ }
811
+ };
812
+ DawPianoRollElement.styles = import_lit4.css`
813
+ :host {
814
+ display: block;
815
+ position: relative;
816
+ }
817
+ .container {
818
+ position: relative;
819
+ background: var(--daw-piano-roll-background, #1a1a2e);
820
+ }
821
+ canvas {
822
+ position: absolute;
823
+ top: 0;
824
+ image-rendering: pixelated;
825
+ image-rendering: crisp-edges;
826
+ }
827
+ `;
828
+ __decorateClass([
829
+ (0, import_decorators4.property)({ attribute: false })
830
+ ], DawPianoRollElement.prototype, "midiNotes", 2);
831
+ __decorateClass([
832
+ (0, import_decorators4.property)({ type: Number, attribute: false })
833
+ ], DawPianoRollElement.prototype, "length", 2);
834
+ __decorateClass([
835
+ (0, import_decorators4.property)({ type: Number, attribute: false })
836
+ ], DawPianoRollElement.prototype, "waveHeight", 2);
837
+ __decorateClass([
838
+ (0, import_decorators4.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
839
+ ], DawPianoRollElement.prototype, "samplesPerPixel", 1);
840
+ __decorateClass([
841
+ (0, import_decorators4.property)({ type: Number, attribute: "sample-rate", noAccessor: true })
842
+ ], DawPianoRollElement.prototype, "sampleRate", 1);
843
+ __decorateClass([
844
+ (0, import_decorators4.property)({ type: Number, attribute: false })
845
+ ], DawPianoRollElement.prototype, "clipOffsetSeconds", 2);
846
+ __decorateClass([
847
+ (0, import_decorators4.property)({ type: Number, attribute: false })
848
+ ], DawPianoRollElement.prototype, "visibleStart", 2);
849
+ __decorateClass([
850
+ (0, import_decorators4.property)({ type: Number, attribute: false })
851
+ ], DawPianoRollElement.prototype, "visibleEnd", 2);
852
+ __decorateClass([
853
+ (0, import_decorators4.property)({ type: Number, attribute: false })
854
+ ], DawPianoRollElement.prototype, "originX", 2);
855
+ __decorateClass([
856
+ (0, import_decorators4.property)({ type: Boolean, reflect: true })
857
+ ], DawPianoRollElement.prototype, "selected", 2);
858
+ DawPianoRollElement = __decorateClass([
859
+ (0, import_decorators4.customElement)("daw-piano-roll")
860
+ ], DawPianoRollElement);
861
+
862
+ // src/elements/daw-playhead.ts
863
+ var import_lit5 = require("lit");
864
+ var import_decorators5 = require("lit/decorators.js");
591
865
 
592
866
  // src/controllers/animation-controller.ts
593
867
  var AnimationController = class {
@@ -620,14 +894,14 @@ var AnimationController = class {
620
894
  };
621
895
 
622
896
  // src/elements/daw-playhead.ts
623
- var DawPlayheadElement = class extends import_lit4.LitElement {
897
+ var DawPlayheadElement = class extends import_lit5.LitElement {
624
898
  constructor() {
625
899
  super(...arguments);
626
900
  this._animation = new AnimationController(this);
627
901
  this._line = null;
628
902
  }
629
903
  render() {
630
- return import_lit4.html`<div></div>`;
904
+ return import_lit5.html`<div></div>`;
631
905
  }
632
906
  firstUpdated() {
633
907
  this._line = this.shadowRoot.querySelector("div");
@@ -683,7 +957,7 @@ var DawPlayheadElement = class extends import_lit4.LitElement {
683
957
  }
684
958
  }
685
959
  };
686
- DawPlayheadElement.styles = import_lit4.css`
960
+ DawPlayheadElement.styles = import_lit5.css`
687
961
  :host {
688
962
  position: absolute;
689
963
  top: 0;
@@ -702,13 +976,13 @@ DawPlayheadElement.styles = import_lit4.css`
702
976
  }
703
977
  `;
704
978
  DawPlayheadElement = __decorateClass([
705
- (0, import_decorators4.customElement)("daw-playhead")
979
+ (0, import_decorators5.customElement)("daw-playhead")
706
980
  ], DawPlayheadElement);
707
981
 
708
982
  // src/elements/daw-transport.ts
709
- var import_lit5 = require("lit");
710
- var import_decorators5 = require("lit/decorators.js");
711
- var DawTransportElement = class extends import_lit5.LitElement {
983
+ var import_lit6 = require("lit");
984
+ var import_decorators6 = require("lit/decorators.js");
985
+ var DawTransportElement = class extends import_lit6.LitElement {
712
986
  constructor() {
713
987
  super(...arguments);
714
988
  this.for = "";
@@ -723,25 +997,25 @@ var DawTransportElement = class extends import_lit5.LitElement {
723
997
  }
724
998
  };
725
999
  __decorateClass([
726
- (0, import_decorators5.property)()
1000
+ (0, import_decorators6.property)()
727
1001
  ], DawTransportElement.prototype, "for", 2);
728
1002
  DawTransportElement = __decorateClass([
729
- (0, import_decorators5.customElement)("daw-transport")
1003
+ (0, import_decorators6.customElement)("daw-transport")
730
1004
  ], DawTransportElement);
731
1005
 
732
1006
  // src/elements/daw-play-button.ts
733
- var import_lit7 = require("lit");
734
- var import_decorators6 = require("lit/decorators.js");
1007
+ var import_lit8 = require("lit");
1008
+ var import_decorators7 = require("lit/decorators.js");
735
1009
 
736
1010
  // src/elements/daw-transport-button.ts
737
- var import_lit6 = require("lit");
738
- var DawTransportButton = class extends import_lit6.LitElement {
1011
+ var import_lit7 = require("lit");
1012
+ var DawTransportButton = class extends import_lit7.LitElement {
739
1013
  get target() {
740
1014
  const transport = this.closest("daw-transport");
741
1015
  return transport?.target ?? null;
742
1016
  }
743
1017
  };
744
- DawTransportButton.styles = import_lit6.css`
1018
+ DawTransportButton.styles = import_lit7.css`
745
1019
  button {
746
1020
  cursor: pointer;
747
1021
  background: var(--daw-controls-background, #1a1a2e);
@@ -793,7 +1067,7 @@ var DawPlayButtonElement = class extends DawTransportButton {
793
1067
  }
794
1068
  }
795
1069
  render() {
796
- return import_lit7.html`
1070
+ return import_lit8.html`
797
1071
  <button part="button" ?disabled=${this._isRecording} @click=${this._onClick}>
798
1072
  <slot>Play</slot>
799
1073
  </button>
@@ -811,15 +1085,15 @@ var DawPlayButtonElement = class extends DawTransportButton {
811
1085
  }
812
1086
  };
813
1087
  __decorateClass([
814
- (0, import_decorators6.state)()
1088
+ (0, import_decorators7.state)()
815
1089
  ], DawPlayButtonElement.prototype, "_isRecording", 2);
816
1090
  DawPlayButtonElement = __decorateClass([
817
- (0, import_decorators6.customElement)("daw-play-button")
1091
+ (0, import_decorators7.customElement)("daw-play-button")
818
1092
  ], DawPlayButtonElement);
819
1093
 
820
1094
  // src/elements/daw-pause-button.ts
821
- var import_lit8 = require("lit");
822
- var import_decorators7 = require("lit/decorators.js");
1095
+ var import_lit9 = require("lit");
1096
+ var import_decorators8 = require("lit/decorators.js");
823
1097
  var DawPauseButtonElement = class extends DawTransportButton {
824
1098
  constructor() {
825
1099
  super(...arguments);
@@ -833,6 +1107,12 @@ var DawPauseButtonElement = class extends DawTransportButton {
833
1107
  this._isRecording = false;
834
1108
  this._isPaused = false;
835
1109
  };
1110
+ this._onRecPause = () => {
1111
+ this._isPaused = true;
1112
+ };
1113
+ this._onRecResume = () => {
1114
+ this._isPaused = false;
1115
+ };
836
1116
  }
837
1117
  connectedCallback() {
838
1118
  super.connectedCallback();
@@ -843,6 +1123,8 @@ var DawPauseButtonElement = class extends DawTransportButton {
843
1123
  target.addEventListener("daw-recording-start", this._onRecStart);
844
1124
  target.addEventListener("daw-recording-complete", this._onRecEnd);
845
1125
  target.addEventListener("daw-recording-error", this._onRecEnd);
1126
+ target.addEventListener("daw-recording-pause", this._onRecPause);
1127
+ target.addEventListener("daw-recording-resume", this._onRecResume);
846
1128
  });
847
1129
  }
848
1130
  disconnectedCallback() {
@@ -851,11 +1133,13 @@ var DawPauseButtonElement = class extends DawTransportButton {
851
1133
  this._targetRef.removeEventListener("daw-recording-start", this._onRecStart);
852
1134
  this._targetRef.removeEventListener("daw-recording-complete", this._onRecEnd);
853
1135
  this._targetRef.removeEventListener("daw-recording-error", this._onRecEnd);
1136
+ this._targetRef.removeEventListener("daw-recording-pause", this._onRecPause);
1137
+ this._targetRef.removeEventListener("daw-recording-resume", this._onRecResume);
854
1138
  this._targetRef = null;
855
1139
  }
856
1140
  }
857
1141
  render() {
858
- return import_lit8.html`
1142
+ return import_lit9.html`
859
1143
  <button part="button" ?data-paused=${this._isPaused} @click=${this._onClick}>
860
1144
  <slot>Pause</slot>
861
1145
  </button>
@@ -870,15 +1154,7 @@ var DawPauseButtonElement = class extends DawTransportButton {
870
1154
  return;
871
1155
  }
872
1156
  if (this._isRecording) {
873
- if (this._isPaused) {
874
- target.resumeRecording();
875
- target.play(target.currentTime);
876
- this._isPaused = false;
877
- } else {
878
- target.pauseRecording();
879
- target.pause();
880
- this._isPaused = true;
881
- }
1157
+ target.togglePauseRecording();
882
1158
  } else {
883
1159
  target.pause();
884
1160
  }
@@ -886,7 +1162,7 @@ var DawPauseButtonElement = class extends DawTransportButton {
886
1162
  };
887
1163
  DawPauseButtonElement.styles = [
888
1164
  DawTransportButton.styles,
889
- import_lit8.css`
1165
+ import_lit9.css`
890
1166
  button[data-paused] {
891
1167
  background: rgba(255, 255, 255, 0.1);
892
1168
  border-color: var(--daw-controls-text, #e0d4c8);
@@ -894,21 +1170,21 @@ DawPauseButtonElement.styles = [
894
1170
  `
895
1171
  ];
896
1172
  __decorateClass([
897
- (0, import_decorators7.state)()
1173
+ (0, import_decorators8.state)()
898
1174
  ], DawPauseButtonElement.prototype, "_isPaused", 2);
899
1175
  __decorateClass([
900
- (0, import_decorators7.state)()
1176
+ (0, import_decorators8.state)()
901
1177
  ], DawPauseButtonElement.prototype, "_isRecording", 2);
902
1178
  DawPauseButtonElement = __decorateClass([
903
- (0, import_decorators7.customElement)("daw-pause-button")
1179
+ (0, import_decorators8.customElement)("daw-pause-button")
904
1180
  ], DawPauseButtonElement);
905
1181
 
906
1182
  // src/elements/daw-stop-button.ts
907
- var import_lit9 = require("lit");
908
- var import_decorators8 = require("lit/decorators.js");
1183
+ var import_lit10 = require("lit");
1184
+ var import_decorators9 = require("lit/decorators.js");
909
1185
  var DawStopButtonElement = class extends DawTransportButton {
910
1186
  render() {
911
- return import_lit9.html`
1187
+ return import_lit10.html`
912
1188
  <button part="button" @click=${this._onClick}>
913
1189
  <slot>Stop</slot>
914
1190
  </button>
@@ -923,18 +1199,31 @@ var DawStopButtonElement = class extends DawTransportButton {
923
1199
  return;
924
1200
  }
925
1201
  if (target.isRecording) {
926
- target.stopRecording();
1202
+ target.stopRecording().catch((err) => {
1203
+ console.warn("[dawcore] stopRecording failed: " + String(err));
1204
+ }).then(() => {
1205
+ try {
1206
+ target.stop();
1207
+ } catch (err) {
1208
+ console.warn("[dawcore] stop after stopRecording failed: " + String(err));
1209
+ }
1210
+ });
1211
+ } else {
1212
+ try {
1213
+ target.stop();
1214
+ } catch (err) {
1215
+ console.warn("[dawcore] stop failed: " + String(err));
1216
+ }
927
1217
  }
928
- target.stop();
929
1218
  }
930
1219
  };
931
1220
  DawStopButtonElement = __decorateClass([
932
- (0, import_decorators8.customElement)("daw-stop-button")
1221
+ (0, import_decorators9.customElement)("daw-stop-button")
933
1222
  ], DawStopButtonElement);
934
1223
 
935
1224
  // src/elements/daw-editor.ts
936
- var import_lit13 = require("lit");
937
- var import_decorators11 = require("lit/decorators.js");
1225
+ var import_lit14 = require("lit");
1226
+ var import_decorators12 = require("lit/decorators.js");
938
1227
 
939
1228
  // src/types.ts
940
1229
  function isDomClip(desc) {
@@ -1414,9 +1703,9 @@ var PeakPipeline = class {
1414
1703
  };
1415
1704
 
1416
1705
  // src/elements/daw-track-controls.ts
1417
- var import_lit10 = require("lit");
1418
- var import_decorators9 = require("lit/decorators.js");
1419
- var DawTrackControlsElement = class extends import_lit10.LitElement {
1706
+ var import_lit11 = require("lit");
1707
+ var import_decorators10 = require("lit/decorators.js");
1708
+ var DawTrackControlsElement = class extends import_lit11.LitElement {
1420
1709
  constructor() {
1421
1710
  super(...arguments);
1422
1711
  this.trackId = null;
@@ -1464,7 +1753,7 @@ var DawTrackControlsElement = class extends import_lit10.LitElement {
1464
1753
  const volPercent = Math.round(this.volume * 100);
1465
1754
  const panPercent = Math.round(Math.abs(this.pan) * 100);
1466
1755
  const panDisplay = this.pan === 0 ? "C" : (this.pan > 0 ? "R" : "L") + panPercent;
1467
- return import_lit10.html`
1756
+ return import_lit11.html`
1468
1757
  <div class="header">
1469
1758
  <span class="name" title=${this.trackName}>${this.trackName || "Untitled"}</span>
1470
1759
  <button class="remove-btn" @click=${this._onRemoveClick} title="Remove track">
@@ -1514,7 +1803,7 @@ var DawTrackControlsElement = class extends import_lit10.LitElement {
1514
1803
  `;
1515
1804
  }
1516
1805
  };
1517
- DawTrackControlsElement.styles = import_lit10.css`
1806
+ DawTrackControlsElement.styles = import_lit11.css`
1518
1807
  :host {
1519
1808
  display: flex;
1520
1809
  flex-direction: column;
@@ -1648,30 +1937,30 @@ DawTrackControlsElement.styles = import_lit10.css`
1648
1937
  }
1649
1938
  `;
1650
1939
  __decorateClass([
1651
- (0, import_decorators9.property)({ attribute: false })
1940
+ (0, import_decorators10.property)({ attribute: false })
1652
1941
  ], DawTrackControlsElement.prototype, "trackId", 2);
1653
1942
  __decorateClass([
1654
- (0, import_decorators9.property)({ attribute: false })
1943
+ (0, import_decorators10.property)({ attribute: false })
1655
1944
  ], DawTrackControlsElement.prototype, "trackName", 2);
1656
1945
  __decorateClass([
1657
- (0, import_decorators9.property)({ type: Number, attribute: false })
1946
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1658
1947
  ], DawTrackControlsElement.prototype, "volume", 2);
1659
1948
  __decorateClass([
1660
- (0, import_decorators9.property)({ type: Number, attribute: false })
1949
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1661
1950
  ], DawTrackControlsElement.prototype, "pan", 2);
1662
1951
  __decorateClass([
1663
- (0, import_decorators9.property)({ type: Boolean, attribute: false })
1952
+ (0, import_decorators10.property)({ type: Boolean, attribute: false })
1664
1953
  ], DawTrackControlsElement.prototype, "muted", 2);
1665
1954
  __decorateClass([
1666
- (0, import_decorators9.property)({ type: Boolean, attribute: false })
1955
+ (0, import_decorators10.property)({ type: Boolean, attribute: false })
1667
1956
  ], DawTrackControlsElement.prototype, "soloed", 2);
1668
1957
  DawTrackControlsElement = __decorateClass([
1669
- (0, import_decorators9.customElement)("daw-track-controls")
1958
+ (0, import_decorators10.customElement)("daw-track-controls")
1670
1959
  ], DawTrackControlsElement);
1671
1960
 
1672
1961
  // src/elements/daw-grid.ts
1673
- var import_lit11 = require("lit");
1674
- var import_decorators10 = require("lit/decorators.js");
1962
+ var import_lit12 = require("lit");
1963
+ var import_decorators11 = require("lit/decorators.js");
1675
1964
  var import_core2 = require("@waveform-playlist/core");
1676
1965
 
1677
1966
  // src/utils/musical-tick-cache.ts
@@ -1702,8 +1991,8 @@ function getCachedMusicalTicks(params) {
1702
1991
  }
1703
1992
 
1704
1993
  // src/elements/daw-grid.ts
1705
- var MAX_CANVAS_WIDTH2 = 1e3;
1706
- var DawGridElement = class extends import_lit11.LitElement {
1994
+ var MAX_CANVAS_WIDTH3 = 1e3;
1995
+ var DawGridElement = class extends import_lit12.LitElement {
1707
1996
  constructor() {
1708
1997
  super(...arguments);
1709
1998
  this.ticksPerPixel = 24;
@@ -1731,25 +2020,25 @@ var DawGridElement = class extends import_lit11.LitElement {
1731
2020
  }
1732
2021
  }
1733
2022
  render() {
1734
- if (!this._tickData) return import_lit11.html``;
2023
+ if (!this._tickData) return import_lit12.html``;
1735
2024
  const totalWidth = this.length;
1736
2025
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1737
2026
  const indices = getVisibleChunkIndices(
1738
2027
  totalWidth,
1739
- MAX_CANVAS_WIDTH2,
2028
+ MAX_CANVAS_WIDTH3,
1740
2029
  this.visibleStart,
1741
2030
  this.visibleEnd
1742
2031
  );
1743
- return import_lit11.html`
2032
+ return import_lit12.html`
1744
2033
  <div class="container" style="width: ${totalWidth}px; height: ${this.height}px;">
1745
2034
  ${indices.map((i) => {
1746
- const width = Math.min(MAX_CANVAS_WIDTH2, totalWidth - i * MAX_CANVAS_WIDTH2);
1747
- return import_lit11.html`
2035
+ const width = Math.min(MAX_CANVAS_WIDTH3, totalWidth - i * MAX_CANVAS_WIDTH3);
2036
+ return import_lit12.html`
1748
2037
  <canvas
1749
2038
  data-index=${i}
1750
2039
  width=${width * dpr}
1751
2040
  height=${this.height * dpr}
1752
- style="left: ${i * MAX_CANVAS_WIDTH2}px; width: ${width}px; height: ${this.height}px;"
2041
+ style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.height}px;"
1753
2042
  ></canvas>
1754
2043
  `;
1755
2044
  })}
@@ -1773,8 +2062,8 @@ var DawGridElement = class extends import_lit11.LitElement {
1773
2062
  const idx = Number(canvas.dataset.index);
1774
2063
  const ctx = canvas.getContext("2d");
1775
2064
  if (!ctx) continue;
1776
- const chunkLeft = idx * MAX_CANVAS_WIDTH2;
1777
- const canvasWidth = Math.min(MAX_CANVAS_WIDTH2, this.length - chunkLeft);
2065
+ const chunkLeft = idx * MAX_CANVAS_WIDTH3;
2066
+ const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, this.length - chunkLeft);
1778
2067
  ctx.resetTransform();
1779
2068
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1780
2069
  ctx.scale(dpr, dpr);
@@ -1805,7 +2094,7 @@ var DawGridElement = class extends import_lit11.LitElement {
1805
2094
  }
1806
2095
  }
1807
2096
  };
1808
- DawGridElement.styles = import_lit11.css`
2097
+ DawGridElement.styles = import_lit12.css`
1809
2098
  :host {
1810
2099
  display: block;
1811
2100
  position: absolute;
@@ -1823,33 +2112,33 @@ DawGridElement.styles = import_lit11.css`
1823
2112
  }
1824
2113
  `;
1825
2114
  __decorateClass([
1826
- (0, import_decorators10.property)({ type: Number, attribute: false })
2115
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1827
2116
  ], DawGridElement.prototype, "ticksPerPixel", 2);
1828
2117
  __decorateClass([
1829
- (0, import_decorators10.property)({ attribute: false })
2118
+ (0, import_decorators11.property)({ attribute: false })
1830
2119
  ], DawGridElement.prototype, "meterEntries", 2);
1831
2120
  __decorateClass([
1832
- (0, import_decorators10.property)({ type: Number, attribute: false })
2121
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1833
2122
  ], DawGridElement.prototype, "ppqn", 2);
1834
2123
  __decorateClass([
1835
- (0, import_decorators10.property)({ type: Number, attribute: false })
2124
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1836
2125
  ], DawGridElement.prototype, "visibleStart", 2);
1837
2126
  __decorateClass([
1838
- (0, import_decorators10.property)({ type: Number, attribute: false })
2127
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1839
2128
  ], DawGridElement.prototype, "visibleEnd", 2);
1840
2129
  __decorateClass([
1841
- (0, import_decorators10.property)({ type: Number, attribute: false })
2130
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1842
2131
  ], DawGridElement.prototype, "length", 2);
1843
2132
  __decorateClass([
1844
- (0, import_decorators10.property)({ type: Number, attribute: false })
2133
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1845
2134
  ], DawGridElement.prototype, "height", 2);
1846
2135
  DawGridElement = __decorateClass([
1847
- (0, import_decorators10.customElement)("daw-grid")
2136
+ (0, import_decorators11.customElement)("daw-grid")
1848
2137
  ], DawGridElement);
1849
2138
 
1850
2139
  // src/styles/theme.ts
1851
- var import_lit12 = require("lit");
1852
- var hostStyles = import_lit12.css`
2140
+ var import_lit13 = require("lit");
2141
+ var hostStyles = import_lit13.css`
1853
2142
  :host {
1854
2143
  --daw-wave-color: #c49a6c;
1855
2144
  --daw-progress-color: #63c75f;
@@ -1865,7 +2154,7 @@ var hostStyles = import_lit12.css`
1865
2154
  --daw-clip-header-text: #e0d4c8;
1866
2155
  }
1867
2156
  `;
1868
- var clipStyles = import_lit12.css`
2157
+ var clipStyles = import_lit13.css`
1869
2158
  .clip-container {
1870
2159
  position: absolute;
1871
2160
  overflow: hidden;
@@ -2110,6 +2399,9 @@ var RecordingController = class {
2110
2399
  constructor(host) {
2111
2400
  this._sessions = /* @__PURE__ */ new Map();
2112
2401
  this._workletLoadedCtx = null;
2402
+ /** Tracks worklet pause state explicitly so external consumers (editor,
2403
+ * pause button, spacebar) can share one source of truth. */
2404
+ this._isPaused = false;
2113
2405
  this._host = host;
2114
2406
  host.addController(this);
2115
2407
  }
@@ -2124,6 +2416,9 @@ var RecordingController = class {
2124
2416
  get isRecording() {
2125
2417
  return this._sessions.size > 0;
2126
2418
  }
2419
+ get isPaused() {
2420
+ return this._isPaused && this._sessions.size > 0;
2421
+ }
2127
2422
  getSession(trackId) {
2128
2423
  return this._sessions.get(trackId);
2129
2424
  }
@@ -2196,7 +2491,9 @@ var RecordingController = class {
2196
2491
  latencySamples,
2197
2492
  wasOverdub: options.overdub ?? false,
2198
2493
  _onTrackEnded: onTrackEnded,
2199
- _audioTrack: audioTrack
2494
+ _audioTrack: audioTrack,
2495
+ stopAckResolve: null,
2496
+ stopping: false
2200
2497
  };
2201
2498
  this._sessions.set(trackId, session);
2202
2499
  workletNode.port.onmessage = (e) => {
@@ -2234,25 +2531,69 @@ var RecordingController = class {
2234
2531
  const id = trackId ?? [...this._sessions.keys()][0];
2235
2532
  if (!id) return;
2236
2533
  const session = this._sessions.get(id);
2237
- if (!session) return;
2534
+ if (!session || session.stopping) return;
2238
2535
  session.workletNode.port.postMessage({ command: "pause" });
2536
+ this._isPaused = true;
2537
+ this._host.dispatchEvent(
2538
+ new CustomEvent("daw-recording-pause", {
2539
+ bubbles: true,
2540
+ composed: true,
2541
+ detail: { trackId: id }
2542
+ })
2543
+ );
2239
2544
  }
2240
2545
  resumeRecording(trackId) {
2241
2546
  const id = trackId ?? [...this._sessions.keys()][0];
2242
2547
  if (!id) return;
2243
2548
  const session = this._sessions.get(id);
2244
- if (!session) return;
2549
+ if (!session || session.stopping) return;
2245
2550
  session.workletNode.port.postMessage({ command: "resume" });
2551
+ this._isPaused = false;
2552
+ this._host.dispatchEvent(
2553
+ new CustomEvent("daw-recording-resume", {
2554
+ bubbles: true,
2555
+ composed: true,
2556
+ detail: { trackId: id }
2557
+ })
2558
+ );
2246
2559
  }
2247
- stopRecording(trackId) {
2560
+ async stopRecording(trackId) {
2248
2561
  const id = trackId ?? [...this._sessions.keys()][0];
2249
2562
  if (!id) return;
2250
2563
  const session = this._sessions.get(id);
2251
2564
  if (!session) return;
2565
+ const wasPaused = this._isPaused;
2566
+ this._isPaused = false;
2567
+ session.stopping = true;
2252
2568
  if (session.wasOverdub && typeof this._host.stop === "function") {
2253
2569
  this._host.stop();
2254
2570
  }
2255
- session.workletNode.port.postMessage({ command: "stop" });
2571
+ if (wasPaused) {
2572
+ session.workletNode.port.postMessage({ command: "stop" });
2573
+ } else {
2574
+ const stopAck = new Promise((resolve) => {
2575
+ session.stopAckResolve = resolve;
2576
+ });
2577
+ let timeoutId;
2578
+ const timeout = new Promise((resolve) => {
2579
+ timeoutId = setTimeout(resolve, 1e3);
2580
+ });
2581
+ session.workletNode.port.postMessage({ command: "stop" });
2582
+ await Promise.race([stopAck, timeout]);
2583
+ clearTimeout(timeoutId);
2584
+ session.stopAckResolve = null;
2585
+ let lastSamples = -1;
2586
+ let stable = 0;
2587
+ for (let i = 0; i < 50; i++) {
2588
+ if (session.totalSamples === lastSamples) {
2589
+ if (++stable >= 3) break;
2590
+ } else {
2591
+ stable = 0;
2592
+ lastSamples = session.totalSamples;
2593
+ }
2594
+ await new Promise((r) => setTimeout(r, 5));
2595
+ }
2596
+ }
2256
2597
  session.source.disconnect();
2257
2598
  session.workletNode.disconnect();
2258
2599
  this._removeTrackEndedListener(session);
@@ -2324,8 +2665,20 @@ var RecordingController = class {
2324
2665
  _onWorkletMessage(trackId, data) {
2325
2666
  const session = this._sessions.get(trackId);
2326
2667
  if (!session) return;
2327
- const { channels } = data;
2328
- if (!channels || channels.length === 0 || !channels[0]) return;
2668
+ const { channels, done } = data;
2669
+ try {
2670
+ const hasSamples = !!(channels && channels.length > 0 && channels[0] && channels[0].length > 0);
2671
+ if (!hasSamples) return;
2672
+ this._processWorkletSamples(trackId, session, channels);
2673
+ } finally {
2674
+ if (done && session.stopAckResolve) {
2675
+ const resolve = session.stopAckResolve;
2676
+ session.stopAckResolve = null;
2677
+ resolve();
2678
+ }
2679
+ }
2680
+ }
2681
+ _processWorkletSamples(trackId, session, channels) {
2329
2682
  const samplesProcessedBefore = session.totalSamples;
2330
2683
  for (let ch = 0; ch < session.channelCount; ch++) {
2331
2684
  if (channels[ch]) {
@@ -2333,6 +2686,7 @@ var RecordingController = class {
2333
2686
  }
2334
2687
  }
2335
2688
  session.totalSamples += channels[0].length;
2689
+ if (session.stopAckResolve !== null) return;
2336
2690
  for (let ch = 0; ch < session.channelCount; ch++) {
2337
2691
  if (!channels[ch]) continue;
2338
2692
  const oldPeakCount = Math.floor(session.peaks[ch].length / 2);
@@ -2667,6 +3021,7 @@ var ClipPointerHandler = class {
2667
3021
  const trackId = boundary.dataset.trackId;
2668
3022
  const edge = boundary.dataset.boundaryEdge;
2669
3023
  if (!clipId || !trackId || edge !== "left" && edge !== "right") return false;
3024
+ if (this._host.isMidiClip(trackId, clipId)) return true;
2670
3025
  this._beginDrag(edge === "left" ? "trim-left" : "trim-right", clipId, trackId, e);
2671
3026
  this._boundaryEl = boundary;
2672
3027
  return true;
@@ -2960,6 +3315,7 @@ async function loadFiles(host, files) {
2960
3315
  pan: 0,
2961
3316
  muted: false,
2962
3317
  soloed: false,
3318
+ renderMode: "waveform",
2963
3319
  clips: [
2964
3320
  {
2965
3321
  kind: "drop",
@@ -2972,7 +3328,10 @@ async function loadFiles(host, files) {
2972
3328
  name,
2973
3329
  fadeIn: 0,
2974
3330
  fadeOut: 0,
2975
- fadeType: "linear"
3331
+ fadeType: "linear",
3332
+ midiNotes: null,
3333
+ midiChannel: null,
3334
+ midiProgram: null
2976
3335
  }
2977
3336
  ]
2978
3337
  });
@@ -3059,7 +3418,10 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamp
3059
3418
  name: "Recording",
3060
3419
  fadeIn: 0,
3061
3420
  fadeOut: 0,
3062
- fadeType: "linear"
3421
+ fadeType: "linear",
3422
+ midiNotes: null,
3423
+ midiChannel: null,
3424
+ midiProgram: null
3063
3425
  };
3064
3426
  host._tracks = new Map(host._tracks).set(trackId, {
3065
3427
  ...desc,
@@ -3118,7 +3480,10 @@ function canSplitAtTime(host, time) {
3118
3480
  const track = state5.tracks.find((t) => t.id === state5.selectedTrackId);
3119
3481
  if (!track) return false;
3120
3482
  const atSample = Math.round(time * host.effectiveSampleRate);
3121
- return !!findClipAtSample(track.clips, atSample);
3483
+ const clip = findClipAtSample(track.clips, atSample);
3484
+ if (!clip) return false;
3485
+ if (clip.midiNotes != null) return false;
3486
+ return true;
3122
3487
  }
3123
3488
  function performSplit(host, time) {
3124
3489
  const { engine } = host;
@@ -3273,7 +3638,7 @@ async function loadWaveformDataFromUrl(src) {
3273
3638
 
3274
3639
  // src/elements/daw-editor.ts
3275
3640
  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();";
3276
- var DawEditorElement = class extends import_lit13.LitElement {
3641
+ var DawEditorElement = class extends import_lit14.LitElement {
3277
3642
  constructor() {
3278
3643
  super(...arguments);
3279
3644
  this._samplesPerPixel = 1024;
@@ -3432,7 +3797,10 @@ var DawEditorElement = class extends import_lit13.LitElement {
3432
3797
  name: clipEl.name,
3433
3798
  fadeIn: clipEl.fadeIn,
3434
3799
  fadeOut: clipEl.fadeOut,
3435
- fadeType: clipEl.fadeType
3800
+ fadeType: clipEl.fadeType,
3801
+ midiNotes: clipEl.midiNotes,
3802
+ midiChannel: clipEl.midiChannel,
3803
+ midiProgram: clipEl.midiProgram
3436
3804
  };
3437
3805
  this._loadAndAppendClip(trackId, clipDesc);
3438
3806
  };
@@ -3483,6 +3851,9 @@ var DawEditorElement = class extends import_lit13.LitElement {
3483
3851
  };
3484
3852
  // --- Recording ---
3485
3853
  this.recordingStream = null;
3854
+ /** Set in togglePauseRecording when Transport is paused alongside the
3855
+ * worklet, so resume can restart it. Cleared on resume and on stop. */
3856
+ this._wasPlayingDuringRecording = false;
3486
3857
  }
3487
3858
  get samplesPerPixel() {
3488
3859
  return this._samplesPerPixel;
@@ -3582,6 +3953,17 @@ var DawEditorElement = class extends import_lit13.LitElement {
3582
3953
  );
3583
3954
  return result.get(clipId) ?? null;
3584
3955
  }
3956
+ /**
3957
+ * Returns true if the clip is a MIDI clip (has midiNotes).
3958
+ * Used by ClipPointerHandler to make trim handles inert for MIDI clips.
3959
+ * Returns false for unknown track/clip IDs (defensive).
3960
+ */
3961
+ isMidiClip(trackId, clipId) {
3962
+ const track = this._engineTracks.get(trackId);
3963
+ if (!track) return false;
3964
+ const clip = track.clips.find((c) => c.id === clipId);
3965
+ return clip?.midiNotes != null;
3966
+ }
3585
3967
  get effectiveSampleRate() {
3586
3968
  return this._resolvedSampleRate ?? this.sampleRate;
3587
3969
  }
@@ -3931,6 +4313,65 @@ var DawEditorElement = class extends import_lit13.LitElement {
3931
4313
  }
3932
4314
  return clip;
3933
4315
  }
4316
+ /**
4317
+ * Filter MIDI notes to only those with finite, in-range fields. Logs a
4318
+ * warning for each dropped note. Used by _buildMidiClip and the
4319
+ * _applyClipUpdate MIDI branch to prevent NaN propagation through the
4320
+ * timeline.
4321
+ */
4322
+ _validMidiNotes(notes) {
4323
+ return notes.filter((n) => {
4324
+ 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;
4325
+ if (!ok) {
4326
+ console.warn("[dawcore] dropping malformed MIDI note: " + JSON.stringify(n));
4327
+ }
4328
+ return ok;
4329
+ });
4330
+ }
4331
+ /**
4332
+ * A clip descriptor is treated as MIDI when it has no audio src.
4333
+ * Includes placeholder MIDI clips (no notes, no duration yet — registered
4334
+ * with a 1s default span; notes upgrade via _applyClipUpdate). Warns when
4335
+ * a clip ambiguously has both src and midiNotes — the audio path runs
4336
+ * and notes would be silently ignored.
4337
+ */
4338
+ _isMidiDescriptor(clipDesc) {
4339
+ if (clipDesc.src) {
4340
+ if (clipDesc.midiNotes != null) {
4341
+ console.warn(
4342
+ '[dawcore] clip "' + (clipDesc.name || (isDomClip(clipDesc) ? clipDesc.clipId : "?")) + '" has both src and midiNotes \u2014 treating as audio (notes will be ignored)'
4343
+ );
4344
+ }
4345
+ return false;
4346
+ }
4347
+ return true;
4348
+ }
4349
+ /**
4350
+ * Build an engine clip from a MIDI clip descriptor. Always returns a clip
4351
+ * — empty notes / no declared duration get a 1-second placeholder span so
4352
+ * the clip is reachable via `engine.updateTrack` once notes arrive.
4353
+ */
4354
+ _buildMidiClip(clipDesc) {
4355
+ const sr = this.effectiveSampleRate;
4356
+ const notes = this._validMidiNotes(clipDesc.midiNotes ?? []);
4357
+ const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
4358
+ const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipDesc.duration, 1) * sr);
4359
+ const requestedDurationSamples = clipDesc.duration > 0 ? Math.round(clipDesc.duration * sr) : sourceDurationSamples;
4360
+ const clip = (0, import_core8.createClip)({
4361
+ startSample: Math.round(clipDesc.start * sr),
4362
+ durationSamples: requestedDurationSamples,
4363
+ offsetSamples: Math.round(clipDesc.offset * sr),
4364
+ sampleRate: sr,
4365
+ sourceDurationSamples,
4366
+ gain: clipDesc.gain,
4367
+ name: clipDesc.name,
4368
+ midiNotes: notes,
4369
+ midiChannel: clipDesc.midiChannel ?? void 0,
4370
+ midiProgram: clipDesc.midiProgram ?? void 0
4371
+ });
4372
+ if (isDomClip(clipDesc)) clip.id = clipDesc.clipId;
4373
+ return clip;
4374
+ }
3934
4375
  /** Remove a single clip from all per-clip caches. Used by error rollbacks. */
3935
4376
  _purgeClipCaches(clipId) {
3936
4377
  const nextBuffers = new Map(this._clipBuffers);
@@ -3968,6 +4409,34 @@ var DawEditorElement = class extends import_lit13.LitElement {
3968
4409
  }
3969
4410
  const oldClip = t.clips[idx];
3970
4411
  const sr = oldClip.sampleRate ?? this.effectiveSampleRate;
4412
+ const isMidiNow = clipEl.midiNotes != null;
4413
+ const wasMidi = oldClip.midiNotes != null;
4414
+ if (isMidiNow || wasMidi) {
4415
+ const notes = this._validMidiNotes(clipEl.midiNotes ?? []);
4416
+ const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
4417
+ const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipEl.duration, 1) * sr);
4418
+ const requestedDurationSamples = clipEl.duration > 0 ? Math.round(clipEl.duration * sr) : sourceDurationSamples;
4419
+ const updatedClip2 = {
4420
+ ...oldClip,
4421
+ audioBuffer: void 0,
4422
+ startSample: Math.round(clipEl.start * sr),
4423
+ offsetSamples: Math.round(clipEl.offset * sr),
4424
+ durationSamples: requestedDurationSamples,
4425
+ sourceDurationSamples,
4426
+ gain: clipEl.gain,
4427
+ name: clipEl.name || oldClip.name,
4428
+ midiNotes: notes,
4429
+ midiChannel: clipEl.midiChannel ?? void 0,
4430
+ midiProgram: clipEl.midiProgram ?? void 0
4431
+ };
4432
+ const updatedClips2 = [...t.clips];
4433
+ updatedClips2[idx] = updatedClip2;
4434
+ const updatedTrack2 = { ...t, clips: updatedClips2 };
4435
+ this._engineTracks = new Map(this._engineTracks).set(trackId, updatedTrack2);
4436
+ this._purgeClipCaches(clipId);
4437
+ this._commitTrackChange(trackId, updatedTrack2);
4438
+ return;
4439
+ }
3971
4440
  const newStartSample = Math.round(clipEl.start * sr);
3972
4441
  const newDurationSamples = clipEl.duration > 0 ? Math.round(clipEl.duration * sr) : oldClip.durationSamples;
3973
4442
  const newOffsetSamples = Math.round(clipEl.offset * sr);
@@ -4044,7 +4513,10 @@ var DawEditorElement = class extends import_lit13.LitElement {
4044
4513
  name: trackEl.name || "",
4045
4514
  fadeIn: 0,
4046
4515
  fadeOut: 0,
4047
- fadeType: "linear"
4516
+ fadeType: "linear",
4517
+ midiNotes: null,
4518
+ midiChannel: null,
4519
+ midiProgram: null
4048
4520
  });
4049
4521
  } else {
4050
4522
  for (const clipEl of clipEls) {
@@ -4060,7 +4532,10 @@ var DawEditorElement = class extends import_lit13.LitElement {
4060
4532
  name: clipEl.name,
4061
4533
  fadeIn: clipEl.fadeIn,
4062
4534
  fadeOut: clipEl.fadeOut,
4063
- fadeType: clipEl.fadeType
4535
+ fadeType: clipEl.fadeType,
4536
+ midiNotes: clipEl.midiNotes,
4537
+ midiChannel: clipEl.midiChannel,
4538
+ midiProgram: clipEl.midiProgram
4064
4539
  });
4065
4540
  }
4066
4541
  }
@@ -4071,6 +4546,7 @@ var DawEditorElement = class extends import_lit13.LitElement {
4071
4546
  pan: trackEl.pan,
4072
4547
  muted: trackEl.muted,
4073
4548
  soloed: trackEl.soloed,
4549
+ renderMode: trackEl.renderMode,
4074
4550
  clips
4075
4551
  };
4076
4552
  }
@@ -4079,7 +4555,10 @@ var DawEditorElement = class extends import_lit13.LitElement {
4079
4555
  try {
4080
4556
  const clips = [];
4081
4557
  for (const clipDesc of descriptor.clips) {
4082
- if (!clipDesc.src) continue;
4558
+ if (this._isMidiDescriptor(clipDesc)) {
4559
+ clips.push(this._buildMidiClip(clipDesc));
4560
+ continue;
4561
+ }
4083
4562
  try {
4084
4563
  const waveformDataPromise = clipDesc.peaksSrc ? this._resolvePeaks(clipDesc.peaksSrc) : Promise.resolve(null);
4085
4564
  const audioPromise = this._fetchAndDecode(clipDesc.src);
@@ -4291,7 +4770,11 @@ var DawEditorElement = class extends import_lit13.LitElement {
4291
4770
  nextTracks.set(track.id, track);
4292
4771
  }
4293
4772
  this._engineTracks = nextTracks;
4294
- syncPeaksForChangedClips(this, engineState.tracks);
4773
+ const audioTracks = engineState.tracks.filter((t) => {
4774
+ const desc = this._tracks.get(t.id);
4775
+ return desc?.renderMode !== "piano-roll";
4776
+ });
4777
+ syncPeaksForChangedClips(this, audioTracks);
4295
4778
  }
4296
4779
  });
4297
4780
  engine.on("pause", () => {
@@ -4366,7 +4849,17 @@ var DawEditorElement = class extends import_lit13.LitElement {
4366
4849
  if (config.pan !== void 0) trackEl.pan = config.pan;
4367
4850
  if (config.muted) trackEl.setAttribute("muted", "");
4368
4851
  if (config.soloed) trackEl.setAttribute("soloed", "");
4369
- for (const clipConfig of config.clips ?? []) {
4852
+ const renderMode = config.renderMode ?? (config.midi ? "piano-roll" : void 0);
4853
+ if (renderMode !== void 0) trackEl.setAttribute("render-mode", renderMode);
4854
+ const clipConfigs = [...config.clips ?? []];
4855
+ if (config.midi) {
4856
+ clipConfigs.push({
4857
+ midiNotes: config.midi.notes,
4858
+ midiChannel: config.midi.channel,
4859
+ midiProgram: config.midi.program
4860
+ });
4861
+ }
4862
+ for (const clipConfig of clipConfigs) {
4370
4863
  trackEl.appendChild(this._buildClipElement(clipConfig));
4371
4864
  }
4372
4865
  return this._awaitId(
@@ -4412,6 +4905,7 @@ var DawEditorElement = class extends import_lit13.LitElement {
4412
4905
  if (partial.soloed) trackEl.setAttribute("soloed", "");
4413
4906
  else trackEl.removeAttribute("soloed");
4414
4907
  }
4908
+ if (partial.renderMode !== void 0) trackEl.setAttribute("render-mode", partial.renderMode);
4415
4909
  return;
4416
4910
  }
4417
4911
  const oldDesc = this._tracks.get(trackId);
@@ -4422,7 +4916,8 @@ var DawEditorElement = class extends import_lit13.LitElement {
4422
4916
  ...partial.volume !== void 0 && { volume: partial.volume },
4423
4917
  ...partial.pan !== void 0 && { pan: partial.pan },
4424
4918
  ...partial.muted !== void 0 && { muted: partial.muted },
4425
- ...partial.soloed !== void 0 && { soloed: partial.soloed }
4919
+ ...partial.soloed !== void 0 && { soloed: partial.soloed },
4920
+ ...partial.renderMode !== void 0 && { renderMode: partial.renderMode }
4426
4921
  };
4427
4922
  this._tracks = new Map(this._tracks).set(trackId, newDesc);
4428
4923
  if (this._engine) {
@@ -4557,6 +5052,11 @@ var DawEditorElement = class extends import_lit13.LitElement {
4557
5052
  if (config.fadeIn !== void 0) clipEl.fadeIn = config.fadeIn;
4558
5053
  if (config.fadeOut !== void 0) clipEl.fadeOut = config.fadeOut;
4559
5054
  if (config.fadeType !== void 0) clipEl.setAttribute("fade-type", config.fadeType);
5055
+ if (config.midiNotes !== void 0) clipEl.midiNotes = config.midiNotes;
5056
+ if (config.midiChannel !== void 0)
5057
+ clipEl.setAttribute("midi-channel", String(config.midiChannel));
5058
+ if (config.midiProgram !== void 0)
5059
+ clipEl.setAttribute("midi-program", String(config.midiProgram));
4560
5060
  return clipEl;
4561
5061
  }
4562
5062
  // --- Playback ---
@@ -4592,7 +5092,9 @@ var DawEditorElement = class extends import_lit13.LitElement {
4592
5092
  }
4593
5093
  /** Toggle between play and pause. */
4594
5094
  togglePlayPause() {
4595
- if (this._isPlaying) {
5095
+ if (this.isRecording) {
5096
+ this.togglePauseRecording();
5097
+ } else if (this._isPlaying) {
4596
5098
  this.pause();
4597
5099
  } else {
4598
5100
  this.play();
@@ -4666,14 +5168,41 @@ var DawEditorElement = class extends import_lit13.LitElement {
4666
5168
  get isRecording() {
4667
5169
  return this._recordingController.isRecording;
4668
5170
  }
5171
+ get isRecordingPaused() {
5172
+ return this._recordingController.isPaused;
5173
+ }
4669
5174
  pauseRecording() {
4670
5175
  this._recordingController.pauseRecording();
4671
5176
  }
4672
5177
  resumeRecording() {
4673
5178
  this._recordingController.resumeRecording();
5179
+ this._wasPlayingDuringRecording = false;
5180
+ }
5181
+ /**
5182
+ * Audacity-style pause toggle for active recordings: pauses both the
5183
+ * worklet capture and (if running) the playback Transport. On resume,
5184
+ * Transport restarts only if it was running before — non-overdub
5185
+ * recordings stay silent on resume.
5186
+ */
5187
+ togglePauseRecording() {
5188
+ if (!this.isRecording) return;
5189
+ if (this.isRecordingPaused) {
5190
+ const wasPlaying = this._wasPlayingDuringRecording;
5191
+ this.resumeRecording();
5192
+ if (wasPlaying) {
5193
+ void this.play(this.currentTime);
5194
+ }
5195
+ } else {
5196
+ this.pauseRecording();
5197
+ if (this._isPlaying) {
5198
+ this._wasPlayingDuringRecording = true;
5199
+ this.pause();
5200
+ }
5201
+ }
4674
5202
  }
4675
5203
  stopRecording() {
4676
- this._recordingController.stopRecording();
5204
+ this._wasPlayingDuringRecording = false;
5205
+ return this._recordingController.stopRecording();
4677
5206
  }
4678
5207
  _addRecordedClip(trackId, buf, startSample, durSamples, offsetSamples = 0) {
4679
5208
  addRecordedClip(this, trackId, buf, startSample, durSamples, offsetSamples);
@@ -4709,7 +5238,7 @@ var DawEditorElement = class extends import_lit13.LitElement {
4709
5238
  const w = Math.floor(audibleSamples / renderSpp);
4710
5239
  return rs.peaks.map((chPeaks, ch) => {
4711
5240
  const slicedPeaks = latencyPixels > 0 ? chPeaks.slice(latencyPixels * 2) : chPeaks;
4712
- return import_lit13.html`
5241
+ return import_lit14.html`
4713
5242
  <daw-waveform
4714
5243
  data-recording-track=${trackId}
4715
5244
  data-recording-channel=${ch}
@@ -4806,11 +5335,11 @@ var DawEditorElement = class extends import_lit13.LitElement {
4806
5335
  trackHeight: this.waveHeight * numChannels + (this.clipHeaders ? this.clipHeaderHeight : 0)
4807
5336
  };
4808
5337
  });
4809
- return import_lit13.html`
4810
- ${orderedTracks.length > 0 || this.indefinitePlayback ? import_lit13.html`<div class="controls-column">
4811
- ${this.timescale ? import_lit13.html`<div style="height: 30px;"></div>` : ""}
5338
+ return import_lit14.html`
5339
+ ${orderedTracks.length > 0 || this.indefinitePlayback ? import_lit14.html`<div class="controls-column">
5340
+ ${this.timescale ? import_lit14.html`<div style="height: 30px;"></div>` : ""}
4812
5341
  ${orderedTracks.map(
4813
- (t) => import_lit13.html`
5342
+ (t) => import_lit14.html`
4814
5343
  <daw-track-controls
4815
5344
  style="height: ${t.trackHeight}px;"
4816
5345
  .trackId=${t.trackId}
@@ -4833,7 +5362,7 @@ var DawEditorElement = class extends import_lit13.LitElement {
4833
5362
  @dragleave=${this._onDragLeave}
4834
5363
  @drop=${this._onDrop}
4835
5364
  >
4836
- ${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ? import_lit13.html`<daw-ruler
5365
+ ${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ? import_lit14.html`<daw-ruler
4837
5366
  .samplesPerPixel=${spp}
4838
5367
  .sampleRate=${this.effectiveSampleRate}
4839
5368
  .duration=${this._duration}
@@ -4843,7 +5372,7 @@ var DawEditorElement = class extends import_lit13.LitElement {
4843
5372
  .ppqn=${this.ppqn}
4844
5373
  .totalWidth=${this._totalWidth}
4845
5374
  ></daw-ruler>` : ""}
4846
- ${this.scaleMode === "beats" ? import_lit13.html`<daw-grid
5375
+ ${this.scaleMode === "beats" ? import_lit14.html`<daw-grid
4847
5376
  style="top: ${this.timescale ? 30 : 0}px;"
4848
5377
  .ticksPerPixel=${this.ticksPerPixel}
4849
5378
  .meterEntries=${this._meterEntries}
@@ -4853,11 +5382,11 @@ var DawEditorElement = class extends import_lit13.LitElement {
4853
5382
  .length=${this._totalWidth}
4854
5383
  .height=${orderedTracks.length > 0 ? orderedTracks.reduce((sum, t) => sum + t.trackHeight + 1, 0) : this._emptyGridHeight}
4855
5384
  ></daw-grid>` : ""}
4856
- ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? import_lit13.html`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
5385
+ ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? import_lit14.html`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
4857
5386
  <daw-playhead></daw-playhead>` : ""}
4858
5387
  ${orderedTracks.map((t) => {
4859
5388
  const channelHeight = this.waveHeight;
4860
- return import_lit13.html`
5389
+ return import_lit14.html`
4861
5390
  <div
4862
5391
  class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
4863
5392
  style="height: ${t.trackHeight}px;"
@@ -4920,12 +5449,12 @@ var DawEditorElement = class extends import_lit13.LitElement {
4920
5449
  const channels = segmentChannels ?? peakData?.data ?? [new Int16Array(0)];
4921
5450
  const hdrH = this.clipHeaders ? this.clipHeaderHeight : 0;
4922
5451
  const chH = this.waveHeight;
4923
- return import_lit13.html` <div
5452
+ return import_lit14.html` <div
4924
5453
  class="clip-container"
4925
5454
  style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
4926
5455
  data-clip-id=${clip.id}
4927
5456
  >
4928
- ${hdrH > 0 ? import_lit13.html`<div
5457
+ ${hdrH > 0 ? import_lit14.html`<div
4929
5458
  class="clip-header"
4930
5459
  data-clip-id=${clip.id}
4931
5460
  data-track-id=${t.trackId}
@@ -4933,21 +5462,33 @@ var DawEditorElement = class extends import_lit13.LitElement {
4933
5462
  >
4934
5463
  <span>${clip.name || t.descriptor?.name || ""}</span>
4935
5464
  </div>` : ""}
4936
- ${channels.map(
4937
- (chPeaks, chIdx) => import_lit13.html` <daw-waveform
4938
- style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
4939
- .peaks=${chPeaks}
5465
+ ${t.descriptor?.renderMode === "piano-roll" ? import_lit14.html`<daw-piano-roll
5466
+ style="position:absolute;left:0;top:${hdrH}px;"
5467
+ .midiNotes=${clip.midiNotes ?? []}
4940
5468
  .length=${peakData?.length ?? width}
4941
- .waveHeight=${chH}
4942
- .barWidth=${this.barWidth}
4943
- .barGap=${this.barGap}
5469
+ .waveHeight=${chH * channels.length}
5470
+ .samplesPerPixel=${this._renderSpp}
5471
+ .sampleRate=${this.effectiveSampleRate}
5472
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
4944
5473
  .visibleStart=${this._viewport.visibleStart}
4945
5474
  .visibleEnd=${this._viewport.visibleEnd}
4946
5475
  .originX=${clipLeft}
4947
- .segments=${clipSegments}
4948
- ></daw-waveform>`
5476
+ ?selected=${t.trackId === this._selectedTrackId}
5477
+ ></daw-piano-roll>` : channels.map(
5478
+ (chPeaks, chIdx) => import_lit14.html` <daw-waveform
5479
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5480
+ .peaks=${chPeaks}
5481
+ .length=${peakData?.length ?? width}
5482
+ .waveHeight=${chH}
5483
+ .barWidth=${this.barWidth}
5484
+ .barGap=${this.barGap}
5485
+ .visibleStart=${this._viewport.visibleStart}
5486
+ .visibleEnd=${this._viewport.visibleEnd}
5487
+ .originX=${clipLeft}
5488
+ .segments=${clipSegments}
5489
+ ></daw-waveform>`
4949
5490
  )}
4950
- ${this.interactiveClips ? import_lit13.html` <div
5491
+ ${this.interactiveClips ? import_lit14.html` <div
4951
5492
  class="clip-boundary"
4952
5493
  data-boundary-edge="left"
4953
5494
  data-clip-id=${clip.id}
@@ -4973,7 +5514,7 @@ var DawEditorElement = class extends import_lit13.LitElement {
4973
5514
  };
4974
5515
  DawEditorElement.styles = [
4975
5516
  hostStyles,
4976
- import_lit13.css`
5517
+ import_lit14.css`
4977
5518
  :host {
4978
5519
  display: flex;
4979
5520
  position: relative;
@@ -5021,99 +5562,99 @@ DawEditorElement.styles = [
5021
5562
  ];
5022
5563
  DawEditorElement._CONTROL_PROPS = /* @__PURE__ */ new Set(["volume", "pan", "muted", "soloed"]);
5023
5564
  __decorateClass([
5024
- (0, import_decorators11.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
5565
+ (0, import_decorators12.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
5025
5566
  ], DawEditorElement.prototype, "samplesPerPixel", 1);
5026
5567
  __decorateClass([
5027
- (0, import_decorators11.property)({ type: Number, attribute: "wave-height" })
5568
+ (0, import_decorators12.property)({ type: Number, attribute: "wave-height" })
5028
5569
  ], DawEditorElement.prototype, "waveHeight", 2);
5029
5570
  __decorateClass([
5030
- (0, import_decorators11.property)({ type: Boolean })
5571
+ (0, import_decorators12.property)({ type: Boolean })
5031
5572
  ], DawEditorElement.prototype, "timescale", 2);
5032
5573
  __decorateClass([
5033
- (0, import_decorators11.property)({ type: Boolean })
5574
+ (0, import_decorators12.property)({ type: Boolean })
5034
5575
  ], DawEditorElement.prototype, "mono", 2);
5035
5576
  __decorateClass([
5036
- (0, import_decorators11.property)({ type: Number, attribute: "bar-width" })
5577
+ (0, import_decorators12.property)({ type: Number, attribute: "bar-width" })
5037
5578
  ], DawEditorElement.prototype, "barWidth", 2);
5038
5579
  __decorateClass([
5039
- (0, import_decorators11.property)({ type: Number, attribute: "bar-gap" })
5580
+ (0, import_decorators12.property)({ type: Number, attribute: "bar-gap" })
5040
5581
  ], DawEditorElement.prototype, "barGap", 2);
5041
5582
  __decorateClass([
5042
- (0, import_decorators11.property)({ type: Boolean, attribute: "file-drop" })
5583
+ (0, import_decorators12.property)({ type: Boolean, attribute: "file-drop" })
5043
5584
  ], DawEditorElement.prototype, "fileDrop", 2);
5044
5585
  __decorateClass([
5045
- (0, import_decorators11.property)({ type: Boolean, attribute: "clip-headers" })
5586
+ (0, import_decorators12.property)({ type: Boolean, attribute: "clip-headers" })
5046
5587
  ], DawEditorElement.prototype, "clipHeaders", 2);
5047
5588
  __decorateClass([
5048
- (0, import_decorators11.property)({ type: Number, attribute: "clip-header-height" })
5589
+ (0, import_decorators12.property)({ type: Number, attribute: "clip-header-height" })
5049
5590
  ], DawEditorElement.prototype, "clipHeaderHeight", 2);
5050
5591
  __decorateClass([
5051
- (0, import_decorators11.property)({ type: Boolean, attribute: "interactive-clips" })
5592
+ (0, import_decorators12.property)({ type: Boolean, attribute: "interactive-clips" })
5052
5593
  ], DawEditorElement.prototype, "interactiveClips", 2);
5053
5594
  __decorateClass([
5054
- (0, import_decorators11.property)({ type: Boolean, attribute: "indefinite-playback" })
5595
+ (0, import_decorators12.property)({ type: Boolean, attribute: "indefinite-playback" })
5055
5596
  ], DawEditorElement.prototype, "indefinitePlayback", 2);
5056
5597
  __decorateClass([
5057
- (0, import_decorators11.property)({ type: String, attribute: "scale-mode" })
5598
+ (0, import_decorators12.property)({ type: String, attribute: "scale-mode" })
5058
5599
  ], DawEditorElement.prototype, "scaleMode", 2);
5059
5600
  __decorateClass([
5060
- (0, import_decorators11.property)({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
5601
+ (0, import_decorators12.property)({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
5061
5602
  ], DawEditorElement.prototype, "ticksPerPixel", 1);
5062
5603
  __decorateClass([
5063
- (0, import_decorators11.property)({ type: Number, noAccessor: true })
5604
+ (0, import_decorators12.property)({ type: Number, noAccessor: true })
5064
5605
  ], DawEditorElement.prototype, "bpm", 1);
5065
5606
  __decorateClass([
5066
- (0, import_decorators11.property)({ attribute: false })
5607
+ (0, import_decorators12.property)({ attribute: false })
5067
5608
  ], DawEditorElement.prototype, "timeSignature", 2);
5068
5609
  __decorateClass([
5069
- (0, import_decorators11.property)({ attribute: false })
5610
+ (0, import_decorators12.property)({ attribute: false })
5070
5611
  ], DawEditorElement.prototype, "meterEntries", 2);
5071
5612
  __decorateClass([
5072
- (0, import_decorators11.property)({ type: Number, noAccessor: true })
5613
+ (0, import_decorators12.property)({ type: Number, noAccessor: true })
5073
5614
  ], DawEditorElement.prototype, "ppqn", 1);
5074
5615
  __decorateClass([
5075
- (0, import_decorators11.property)({ type: String, attribute: "snap-to" })
5616
+ (0, import_decorators12.property)({ type: String, attribute: "snap-to" })
5076
5617
  ], DawEditorElement.prototype, "snapTo", 2);
5077
5618
  __decorateClass([
5078
- (0, import_decorators11.property)({ attribute: false })
5619
+ (0, import_decorators12.property)({ attribute: false })
5079
5620
  ], DawEditorElement.prototype, "secondsToTicks", 2);
5080
5621
  __decorateClass([
5081
- (0, import_decorators11.property)({ attribute: false })
5622
+ (0, import_decorators12.property)({ attribute: false })
5082
5623
  ], DawEditorElement.prototype, "ticksToSeconds", 2);
5083
5624
  __decorateClass([
5084
- (0, import_decorators11.state)()
5625
+ (0, import_decorators12.state)()
5085
5626
  ], DawEditorElement.prototype, "_tracks", 2);
5086
5627
  __decorateClass([
5087
- (0, import_decorators11.state)()
5628
+ (0, import_decorators12.state)()
5088
5629
  ], DawEditorElement.prototype, "_engineTracks", 2);
5089
5630
  __decorateClass([
5090
- (0, import_decorators11.state)()
5631
+ (0, import_decorators12.state)()
5091
5632
  ], DawEditorElement.prototype, "_peaksData", 2);
5092
5633
  __decorateClass([
5093
- (0, import_decorators11.state)()
5634
+ (0, import_decorators12.state)()
5094
5635
  ], DawEditorElement.prototype, "_isPlaying", 2);
5095
5636
  __decorateClass([
5096
- (0, import_decorators11.state)()
5637
+ (0, import_decorators12.state)()
5097
5638
  ], DawEditorElement.prototype, "_duration", 2);
5098
5639
  __decorateClass([
5099
- (0, import_decorators11.state)()
5640
+ (0, import_decorators12.state)()
5100
5641
  ], DawEditorElement.prototype, "_selectedTrackId", 2);
5101
5642
  __decorateClass([
5102
- (0, import_decorators11.state)()
5643
+ (0, import_decorators12.state)()
5103
5644
  ], DawEditorElement.prototype, "_dragOver", 2);
5104
5645
  __decorateClass([
5105
- (0, import_decorators11.property)({ attribute: false })
5646
+ (0, import_decorators12.property)({ attribute: false })
5106
5647
  ], DawEditorElement.prototype, "adapter", 1);
5107
5648
  __decorateClass([
5108
- (0, import_decorators11.property)({ attribute: "eager-resume" })
5649
+ (0, import_decorators12.property)({ attribute: "eager-resume" })
5109
5650
  ], DawEditorElement.prototype, "eagerResume", 2);
5110
5651
  DawEditorElement = __decorateClass([
5111
- (0, import_decorators11.customElement)("daw-editor")
5652
+ (0, import_decorators12.customElement)("daw-editor")
5112
5653
  ], DawEditorElement);
5113
5654
 
5114
5655
  // src/elements/daw-ruler.ts
5115
- var import_lit14 = require("lit");
5116
- var import_decorators12 = require("lit/decorators.js");
5656
+ var import_lit15 = require("lit");
5657
+ var import_decorators13 = require("lit/decorators.js");
5117
5658
 
5118
5659
  // src/utils/time-format.ts
5119
5660
  function formatTime(milliseconds) {
@@ -5164,8 +5705,8 @@ function computeTemporalTicks(samplesPerPixel, sampleRate, duration, rulerHeight
5164
5705
  }
5165
5706
 
5166
5707
  // src/elements/daw-ruler.ts
5167
- var MAX_CANVAS_WIDTH3 = 1e3;
5168
- var DawRulerElement = class extends import_lit14.LitElement {
5708
+ var MAX_CANVAS_WIDTH4 = 1e3;
5709
+ var DawRulerElement = class extends import_lit15.LitElement {
5169
5710
  constructor() {
5170
5711
  super(...arguments);
5171
5712
  this.samplesPerPixel = 1024;
@@ -5209,33 +5750,33 @@ var DawRulerElement = class extends import_lit14.LitElement {
5209
5750
  }
5210
5751
  render() {
5211
5752
  const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
5212
- if (widthX <= 0) return import_lit14.html``;
5213
- const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH3);
5753
+ if (widthX <= 0) return import_lit15.html``;
5754
+ const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH4);
5214
5755
  const indices = Array.from({ length: totalChunks }, (_, i) => i);
5215
5756
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
5216
5757
  const beatsLabels = this.scaleMode === "beats" ? this._musicalTickData?.ticks.filter((t) => t.label) ?? [] : [];
5217
5758
  const temporalLabels = this.scaleMode !== "beats" ? this._tickData?.labels ?? [] : [];
5218
- return import_lit14.html`
5759
+ return import_lit15.html`
5219
5760
  <div class="container" style="width: ${widthX}px; height: ${this.rulerHeight}px;">
5220
5761
  ${indices.map((i) => {
5221
- const width = Math.min(MAX_CANVAS_WIDTH3, widthX - i * MAX_CANVAS_WIDTH3);
5222
- return import_lit14.html`
5762
+ const width = Math.min(MAX_CANVAS_WIDTH4, widthX - i * MAX_CANVAS_WIDTH4);
5763
+ return import_lit15.html`
5223
5764
  <canvas
5224
5765
  data-index=${i}
5225
5766
  width=${width * dpr}
5226
5767
  height=${this.rulerHeight * dpr}
5227
- style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.rulerHeight}px;"
5768
+ style="left: ${i * MAX_CANVAS_WIDTH4}px; width: ${width}px; height: ${this.rulerHeight}px;"
5228
5769
  ></canvas>
5229
5770
  `;
5230
5771
  })}
5231
5772
  ${this.scaleMode === "beats" ? beatsLabels.map(
5232
- (t) => import_lit14.html`<span
5773
+ (t) => import_lit15.html`<span
5233
5774
  class="label ${t.pixel > 0 ? "centered" : ""}"
5234
5775
  style="left: ${t.pixel > 0 ? t.pixel : t.pixel + 4}px;"
5235
5776
  >${t.label}</span
5236
5777
  >`
5237
5778
  ) : temporalLabels.map(
5238
- ({ pix, text }) => import_lit14.html`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
5779
+ ({ pix, text }) => import_lit15.html`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
5239
5780
  )}
5240
5781
  </div>
5241
5782
  `;
@@ -5253,8 +5794,8 @@ var DawRulerElement = class extends import_lit14.LitElement {
5253
5794
  const idx = Number(canvas.dataset.index);
5254
5795
  const ctx = canvas.getContext("2d");
5255
5796
  if (!ctx) continue;
5256
- const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, widthX - idx * MAX_CANVAS_WIDTH3);
5257
- const globalOffset = idx * MAX_CANVAS_WIDTH3;
5797
+ const canvasWidth = Math.min(MAX_CANVAS_WIDTH4, widthX - idx * MAX_CANVAS_WIDTH4);
5798
+ const globalOffset = idx * MAX_CANVAS_WIDTH4;
5258
5799
  ctx.resetTransform();
5259
5800
  ctx.clearRect(0, 0, canvas.width, canvas.height);
5260
5801
  ctx.scale(dpr, dpr);
@@ -5286,7 +5827,7 @@ var DawRulerElement = class extends import_lit14.LitElement {
5286
5827
  }
5287
5828
  }
5288
5829
  };
5289
- DawRulerElement.styles = import_lit14.css`
5830
+ DawRulerElement.styles = import_lit15.css`
5290
5831
  :host {
5291
5832
  display: block;
5292
5833
  position: relative;
@@ -5312,40 +5853,40 @@ DawRulerElement.styles = import_lit14.css`
5312
5853
  }
5313
5854
  `;
5314
5855
  __decorateClass([
5315
- (0, import_decorators12.property)({ type: Number, attribute: false })
5856
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5316
5857
  ], DawRulerElement.prototype, "samplesPerPixel", 2);
5317
5858
  __decorateClass([
5318
- (0, import_decorators12.property)({ type: Number, attribute: false })
5859
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5319
5860
  ], DawRulerElement.prototype, "sampleRate", 2);
5320
5861
  __decorateClass([
5321
- (0, import_decorators12.property)({ type: Number, attribute: false })
5862
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5322
5863
  ], DawRulerElement.prototype, "duration", 2);
5323
5864
  __decorateClass([
5324
- (0, import_decorators12.property)({ type: Number, attribute: false })
5865
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5325
5866
  ], DawRulerElement.prototype, "rulerHeight", 2);
5326
5867
  __decorateClass([
5327
- (0, import_decorators12.property)({ type: String, attribute: false })
5868
+ (0, import_decorators13.property)({ type: String, attribute: false })
5328
5869
  ], DawRulerElement.prototype, "scaleMode", 2);
5329
5870
  __decorateClass([
5330
- (0, import_decorators12.property)({ type: Number, attribute: false })
5871
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5331
5872
  ], DawRulerElement.prototype, "ticksPerPixel", 2);
5332
5873
  __decorateClass([
5333
- (0, import_decorators12.property)({ attribute: false })
5874
+ (0, import_decorators13.property)({ attribute: false })
5334
5875
  ], DawRulerElement.prototype, "meterEntries", 2);
5335
5876
  __decorateClass([
5336
- (0, import_decorators12.property)({ type: Number, attribute: false })
5877
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5337
5878
  ], DawRulerElement.prototype, "ppqn", 2);
5338
5879
  __decorateClass([
5339
- (0, import_decorators12.property)({ type: Number, attribute: false })
5880
+ (0, import_decorators13.property)({ type: Number, attribute: false })
5340
5881
  ], DawRulerElement.prototype, "totalWidth", 2);
5341
5882
  DawRulerElement = __decorateClass([
5342
- (0, import_decorators12.customElement)("daw-ruler")
5883
+ (0, import_decorators13.customElement)("daw-ruler")
5343
5884
  ], DawRulerElement);
5344
5885
 
5345
5886
  // src/elements/daw-selection.ts
5346
- var import_lit15 = require("lit");
5347
- var import_decorators13 = require("lit/decorators.js");
5348
- var DawSelectionElement = class extends import_lit15.LitElement {
5887
+ var import_lit16 = require("lit");
5888
+ var import_decorators14 = require("lit/decorators.js");
5889
+ var DawSelectionElement = class extends import_lit16.LitElement {
5349
5890
  constructor() {
5350
5891
  super(...arguments);
5351
5892
  this.startPx = 0;
@@ -5354,11 +5895,11 @@ var DawSelectionElement = class extends import_lit15.LitElement {
5354
5895
  render() {
5355
5896
  const left = Math.min(this.startPx, this.endPx);
5356
5897
  const width = Math.abs(this.endPx - this.startPx);
5357
- if (width === 0) return import_lit15.html``;
5358
- return import_lit15.html`<div style="left: ${left}px; width: ${width}px;"></div>`;
5898
+ if (width === 0) return import_lit16.html``;
5899
+ return import_lit16.html`<div style="left: ${left}px; width: ${width}px;"></div>`;
5359
5900
  }
5360
5901
  };
5361
- DawSelectionElement.styles = import_lit15.css`
5902
+ DawSelectionElement.styles = import_lit16.css`
5362
5903
  :host {
5363
5904
  position: absolute;
5364
5905
  top: 0;
@@ -5375,18 +5916,18 @@ DawSelectionElement.styles = import_lit15.css`
5375
5916
  }
5376
5917
  `;
5377
5918
  __decorateClass([
5378
- (0, import_decorators13.property)({ type: Number, attribute: false })
5919
+ (0, import_decorators14.property)({ type: Number, attribute: false })
5379
5920
  ], DawSelectionElement.prototype, "startPx", 2);
5380
5921
  __decorateClass([
5381
- (0, import_decorators13.property)({ type: Number, attribute: false })
5922
+ (0, import_decorators14.property)({ type: Number, attribute: false })
5382
5923
  ], DawSelectionElement.prototype, "endPx", 2);
5383
5924
  DawSelectionElement = __decorateClass([
5384
- (0, import_decorators13.customElement)("daw-selection")
5925
+ (0, import_decorators14.customElement)("daw-selection")
5385
5926
  ], DawSelectionElement);
5386
5927
 
5387
5928
  // src/elements/daw-record-button.ts
5388
- var import_lit16 = require("lit");
5389
- var import_decorators14 = require("lit/decorators.js");
5929
+ var import_lit17 = require("lit");
5930
+ var import_decorators15 = require("lit/decorators.js");
5390
5931
  var DawRecordButtonElement = class extends DawTransportButton {
5391
5932
  constructor() {
5392
5933
  super(...arguments);
@@ -5427,7 +5968,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
5427
5968
  }
5428
5969
  }
5429
5970
  render() {
5430
- return import_lit16.html`
5971
+ return import_lit17.html`
5431
5972
  <button part="button" ?data-recording=${this._isRecording} @click=${this._onClick}>
5432
5973
  <slot>Record</slot>
5433
5974
  </button>
@@ -5447,7 +5988,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
5447
5988
  };
5448
5989
  DawRecordButtonElement.styles = [
5449
5990
  DawTransportButton.styles,
5450
- import_lit16.css`
5991
+ import_lit17.css`
5451
5992
  button[data-recording] {
5452
5993
  color: #d08070;
5453
5994
  border-color: #d08070;
@@ -5456,17 +5997,17 @@ DawRecordButtonElement.styles = [
5456
5997
  `
5457
5998
  ];
5458
5999
  __decorateClass([
5459
- (0, import_decorators14.state)()
6000
+ (0, import_decorators15.state)()
5460
6001
  ], DawRecordButtonElement.prototype, "_isRecording", 2);
5461
6002
  DawRecordButtonElement = __decorateClass([
5462
- (0, import_decorators14.customElement)("daw-record-button")
6003
+ (0, import_decorators15.customElement)("daw-record-button")
5463
6004
  ], DawRecordButtonElement);
5464
6005
 
5465
6006
  // src/elements/daw-keyboard-shortcuts.ts
5466
- var import_lit17 = require("lit");
5467
- var import_decorators15 = require("lit/decorators.js");
6007
+ var import_lit18 = require("lit");
6008
+ var import_decorators16 = require("lit/decorators.js");
5468
6009
  var import_core9 = require("@waveform-playlist/core");
5469
- var DawKeyboardShortcutsElement = class extends import_lit17.LitElement {
6010
+ var DawKeyboardShortcutsElement = class extends import_lit18.LitElement {
5470
6011
  constructor() {
5471
6012
  super(...arguments);
5472
6013
  this.playback = false;
@@ -5620,16 +6161,16 @@ var DawKeyboardShortcutsElement = class extends import_lit17.LitElement {
5620
6161
  }
5621
6162
  };
5622
6163
  __decorateClass([
5623
- (0, import_decorators15.property)({ type: Boolean })
6164
+ (0, import_decorators16.property)({ type: Boolean })
5624
6165
  ], DawKeyboardShortcutsElement.prototype, "playback", 2);
5625
6166
  __decorateClass([
5626
- (0, import_decorators15.property)({ type: Boolean })
6167
+ (0, import_decorators16.property)({ type: Boolean })
5627
6168
  ], DawKeyboardShortcutsElement.prototype, "splitting", 2);
5628
6169
  __decorateClass([
5629
- (0, import_decorators15.property)({ type: Boolean })
6170
+ (0, import_decorators16.property)({ type: Boolean })
5630
6171
  ], DawKeyboardShortcutsElement.prototype, "undo", 2);
5631
6172
  DawKeyboardShortcutsElement = __decorateClass([
5632
- (0, import_decorators15.customElement)("daw-keyboard-shortcuts")
6173
+ (0, import_decorators16.customElement)("daw-keyboard-shortcuts")
5633
6174
  ], DawKeyboardShortcutsElement);
5634
6175
  // Annotate the CommonJS export names for ESM import in node:
5635
6176
  0 && (module.exports = {
@@ -5640,6 +6181,7 @@ DawKeyboardShortcutsElement = __decorateClass([
5640
6181
  DawGridElement,
5641
6182
  DawKeyboardShortcutsElement,
5642
6183
  DawPauseButtonElement,
6184
+ DawPianoRollElement,
5643
6185
  DawPlayButtonElement,
5644
6186
  DawPlayheadElement,
5645
6187
  DawRecordButtonElement,