@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.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,7 @@ var DawTrackElement = class extends LitElement2 {
125
174
  this.pan = 0;
126
175
  this.muted = false;
127
176
  this.soloed = false;
177
+ this.renderMode = "waveform";
128
178
  this.trackId = crypto.randomUUID();
129
179
  // Track removal is detected by the editor's MutationObserver,
130
180
  // not by dispatching from disconnectedCallback (detached elements
@@ -152,7 +202,7 @@ var DawTrackElement = class extends LitElement2 {
152
202
  this._hasRendered = true;
153
203
  return;
154
204
  }
155
- const trackProps = ["volume", "pan", "muted", "soloed", "src", "name"];
205
+ const trackProps = ["volume", "pan", "muted", "soloed", "src", "name", "renderMode"];
156
206
  const hasTrackChange = trackProps.some((p) => changed.has(p));
157
207
  if (hasTrackChange) {
158
208
  this.dispatchEvent(
@@ -183,6 +233,9 @@ __decorateClass([
183
233
  __decorateClass([
184
234
  property2({ type: Boolean })
185
235
  ], DawTrackElement.prototype, "soloed", 2);
236
+ __decorateClass([
237
+ property2({ attribute: "render-mode" })
238
+ ], DawTrackElement.prototype, "renderMode", 2);
186
239
  DawTrackElement = __decorateClass([
187
240
  customElement2("daw-track")
188
241
  ], DawTrackElement);
@@ -532,9 +585,229 @@ DawWaveformElement = __decorateClass([
532
585
  customElement3("daw-waveform")
533
586
  ], DawWaveformElement);
534
587
 
535
- // src/elements/daw-playhead.ts
588
+ // src/elements/daw-piano-roll.ts
536
589
  import { LitElement as LitElement4, html as html2, css as css2 } from "lit";
537
- import { customElement as customElement4 } from "lit/decorators.js";
590
+ import { customElement as customElement4, property as property4 } from "lit/decorators.js";
591
+ var MAX_CANVAS_WIDTH2 = 1e3;
592
+ var LAYOUT_PROPS2 = /* @__PURE__ */ new Set([
593
+ "length",
594
+ "waveHeight",
595
+ "samplesPerPixel",
596
+ "sampleRate",
597
+ "clipOffsetSeconds",
598
+ "midiNotes",
599
+ "selected"
600
+ ]);
601
+ var DawPianoRollElement = class extends LitElement4 {
602
+ constructor() {
603
+ super(...arguments);
604
+ this.midiNotes = [];
605
+ this.length = 0;
606
+ this.waveHeight = 128;
607
+ this._samplesPerPixel = 1024;
608
+ this._sampleRate = 48e3;
609
+ this.clipOffsetSeconds = 0;
610
+ this.visibleStart = -Infinity;
611
+ this.visibleEnd = Infinity;
612
+ this.originX = 0;
613
+ this.selected = false;
614
+ this._rafHandle = null;
615
+ }
616
+ get samplesPerPixel() {
617
+ return this._samplesPerPixel;
618
+ }
619
+ set samplesPerPixel(value) {
620
+ if (!Number.isFinite(value) || value <= 0) {
621
+ console.warn("[dawcore] daw-piano-roll samplesPerPixel " + value + " is invalid \u2014 ignored");
622
+ return;
623
+ }
624
+ const old = this._samplesPerPixel;
625
+ this._samplesPerPixel = value;
626
+ this.requestUpdate("samplesPerPixel", old);
627
+ }
628
+ get sampleRate() {
629
+ return this._sampleRate;
630
+ }
631
+ set sampleRate(value) {
632
+ if (!Number.isFinite(value) || value <= 0) {
633
+ console.warn("[dawcore] daw-piano-roll sampleRate " + value + " is invalid \u2014 ignored");
634
+ return;
635
+ }
636
+ const old = this._sampleRate;
637
+ this._sampleRate = value;
638
+ this.requestUpdate("sampleRate", old);
639
+ }
640
+ _scheduleDraw() {
641
+ if (this._rafHandle !== null) return;
642
+ this._rafHandle = requestAnimationFrame(() => {
643
+ this._rafHandle = null;
644
+ this._draw();
645
+ });
646
+ }
647
+ willUpdate(_changed) {
648
+ this._scheduleDraw();
649
+ }
650
+ updated(changedProperties) {
651
+ const needsFullDraw = [...changedProperties.keys()].some((key) => LAYOUT_PROPS2.has(key));
652
+ if (needsFullDraw) return;
653
+ if (changedProperties.has("visibleStart") || changedProperties.has("visibleEnd") || changedProperties.has("originX")) {
654
+ this._scheduleDraw();
655
+ }
656
+ }
657
+ _getPitchRange() {
658
+ if (this.midiNotes.length === 0) return { minMidi: 0, maxMidi: 127 };
659
+ let min = 127;
660
+ let max = 0;
661
+ for (const note of this.midiNotes) {
662
+ if (note.midi < min) min = note.midi;
663
+ if (note.midi > max) max = note.midi;
664
+ }
665
+ return {
666
+ minMidi: Math.max(0, min - 1),
667
+ maxMidi: Math.min(127, max + 1)
668
+ };
669
+ }
670
+ _getNoteColor() {
671
+ const cs = getComputedStyle(this);
672
+ const note = cs.getPropertyValue("--daw-piano-roll-note-color").trim() || "#2a7070";
673
+ const selectedColor = cs.getPropertyValue("--daw-piano-roll-selected-note-color").trim() || "#3d9e9e";
674
+ return this.selected ? selectedColor : note;
675
+ }
676
+ _draw() {
677
+ if (!this.shadowRoot) return;
678
+ const canvases = this.shadowRoot.querySelectorAll("canvas");
679
+ if (canvases.length === 0) return;
680
+ const { minMidi, maxMidi } = this._getPitchRange();
681
+ const noteRange = maxMidi - minMidi + 1;
682
+ const noteHeight = Math.max(2, this.waveHeight / noteRange);
683
+ const pixelsPerSecond = this.sampleRate / this.samplesPerPixel;
684
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
685
+ const color = this._getNoteColor();
686
+ for (const canvas of canvases) {
687
+ const chunkIdx = Number(canvas.dataset.index);
688
+ const chunkPixelStart = chunkIdx * MAX_CANVAS_WIDTH2;
689
+ const canvasWidth = canvas.width / dpr;
690
+ const ctx = canvas.getContext("2d");
691
+ if (!ctx) continue;
692
+ ctx.resetTransform();
693
+ ctx.clearRect(
694
+ 0,
695
+ 0,
696
+ canvas.width,
697
+ canvas.height
698
+ );
699
+ ctx.imageSmoothingEnabled = false;
700
+ ctx.scale(dpr, dpr);
701
+ const chunkStartTime = chunkPixelStart * this.samplesPerPixel / this.sampleRate;
702
+ const chunkEndTime = (chunkPixelStart + canvasWidth) * this.samplesPerPixel / this.sampleRate;
703
+ for (const note of this.midiNotes) {
704
+ const noteStart = note.time - this.clipOffsetSeconds;
705
+ const noteEnd = noteStart + note.duration;
706
+ if (noteEnd <= chunkStartTime || noteStart >= chunkEndTime) continue;
707
+ const x = noteStart * pixelsPerSecond - chunkPixelStart;
708
+ const w = Math.max(2, note.duration * pixelsPerSecond);
709
+ const y = (maxMidi - note.midi) / noteRange * this.waveHeight;
710
+ const alpha = 0.3 + note.velocity * 0.7;
711
+ ctx.fillStyle = color;
712
+ ctx.globalAlpha = alpha;
713
+ ctx.beginPath();
714
+ ctx.roundRect(x, y, w, noteHeight, 1);
715
+ ctx.fill();
716
+ }
717
+ ctx.globalAlpha = 1;
718
+ }
719
+ }
720
+ connectedCallback() {
721
+ super.connectedCallback();
722
+ this._scheduleDraw();
723
+ }
724
+ disconnectedCallback() {
725
+ super.disconnectedCallback();
726
+ if (this._rafHandle !== null) {
727
+ cancelAnimationFrame(this._rafHandle);
728
+ this._rafHandle = null;
729
+ }
730
+ }
731
+ render() {
732
+ if (this.length <= 0)
733
+ return html2`<div class="container" style="width: 0; height: ${this.waveHeight}px;"></div>`;
734
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
735
+ const visibleIndices = getVisibleChunkIndices(
736
+ this.length,
737
+ MAX_CANVAS_WIDTH2,
738
+ this.visibleStart,
739
+ this.visibleEnd,
740
+ this.originX
741
+ );
742
+ return html2`
743
+ <div class="container" style="width: ${this.length}px; height: ${this.waveHeight}px;">
744
+ ${visibleIndices.map((i) => {
745
+ const chunkLeft = i * MAX_CANVAS_WIDTH2;
746
+ const chunkWidth = Math.min(this.length - chunkLeft, MAX_CANVAS_WIDTH2);
747
+ return html2`<canvas
748
+ data-index=${i}
749
+ width=${chunkWidth * dpr}
750
+ height=${this.waveHeight * dpr}
751
+ style="left: ${chunkLeft}px; width: ${chunkWidth}px; height: ${this.waveHeight}px;"
752
+ ></canvas>`;
753
+ })}
754
+ </div>
755
+ `;
756
+ }
757
+ };
758
+ DawPianoRollElement.styles = css2`
759
+ :host {
760
+ display: block;
761
+ position: relative;
762
+ }
763
+ .container {
764
+ position: relative;
765
+ background: var(--daw-piano-roll-background, #1a1a2e);
766
+ }
767
+ canvas {
768
+ position: absolute;
769
+ top: 0;
770
+ image-rendering: pixelated;
771
+ image-rendering: crisp-edges;
772
+ }
773
+ `;
774
+ __decorateClass([
775
+ property4({ attribute: false })
776
+ ], DawPianoRollElement.prototype, "midiNotes", 2);
777
+ __decorateClass([
778
+ property4({ type: Number, attribute: false })
779
+ ], DawPianoRollElement.prototype, "length", 2);
780
+ __decorateClass([
781
+ property4({ type: Number, attribute: false })
782
+ ], DawPianoRollElement.prototype, "waveHeight", 2);
783
+ __decorateClass([
784
+ property4({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
785
+ ], DawPianoRollElement.prototype, "samplesPerPixel", 1);
786
+ __decorateClass([
787
+ property4({ type: Number, attribute: "sample-rate", noAccessor: true })
788
+ ], DawPianoRollElement.prototype, "sampleRate", 1);
789
+ __decorateClass([
790
+ property4({ type: Number, attribute: false })
791
+ ], DawPianoRollElement.prototype, "clipOffsetSeconds", 2);
792
+ __decorateClass([
793
+ property4({ type: Number, attribute: false })
794
+ ], DawPianoRollElement.prototype, "visibleStart", 2);
795
+ __decorateClass([
796
+ property4({ type: Number, attribute: false })
797
+ ], DawPianoRollElement.prototype, "visibleEnd", 2);
798
+ __decorateClass([
799
+ property4({ type: Number, attribute: false })
800
+ ], DawPianoRollElement.prototype, "originX", 2);
801
+ __decorateClass([
802
+ property4({ type: Boolean, reflect: true })
803
+ ], DawPianoRollElement.prototype, "selected", 2);
804
+ DawPianoRollElement = __decorateClass([
805
+ customElement4("daw-piano-roll")
806
+ ], DawPianoRollElement);
807
+
808
+ // src/elements/daw-playhead.ts
809
+ import { LitElement as LitElement5, html as html3, css as css3 } from "lit";
810
+ import { customElement as customElement5 } from "lit/decorators.js";
538
811
 
539
812
  // src/controllers/animation-controller.ts
540
813
  var AnimationController = class {
@@ -567,14 +840,14 @@ var AnimationController = class {
567
840
  };
568
841
 
569
842
  // src/elements/daw-playhead.ts
570
- var DawPlayheadElement = class extends LitElement4 {
843
+ var DawPlayheadElement = class extends LitElement5 {
571
844
  constructor() {
572
845
  super(...arguments);
573
846
  this._animation = new AnimationController(this);
574
847
  this._line = null;
575
848
  }
576
849
  render() {
577
- return html2`<div></div>`;
850
+ return html3`<div></div>`;
578
851
  }
579
852
  firstUpdated() {
580
853
  this._line = this.shadowRoot.querySelector("div");
@@ -630,7 +903,7 @@ var DawPlayheadElement = class extends LitElement4 {
630
903
  }
631
904
  }
632
905
  };
633
- DawPlayheadElement.styles = css2`
906
+ DawPlayheadElement.styles = css3`
634
907
  :host {
635
908
  position: absolute;
636
909
  top: 0;
@@ -649,13 +922,13 @@ DawPlayheadElement.styles = css2`
649
922
  }
650
923
  `;
651
924
  DawPlayheadElement = __decorateClass([
652
- customElement4("daw-playhead")
925
+ customElement5("daw-playhead")
653
926
  ], DawPlayheadElement);
654
927
 
655
928
  // src/elements/daw-transport.ts
656
- import { LitElement as LitElement5 } from "lit";
657
- import { customElement as customElement5, property as property4 } from "lit/decorators.js";
658
- var DawTransportElement = class extends LitElement5 {
929
+ import { LitElement as LitElement6 } from "lit";
930
+ import { customElement as customElement6, property as property5 } from "lit/decorators.js";
931
+ var DawTransportElement = class extends LitElement6 {
659
932
  constructor() {
660
933
  super(...arguments);
661
934
  this.for = "";
@@ -670,25 +943,25 @@ var DawTransportElement = class extends LitElement5 {
670
943
  }
671
944
  };
672
945
  __decorateClass([
673
- property4()
946
+ property5()
674
947
  ], DawTransportElement.prototype, "for", 2);
675
948
  DawTransportElement = __decorateClass([
676
- customElement5("daw-transport")
949
+ customElement6("daw-transport")
677
950
  ], DawTransportElement);
678
951
 
679
952
  // src/elements/daw-play-button.ts
680
- import { html as html3 } from "lit";
681
- import { customElement as customElement6, state } from "lit/decorators.js";
953
+ import { html as html4 } from "lit";
954
+ import { customElement as customElement7, state } from "lit/decorators.js";
682
955
 
683
956
  // src/elements/daw-transport-button.ts
684
- import { LitElement as LitElement6, css as css3 } from "lit";
685
- var DawTransportButton = class extends LitElement6 {
957
+ import { LitElement as LitElement7, css as css4 } from "lit";
958
+ var DawTransportButton = class extends LitElement7 {
686
959
  get target() {
687
960
  const transport = this.closest("daw-transport");
688
961
  return transport?.target ?? null;
689
962
  }
690
963
  };
691
- DawTransportButton.styles = css3`
964
+ DawTransportButton.styles = css4`
692
965
  button {
693
966
  cursor: pointer;
694
967
  background: var(--daw-controls-background, #1a1a2e);
@@ -740,7 +1013,7 @@ var DawPlayButtonElement = class extends DawTransportButton {
740
1013
  }
741
1014
  }
742
1015
  render() {
743
- return html3`
1016
+ return html4`
744
1017
  <button part="button" ?disabled=${this._isRecording} @click=${this._onClick}>
745
1018
  <slot>Play</slot>
746
1019
  </button>
@@ -761,12 +1034,12 @@ __decorateClass([
761
1034
  state()
762
1035
  ], DawPlayButtonElement.prototype, "_isRecording", 2);
763
1036
  DawPlayButtonElement = __decorateClass([
764
- customElement6("daw-play-button")
1037
+ customElement7("daw-play-button")
765
1038
  ], DawPlayButtonElement);
766
1039
 
767
1040
  // src/elements/daw-pause-button.ts
768
- import { html as html4, css as css4 } from "lit";
769
- import { customElement as customElement7, state as state2 } from "lit/decorators.js";
1041
+ import { html as html5, css as css5 } from "lit";
1042
+ import { customElement as customElement8, state as state2 } from "lit/decorators.js";
770
1043
  var DawPauseButtonElement = class extends DawTransportButton {
771
1044
  constructor() {
772
1045
  super(...arguments);
@@ -780,6 +1053,12 @@ var DawPauseButtonElement = class extends DawTransportButton {
780
1053
  this._isRecording = false;
781
1054
  this._isPaused = false;
782
1055
  };
1056
+ this._onRecPause = () => {
1057
+ this._isPaused = true;
1058
+ };
1059
+ this._onRecResume = () => {
1060
+ this._isPaused = false;
1061
+ };
783
1062
  }
784
1063
  connectedCallback() {
785
1064
  super.connectedCallback();
@@ -790,6 +1069,8 @@ var DawPauseButtonElement = class extends DawTransportButton {
790
1069
  target.addEventListener("daw-recording-start", this._onRecStart);
791
1070
  target.addEventListener("daw-recording-complete", this._onRecEnd);
792
1071
  target.addEventListener("daw-recording-error", this._onRecEnd);
1072
+ target.addEventListener("daw-recording-pause", this._onRecPause);
1073
+ target.addEventListener("daw-recording-resume", this._onRecResume);
793
1074
  });
794
1075
  }
795
1076
  disconnectedCallback() {
@@ -798,11 +1079,13 @@ var DawPauseButtonElement = class extends DawTransportButton {
798
1079
  this._targetRef.removeEventListener("daw-recording-start", this._onRecStart);
799
1080
  this._targetRef.removeEventListener("daw-recording-complete", this._onRecEnd);
800
1081
  this._targetRef.removeEventListener("daw-recording-error", this._onRecEnd);
1082
+ this._targetRef.removeEventListener("daw-recording-pause", this._onRecPause);
1083
+ this._targetRef.removeEventListener("daw-recording-resume", this._onRecResume);
801
1084
  this._targetRef = null;
802
1085
  }
803
1086
  }
804
1087
  render() {
805
- return html4`
1088
+ return html5`
806
1089
  <button part="button" ?data-paused=${this._isPaused} @click=${this._onClick}>
807
1090
  <slot>Pause</slot>
808
1091
  </button>
@@ -817,15 +1100,7 @@ var DawPauseButtonElement = class extends DawTransportButton {
817
1100
  return;
818
1101
  }
819
1102
  if (this._isRecording) {
820
- if (this._isPaused) {
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
- }
1103
+ target.togglePauseRecording();
829
1104
  } else {
830
1105
  target.pause();
831
1106
  }
@@ -833,7 +1108,7 @@ var DawPauseButtonElement = class extends DawTransportButton {
833
1108
  };
834
1109
  DawPauseButtonElement.styles = [
835
1110
  DawTransportButton.styles,
836
- css4`
1111
+ css5`
837
1112
  button[data-paused] {
838
1113
  background: rgba(255, 255, 255, 0.1);
839
1114
  border-color: var(--daw-controls-text, #e0d4c8);
@@ -847,15 +1122,15 @@ __decorateClass([
847
1122
  state2()
848
1123
  ], DawPauseButtonElement.prototype, "_isRecording", 2);
849
1124
  DawPauseButtonElement = __decorateClass([
850
- customElement7("daw-pause-button")
1125
+ customElement8("daw-pause-button")
851
1126
  ], DawPauseButtonElement);
852
1127
 
853
1128
  // src/elements/daw-stop-button.ts
854
- import { html as html5 } from "lit";
855
- import { customElement as customElement8 } from "lit/decorators.js";
1129
+ import { html as html6 } from "lit";
1130
+ import { customElement as customElement9 } from "lit/decorators.js";
856
1131
  var DawStopButtonElement = class extends DawTransportButton {
857
1132
  render() {
858
- return html5`
1133
+ return html6`
859
1134
  <button part="button" @click=${this._onClick}>
860
1135
  <slot>Stop</slot>
861
1136
  </button>
@@ -870,18 +1145,31 @@ var DawStopButtonElement = class extends DawTransportButton {
870
1145
  return;
871
1146
  }
872
1147
  if (target.isRecording) {
873
- target.stopRecording();
1148
+ target.stopRecording().catch((err) => {
1149
+ console.warn("[dawcore] stopRecording failed: " + String(err));
1150
+ }).then(() => {
1151
+ try {
1152
+ target.stop();
1153
+ } catch (err) {
1154
+ console.warn("[dawcore] stop after stopRecording failed: " + String(err));
1155
+ }
1156
+ });
1157
+ } else {
1158
+ try {
1159
+ target.stop();
1160
+ } catch (err) {
1161
+ console.warn("[dawcore] stop failed: " + String(err));
1162
+ }
874
1163
  }
875
- target.stop();
876
1164
  }
877
1165
  };
878
1166
  DawStopButtonElement = __decorateClass([
879
- customElement8("daw-stop-button")
1167
+ customElement9("daw-stop-button")
880
1168
  ], DawStopButtonElement);
881
1169
 
882
1170
  // src/elements/daw-editor.ts
883
- import { LitElement as LitElement9, html as html8, css as css8 } from "lit";
884
- import { customElement as customElement11, property as property7, state as state3 } from "lit/decorators.js";
1171
+ import { LitElement as LitElement10, html as html9, css as css9 } from "lit";
1172
+ import { customElement as customElement12, property as property8, state as state3 } from "lit/decorators.js";
885
1173
 
886
1174
  // src/types.ts
887
1175
  function isDomClip(desc) {
@@ -1366,9 +1654,9 @@ var PeakPipeline = class {
1366
1654
  };
1367
1655
 
1368
1656
  // src/elements/daw-track-controls.ts
1369
- import { LitElement as LitElement7, html as html6, css as css5 } from "lit";
1370
- import { customElement as customElement9, property as property5 } from "lit/decorators.js";
1371
- var DawTrackControlsElement = class extends LitElement7 {
1657
+ import { LitElement as LitElement8, html as html7, css as css6 } from "lit";
1658
+ import { customElement as customElement10, property as property6 } from "lit/decorators.js";
1659
+ var DawTrackControlsElement = class extends LitElement8 {
1372
1660
  constructor() {
1373
1661
  super(...arguments);
1374
1662
  this.trackId = null;
@@ -1416,7 +1704,7 @@ var DawTrackControlsElement = class extends LitElement7 {
1416
1704
  const volPercent = Math.round(this.volume * 100);
1417
1705
  const panPercent = Math.round(Math.abs(this.pan) * 100);
1418
1706
  const panDisplay = this.pan === 0 ? "C" : (this.pan > 0 ? "R" : "L") + panPercent;
1419
- return html6`
1707
+ return html7`
1420
1708
  <div class="header">
1421
1709
  <span class="name" title=${this.trackName}>${this.trackName || "Untitled"}</span>
1422
1710
  <button class="remove-btn" @click=${this._onRemoveClick} title="Remove track">
@@ -1466,7 +1754,7 @@ var DawTrackControlsElement = class extends LitElement7 {
1466
1754
  `;
1467
1755
  }
1468
1756
  };
1469
- DawTrackControlsElement.styles = css5`
1757
+ DawTrackControlsElement.styles = css6`
1470
1758
  :host {
1471
1759
  display: flex;
1472
1760
  flex-direction: column;
@@ -1600,30 +1888,30 @@ DawTrackControlsElement.styles = css5`
1600
1888
  }
1601
1889
  `;
1602
1890
  __decorateClass([
1603
- property5({ attribute: false })
1891
+ property6({ attribute: false })
1604
1892
  ], DawTrackControlsElement.prototype, "trackId", 2);
1605
1893
  __decorateClass([
1606
- property5({ attribute: false })
1894
+ property6({ attribute: false })
1607
1895
  ], DawTrackControlsElement.prototype, "trackName", 2);
1608
1896
  __decorateClass([
1609
- property5({ type: Number, attribute: false })
1897
+ property6({ type: Number, attribute: false })
1610
1898
  ], DawTrackControlsElement.prototype, "volume", 2);
1611
1899
  __decorateClass([
1612
- property5({ type: Number, attribute: false })
1900
+ property6({ type: Number, attribute: false })
1613
1901
  ], DawTrackControlsElement.prototype, "pan", 2);
1614
1902
  __decorateClass([
1615
- property5({ type: Boolean, attribute: false })
1903
+ property6({ type: Boolean, attribute: false })
1616
1904
  ], DawTrackControlsElement.prototype, "muted", 2);
1617
1905
  __decorateClass([
1618
- property5({ type: Boolean, attribute: false })
1906
+ property6({ type: Boolean, attribute: false })
1619
1907
  ], DawTrackControlsElement.prototype, "soloed", 2);
1620
1908
  DawTrackControlsElement = __decorateClass([
1621
- customElement9("daw-track-controls")
1909
+ customElement10("daw-track-controls")
1622
1910
  ], DawTrackControlsElement);
1623
1911
 
1624
1912
  // src/elements/daw-grid.ts
1625
- import { LitElement as LitElement8, html as html7, css as css6 } from "lit";
1626
- import { customElement as customElement10, property as property6 } from "lit/decorators.js";
1913
+ import { LitElement as LitElement9, html as html8, css as css7 } from "lit";
1914
+ import { customElement as customElement11, property as property7 } from "lit/decorators.js";
1627
1915
  import { MIN_PIXELS_PER_UNIT } from "@waveform-playlist/core";
1628
1916
 
1629
1917
  // src/utils/musical-tick-cache.ts
@@ -1654,8 +1942,8 @@ function getCachedMusicalTicks(params) {
1654
1942
  }
1655
1943
 
1656
1944
  // src/elements/daw-grid.ts
1657
- var MAX_CANVAS_WIDTH2 = 1e3;
1658
- var DawGridElement = class extends LitElement8 {
1945
+ var MAX_CANVAS_WIDTH3 = 1e3;
1946
+ var DawGridElement = class extends LitElement9 {
1659
1947
  constructor() {
1660
1948
  super(...arguments);
1661
1949
  this.ticksPerPixel = 24;
@@ -1683,25 +1971,25 @@ var DawGridElement = class extends LitElement8 {
1683
1971
  }
1684
1972
  }
1685
1973
  render() {
1686
- if (!this._tickData) return html7``;
1974
+ if (!this._tickData) return html8``;
1687
1975
  const totalWidth = this.length;
1688
1976
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1689
1977
  const indices = getVisibleChunkIndices(
1690
1978
  totalWidth,
1691
- MAX_CANVAS_WIDTH2,
1979
+ MAX_CANVAS_WIDTH3,
1692
1980
  this.visibleStart,
1693
1981
  this.visibleEnd
1694
1982
  );
1695
- return html7`
1983
+ return html8`
1696
1984
  <div class="container" style="width: ${totalWidth}px; height: ${this.height}px;">
1697
1985
  ${indices.map((i) => {
1698
- const width = Math.min(MAX_CANVAS_WIDTH2, totalWidth - i * MAX_CANVAS_WIDTH2);
1699
- return html7`
1986
+ const width = Math.min(MAX_CANVAS_WIDTH3, totalWidth - i * MAX_CANVAS_WIDTH3);
1987
+ return html8`
1700
1988
  <canvas
1701
1989
  data-index=${i}
1702
1990
  width=${width * dpr}
1703
1991
  height=${this.height * dpr}
1704
- style="left: ${i * MAX_CANVAS_WIDTH2}px; width: ${width}px; height: ${this.height}px;"
1992
+ style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.height}px;"
1705
1993
  ></canvas>
1706
1994
  `;
1707
1995
  })}
@@ -1725,8 +2013,8 @@ var DawGridElement = class extends LitElement8 {
1725
2013
  const idx = Number(canvas.dataset.index);
1726
2014
  const ctx = canvas.getContext("2d");
1727
2015
  if (!ctx) continue;
1728
- const chunkLeft = idx * MAX_CANVAS_WIDTH2;
1729
- const canvasWidth = Math.min(MAX_CANVAS_WIDTH2, this.length - chunkLeft);
2016
+ const chunkLeft = idx * MAX_CANVAS_WIDTH3;
2017
+ const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, this.length - chunkLeft);
1730
2018
  ctx.resetTransform();
1731
2019
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1732
2020
  ctx.scale(dpr, dpr);
@@ -1757,7 +2045,7 @@ var DawGridElement = class extends LitElement8 {
1757
2045
  }
1758
2046
  }
1759
2047
  };
1760
- DawGridElement.styles = css6`
2048
+ DawGridElement.styles = css7`
1761
2049
  :host {
1762
2050
  display: block;
1763
2051
  position: absolute;
@@ -1775,33 +2063,33 @@ DawGridElement.styles = css6`
1775
2063
  }
1776
2064
  `;
1777
2065
  __decorateClass([
1778
- property6({ type: Number, attribute: false })
2066
+ property7({ type: Number, attribute: false })
1779
2067
  ], DawGridElement.prototype, "ticksPerPixel", 2);
1780
2068
  __decorateClass([
1781
- property6({ attribute: false })
2069
+ property7({ attribute: false })
1782
2070
  ], DawGridElement.prototype, "meterEntries", 2);
1783
2071
  __decorateClass([
1784
- property6({ type: Number, attribute: false })
2072
+ property7({ type: Number, attribute: false })
1785
2073
  ], DawGridElement.prototype, "ppqn", 2);
1786
2074
  __decorateClass([
1787
- property6({ type: Number, attribute: false })
2075
+ property7({ type: Number, attribute: false })
1788
2076
  ], DawGridElement.prototype, "visibleStart", 2);
1789
2077
  __decorateClass([
1790
- property6({ type: Number, attribute: false })
2078
+ property7({ type: Number, attribute: false })
1791
2079
  ], DawGridElement.prototype, "visibleEnd", 2);
1792
2080
  __decorateClass([
1793
- property6({ type: Number, attribute: false })
2081
+ property7({ type: Number, attribute: false })
1794
2082
  ], DawGridElement.prototype, "length", 2);
1795
2083
  __decorateClass([
1796
- property6({ type: Number, attribute: false })
2084
+ property7({ type: Number, attribute: false })
1797
2085
  ], DawGridElement.prototype, "height", 2);
1798
2086
  DawGridElement = __decorateClass([
1799
- customElement10("daw-grid")
2087
+ customElement11("daw-grid")
1800
2088
  ], DawGridElement);
1801
2089
 
1802
2090
  // src/styles/theme.ts
1803
- import { css as css7 } from "lit";
1804
- var hostStyles = css7`
2091
+ import { css as css8 } from "lit";
2092
+ var hostStyles = css8`
1805
2093
  :host {
1806
2094
  --daw-wave-color: #c49a6c;
1807
2095
  --daw-progress-color: #63c75f;
@@ -1817,7 +2105,7 @@ var hostStyles = css7`
1817
2105
  --daw-clip-header-text: #e0d4c8;
1818
2106
  }
1819
2107
  `;
1820
- var clipStyles = css7`
2108
+ var clipStyles = css8`
1821
2109
  .clip-container {
1822
2110
  position: absolute;
1823
2111
  overflow: hidden;
@@ -2062,6 +2350,9 @@ var RecordingController = class {
2062
2350
  constructor(host) {
2063
2351
  this._sessions = /* @__PURE__ */ new Map();
2064
2352
  this._workletLoadedCtx = null;
2353
+ /** Tracks worklet pause state explicitly so external consumers (editor,
2354
+ * pause button, spacebar) can share one source of truth. */
2355
+ this._isPaused = false;
2065
2356
  this._host = host;
2066
2357
  host.addController(this);
2067
2358
  }
@@ -2076,6 +2367,9 @@ var RecordingController = class {
2076
2367
  get isRecording() {
2077
2368
  return this._sessions.size > 0;
2078
2369
  }
2370
+ get isPaused() {
2371
+ return this._isPaused && this._sessions.size > 0;
2372
+ }
2079
2373
  getSession(trackId) {
2080
2374
  return this._sessions.get(trackId);
2081
2375
  }
@@ -2148,7 +2442,9 @@ var RecordingController = class {
2148
2442
  latencySamples,
2149
2443
  wasOverdub: options.overdub ?? false,
2150
2444
  _onTrackEnded: onTrackEnded,
2151
- _audioTrack: audioTrack
2445
+ _audioTrack: audioTrack,
2446
+ stopAckResolve: null,
2447
+ stopping: false
2152
2448
  };
2153
2449
  this._sessions.set(trackId, session);
2154
2450
  workletNode.port.onmessage = (e) => {
@@ -2186,25 +2482,69 @@ var RecordingController = class {
2186
2482
  const id = trackId ?? [...this._sessions.keys()][0];
2187
2483
  if (!id) return;
2188
2484
  const session = this._sessions.get(id);
2189
- if (!session) return;
2485
+ if (!session || session.stopping) return;
2190
2486
  session.workletNode.port.postMessage({ command: "pause" });
2487
+ this._isPaused = true;
2488
+ this._host.dispatchEvent(
2489
+ new CustomEvent("daw-recording-pause", {
2490
+ bubbles: true,
2491
+ composed: true,
2492
+ detail: { trackId: id }
2493
+ })
2494
+ );
2191
2495
  }
2192
2496
  resumeRecording(trackId) {
2193
2497
  const id = trackId ?? [...this._sessions.keys()][0];
2194
2498
  if (!id) return;
2195
2499
  const session = this._sessions.get(id);
2196
- if (!session) return;
2500
+ if (!session || session.stopping) return;
2197
2501
  session.workletNode.port.postMessage({ command: "resume" });
2502
+ this._isPaused = false;
2503
+ this._host.dispatchEvent(
2504
+ new CustomEvent("daw-recording-resume", {
2505
+ bubbles: true,
2506
+ composed: true,
2507
+ detail: { trackId: id }
2508
+ })
2509
+ );
2198
2510
  }
2199
- stopRecording(trackId) {
2511
+ async stopRecording(trackId) {
2200
2512
  const id = trackId ?? [...this._sessions.keys()][0];
2201
2513
  if (!id) return;
2202
2514
  const session = this._sessions.get(id);
2203
2515
  if (!session) return;
2516
+ const wasPaused = this._isPaused;
2517
+ this._isPaused = false;
2518
+ session.stopping = true;
2204
2519
  if (session.wasOverdub && typeof this._host.stop === "function") {
2205
2520
  this._host.stop();
2206
2521
  }
2207
- session.workletNode.port.postMessage({ command: "stop" });
2522
+ if (wasPaused) {
2523
+ session.workletNode.port.postMessage({ command: "stop" });
2524
+ } else {
2525
+ const stopAck = new Promise((resolve) => {
2526
+ session.stopAckResolve = resolve;
2527
+ });
2528
+ let timeoutId;
2529
+ const timeout = new Promise((resolve) => {
2530
+ timeoutId = setTimeout(resolve, 1e3);
2531
+ });
2532
+ session.workletNode.port.postMessage({ command: "stop" });
2533
+ await Promise.race([stopAck, timeout]);
2534
+ clearTimeout(timeoutId);
2535
+ session.stopAckResolve = null;
2536
+ let lastSamples = -1;
2537
+ let stable = 0;
2538
+ for (let i = 0; i < 50; i++) {
2539
+ if (session.totalSamples === lastSamples) {
2540
+ if (++stable >= 3) break;
2541
+ } else {
2542
+ stable = 0;
2543
+ lastSamples = session.totalSamples;
2544
+ }
2545
+ await new Promise((r) => setTimeout(r, 5));
2546
+ }
2547
+ }
2208
2548
  session.source.disconnect();
2209
2549
  session.workletNode.disconnect();
2210
2550
  this._removeTrackEndedListener(session);
@@ -2276,8 +2616,20 @@ var RecordingController = class {
2276
2616
  _onWorkletMessage(trackId, data) {
2277
2617
  const session = this._sessions.get(trackId);
2278
2618
  if (!session) return;
2279
- const { channels } = data;
2280
- if (!channels || channels.length === 0 || !channels[0]) return;
2619
+ const { channels, done } = data;
2620
+ try {
2621
+ const hasSamples = !!(channels && channels.length > 0 && channels[0] && channels[0].length > 0);
2622
+ if (!hasSamples) return;
2623
+ this._processWorkletSamples(trackId, session, channels);
2624
+ } finally {
2625
+ if (done && session.stopAckResolve) {
2626
+ const resolve = session.stopAckResolve;
2627
+ session.stopAckResolve = null;
2628
+ resolve();
2629
+ }
2630
+ }
2631
+ }
2632
+ _processWorkletSamples(trackId, session, channels) {
2281
2633
  const samplesProcessedBefore = session.totalSamples;
2282
2634
  for (let ch = 0; ch < session.channelCount; ch++) {
2283
2635
  if (channels[ch]) {
@@ -2285,6 +2637,7 @@ var RecordingController = class {
2285
2637
  }
2286
2638
  }
2287
2639
  session.totalSamples += channels[0].length;
2640
+ if (session.stopAckResolve !== null) return;
2288
2641
  for (let ch = 0; ch < session.channelCount; ch++) {
2289
2642
  if (!channels[ch]) continue;
2290
2643
  const oldPeakCount = Math.floor(session.peaks[ch].length / 2);
@@ -2619,6 +2972,7 @@ var ClipPointerHandler = class {
2619
2972
  const trackId = boundary.dataset.trackId;
2620
2973
  const edge = boundary.dataset.boundaryEdge;
2621
2974
  if (!clipId || !trackId || edge !== "left" && edge !== "right") return false;
2975
+ if (this._host.isMidiClip(trackId, clipId)) return true;
2622
2976
  this._beginDrag(edge === "left" ? "trim-left" : "trim-right", clipId, trackId, e);
2623
2977
  this._boundaryEl = boundary;
2624
2978
  return true;
@@ -2912,6 +3266,7 @@ async function loadFiles(host, files) {
2912
3266
  pan: 0,
2913
3267
  muted: false,
2914
3268
  soloed: false,
3269
+ renderMode: "waveform",
2915
3270
  clips: [
2916
3271
  {
2917
3272
  kind: "drop",
@@ -2924,7 +3279,10 @@ async function loadFiles(host, files) {
2924
3279
  name,
2925
3280
  fadeIn: 0,
2926
3281
  fadeOut: 0,
2927
- fadeType: "linear"
3282
+ fadeType: "linear",
3283
+ midiNotes: null,
3284
+ midiChannel: null,
3285
+ midiProgram: null
2928
3286
  }
2929
3287
  ]
2930
3288
  });
@@ -3011,7 +3369,10 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamp
3011
3369
  name: "Recording",
3012
3370
  fadeIn: 0,
3013
3371
  fadeOut: 0,
3014
- fadeType: "linear"
3372
+ fadeType: "linear",
3373
+ midiNotes: null,
3374
+ midiChannel: null,
3375
+ midiProgram: null
3015
3376
  };
3016
3377
  host._tracks = new Map(host._tracks).set(trackId, {
3017
3378
  ...desc,
@@ -3070,7 +3431,10 @@ function canSplitAtTime(host, time) {
3070
3431
  const track = state5.tracks.find((t) => t.id === state5.selectedTrackId);
3071
3432
  if (!track) return false;
3072
3433
  const atSample = Math.round(time * host.effectiveSampleRate);
3073
- return !!findClipAtSample(track.clips, atSample);
3434
+ const clip = findClipAtSample(track.clips, atSample);
3435
+ if (!clip) return false;
3436
+ if (clip.midiNotes != null) return false;
3437
+ return true;
3074
3438
  }
3075
3439
  function performSplit(host, time) {
3076
3440
  const { engine } = host;
@@ -3225,7 +3589,7 @@ async function loadWaveformDataFromUrl(src) {
3225
3589
 
3226
3590
  // src/elements/daw-editor.ts
3227
3591
  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 LitElement9 {
3592
+ var DawEditorElement = class extends LitElement10 {
3229
3593
  constructor() {
3230
3594
  super(...arguments);
3231
3595
  this._samplesPerPixel = 1024;
@@ -3384,7 +3748,10 @@ var DawEditorElement = class extends LitElement9 {
3384
3748
  name: clipEl.name,
3385
3749
  fadeIn: clipEl.fadeIn,
3386
3750
  fadeOut: clipEl.fadeOut,
3387
- fadeType: clipEl.fadeType
3751
+ fadeType: clipEl.fadeType,
3752
+ midiNotes: clipEl.midiNotes,
3753
+ midiChannel: clipEl.midiChannel,
3754
+ midiProgram: clipEl.midiProgram
3388
3755
  };
3389
3756
  this._loadAndAppendClip(trackId, clipDesc);
3390
3757
  };
@@ -3435,6 +3802,9 @@ var DawEditorElement = class extends LitElement9 {
3435
3802
  };
3436
3803
  // --- Recording ---
3437
3804
  this.recordingStream = null;
3805
+ /** Set in togglePauseRecording when Transport is paused alongside the
3806
+ * worklet, so resume can restart it. Cleared on resume and on stop. */
3807
+ this._wasPlayingDuringRecording = false;
3438
3808
  }
3439
3809
  get samplesPerPixel() {
3440
3810
  return this._samplesPerPixel;
@@ -3534,6 +3904,17 @@ var DawEditorElement = class extends LitElement9 {
3534
3904
  );
3535
3905
  return result.get(clipId) ?? null;
3536
3906
  }
3907
+ /**
3908
+ * Returns true if the clip is a MIDI clip (has midiNotes).
3909
+ * Used by ClipPointerHandler to make trim handles inert for MIDI clips.
3910
+ * Returns false for unknown track/clip IDs (defensive).
3911
+ */
3912
+ isMidiClip(trackId, clipId) {
3913
+ const track = this._engineTracks.get(trackId);
3914
+ if (!track) return false;
3915
+ const clip = track.clips.find((c) => c.id === clipId);
3916
+ return clip?.midiNotes != null;
3917
+ }
3537
3918
  get effectiveSampleRate() {
3538
3919
  return this._resolvedSampleRate ?? this.sampleRate;
3539
3920
  }
@@ -3883,6 +4264,65 @@ var DawEditorElement = class extends LitElement9 {
3883
4264
  }
3884
4265
  return clip;
3885
4266
  }
4267
+ /**
4268
+ * Filter MIDI notes to only those with finite, in-range fields. Logs a
4269
+ * warning for each dropped note. Used by _buildMidiClip and the
4270
+ * _applyClipUpdate MIDI branch to prevent NaN propagation through the
4271
+ * timeline.
4272
+ */
4273
+ _validMidiNotes(notes) {
4274
+ return notes.filter((n) => {
4275
+ 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;
4276
+ if (!ok) {
4277
+ console.warn("[dawcore] dropping malformed MIDI note: " + JSON.stringify(n));
4278
+ }
4279
+ return ok;
4280
+ });
4281
+ }
4282
+ /**
4283
+ * A clip descriptor is treated as MIDI when it has no audio src.
4284
+ * Includes placeholder MIDI clips (no notes, no duration yet — registered
4285
+ * with a 1s default span; notes upgrade via _applyClipUpdate). Warns when
4286
+ * a clip ambiguously has both src and midiNotes — the audio path runs
4287
+ * and notes would be silently ignored.
4288
+ */
4289
+ _isMidiDescriptor(clipDesc) {
4290
+ if (clipDesc.src) {
4291
+ if (clipDesc.midiNotes != null) {
4292
+ console.warn(
4293
+ '[dawcore] clip "' + (clipDesc.name || (isDomClip(clipDesc) ? clipDesc.clipId : "?")) + '" has both src and midiNotes \u2014 treating as audio (notes will be ignored)'
4294
+ );
4295
+ }
4296
+ return false;
4297
+ }
4298
+ return true;
4299
+ }
4300
+ /**
4301
+ * Build an engine clip from a MIDI clip descriptor. Always returns a clip
4302
+ * — empty notes / no declared duration get a 1-second placeholder span so
4303
+ * the clip is reachable via `engine.updateTrack` once notes arrive.
4304
+ */
4305
+ _buildMidiClip(clipDesc) {
4306
+ const sr = this.effectiveSampleRate;
4307
+ const notes = this._validMidiNotes(clipDesc.midiNotes ?? []);
4308
+ const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
4309
+ const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipDesc.duration, 1) * sr);
4310
+ const requestedDurationSamples = clipDesc.duration > 0 ? Math.round(clipDesc.duration * sr) : sourceDurationSamples;
4311
+ const clip = createClip3({
4312
+ startSample: Math.round(clipDesc.start * sr),
4313
+ durationSamples: requestedDurationSamples,
4314
+ offsetSamples: Math.round(clipDesc.offset * sr),
4315
+ sampleRate: sr,
4316
+ sourceDurationSamples,
4317
+ gain: clipDesc.gain,
4318
+ name: clipDesc.name,
4319
+ midiNotes: notes,
4320
+ midiChannel: clipDesc.midiChannel ?? void 0,
4321
+ midiProgram: clipDesc.midiProgram ?? void 0
4322
+ });
4323
+ if (isDomClip(clipDesc)) clip.id = clipDesc.clipId;
4324
+ return clip;
4325
+ }
3886
4326
  /** Remove a single clip from all per-clip caches. Used by error rollbacks. */
3887
4327
  _purgeClipCaches(clipId) {
3888
4328
  const nextBuffers = new Map(this._clipBuffers);
@@ -3920,6 +4360,34 @@ var DawEditorElement = class extends LitElement9 {
3920
4360
  }
3921
4361
  const oldClip = t.clips[idx];
3922
4362
  const sr = oldClip.sampleRate ?? this.effectiveSampleRate;
4363
+ const isMidiNow = clipEl.midiNotes != null;
4364
+ const wasMidi = oldClip.midiNotes != null;
4365
+ if (isMidiNow || wasMidi) {
4366
+ const notes = this._validMidiNotes(clipEl.midiNotes ?? []);
4367
+ const noteSpanSeconds = notes.length ? notes.reduce((max, n) => Math.max(max, n.time + n.duration), 0) : 0;
4368
+ const sourceDurationSamples = Math.ceil(Math.max(noteSpanSeconds, clipEl.duration, 1) * sr);
4369
+ const requestedDurationSamples = clipEl.duration > 0 ? Math.round(clipEl.duration * sr) : sourceDurationSamples;
4370
+ const updatedClip2 = {
4371
+ ...oldClip,
4372
+ audioBuffer: void 0,
4373
+ startSample: Math.round(clipEl.start * sr),
4374
+ offsetSamples: Math.round(clipEl.offset * sr),
4375
+ durationSamples: requestedDurationSamples,
4376
+ sourceDurationSamples,
4377
+ gain: clipEl.gain,
4378
+ name: clipEl.name || oldClip.name,
4379
+ midiNotes: notes,
4380
+ midiChannel: clipEl.midiChannel ?? void 0,
4381
+ midiProgram: clipEl.midiProgram ?? void 0
4382
+ };
4383
+ const updatedClips2 = [...t.clips];
4384
+ updatedClips2[idx] = updatedClip2;
4385
+ const updatedTrack2 = { ...t, clips: updatedClips2 };
4386
+ this._engineTracks = new Map(this._engineTracks).set(trackId, updatedTrack2);
4387
+ this._purgeClipCaches(clipId);
4388
+ this._commitTrackChange(trackId, updatedTrack2);
4389
+ return;
4390
+ }
3923
4391
  const newStartSample = Math.round(clipEl.start * sr);
3924
4392
  const newDurationSamples = clipEl.duration > 0 ? Math.round(clipEl.duration * sr) : oldClip.durationSamples;
3925
4393
  const newOffsetSamples = Math.round(clipEl.offset * sr);
@@ -3996,7 +4464,10 @@ var DawEditorElement = class extends LitElement9 {
3996
4464
  name: trackEl.name || "",
3997
4465
  fadeIn: 0,
3998
4466
  fadeOut: 0,
3999
- fadeType: "linear"
4467
+ fadeType: "linear",
4468
+ midiNotes: null,
4469
+ midiChannel: null,
4470
+ midiProgram: null
4000
4471
  });
4001
4472
  } else {
4002
4473
  for (const clipEl of clipEls) {
@@ -4012,7 +4483,10 @@ var DawEditorElement = class extends LitElement9 {
4012
4483
  name: clipEl.name,
4013
4484
  fadeIn: clipEl.fadeIn,
4014
4485
  fadeOut: clipEl.fadeOut,
4015
- fadeType: clipEl.fadeType
4486
+ fadeType: clipEl.fadeType,
4487
+ midiNotes: clipEl.midiNotes,
4488
+ midiChannel: clipEl.midiChannel,
4489
+ midiProgram: clipEl.midiProgram
4016
4490
  });
4017
4491
  }
4018
4492
  }
@@ -4023,6 +4497,7 @@ var DawEditorElement = class extends LitElement9 {
4023
4497
  pan: trackEl.pan,
4024
4498
  muted: trackEl.muted,
4025
4499
  soloed: trackEl.soloed,
4500
+ renderMode: trackEl.renderMode,
4026
4501
  clips
4027
4502
  };
4028
4503
  }
@@ -4031,7 +4506,10 @@ var DawEditorElement = class extends LitElement9 {
4031
4506
  try {
4032
4507
  const clips = [];
4033
4508
  for (const clipDesc of descriptor.clips) {
4034
- if (!clipDesc.src) continue;
4509
+ if (this._isMidiDescriptor(clipDesc)) {
4510
+ clips.push(this._buildMidiClip(clipDesc));
4511
+ continue;
4512
+ }
4035
4513
  try {
4036
4514
  const waveformDataPromise = clipDesc.peaksSrc ? this._resolvePeaks(clipDesc.peaksSrc) : Promise.resolve(null);
4037
4515
  const audioPromise = this._fetchAndDecode(clipDesc.src);
@@ -4243,7 +4721,11 @@ var DawEditorElement = class extends LitElement9 {
4243
4721
  nextTracks.set(track.id, track);
4244
4722
  }
4245
4723
  this._engineTracks = nextTracks;
4246
- syncPeaksForChangedClips(this, engineState.tracks);
4724
+ const audioTracks = engineState.tracks.filter((t) => {
4725
+ const desc = this._tracks.get(t.id);
4726
+ return desc?.renderMode !== "piano-roll";
4727
+ });
4728
+ syncPeaksForChangedClips(this, audioTracks);
4247
4729
  }
4248
4730
  });
4249
4731
  engine.on("pause", () => {
@@ -4318,7 +4800,17 @@ var DawEditorElement = class extends LitElement9 {
4318
4800
  if (config.pan !== void 0) trackEl.pan = config.pan;
4319
4801
  if (config.muted) trackEl.setAttribute("muted", "");
4320
4802
  if (config.soloed) trackEl.setAttribute("soloed", "");
4321
- for (const clipConfig of config.clips ?? []) {
4803
+ const renderMode = config.renderMode ?? (config.midi ? "piano-roll" : void 0);
4804
+ if (renderMode !== void 0) trackEl.setAttribute("render-mode", renderMode);
4805
+ const clipConfigs = [...config.clips ?? []];
4806
+ if (config.midi) {
4807
+ clipConfigs.push({
4808
+ midiNotes: config.midi.notes,
4809
+ midiChannel: config.midi.channel,
4810
+ midiProgram: config.midi.program
4811
+ });
4812
+ }
4813
+ for (const clipConfig of clipConfigs) {
4322
4814
  trackEl.appendChild(this._buildClipElement(clipConfig));
4323
4815
  }
4324
4816
  return this._awaitId(
@@ -4364,6 +4856,7 @@ var DawEditorElement = class extends LitElement9 {
4364
4856
  if (partial.soloed) trackEl.setAttribute("soloed", "");
4365
4857
  else trackEl.removeAttribute("soloed");
4366
4858
  }
4859
+ if (partial.renderMode !== void 0) trackEl.setAttribute("render-mode", partial.renderMode);
4367
4860
  return;
4368
4861
  }
4369
4862
  const oldDesc = this._tracks.get(trackId);
@@ -4374,7 +4867,8 @@ var DawEditorElement = class extends LitElement9 {
4374
4867
  ...partial.volume !== void 0 && { volume: partial.volume },
4375
4868
  ...partial.pan !== void 0 && { pan: partial.pan },
4376
4869
  ...partial.muted !== void 0 && { muted: partial.muted },
4377
- ...partial.soloed !== void 0 && { soloed: partial.soloed }
4870
+ ...partial.soloed !== void 0 && { soloed: partial.soloed },
4871
+ ...partial.renderMode !== void 0 && { renderMode: partial.renderMode }
4378
4872
  };
4379
4873
  this._tracks = new Map(this._tracks).set(trackId, newDesc);
4380
4874
  if (this._engine) {
@@ -4509,6 +5003,11 @@ var DawEditorElement = class extends LitElement9 {
4509
5003
  if (config.fadeIn !== void 0) clipEl.fadeIn = config.fadeIn;
4510
5004
  if (config.fadeOut !== void 0) clipEl.fadeOut = config.fadeOut;
4511
5005
  if (config.fadeType !== void 0) clipEl.setAttribute("fade-type", config.fadeType);
5006
+ if (config.midiNotes !== void 0) clipEl.midiNotes = config.midiNotes;
5007
+ if (config.midiChannel !== void 0)
5008
+ clipEl.setAttribute("midi-channel", String(config.midiChannel));
5009
+ if (config.midiProgram !== void 0)
5010
+ clipEl.setAttribute("midi-program", String(config.midiProgram));
4512
5011
  return clipEl;
4513
5012
  }
4514
5013
  // --- Playback ---
@@ -4544,7 +5043,9 @@ var DawEditorElement = class extends LitElement9 {
4544
5043
  }
4545
5044
  /** Toggle between play and pause. */
4546
5045
  togglePlayPause() {
4547
- if (this._isPlaying) {
5046
+ if (this.isRecording) {
5047
+ this.togglePauseRecording();
5048
+ } else if (this._isPlaying) {
4548
5049
  this.pause();
4549
5050
  } else {
4550
5051
  this.play();
@@ -4618,14 +5119,41 @@ var DawEditorElement = class extends LitElement9 {
4618
5119
  get isRecording() {
4619
5120
  return this._recordingController.isRecording;
4620
5121
  }
5122
+ get isRecordingPaused() {
5123
+ return this._recordingController.isPaused;
5124
+ }
4621
5125
  pauseRecording() {
4622
5126
  this._recordingController.pauseRecording();
4623
5127
  }
4624
5128
  resumeRecording() {
4625
5129
  this._recordingController.resumeRecording();
5130
+ this._wasPlayingDuringRecording = false;
5131
+ }
5132
+ /**
5133
+ * Audacity-style pause toggle for active recordings: pauses both the
5134
+ * worklet capture and (if running) the playback Transport. On resume,
5135
+ * Transport restarts only if it was running before — non-overdub
5136
+ * recordings stay silent on resume.
5137
+ */
5138
+ togglePauseRecording() {
5139
+ if (!this.isRecording) return;
5140
+ if (this.isRecordingPaused) {
5141
+ const wasPlaying = this._wasPlayingDuringRecording;
5142
+ this.resumeRecording();
5143
+ if (wasPlaying) {
5144
+ void this.play(this.currentTime);
5145
+ }
5146
+ } else {
5147
+ this.pauseRecording();
5148
+ if (this._isPlaying) {
5149
+ this._wasPlayingDuringRecording = true;
5150
+ this.pause();
5151
+ }
5152
+ }
4626
5153
  }
4627
5154
  stopRecording() {
4628
- this._recordingController.stopRecording();
5155
+ this._wasPlayingDuringRecording = false;
5156
+ return this._recordingController.stopRecording();
4629
5157
  }
4630
5158
  _addRecordedClip(trackId, buf, startSample, durSamples, offsetSamples = 0) {
4631
5159
  addRecordedClip(this, trackId, buf, startSample, durSamples, offsetSamples);
@@ -4661,7 +5189,7 @@ var DawEditorElement = class extends LitElement9 {
4661
5189
  const w = Math.floor(audibleSamples / renderSpp);
4662
5190
  return rs.peaks.map((chPeaks, ch) => {
4663
5191
  const slicedPeaks = latencyPixels > 0 ? chPeaks.slice(latencyPixels * 2) : chPeaks;
4664
- return html8`
5192
+ return html9`
4665
5193
  <daw-waveform
4666
5194
  data-recording-track=${trackId}
4667
5195
  data-recording-channel=${ch}
@@ -4758,11 +5286,11 @@ var DawEditorElement = class extends LitElement9 {
4758
5286
  trackHeight: this.waveHeight * numChannels + (this.clipHeaders ? this.clipHeaderHeight : 0)
4759
5287
  };
4760
5288
  });
4761
- return html8`
4762
- ${orderedTracks.length > 0 || this.indefinitePlayback ? html8`<div class="controls-column">
4763
- ${this.timescale ? html8`<div style="height: 30px;"></div>` : ""}
5289
+ return html9`
5290
+ ${orderedTracks.length > 0 || this.indefinitePlayback ? html9`<div class="controls-column">
5291
+ ${this.timescale ? html9`<div style="height: 30px;"></div>` : ""}
4764
5292
  ${orderedTracks.map(
4765
- (t) => html8`
5293
+ (t) => html9`
4766
5294
  <daw-track-controls
4767
5295
  style="height: ${t.trackHeight}px;"
4768
5296
  .trackId=${t.trackId}
@@ -4785,7 +5313,7 @@ var DawEditorElement = class extends LitElement9 {
4785
5313
  @dragleave=${this._onDragLeave}
4786
5314
  @drop=${this._onDrop}
4787
5315
  >
4788
- ${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ? html8`<daw-ruler
5316
+ ${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ? html9`<daw-ruler
4789
5317
  .samplesPerPixel=${spp}
4790
5318
  .sampleRate=${this.effectiveSampleRate}
4791
5319
  .duration=${this._duration}
@@ -4795,7 +5323,7 @@ var DawEditorElement = class extends LitElement9 {
4795
5323
  .ppqn=${this.ppqn}
4796
5324
  .totalWidth=${this._totalWidth}
4797
5325
  ></daw-ruler>` : ""}
4798
- ${this.scaleMode === "beats" ? html8`<daw-grid
5326
+ ${this.scaleMode === "beats" ? html9`<daw-grid
4799
5327
  style="top: ${this.timescale ? 30 : 0}px;"
4800
5328
  .ticksPerPixel=${this.ticksPerPixel}
4801
5329
  .meterEntries=${this._meterEntries}
@@ -4805,11 +5333,11 @@ var DawEditorElement = class extends LitElement9 {
4805
5333
  .length=${this._totalWidth}
4806
5334
  .height=${orderedTracks.length > 0 ? orderedTracks.reduce((sum, t) => sum + t.trackHeight + 1, 0) : this._emptyGridHeight}
4807
5335
  ></daw-grid>` : ""}
4808
- ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? html8`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
5336
+ ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? html9`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
4809
5337
  <daw-playhead></daw-playhead>` : ""}
4810
5338
  ${orderedTracks.map((t) => {
4811
5339
  const channelHeight = this.waveHeight;
4812
- return html8`
5340
+ return html9`
4813
5341
  <div
4814
5342
  class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
4815
5343
  style="height: ${t.trackHeight}px;"
@@ -4872,12 +5400,12 @@ var DawEditorElement = class extends LitElement9 {
4872
5400
  const channels = segmentChannels ?? peakData?.data ?? [new Int16Array(0)];
4873
5401
  const hdrH = this.clipHeaders ? this.clipHeaderHeight : 0;
4874
5402
  const chH = this.waveHeight;
4875
- return html8` <div
5403
+ return html9` <div
4876
5404
  class="clip-container"
4877
5405
  style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
4878
5406
  data-clip-id=${clip.id}
4879
5407
  >
4880
- ${hdrH > 0 ? html8`<div
5408
+ ${hdrH > 0 ? html9`<div
4881
5409
  class="clip-header"
4882
5410
  data-clip-id=${clip.id}
4883
5411
  data-track-id=${t.trackId}
@@ -4885,21 +5413,33 @@ var DawEditorElement = class extends LitElement9 {
4885
5413
  >
4886
5414
  <span>${clip.name || t.descriptor?.name || ""}</span>
4887
5415
  </div>` : ""}
4888
- ${channels.map(
4889
- (chPeaks, chIdx) => html8` <daw-waveform
4890
- style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
4891
- .peaks=${chPeaks}
5416
+ ${t.descriptor?.renderMode === "piano-roll" ? html9`<daw-piano-roll
5417
+ style="position:absolute;left:0;top:${hdrH}px;"
5418
+ .midiNotes=${clip.midiNotes ?? []}
4892
5419
  .length=${peakData?.length ?? width}
4893
- .waveHeight=${chH}
4894
- .barWidth=${this.barWidth}
4895
- .barGap=${this.barGap}
5420
+ .waveHeight=${chH * channels.length}
5421
+ .samplesPerPixel=${this._renderSpp}
5422
+ .sampleRate=${this.effectiveSampleRate}
5423
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
4896
5424
  .visibleStart=${this._viewport.visibleStart}
4897
5425
  .visibleEnd=${this._viewport.visibleEnd}
4898
5426
  .originX=${clipLeft}
4899
- .segments=${clipSegments}
4900
- ></daw-waveform>`
5427
+ ?selected=${t.trackId === this._selectedTrackId}
5428
+ ></daw-piano-roll>` : channels.map(
5429
+ (chPeaks, chIdx) => html9` <daw-waveform
5430
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5431
+ .peaks=${chPeaks}
5432
+ .length=${peakData?.length ?? width}
5433
+ .waveHeight=${chH}
5434
+ .barWidth=${this.barWidth}
5435
+ .barGap=${this.barGap}
5436
+ .visibleStart=${this._viewport.visibleStart}
5437
+ .visibleEnd=${this._viewport.visibleEnd}
5438
+ .originX=${clipLeft}
5439
+ .segments=${clipSegments}
5440
+ ></daw-waveform>`
4901
5441
  )}
4902
- ${this.interactiveClips ? html8` <div
5442
+ ${this.interactiveClips ? html9` <div
4903
5443
  class="clip-boundary"
4904
5444
  data-boundary-edge="left"
4905
5445
  data-clip-id=${clip.id}
@@ -4925,7 +5465,7 @@ var DawEditorElement = class extends LitElement9 {
4925
5465
  };
4926
5466
  DawEditorElement.styles = [
4927
5467
  hostStyles,
4928
- css8`
5468
+ css9`
4929
5469
  :host {
4930
5470
  display: flex;
4931
5471
  position: relative;
@@ -4973,64 +5513,64 @@ DawEditorElement.styles = [
4973
5513
  ];
4974
5514
  DawEditorElement._CONTROL_PROPS = /* @__PURE__ */ new Set(["volume", "pan", "muted", "soloed"]);
4975
5515
  __decorateClass([
4976
- property7({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
5516
+ property8({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
4977
5517
  ], DawEditorElement.prototype, "samplesPerPixel", 1);
4978
5518
  __decorateClass([
4979
- property7({ type: Number, attribute: "wave-height" })
5519
+ property8({ type: Number, attribute: "wave-height" })
4980
5520
  ], DawEditorElement.prototype, "waveHeight", 2);
4981
5521
  __decorateClass([
4982
- property7({ type: Boolean })
5522
+ property8({ type: Boolean })
4983
5523
  ], DawEditorElement.prototype, "timescale", 2);
4984
5524
  __decorateClass([
4985
- property7({ type: Boolean })
5525
+ property8({ type: Boolean })
4986
5526
  ], DawEditorElement.prototype, "mono", 2);
4987
5527
  __decorateClass([
4988
- property7({ type: Number, attribute: "bar-width" })
5528
+ property8({ type: Number, attribute: "bar-width" })
4989
5529
  ], DawEditorElement.prototype, "barWidth", 2);
4990
5530
  __decorateClass([
4991
- property7({ type: Number, attribute: "bar-gap" })
5531
+ property8({ type: Number, attribute: "bar-gap" })
4992
5532
  ], DawEditorElement.prototype, "barGap", 2);
4993
5533
  __decorateClass([
4994
- property7({ type: Boolean, attribute: "file-drop" })
5534
+ property8({ type: Boolean, attribute: "file-drop" })
4995
5535
  ], DawEditorElement.prototype, "fileDrop", 2);
4996
5536
  __decorateClass([
4997
- property7({ type: Boolean, attribute: "clip-headers" })
5537
+ property8({ type: Boolean, attribute: "clip-headers" })
4998
5538
  ], DawEditorElement.prototype, "clipHeaders", 2);
4999
5539
  __decorateClass([
5000
- property7({ type: Number, attribute: "clip-header-height" })
5540
+ property8({ type: Number, attribute: "clip-header-height" })
5001
5541
  ], DawEditorElement.prototype, "clipHeaderHeight", 2);
5002
5542
  __decorateClass([
5003
- property7({ type: Boolean, attribute: "interactive-clips" })
5543
+ property8({ type: Boolean, attribute: "interactive-clips" })
5004
5544
  ], DawEditorElement.prototype, "interactiveClips", 2);
5005
5545
  __decorateClass([
5006
- property7({ type: Boolean, attribute: "indefinite-playback" })
5546
+ property8({ type: Boolean, attribute: "indefinite-playback" })
5007
5547
  ], DawEditorElement.prototype, "indefinitePlayback", 2);
5008
5548
  __decorateClass([
5009
- property7({ type: String, attribute: "scale-mode" })
5549
+ property8({ type: String, attribute: "scale-mode" })
5010
5550
  ], DawEditorElement.prototype, "scaleMode", 2);
5011
5551
  __decorateClass([
5012
- property7({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
5552
+ property8({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
5013
5553
  ], DawEditorElement.prototype, "ticksPerPixel", 1);
5014
5554
  __decorateClass([
5015
- property7({ type: Number, noAccessor: true })
5555
+ property8({ type: Number, noAccessor: true })
5016
5556
  ], DawEditorElement.prototype, "bpm", 1);
5017
5557
  __decorateClass([
5018
- property7({ attribute: false })
5558
+ property8({ attribute: false })
5019
5559
  ], DawEditorElement.prototype, "timeSignature", 2);
5020
5560
  __decorateClass([
5021
- property7({ attribute: false })
5561
+ property8({ attribute: false })
5022
5562
  ], DawEditorElement.prototype, "meterEntries", 2);
5023
5563
  __decorateClass([
5024
- property7({ type: Number, noAccessor: true })
5564
+ property8({ type: Number, noAccessor: true })
5025
5565
  ], DawEditorElement.prototype, "ppqn", 1);
5026
5566
  __decorateClass([
5027
- property7({ type: String, attribute: "snap-to" })
5567
+ property8({ type: String, attribute: "snap-to" })
5028
5568
  ], DawEditorElement.prototype, "snapTo", 2);
5029
5569
  __decorateClass([
5030
- property7({ attribute: false })
5570
+ property8({ attribute: false })
5031
5571
  ], DawEditorElement.prototype, "secondsToTicks", 2);
5032
5572
  __decorateClass([
5033
- property7({ attribute: false })
5573
+ property8({ attribute: false })
5034
5574
  ], DawEditorElement.prototype, "ticksToSeconds", 2);
5035
5575
  __decorateClass([
5036
5576
  state3()
@@ -5054,18 +5594,18 @@ __decorateClass([
5054
5594
  state3()
5055
5595
  ], DawEditorElement.prototype, "_dragOver", 2);
5056
5596
  __decorateClass([
5057
- property7({ attribute: false })
5597
+ property8({ attribute: false })
5058
5598
  ], DawEditorElement.prototype, "adapter", 1);
5059
5599
  __decorateClass([
5060
- property7({ attribute: "eager-resume" })
5600
+ property8({ attribute: "eager-resume" })
5061
5601
  ], DawEditorElement.prototype, "eagerResume", 2);
5062
5602
  DawEditorElement = __decorateClass([
5063
- customElement11("daw-editor")
5603
+ customElement12("daw-editor")
5064
5604
  ], DawEditorElement);
5065
5605
 
5066
5606
  // src/elements/daw-ruler.ts
5067
- import { LitElement as LitElement10, html as html9, css as css9 } from "lit";
5068
- import { customElement as customElement12, property as property8 } from "lit/decorators.js";
5607
+ import { LitElement as LitElement11, html as html10, css as css10 } from "lit";
5608
+ import { customElement as customElement13, property as property9 } from "lit/decorators.js";
5069
5609
 
5070
5610
  // src/utils/time-format.ts
5071
5611
  function formatTime(milliseconds) {
@@ -5116,8 +5656,8 @@ function computeTemporalTicks(samplesPerPixel, sampleRate, duration, rulerHeight
5116
5656
  }
5117
5657
 
5118
5658
  // src/elements/daw-ruler.ts
5119
- var MAX_CANVAS_WIDTH3 = 1e3;
5120
- var DawRulerElement = class extends LitElement10 {
5659
+ var MAX_CANVAS_WIDTH4 = 1e3;
5660
+ var DawRulerElement = class extends LitElement11 {
5121
5661
  constructor() {
5122
5662
  super(...arguments);
5123
5663
  this.samplesPerPixel = 1024;
@@ -5161,33 +5701,33 @@ var DawRulerElement = class extends LitElement10 {
5161
5701
  }
5162
5702
  render() {
5163
5703
  const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
5164
- if (widthX <= 0) return html9``;
5165
- const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH3);
5704
+ if (widthX <= 0) return html10``;
5705
+ const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH4);
5166
5706
  const indices = Array.from({ length: totalChunks }, (_, i) => i);
5167
5707
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
5168
5708
  const beatsLabels = this.scaleMode === "beats" ? this._musicalTickData?.ticks.filter((t) => t.label) ?? [] : [];
5169
5709
  const temporalLabels = this.scaleMode !== "beats" ? this._tickData?.labels ?? [] : [];
5170
- return html9`
5710
+ return html10`
5171
5711
  <div class="container" style="width: ${widthX}px; height: ${this.rulerHeight}px;">
5172
5712
  ${indices.map((i) => {
5173
- const width = Math.min(MAX_CANVAS_WIDTH3, widthX - i * MAX_CANVAS_WIDTH3);
5174
- return html9`
5713
+ const width = Math.min(MAX_CANVAS_WIDTH4, widthX - i * MAX_CANVAS_WIDTH4);
5714
+ return html10`
5175
5715
  <canvas
5176
5716
  data-index=${i}
5177
5717
  width=${width * dpr}
5178
5718
  height=${this.rulerHeight * dpr}
5179
- style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.rulerHeight}px;"
5719
+ style="left: ${i * MAX_CANVAS_WIDTH4}px; width: ${width}px; height: ${this.rulerHeight}px;"
5180
5720
  ></canvas>
5181
5721
  `;
5182
5722
  })}
5183
5723
  ${this.scaleMode === "beats" ? beatsLabels.map(
5184
- (t) => html9`<span
5724
+ (t) => html10`<span
5185
5725
  class="label ${t.pixel > 0 ? "centered" : ""}"
5186
5726
  style="left: ${t.pixel > 0 ? t.pixel : t.pixel + 4}px;"
5187
5727
  >${t.label}</span
5188
5728
  >`
5189
5729
  ) : temporalLabels.map(
5190
- ({ pix, text }) => html9`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
5730
+ ({ pix, text }) => html10`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
5191
5731
  )}
5192
5732
  </div>
5193
5733
  `;
@@ -5205,8 +5745,8 @@ var DawRulerElement = class extends LitElement10 {
5205
5745
  const idx = Number(canvas.dataset.index);
5206
5746
  const ctx = canvas.getContext("2d");
5207
5747
  if (!ctx) continue;
5208
- const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, widthX - idx * MAX_CANVAS_WIDTH3);
5209
- const globalOffset = idx * MAX_CANVAS_WIDTH3;
5748
+ const canvasWidth = Math.min(MAX_CANVAS_WIDTH4, widthX - idx * MAX_CANVAS_WIDTH4);
5749
+ const globalOffset = idx * MAX_CANVAS_WIDTH4;
5210
5750
  ctx.resetTransform();
5211
5751
  ctx.clearRect(0, 0, canvas.width, canvas.height);
5212
5752
  ctx.scale(dpr, dpr);
@@ -5238,7 +5778,7 @@ var DawRulerElement = class extends LitElement10 {
5238
5778
  }
5239
5779
  }
5240
5780
  };
5241
- DawRulerElement.styles = css9`
5781
+ DawRulerElement.styles = css10`
5242
5782
  :host {
5243
5783
  display: block;
5244
5784
  position: relative;
@@ -5264,40 +5804,40 @@ DawRulerElement.styles = css9`
5264
5804
  }
5265
5805
  `;
5266
5806
  __decorateClass([
5267
- property8({ type: Number, attribute: false })
5807
+ property9({ type: Number, attribute: false })
5268
5808
  ], DawRulerElement.prototype, "samplesPerPixel", 2);
5269
5809
  __decorateClass([
5270
- property8({ type: Number, attribute: false })
5810
+ property9({ type: Number, attribute: false })
5271
5811
  ], DawRulerElement.prototype, "sampleRate", 2);
5272
5812
  __decorateClass([
5273
- property8({ type: Number, attribute: false })
5813
+ property9({ type: Number, attribute: false })
5274
5814
  ], DawRulerElement.prototype, "duration", 2);
5275
5815
  __decorateClass([
5276
- property8({ type: Number, attribute: false })
5816
+ property9({ type: Number, attribute: false })
5277
5817
  ], DawRulerElement.prototype, "rulerHeight", 2);
5278
5818
  __decorateClass([
5279
- property8({ type: String, attribute: false })
5819
+ property9({ type: String, attribute: false })
5280
5820
  ], DawRulerElement.prototype, "scaleMode", 2);
5281
5821
  __decorateClass([
5282
- property8({ type: Number, attribute: false })
5822
+ property9({ type: Number, attribute: false })
5283
5823
  ], DawRulerElement.prototype, "ticksPerPixel", 2);
5284
5824
  __decorateClass([
5285
- property8({ attribute: false })
5825
+ property9({ attribute: false })
5286
5826
  ], DawRulerElement.prototype, "meterEntries", 2);
5287
5827
  __decorateClass([
5288
- property8({ type: Number, attribute: false })
5828
+ property9({ type: Number, attribute: false })
5289
5829
  ], DawRulerElement.prototype, "ppqn", 2);
5290
5830
  __decorateClass([
5291
- property8({ type: Number, attribute: false })
5831
+ property9({ type: Number, attribute: false })
5292
5832
  ], DawRulerElement.prototype, "totalWidth", 2);
5293
5833
  DawRulerElement = __decorateClass([
5294
- customElement12("daw-ruler")
5834
+ customElement13("daw-ruler")
5295
5835
  ], DawRulerElement);
5296
5836
 
5297
5837
  // src/elements/daw-selection.ts
5298
- import { LitElement as LitElement11, html as html10, css as css10 } from "lit";
5299
- import { customElement as customElement13, property as property9 } from "lit/decorators.js";
5300
- var DawSelectionElement = class extends LitElement11 {
5838
+ import { LitElement as LitElement12, html as html11, css as css11 } from "lit";
5839
+ import { customElement as customElement14, property as property10 } from "lit/decorators.js";
5840
+ var DawSelectionElement = class extends LitElement12 {
5301
5841
  constructor() {
5302
5842
  super(...arguments);
5303
5843
  this.startPx = 0;
@@ -5306,11 +5846,11 @@ var DawSelectionElement = class extends LitElement11 {
5306
5846
  render() {
5307
5847
  const left = Math.min(this.startPx, this.endPx);
5308
5848
  const width = Math.abs(this.endPx - this.startPx);
5309
- if (width === 0) return html10``;
5310
- return html10`<div style="left: ${left}px; width: ${width}px;"></div>`;
5849
+ if (width === 0) return html11``;
5850
+ return html11`<div style="left: ${left}px; width: ${width}px;"></div>`;
5311
5851
  }
5312
5852
  };
5313
- DawSelectionElement.styles = css10`
5853
+ DawSelectionElement.styles = css11`
5314
5854
  :host {
5315
5855
  position: absolute;
5316
5856
  top: 0;
@@ -5327,18 +5867,18 @@ DawSelectionElement.styles = css10`
5327
5867
  }
5328
5868
  `;
5329
5869
  __decorateClass([
5330
- property9({ type: Number, attribute: false })
5870
+ property10({ type: Number, attribute: false })
5331
5871
  ], DawSelectionElement.prototype, "startPx", 2);
5332
5872
  __decorateClass([
5333
- property9({ type: Number, attribute: false })
5873
+ property10({ type: Number, attribute: false })
5334
5874
  ], DawSelectionElement.prototype, "endPx", 2);
5335
5875
  DawSelectionElement = __decorateClass([
5336
- customElement13("daw-selection")
5876
+ customElement14("daw-selection")
5337
5877
  ], DawSelectionElement);
5338
5878
 
5339
5879
  // src/elements/daw-record-button.ts
5340
- import { html as html11, css as css11 } from "lit";
5341
- import { customElement as customElement14, state as state4 } from "lit/decorators.js";
5880
+ import { html as html12, css as css12 } from "lit";
5881
+ import { customElement as customElement15, state as state4 } from "lit/decorators.js";
5342
5882
  var DawRecordButtonElement = class extends DawTransportButton {
5343
5883
  constructor() {
5344
5884
  super(...arguments);
@@ -5379,7 +5919,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
5379
5919
  }
5380
5920
  }
5381
5921
  render() {
5382
- return html11`
5922
+ return html12`
5383
5923
  <button part="button" ?data-recording=${this._isRecording} @click=${this._onClick}>
5384
5924
  <slot>Record</slot>
5385
5925
  </button>
@@ -5399,7 +5939,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
5399
5939
  };
5400
5940
  DawRecordButtonElement.styles = [
5401
5941
  DawTransportButton.styles,
5402
- css11`
5942
+ css12`
5403
5943
  button[data-recording] {
5404
5944
  color: #d08070;
5405
5945
  border-color: #d08070;
@@ -5411,14 +5951,14 @@ __decorateClass([
5411
5951
  state4()
5412
5952
  ], DawRecordButtonElement.prototype, "_isRecording", 2);
5413
5953
  DawRecordButtonElement = __decorateClass([
5414
- customElement14("daw-record-button")
5954
+ customElement15("daw-record-button")
5415
5955
  ], DawRecordButtonElement);
5416
5956
 
5417
5957
  // src/elements/daw-keyboard-shortcuts.ts
5418
- import { LitElement as LitElement12 } from "lit";
5419
- import { customElement as customElement15, property as property10 } from "lit/decorators.js";
5958
+ import { LitElement as LitElement13 } from "lit";
5959
+ import { customElement as customElement16, property as property11 } from "lit/decorators.js";
5420
5960
  import { handleKeyboardEvent } from "@waveform-playlist/core";
5421
- var DawKeyboardShortcutsElement = class extends LitElement12 {
5961
+ var DawKeyboardShortcutsElement = class extends LitElement13 {
5422
5962
  constructor() {
5423
5963
  super(...arguments);
5424
5964
  this.playback = false;
@@ -5572,16 +6112,16 @@ var DawKeyboardShortcutsElement = class extends LitElement12 {
5572
6112
  }
5573
6113
  };
5574
6114
  __decorateClass([
5575
- property10({ type: Boolean })
6115
+ property11({ type: Boolean })
5576
6116
  ], DawKeyboardShortcutsElement.prototype, "playback", 2);
5577
6117
  __decorateClass([
5578
- property10({ type: Boolean })
6118
+ property11({ type: Boolean })
5579
6119
  ], DawKeyboardShortcutsElement.prototype, "splitting", 2);
5580
6120
  __decorateClass([
5581
- property10({ type: Boolean })
6121
+ property11({ type: Boolean })
5582
6122
  ], DawKeyboardShortcutsElement.prototype, "undo", 2);
5583
6123
  DawKeyboardShortcutsElement = __decorateClass([
5584
- customElement15("daw-keyboard-shortcuts")
6124
+ customElement16("daw-keyboard-shortcuts")
5585
6125
  ], DawKeyboardShortcutsElement);
5586
6126
  export {
5587
6127
  AudioResumeController,
@@ -5591,6 +6131,7 @@ export {
5591
6131
  DawGridElement,
5592
6132
  DawKeyboardShortcutsElement,
5593
6133
  DawPauseButtonElement,
6134
+ DawPianoRollElement,
5594
6135
  DawPlayButtonElement,
5595
6136
  DawPlayheadElement,
5596
6137
  DawRecordButtonElement,