@dawcore/components 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +260 -10
- package/dist/index.d.ts +260 -10
- package/dist/index.js +1138 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1159 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -39,8 +39,10 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
39
39
|
var index_exports = {};
|
|
40
40
|
__export(index_exports, {
|
|
41
41
|
AudioResumeController: () => AudioResumeController,
|
|
42
|
+
ClipPointerHandler: () => ClipPointerHandler,
|
|
42
43
|
DawClipElement: () => DawClipElement,
|
|
43
44
|
DawEditorElement: () => DawEditorElement,
|
|
45
|
+
DawKeyboardShortcutsElement: () => DawKeyboardShortcutsElement,
|
|
44
46
|
DawPauseButtonElement: () => DawPauseButtonElement,
|
|
45
47
|
DawPlayButtonElement: () => DawPlayButtonElement,
|
|
46
48
|
DawPlayheadElement: () => DawPlayheadElement,
|
|
@@ -53,7 +55,8 @@ __export(index_exports, {
|
|
|
53
55
|
DawTransportButton: () => DawTransportButton,
|
|
54
56
|
DawTransportElement: () => DawTransportElement,
|
|
55
57
|
DawWaveformElement: () => DawWaveformElement,
|
|
56
|
-
RecordingController: () => RecordingController
|
|
58
|
+
RecordingController: () => RecordingController,
|
|
59
|
+
splitAtPlayhead: () => splitAtPlayhead
|
|
57
60
|
});
|
|
58
61
|
module.exports = __toCommonJS(index_exports);
|
|
59
62
|
|
|
@@ -629,9 +632,40 @@ DawTransportButton.styles = import_lit6.css`
|
|
|
629
632
|
|
|
630
633
|
// src/elements/daw-play-button.ts
|
|
631
634
|
var DawPlayButtonElement = class extends DawTransportButton {
|
|
635
|
+
constructor() {
|
|
636
|
+
super(...arguments);
|
|
637
|
+
this._isRecording = false;
|
|
638
|
+
this._targetRef = null;
|
|
639
|
+
this._onRecStart = () => {
|
|
640
|
+
this._isRecording = true;
|
|
641
|
+
};
|
|
642
|
+
this._onRecEnd = () => {
|
|
643
|
+
this._isRecording = false;
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
connectedCallback() {
|
|
647
|
+
super.connectedCallback();
|
|
648
|
+
requestAnimationFrame(() => {
|
|
649
|
+
const target = this.target;
|
|
650
|
+
if (!target) return;
|
|
651
|
+
this._targetRef = target;
|
|
652
|
+
target.addEventListener("daw-recording-start", this._onRecStart);
|
|
653
|
+
target.addEventListener("daw-recording-complete", this._onRecEnd);
|
|
654
|
+
target.addEventListener("daw-recording-error", this._onRecEnd);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
disconnectedCallback() {
|
|
658
|
+
super.disconnectedCallback();
|
|
659
|
+
if (this._targetRef) {
|
|
660
|
+
this._targetRef.removeEventListener("daw-recording-start", this._onRecStart);
|
|
661
|
+
this._targetRef.removeEventListener("daw-recording-complete", this._onRecEnd);
|
|
662
|
+
this._targetRef.removeEventListener("daw-recording-error", this._onRecEnd);
|
|
663
|
+
this._targetRef = null;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
632
666
|
render() {
|
|
633
667
|
return import_lit7.html`
|
|
634
|
-
<button part="button" @click=${this._onClick}>
|
|
668
|
+
<button part="button" ?disabled=${this._isRecording} @click=${this._onClick}>
|
|
635
669
|
<slot>Play</slot>
|
|
636
670
|
</button>
|
|
637
671
|
`;
|
|
@@ -647,6 +681,9 @@ var DawPlayButtonElement = class extends DawTransportButton {
|
|
|
647
681
|
target.play();
|
|
648
682
|
}
|
|
649
683
|
};
|
|
684
|
+
__decorateClass([
|
|
685
|
+
(0, import_decorators6.state)()
|
|
686
|
+
], DawPlayButtonElement.prototype, "_isRecording", 2);
|
|
650
687
|
DawPlayButtonElement = __decorateClass([
|
|
651
688
|
(0, import_decorators6.customElement)("daw-play-button")
|
|
652
689
|
], DawPlayButtonElement);
|
|
@@ -655,9 +692,42 @@ DawPlayButtonElement = __decorateClass([
|
|
|
655
692
|
var import_lit8 = require("lit");
|
|
656
693
|
var import_decorators7 = require("lit/decorators.js");
|
|
657
694
|
var DawPauseButtonElement = class extends DawTransportButton {
|
|
695
|
+
constructor() {
|
|
696
|
+
super(...arguments);
|
|
697
|
+
this._isPaused = false;
|
|
698
|
+
this._isRecording = false;
|
|
699
|
+
this._targetRef = null;
|
|
700
|
+
this._onRecStart = () => {
|
|
701
|
+
this._isRecording = true;
|
|
702
|
+
};
|
|
703
|
+
this._onRecEnd = () => {
|
|
704
|
+
this._isRecording = false;
|
|
705
|
+
this._isPaused = false;
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
connectedCallback() {
|
|
709
|
+
super.connectedCallback();
|
|
710
|
+
requestAnimationFrame(() => {
|
|
711
|
+
const target = this.target;
|
|
712
|
+
if (!target) return;
|
|
713
|
+
this._targetRef = target;
|
|
714
|
+
target.addEventListener("daw-recording-start", this._onRecStart);
|
|
715
|
+
target.addEventListener("daw-recording-complete", this._onRecEnd);
|
|
716
|
+
target.addEventListener("daw-recording-error", this._onRecEnd);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
disconnectedCallback() {
|
|
720
|
+
super.disconnectedCallback();
|
|
721
|
+
if (this._targetRef) {
|
|
722
|
+
this._targetRef.removeEventListener("daw-recording-start", this._onRecStart);
|
|
723
|
+
this._targetRef.removeEventListener("daw-recording-complete", this._onRecEnd);
|
|
724
|
+
this._targetRef.removeEventListener("daw-recording-error", this._onRecEnd);
|
|
725
|
+
this._targetRef = null;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
658
728
|
render() {
|
|
659
729
|
return import_lit8.html`
|
|
660
|
-
<button part="button" @click=${this._onClick}>
|
|
730
|
+
<button part="button" ?data-paused=${this._isPaused} @click=${this._onClick}>
|
|
661
731
|
<slot>Pause</slot>
|
|
662
732
|
</button>
|
|
663
733
|
`;
|
|
@@ -670,9 +740,36 @@ var DawPauseButtonElement = class extends DawTransportButton {
|
|
|
670
740
|
);
|
|
671
741
|
return;
|
|
672
742
|
}
|
|
673
|
-
|
|
743
|
+
if (this._isRecording) {
|
|
744
|
+
if (this._isPaused) {
|
|
745
|
+
target.resumeRecording();
|
|
746
|
+
target.play(target.currentTime);
|
|
747
|
+
this._isPaused = false;
|
|
748
|
+
} else {
|
|
749
|
+
target.pauseRecording();
|
|
750
|
+
target.pause();
|
|
751
|
+
this._isPaused = true;
|
|
752
|
+
}
|
|
753
|
+
} else {
|
|
754
|
+
target.pause();
|
|
755
|
+
}
|
|
674
756
|
}
|
|
675
757
|
};
|
|
758
|
+
DawPauseButtonElement.styles = [
|
|
759
|
+
DawTransportButton.styles,
|
|
760
|
+
import_lit8.css`
|
|
761
|
+
button[data-paused] {
|
|
762
|
+
background: rgba(255, 255, 255, 0.1);
|
|
763
|
+
border-color: var(--daw-controls-text, #e0d4c8);
|
|
764
|
+
}
|
|
765
|
+
`
|
|
766
|
+
];
|
|
767
|
+
__decorateClass([
|
|
768
|
+
(0, import_decorators7.state)()
|
|
769
|
+
], DawPauseButtonElement.prototype, "_isPaused", 2);
|
|
770
|
+
__decorateClass([
|
|
771
|
+
(0, import_decorators7.state)()
|
|
772
|
+
], DawPauseButtonElement.prototype, "_isRecording", 2);
|
|
676
773
|
DawPauseButtonElement = __decorateClass([
|
|
677
774
|
(0, import_decorators7.customElement)("daw-pause-button")
|
|
678
775
|
], DawPauseButtonElement);
|
|
@@ -696,6 +793,9 @@ var DawStopButtonElement = class extends DawTransportButton {
|
|
|
696
793
|
);
|
|
697
794
|
return;
|
|
698
795
|
}
|
|
796
|
+
if (target.isRecording) {
|
|
797
|
+
target.stopRecording();
|
|
798
|
+
}
|
|
699
799
|
target.stop();
|
|
700
800
|
}
|
|
701
801
|
};
|
|
@@ -1027,20 +1127,22 @@ function extractPeaks(waveformData, samplesPerPixel, isMono, offsetSamples, dura
|
|
|
1027
1127
|
|
|
1028
1128
|
// src/workers/peakPipeline.ts
|
|
1029
1129
|
var PeakPipeline = class {
|
|
1030
|
-
constructor() {
|
|
1130
|
+
constructor(baseScale = 128, bits = 16) {
|
|
1031
1131
|
this._worker = null;
|
|
1032
1132
|
this._cache = /* @__PURE__ */ new WeakMap();
|
|
1033
1133
|
this._inflight = /* @__PURE__ */ new WeakMap();
|
|
1134
|
+
this._baseScale = baseScale;
|
|
1135
|
+
this._bits = bits;
|
|
1034
1136
|
}
|
|
1035
1137
|
/**
|
|
1036
1138
|
* Generate PeakData for a clip from its AudioBuffer.
|
|
1037
1139
|
* Uses cached WaveformData when available; otherwise generates via worker.
|
|
1038
|
-
*
|
|
1140
|
+
* Worker generates at baseScale (default 128); extractPeaks resamples to the requested zoom.
|
|
1039
1141
|
*/
|
|
1040
|
-
async generatePeaks(audioBuffer, samplesPerPixel, isMono) {
|
|
1041
|
-
const waveformData = await this._getWaveformData(audioBuffer
|
|
1142
|
+
async generatePeaks(audioBuffer, samplesPerPixel, isMono, offsetSamples, durationSamples) {
|
|
1143
|
+
const waveformData = await this._getWaveformData(audioBuffer);
|
|
1042
1144
|
try {
|
|
1043
|
-
return extractPeaks(waveformData, samplesPerPixel, isMono);
|
|
1145
|
+
return extractPeaks(waveformData, samplesPerPixel, isMono, offsetSamples, durationSamples);
|
|
1044
1146
|
} catch (err) {
|
|
1045
1147
|
console.warn("[dawcore] extractPeaks failed: " + String(err));
|
|
1046
1148
|
throw err;
|
|
@@ -1052,14 +1154,24 @@ var PeakPipeline = class {
|
|
|
1052
1154
|
* Returns a new Map of clipId → PeakData. Clips without cached data or where
|
|
1053
1155
|
* the target scale is finer than the cached base are skipped.
|
|
1054
1156
|
*/
|
|
1055
|
-
reextractPeaks(clipBuffers, samplesPerPixel, isMono) {
|
|
1157
|
+
reextractPeaks(clipBuffers, samplesPerPixel, isMono, clipOffsets) {
|
|
1056
1158
|
const result = /* @__PURE__ */ new Map();
|
|
1057
1159
|
for (const [clipId, audioBuffer] of clipBuffers) {
|
|
1058
1160
|
const cached = this._cache.get(audioBuffer);
|
|
1059
1161
|
if (cached) {
|
|
1060
1162
|
if (samplesPerPixel < cached.scale) continue;
|
|
1061
1163
|
try {
|
|
1062
|
-
|
|
1164
|
+
const offsets = clipOffsets?.get(clipId);
|
|
1165
|
+
result.set(
|
|
1166
|
+
clipId,
|
|
1167
|
+
extractPeaks(
|
|
1168
|
+
cached,
|
|
1169
|
+
samplesPerPixel,
|
|
1170
|
+
isMono,
|
|
1171
|
+
offsets?.offsetSamples,
|
|
1172
|
+
offsets?.durationSamples
|
|
1173
|
+
)
|
|
1174
|
+
);
|
|
1063
1175
|
} catch (err) {
|
|
1064
1176
|
console.warn("[dawcore] reextractPeaks failed for clip " + clipId + ": " + String(err));
|
|
1065
1177
|
}
|
|
@@ -1071,9 +1183,9 @@ var PeakPipeline = class {
|
|
|
1071
1183
|
this._worker?.terminate();
|
|
1072
1184
|
this._worker = null;
|
|
1073
1185
|
}
|
|
1074
|
-
async _getWaveformData(audioBuffer
|
|
1186
|
+
async _getWaveformData(audioBuffer) {
|
|
1075
1187
|
const cached = this._cache.get(audioBuffer);
|
|
1076
|
-
if (cached
|
|
1188
|
+
if (cached) return cached;
|
|
1077
1189
|
const inflight = this._inflight.get(audioBuffer);
|
|
1078
1190
|
if (inflight) return inflight;
|
|
1079
1191
|
if (!this._worker) {
|
|
@@ -1087,8 +1199,8 @@ var PeakPipeline = class {
|
|
|
1087
1199
|
channels,
|
|
1088
1200
|
length: audioBuffer.length,
|
|
1089
1201
|
sampleRate: audioBuffer.sampleRate,
|
|
1090
|
-
scale:
|
|
1091
|
-
bits:
|
|
1202
|
+
scale: this._baseScale,
|
|
1203
|
+
bits: this._bits,
|
|
1092
1204
|
splitChannels: true
|
|
1093
1205
|
}).then((waveformData) => {
|
|
1094
1206
|
this._cache.set(audioBuffer, waveformData);
|
|
@@ -1209,7 +1321,7 @@ DawTrackControlsElement.styles = import_lit10.css`
|
|
|
1209
1321
|
:host {
|
|
1210
1322
|
display: flex;
|
|
1211
1323
|
flex-direction: column;
|
|
1212
|
-
justify-content:
|
|
1324
|
+
justify-content: flex-start;
|
|
1213
1325
|
box-sizing: border-box;
|
|
1214
1326
|
padding: 6px 8px;
|
|
1215
1327
|
background: var(--daw-controls-background, #0f0f1a);
|
|
@@ -1217,13 +1329,14 @@ DawTrackControlsElement.styles = import_lit10.css`
|
|
|
1217
1329
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
1218
1330
|
font-family: system-ui, sans-serif;
|
|
1219
1331
|
font-size: 11px;
|
|
1332
|
+
overflow: hidden;
|
|
1220
1333
|
}
|
|
1221
1334
|
.header {
|
|
1222
1335
|
display: flex;
|
|
1223
1336
|
align-items: center;
|
|
1224
1337
|
justify-content: space-between;
|
|
1225
1338
|
gap: 4px;
|
|
1226
|
-
margin-bottom:
|
|
1339
|
+
margin-bottom: 3px;
|
|
1227
1340
|
}
|
|
1228
1341
|
.name {
|
|
1229
1342
|
flex: 1;
|
|
@@ -1250,7 +1363,7 @@ DawTrackControlsElement.styles = import_lit10.css`
|
|
|
1250
1363
|
.buttons {
|
|
1251
1364
|
display: flex;
|
|
1252
1365
|
gap: 3px;
|
|
1253
|
-
margin-bottom:
|
|
1366
|
+
margin-bottom: 3px;
|
|
1254
1367
|
}
|
|
1255
1368
|
.btn {
|
|
1256
1369
|
background: rgba(255, 255, 255, 0.06);
|
|
@@ -1280,7 +1393,7 @@ DawTrackControlsElement.styles = import_lit10.css`
|
|
|
1280
1393
|
display: flex;
|
|
1281
1394
|
align-items: center;
|
|
1282
1395
|
gap: 4px;
|
|
1283
|
-
height:
|
|
1396
|
+
height: 16px;
|
|
1284
1397
|
}
|
|
1285
1398
|
.slider-label {
|
|
1286
1399
|
width: 50px;
|
|
@@ -1377,6 +1490,77 @@ var hostStyles = import_lit11.css`
|
|
|
1377
1490
|
--daw-clip-header-text: #e0d4c8;
|
|
1378
1491
|
}
|
|
1379
1492
|
`;
|
|
1493
|
+
var clipStyles = import_lit11.css`
|
|
1494
|
+
.clip-container {
|
|
1495
|
+
position: absolute;
|
|
1496
|
+
overflow: hidden;
|
|
1497
|
+
}
|
|
1498
|
+
.clip-header {
|
|
1499
|
+
position: relative;
|
|
1500
|
+
z-index: 1;
|
|
1501
|
+
height: 20px;
|
|
1502
|
+
background: var(--daw-clip-header-background, rgba(0, 0, 0, 0.4));
|
|
1503
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
1504
|
+
display: flex;
|
|
1505
|
+
align-items: center;
|
|
1506
|
+
padding: 0 6px;
|
|
1507
|
+
user-select: none;
|
|
1508
|
+
-webkit-user-drag: none;
|
|
1509
|
+
}
|
|
1510
|
+
.clip-header span {
|
|
1511
|
+
font-size: 10px;
|
|
1512
|
+
font-weight: 500;
|
|
1513
|
+
letter-spacing: 0.02em;
|
|
1514
|
+
font-family: system-ui, sans-serif;
|
|
1515
|
+
color: var(--daw-clip-header-text, #e0d4c8);
|
|
1516
|
+
white-space: nowrap;
|
|
1517
|
+
overflow: hidden;
|
|
1518
|
+
text-overflow: ellipsis;
|
|
1519
|
+
opacity: 0.8;
|
|
1520
|
+
}
|
|
1521
|
+
.clip-boundary {
|
|
1522
|
+
position: absolute;
|
|
1523
|
+
top: 0;
|
|
1524
|
+
width: 8px;
|
|
1525
|
+
height: 100%;
|
|
1526
|
+
z-index: 2;
|
|
1527
|
+
cursor: col-resize;
|
|
1528
|
+
background: transparent;
|
|
1529
|
+
border: none;
|
|
1530
|
+
touch-action: none;
|
|
1531
|
+
user-select: none;
|
|
1532
|
+
-webkit-user-drag: none;
|
|
1533
|
+
transition: background 0.1s, border-color 0.1s;
|
|
1534
|
+
}
|
|
1535
|
+
.clip-boundary[data-boundary-edge='left'] {
|
|
1536
|
+
left: 0;
|
|
1537
|
+
}
|
|
1538
|
+
.clip-boundary[data-boundary-edge='right'] {
|
|
1539
|
+
right: 0;
|
|
1540
|
+
}
|
|
1541
|
+
.clip-boundary[data-boundary-edge='left']:hover {
|
|
1542
|
+
background: rgba(255, 255, 255, 0.2);
|
|
1543
|
+
border-left: 2px solid rgba(255, 255, 255, 0.5);
|
|
1544
|
+
}
|
|
1545
|
+
.clip-boundary[data-boundary-edge='right']:hover {
|
|
1546
|
+
background: rgba(255, 255, 255, 0.2);
|
|
1547
|
+
border-right: 2px solid rgba(255, 255, 255, 0.5);
|
|
1548
|
+
}
|
|
1549
|
+
.clip-boundary[data-boundary-edge='left'].dragging {
|
|
1550
|
+
background: rgba(255, 255, 255, 0.4);
|
|
1551
|
+
border-left: 2px solid rgba(255, 255, 255, 0.8);
|
|
1552
|
+
}
|
|
1553
|
+
.clip-boundary[data-boundary-edge='right'].dragging {
|
|
1554
|
+
background: rgba(255, 255, 255, 0.4);
|
|
1555
|
+
border-right: 2px solid rgba(255, 255, 255, 0.8);
|
|
1556
|
+
}
|
|
1557
|
+
.clip-header[data-interactive] {
|
|
1558
|
+
cursor: grab;
|
|
1559
|
+
}
|
|
1560
|
+
.clip-header[data-interactive]:active {
|
|
1561
|
+
cursor: grabbing;
|
|
1562
|
+
}
|
|
1563
|
+
`;
|
|
1380
1564
|
|
|
1381
1565
|
// src/controllers/viewport-controller.ts
|
|
1382
1566
|
var OVERSCAN_MULTIPLIER = 1.5;
|
|
@@ -1559,6 +1743,9 @@ var RecordingController = class {
|
|
|
1559
1743
|
}
|
|
1560
1744
|
const channelCount = stream.getAudioTracks()[0]?.getSettings()?.channelCount ?? 1;
|
|
1561
1745
|
const startSample = options.startSample ?? Math.floor(this._host._currentTime * this._host.effectiveSampleRate);
|
|
1746
|
+
const outputLatency = rawCtx.outputLatency ?? 0;
|
|
1747
|
+
const lookAhead = context.lookAhead ?? 0;
|
|
1748
|
+
const latencySamples = Math.floor((outputLatency + lookAhead) * rawCtx.sampleRate);
|
|
1562
1749
|
const source = context.createMediaStreamSource(stream);
|
|
1563
1750
|
const workletNode = context.createAudioWorkletNode("recording-processor", {
|
|
1564
1751
|
channelCount,
|
|
@@ -1585,6 +1772,8 @@ var RecordingController = class {
|
|
|
1585
1772
|
channelCount,
|
|
1586
1773
|
bits,
|
|
1587
1774
|
isFirstMessage: true,
|
|
1775
|
+
latencySamples,
|
|
1776
|
+
wasOverdub: options.overdub ?? false,
|
|
1588
1777
|
_onTrackEnded: onTrackEnded,
|
|
1589
1778
|
_audioTrack: audioTrack
|
|
1590
1779
|
};
|
|
@@ -1605,6 +1794,9 @@ var RecordingController = class {
|
|
|
1605
1794
|
})
|
|
1606
1795
|
);
|
|
1607
1796
|
this._host.requestUpdate();
|
|
1797
|
+
if (options.overdub && typeof this._host.play === "function") {
|
|
1798
|
+
await this._host.play(this._host._currentTime);
|
|
1799
|
+
}
|
|
1608
1800
|
} catch (err) {
|
|
1609
1801
|
this._cleanupSession(trackId);
|
|
1610
1802
|
console.warn("[dawcore] RecordingController: Failed to start recording: " + String(err));
|
|
@@ -1617,11 +1809,28 @@ var RecordingController = class {
|
|
|
1617
1809
|
);
|
|
1618
1810
|
}
|
|
1619
1811
|
}
|
|
1812
|
+
pauseRecording(trackId) {
|
|
1813
|
+
const id = trackId ?? [...this._sessions.keys()][0];
|
|
1814
|
+
if (!id) return;
|
|
1815
|
+
const session = this._sessions.get(id);
|
|
1816
|
+
if (!session) return;
|
|
1817
|
+
session.workletNode.port.postMessage({ command: "pause" });
|
|
1818
|
+
}
|
|
1819
|
+
resumeRecording(trackId) {
|
|
1820
|
+
const id = trackId ?? [...this._sessions.keys()][0];
|
|
1821
|
+
if (!id) return;
|
|
1822
|
+
const session = this._sessions.get(id);
|
|
1823
|
+
if (!session) return;
|
|
1824
|
+
session.workletNode.port.postMessage({ command: "resume" });
|
|
1825
|
+
}
|
|
1620
1826
|
stopRecording(trackId) {
|
|
1621
1827
|
const id = trackId ?? [...this._sessions.keys()][0];
|
|
1622
1828
|
if (!id) return;
|
|
1623
1829
|
const session = this._sessions.get(id);
|
|
1624
1830
|
if (!session) return;
|
|
1831
|
+
if (session.wasOverdub && typeof this._host.stop === "function") {
|
|
1832
|
+
this._host.stop();
|
|
1833
|
+
}
|
|
1625
1834
|
session.workletNode.port.postMessage({ command: "stop" });
|
|
1626
1835
|
session.source.disconnect();
|
|
1627
1836
|
session.workletNode.disconnect();
|
|
@@ -1639,7 +1848,8 @@ var RecordingController = class {
|
|
|
1639
1848
|
);
|
|
1640
1849
|
return;
|
|
1641
1850
|
}
|
|
1642
|
-
const
|
|
1851
|
+
const context = (0, import_playout2.getGlobalContext)();
|
|
1852
|
+
const stopCtx = context.rawContext;
|
|
1643
1853
|
const channelData = session.chunks.map((chunkArr) => (0, import_recording.concatenateAudioData)(chunkArr));
|
|
1644
1854
|
const audioBuffer = (0, import_recording.createAudioBuffer)(
|
|
1645
1855
|
stopCtx,
|
|
@@ -1647,7 +1857,21 @@ var RecordingController = class {
|
|
|
1647
1857
|
this._host.effectiveSampleRate,
|
|
1648
1858
|
session.channelCount
|
|
1649
1859
|
);
|
|
1650
|
-
const
|
|
1860
|
+
const latencyOffsetSamples = session.latencySamples;
|
|
1861
|
+
const effectiveDuration = Math.max(0, audioBuffer.length - latencyOffsetSamples);
|
|
1862
|
+
if (effectiveDuration === 0) {
|
|
1863
|
+
console.warn("[dawcore] RecordingController: Recording too short for latency compensation");
|
|
1864
|
+
this._sessions.delete(id);
|
|
1865
|
+
this._host.requestUpdate();
|
|
1866
|
+
this._host.dispatchEvent(
|
|
1867
|
+
new CustomEvent("daw-recording-error", {
|
|
1868
|
+
bubbles: true,
|
|
1869
|
+
composed: true,
|
|
1870
|
+
detail: { trackId: id, error: new Error("Recording too short to save") }
|
|
1871
|
+
})
|
|
1872
|
+
);
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1651
1875
|
const event = new CustomEvent("daw-recording-complete", {
|
|
1652
1876
|
bubbles: true,
|
|
1653
1877
|
composed: true,
|
|
@@ -1656,14 +1880,21 @@ var RecordingController = class {
|
|
|
1656
1880
|
trackId: id,
|
|
1657
1881
|
audioBuffer,
|
|
1658
1882
|
startSample: session.startSample,
|
|
1659
|
-
durationSamples
|
|
1883
|
+
durationSamples: effectiveDuration,
|
|
1884
|
+
offsetSamples: latencyOffsetSamples
|
|
1660
1885
|
}
|
|
1661
1886
|
});
|
|
1662
1887
|
const notPrevented = this._host.dispatchEvent(event);
|
|
1663
1888
|
this._sessions.delete(id);
|
|
1664
1889
|
this._host.requestUpdate();
|
|
1665
1890
|
if (notPrevented) {
|
|
1666
|
-
this._createClipFromRecording(
|
|
1891
|
+
this._createClipFromRecording(
|
|
1892
|
+
id,
|
|
1893
|
+
audioBuffer,
|
|
1894
|
+
session.startSample,
|
|
1895
|
+
effectiveDuration,
|
|
1896
|
+
latencyOffsetSamples
|
|
1897
|
+
);
|
|
1667
1898
|
}
|
|
1668
1899
|
}
|
|
1669
1900
|
// Session fields are mutated in place on the hot path (~60fps worklet messages).
|
|
@@ -1694,7 +1925,9 @@ var RecordingController = class {
|
|
|
1694
1925
|
);
|
|
1695
1926
|
const newPeakCount = Math.floor(session.peaks[ch].length / 2);
|
|
1696
1927
|
const waveformSelector = `daw-waveform[data-recording-track="${trackId}"][data-recording-channel="${ch}"]`;
|
|
1697
|
-
const waveformEl = this._host.shadowRoot?.querySelector(
|
|
1928
|
+
const waveformEl = this._host.shadowRoot?.querySelector(
|
|
1929
|
+
waveformSelector
|
|
1930
|
+
);
|
|
1698
1931
|
if (waveformEl) {
|
|
1699
1932
|
if (session.isFirstMessage) {
|
|
1700
1933
|
waveformEl.peaks = session.peaks[ch];
|
|
@@ -1713,9 +1946,15 @@ var RecordingController = class {
|
|
|
1713
1946
|
this._host.requestUpdate();
|
|
1714
1947
|
}
|
|
1715
1948
|
}
|
|
1716
|
-
_createClipFromRecording(trackId, audioBuffer, startSample, durationSamples) {
|
|
1949
|
+
_createClipFromRecording(trackId, audioBuffer, startSample, durationSamples, offsetSamples = 0) {
|
|
1717
1950
|
if (typeof this._host._addRecordedClip === "function") {
|
|
1718
|
-
this._host._addRecordedClip(
|
|
1951
|
+
this._host._addRecordedClip(
|
|
1952
|
+
trackId,
|
|
1953
|
+
audioBuffer,
|
|
1954
|
+
startSample,
|
|
1955
|
+
durationSamples,
|
|
1956
|
+
offsetSamples
|
|
1957
|
+
);
|
|
1719
1958
|
} else {
|
|
1720
1959
|
console.warn(
|
|
1721
1960
|
'[dawcore] RecordingController: host does not implement _addRecordedClip \u2014 clip not created for track "' + trackId + '"'
|
|
@@ -1746,6 +1985,11 @@ var RecordingController = class {
|
|
|
1746
1985
|
|
|
1747
1986
|
// src/interactions/pointer-handler.ts
|
|
1748
1987
|
var import_core = require("@waveform-playlist/core");
|
|
1988
|
+
|
|
1989
|
+
// src/interactions/constants.ts
|
|
1990
|
+
var DRAG_THRESHOLD = 3;
|
|
1991
|
+
|
|
1992
|
+
// src/interactions/pointer-handler.ts
|
|
1749
1993
|
var PointerHandler = class {
|
|
1750
1994
|
constructor(host) {
|
|
1751
1995
|
this._isDragging = false;
|
|
@@ -1754,6 +1998,34 @@ var PointerHandler = class {
|
|
|
1754
1998
|
// Cached from onPointerDown to avoid forced layout reflows at 60fps during drag
|
|
1755
1999
|
this._timelineRect = null;
|
|
1756
2000
|
this.onPointerDown = (e) => {
|
|
2001
|
+
const clipHandler = this._host._clipHandler;
|
|
2002
|
+
if (clipHandler) {
|
|
2003
|
+
const target = e.composedPath()[0];
|
|
2004
|
+
if (target && clipHandler.tryHandle(target, e)) {
|
|
2005
|
+
e.preventDefault();
|
|
2006
|
+
this._timeline = this._host.shadowRoot?.querySelector(".timeline");
|
|
2007
|
+
if (this._timeline) {
|
|
2008
|
+
this._timeline.setPointerCapture(e.pointerId);
|
|
2009
|
+
const onMove = (me) => clipHandler.onPointerMove(me);
|
|
2010
|
+
const onUp = (ue) => {
|
|
2011
|
+
clipHandler.onPointerUp(ue);
|
|
2012
|
+
this._timeline?.removeEventListener("pointermove", onMove);
|
|
2013
|
+
this._timeline?.removeEventListener("pointerup", onUp);
|
|
2014
|
+
try {
|
|
2015
|
+
this._timeline?.releasePointerCapture(ue.pointerId);
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
console.warn(
|
|
2018
|
+
"[dawcore] releasePointerCapture failed (may already be released): " + String(err)
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
this._timeline = null;
|
|
2022
|
+
};
|
|
2023
|
+
this._timeline.addEventListener("pointermove", onMove);
|
|
2024
|
+
this._timeline.addEventListener("pointerup", onUp);
|
|
2025
|
+
}
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
1757
2029
|
this._timeline = this._host.shadowRoot?.querySelector(".timeline");
|
|
1758
2030
|
if (!this._timeline) return;
|
|
1759
2031
|
this._timelineRect = this._timeline.getBoundingClientRect();
|
|
@@ -1766,7 +2038,7 @@ var PointerHandler = class {
|
|
|
1766
2038
|
this._onPointerMove = (e) => {
|
|
1767
2039
|
if (!this._timeline) return;
|
|
1768
2040
|
const currentPx = this._pxFromPointer(e);
|
|
1769
|
-
if (!this._isDragging && Math.abs(currentPx - this._dragStartPx) >
|
|
2041
|
+
if (!this._isDragging && Math.abs(currentPx - this._dragStartPx) > DRAG_THRESHOLD) {
|
|
1770
2042
|
this._isDragging = true;
|
|
1771
2043
|
}
|
|
1772
2044
|
if (this._isDragging) {
|
|
@@ -1901,6 +2173,261 @@ var PointerHandler = class {
|
|
|
1901
2173
|
}
|
|
1902
2174
|
};
|
|
1903
2175
|
|
|
2176
|
+
// src/interactions/clip-pointer-handler.ts
|
|
2177
|
+
var ClipPointerHandler = class {
|
|
2178
|
+
constructor(host) {
|
|
2179
|
+
this._mode = null;
|
|
2180
|
+
this._clipId = "";
|
|
2181
|
+
this._trackId = "";
|
|
2182
|
+
this._startPx = 0;
|
|
2183
|
+
this._isDragging = false;
|
|
2184
|
+
this._lastDeltaPx = 0;
|
|
2185
|
+
this._cumulativeDeltaSamples = 0;
|
|
2186
|
+
// Trim visual feedback: snapshot of original clip state
|
|
2187
|
+
this._clipContainer = null;
|
|
2188
|
+
this._boundaryEl = null;
|
|
2189
|
+
this._originalLeft = 0;
|
|
2190
|
+
this._originalWidth = 0;
|
|
2191
|
+
this._originalOffsetSamples = 0;
|
|
2192
|
+
this._originalDurationSamples = 0;
|
|
2193
|
+
this._host = host;
|
|
2194
|
+
}
|
|
2195
|
+
/** Returns true if a drag interaction is currently in progress. */
|
|
2196
|
+
get isActive() {
|
|
2197
|
+
return this._mode !== null;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Attempts to handle a pointerdown event on the given target element.
|
|
2201
|
+
* Returns true if the target is a recognized clip interaction element.
|
|
2202
|
+
*/
|
|
2203
|
+
tryHandle(target, e) {
|
|
2204
|
+
if (!this._host.interactiveClips) return false;
|
|
2205
|
+
const boundary = target.closest?.(".clip-boundary");
|
|
2206
|
+
const header = target.closest?.(".clip-header");
|
|
2207
|
+
if (boundary && boundary.dataset.boundaryEdge !== void 0) {
|
|
2208
|
+
const clipId = boundary.dataset.clipId;
|
|
2209
|
+
const trackId = boundary.dataset.trackId;
|
|
2210
|
+
const edge = boundary.dataset.boundaryEdge;
|
|
2211
|
+
if (!clipId || !trackId || edge !== "left" && edge !== "right") return false;
|
|
2212
|
+
this._beginDrag(edge === "left" ? "trim-left" : "trim-right", clipId, trackId, e);
|
|
2213
|
+
this._boundaryEl = boundary;
|
|
2214
|
+
return true;
|
|
2215
|
+
}
|
|
2216
|
+
if (header && header.dataset.interactive !== void 0) {
|
|
2217
|
+
const clipId = header.dataset.clipId;
|
|
2218
|
+
const trackId = header.dataset.trackId;
|
|
2219
|
+
if (!clipId || !trackId) return false;
|
|
2220
|
+
this._beginDrag("move", clipId, trackId, e);
|
|
2221
|
+
return true;
|
|
2222
|
+
}
|
|
2223
|
+
return false;
|
|
2224
|
+
}
|
|
2225
|
+
_beginDrag(mode, clipId, trackId, e) {
|
|
2226
|
+
this._mode = mode;
|
|
2227
|
+
this._clipId = clipId;
|
|
2228
|
+
this._trackId = trackId;
|
|
2229
|
+
this._startPx = e.clientX;
|
|
2230
|
+
this._isDragging = false;
|
|
2231
|
+
this._lastDeltaPx = 0;
|
|
2232
|
+
this._cumulativeDeltaSamples = 0;
|
|
2233
|
+
if (this._host.engine) {
|
|
2234
|
+
this._host.engine.beginTransaction();
|
|
2235
|
+
} else {
|
|
2236
|
+
console.warn(
|
|
2237
|
+
"[dawcore] beginDrag: engine unavailable, drag mutations will not be grouped for undo"
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
if (mode === "trim-left" || mode === "trim-right") {
|
|
2241
|
+
const container = this._host.shadowRoot?.querySelector(
|
|
2242
|
+
`.clip-container[data-clip-id="${clipId}"]`
|
|
2243
|
+
);
|
|
2244
|
+
if (container) {
|
|
2245
|
+
this._clipContainer = container;
|
|
2246
|
+
this._originalLeft = parseFloat(container.style.left) || 0;
|
|
2247
|
+
this._originalWidth = parseFloat(container.style.width) || 0;
|
|
2248
|
+
} else {
|
|
2249
|
+
console.warn("[dawcore] clip container not found for trim visual feedback: " + clipId);
|
|
2250
|
+
}
|
|
2251
|
+
const engine = this._host.engine;
|
|
2252
|
+
if (engine) {
|
|
2253
|
+
const bounds = engine.getClipBounds(trackId, clipId);
|
|
2254
|
+
if (bounds) {
|
|
2255
|
+
this._originalOffsetSamples = bounds.offsetSamples;
|
|
2256
|
+
this._originalDurationSamples = bounds.durationSamples;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
/** Processes pointermove events during an active drag. */
|
|
2262
|
+
onPointerMove(e) {
|
|
2263
|
+
if (this._mode === null) return;
|
|
2264
|
+
const totalDeltaPx = e.clientX - this._startPx;
|
|
2265
|
+
if (!this._isDragging && Math.abs(totalDeltaPx) > DRAG_THRESHOLD) {
|
|
2266
|
+
this._isDragging = true;
|
|
2267
|
+
if (this._boundaryEl) {
|
|
2268
|
+
this._boundaryEl.classList.add("dragging");
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
if (!this._isDragging) return;
|
|
2272
|
+
const engine = this._host.engine;
|
|
2273
|
+
if (!engine) return;
|
|
2274
|
+
if (this._mode === "move") {
|
|
2275
|
+
const incrementalDeltaPx = totalDeltaPx - this._lastDeltaPx;
|
|
2276
|
+
this._lastDeltaPx = totalDeltaPx;
|
|
2277
|
+
const incrementalDeltaSamples = Math.round(incrementalDeltaPx * this._host.samplesPerPixel);
|
|
2278
|
+
const applied = engine.moveClip(this._trackId, this._clipId, incrementalDeltaSamples, true);
|
|
2279
|
+
this._cumulativeDeltaSamples += applied;
|
|
2280
|
+
} else {
|
|
2281
|
+
const boundary = this._mode === "trim-left" ? "left" : "right";
|
|
2282
|
+
const rawDeltaSamples = Math.round(totalDeltaPx * this._host.samplesPerPixel);
|
|
2283
|
+
const deltaSamples = engine.constrainTrimDelta(
|
|
2284
|
+
this._trackId,
|
|
2285
|
+
this._clipId,
|
|
2286
|
+
boundary,
|
|
2287
|
+
rawDeltaSamples
|
|
2288
|
+
);
|
|
2289
|
+
const deltaPx = Math.round(deltaSamples / this._host.samplesPerPixel);
|
|
2290
|
+
this._cumulativeDeltaSamples = deltaSamples;
|
|
2291
|
+
if (this._clipContainer) {
|
|
2292
|
+
if (this._mode === "trim-left") {
|
|
2293
|
+
const newLeft = this._originalLeft + deltaPx;
|
|
2294
|
+
const newWidth = this._originalWidth - deltaPx;
|
|
2295
|
+
if (newWidth > 0) {
|
|
2296
|
+
this._clipContainer.style.left = newLeft + "px";
|
|
2297
|
+
this._clipContainer.style.width = newWidth + "px";
|
|
2298
|
+
const newOffset = this._originalOffsetSamples + deltaSamples;
|
|
2299
|
+
const newDuration = this._originalDurationSamples - deltaSamples;
|
|
2300
|
+
if (this._updateWaveformPeaks(newOffset, newDuration)) {
|
|
2301
|
+
const waveforms = this._clipContainer.querySelectorAll("daw-waveform");
|
|
2302
|
+
for (const wf of waveforms) {
|
|
2303
|
+
wf.style.left = "0px";
|
|
2304
|
+
}
|
|
2305
|
+
} else {
|
|
2306
|
+
const waveforms = this._clipContainer.querySelectorAll("daw-waveform");
|
|
2307
|
+
for (const wf of waveforms) {
|
|
2308
|
+
wf.style.left = -deltaPx + "px";
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
} else {
|
|
2313
|
+
const newWidth = this._originalWidth + deltaPx;
|
|
2314
|
+
if (newWidth > 0) {
|
|
2315
|
+
this._clipContainer.style.width = newWidth + "px";
|
|
2316
|
+
const newDuration = this._originalDurationSamples + deltaSamples;
|
|
2317
|
+
this._updateWaveformPeaks(this._originalOffsetSamples, newDuration);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
/** Processes pointerup events to finalize and dispatch result events. */
|
|
2324
|
+
onPointerUp(_e) {
|
|
2325
|
+
if (this._mode === null) return;
|
|
2326
|
+
try {
|
|
2327
|
+
if (!this._isDragging || this._cumulativeDeltaSamples === 0) {
|
|
2328
|
+
this._restoreTrimVisual();
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
const engine = this._host.engine;
|
|
2332
|
+
if (this._mode === "move") {
|
|
2333
|
+
if (engine) {
|
|
2334
|
+
engine.updateTrack(this._trackId);
|
|
2335
|
+
this._host.dispatchEvent(
|
|
2336
|
+
new CustomEvent("daw-clip-move", {
|
|
2337
|
+
bubbles: true,
|
|
2338
|
+
composed: true,
|
|
2339
|
+
detail: {
|
|
2340
|
+
trackId: this._trackId,
|
|
2341
|
+
clipId: this._clipId,
|
|
2342
|
+
deltaSamples: this._cumulativeDeltaSamples
|
|
2343
|
+
}
|
|
2344
|
+
})
|
|
2345
|
+
);
|
|
2346
|
+
} else {
|
|
2347
|
+
console.warn(
|
|
2348
|
+
"[dawcore] engine unavailable at move drop \u2014 audio may be out of sync for track " + this._trackId
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
} else {
|
|
2352
|
+
this._restoreTrimVisual();
|
|
2353
|
+
const boundary = this._mode === "trim-left" ? "left" : "right";
|
|
2354
|
+
if (engine) {
|
|
2355
|
+
engine.trimClip(this._trackId, this._clipId, boundary, this._cumulativeDeltaSamples);
|
|
2356
|
+
this._host.dispatchEvent(
|
|
2357
|
+
new CustomEvent("daw-clip-trim", {
|
|
2358
|
+
bubbles: true,
|
|
2359
|
+
composed: true,
|
|
2360
|
+
detail: {
|
|
2361
|
+
trackId: this._trackId,
|
|
2362
|
+
clipId: this._clipId,
|
|
2363
|
+
boundary,
|
|
2364
|
+
deltaSamples: this._cumulativeDeltaSamples
|
|
2365
|
+
}
|
|
2366
|
+
})
|
|
2367
|
+
);
|
|
2368
|
+
} else {
|
|
2369
|
+
console.warn(
|
|
2370
|
+
"[dawcore] engine unavailable at trim drop \u2014 trim not applied for clip " + this._clipId
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
} finally {
|
|
2375
|
+
if (this._isDragging && this._cumulativeDeltaSamples !== 0) {
|
|
2376
|
+
this._host.engine?.commitTransaction();
|
|
2377
|
+
} else {
|
|
2378
|
+
this._host.engine?.abortTransaction();
|
|
2379
|
+
}
|
|
2380
|
+
this._reset();
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
/** Re-extract peaks from cache and set on waveform elements during trim drag.
|
|
2384
|
+
* Returns true if peaks were successfully updated. */
|
|
2385
|
+
_updateWaveformPeaks(offsetSamples, durationSamples) {
|
|
2386
|
+
if (!this._clipContainer || durationSamples <= 0) return false;
|
|
2387
|
+
const peakSlice = this._host.reextractClipPeaks(this._clipId, offsetSamples, durationSamples);
|
|
2388
|
+
if (!peakSlice) return false;
|
|
2389
|
+
const waveforms = this._clipContainer.querySelectorAll("daw-waveform");
|
|
2390
|
+
for (let i = 0; i < waveforms.length; i++) {
|
|
2391
|
+
const wf = waveforms[i];
|
|
2392
|
+
const channelPeaks = peakSlice.data[i];
|
|
2393
|
+
if (channelPeaks) {
|
|
2394
|
+
wf.peaks = channelPeaks;
|
|
2395
|
+
wf.length = peakSlice.length;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
return true;
|
|
2399
|
+
}
|
|
2400
|
+
/** Restore clip container CSS to original values after trim visual preview. */
|
|
2401
|
+
_restoreTrimVisual() {
|
|
2402
|
+
if (this._clipContainer) {
|
|
2403
|
+
this._clipContainer.style.left = this._originalLeft + "px";
|
|
2404
|
+
this._clipContainer.style.width = this._originalWidth + "px";
|
|
2405
|
+
const waveforms = this._clipContainer.querySelectorAll("daw-waveform");
|
|
2406
|
+
for (const wf of waveforms) {
|
|
2407
|
+
wf.style.left = "0px";
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
_reset() {
|
|
2412
|
+
if (this._boundaryEl) {
|
|
2413
|
+
this._boundaryEl.classList.remove("dragging");
|
|
2414
|
+
this._boundaryEl = null;
|
|
2415
|
+
}
|
|
2416
|
+
this._mode = null;
|
|
2417
|
+
this._clipId = "";
|
|
2418
|
+
this._trackId = "";
|
|
2419
|
+
this._startPx = 0;
|
|
2420
|
+
this._isDragging = false;
|
|
2421
|
+
this._lastDeltaPx = 0;
|
|
2422
|
+
this._cumulativeDeltaSamples = 0;
|
|
2423
|
+
this._clipContainer = null;
|
|
2424
|
+
this._originalLeft = 0;
|
|
2425
|
+
this._originalWidth = 0;
|
|
2426
|
+
this._originalOffsetSamples = 0;
|
|
2427
|
+
this._originalDurationSamples = 0;
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
|
|
1904
2431
|
// src/interactions/file-loader.ts
|
|
1905
2432
|
var import_core2 = require("@waveform-playlist/core");
|
|
1906
2433
|
async function loadFiles(host, files) {
|
|
@@ -1935,10 +2462,16 @@ async function loadFiles(host, files) {
|
|
|
1935
2462
|
sourceDuration: audioBuffer.duration
|
|
1936
2463
|
});
|
|
1937
2464
|
host._clipBuffers = new Map(host._clipBuffers).set(clip.id, audioBuffer);
|
|
2465
|
+
host._clipOffsets.set(clip.id, {
|
|
2466
|
+
offsetSamples: clip.offsetSamples,
|
|
2467
|
+
durationSamples: clip.durationSamples
|
|
2468
|
+
});
|
|
1938
2469
|
const peakData = await host._peakPipeline.generatePeaks(
|
|
1939
2470
|
audioBuffer,
|
|
1940
2471
|
host.samplesPerPixel,
|
|
1941
|
-
host.mono
|
|
2472
|
+
host.mono,
|
|
2473
|
+
clip.offsetSamples,
|
|
2474
|
+
clip.durationSamples
|
|
1942
2475
|
);
|
|
1943
2476
|
host._peaksData = new Map(host._peaksData).set(clip.id, peakData);
|
|
1944
2477
|
const trackId = crypto.randomUUID();
|
|
@@ -1997,17 +2530,31 @@ async function loadFiles(host, files) {
|
|
|
1997
2530
|
|
|
1998
2531
|
// src/interactions/recording-clip.ts
|
|
1999
2532
|
var import_core3 = require("@waveform-playlist/core");
|
|
2000
|
-
function addRecordedClip(host, trackId, buf, startSample, durSamples) {
|
|
2533
|
+
function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamples = 0) {
|
|
2534
|
+
let trimmedBuf = buf;
|
|
2535
|
+
if (offsetSamples > 0 && offsetSamples < buf.length) {
|
|
2536
|
+
const trimmed = new AudioBuffer({
|
|
2537
|
+
numberOfChannels: buf.numberOfChannels,
|
|
2538
|
+
length: durSamples,
|
|
2539
|
+
sampleRate: buf.sampleRate
|
|
2540
|
+
});
|
|
2541
|
+
for (let ch = 0; ch < buf.numberOfChannels; ch++) {
|
|
2542
|
+
const source = buf.getChannelData(ch);
|
|
2543
|
+
trimmed.copyToChannel(source.subarray(offsetSamples, offsetSamples + durSamples), ch);
|
|
2544
|
+
}
|
|
2545
|
+
trimmedBuf = trimmed;
|
|
2546
|
+
}
|
|
2001
2547
|
const clip = (0, import_core3.createClip)({
|
|
2002
|
-
audioBuffer:
|
|
2548
|
+
audioBuffer: trimmedBuf,
|
|
2003
2549
|
startSample,
|
|
2004
2550
|
durationSamples: durSamples,
|
|
2005
2551
|
offsetSamples: 0,
|
|
2552
|
+
// offset already applied by slicing
|
|
2006
2553
|
gain: 1,
|
|
2007
2554
|
name: "Recording"
|
|
2008
2555
|
});
|
|
2009
|
-
host._clipBuffers = new Map(host._clipBuffers).set(clip.id,
|
|
2010
|
-
host._peakPipeline.generatePeaks(
|
|
2556
|
+
host._clipBuffers = new Map(host._clipBuffers).set(clip.id, trimmedBuf);
|
|
2557
|
+
host._peakPipeline.generatePeaks(trimmedBuf, host.samplesPerPixel, host.mono).then((pd) => {
|
|
2011
2558
|
host._peaksData = new Map(host._peaksData).set(clip.id, pd);
|
|
2012
2559
|
const t = host._engineTracks.get(trackId);
|
|
2013
2560
|
if (!t) {
|
|
@@ -2040,7 +2587,12 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples) {
|
|
|
2040
2587
|
});
|
|
2041
2588
|
}
|
|
2042
2589
|
host._recomputeDuration();
|
|
2043
|
-
host.
|
|
2590
|
+
const updatedTrack = host._engineTracks.get(trackId);
|
|
2591
|
+
if (host._engine?.updateTrack && updatedTrack) {
|
|
2592
|
+
host._engine.updateTrack(trackId, updatedTrack);
|
|
2593
|
+
} else {
|
|
2594
|
+
host._engine?.setTracks([...host._engineTracks.values()]);
|
|
2595
|
+
}
|
|
2044
2596
|
}).catch((err) => {
|
|
2045
2597
|
console.warn("[dawcore] Failed to generate peaks for recorded clip: " + String(err));
|
|
2046
2598
|
const next = new Map(host._clipBuffers);
|
|
@@ -2058,6 +2610,169 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples) {
|
|
|
2058
2610
|
});
|
|
2059
2611
|
}
|
|
2060
2612
|
|
|
2613
|
+
// src/interactions/split-handler.ts
|
|
2614
|
+
function splitAtPlayhead(host) {
|
|
2615
|
+
const wasPlaying = host.isPlaying;
|
|
2616
|
+
const time = host.currentTime;
|
|
2617
|
+
if (!canSplitAtTime(host, time)) return false;
|
|
2618
|
+
if (wasPlaying) {
|
|
2619
|
+
host.stop();
|
|
2620
|
+
}
|
|
2621
|
+
let result;
|
|
2622
|
+
try {
|
|
2623
|
+
result = performSplit(host, time);
|
|
2624
|
+
} catch (err) {
|
|
2625
|
+
console.warn("[dawcore] splitAtPlayhead failed: " + String(err));
|
|
2626
|
+
result = false;
|
|
2627
|
+
}
|
|
2628
|
+
if (wasPlaying) {
|
|
2629
|
+
host.play(time);
|
|
2630
|
+
}
|
|
2631
|
+
return result;
|
|
2632
|
+
}
|
|
2633
|
+
function canSplitAtTime(host, time) {
|
|
2634
|
+
const { engine } = host;
|
|
2635
|
+
if (!engine) return false;
|
|
2636
|
+
const state5 = engine.getState();
|
|
2637
|
+
if (!state5.selectedTrackId) return false;
|
|
2638
|
+
const track = state5.tracks.find((t) => t.id === state5.selectedTrackId);
|
|
2639
|
+
if (!track) return false;
|
|
2640
|
+
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
2641
|
+
return !!findClipAtSample(track.clips, atSample);
|
|
2642
|
+
}
|
|
2643
|
+
function performSplit(host, time) {
|
|
2644
|
+
const { engine } = host;
|
|
2645
|
+
if (!engine) return false;
|
|
2646
|
+
const stateBefore = engine.getState();
|
|
2647
|
+
const { selectedTrackId, tracks } = stateBefore;
|
|
2648
|
+
if (!selectedTrackId) return false;
|
|
2649
|
+
const track = tracks.find((t) => t.id === selectedTrackId);
|
|
2650
|
+
if (!track) return false;
|
|
2651
|
+
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
2652
|
+
const clip = findClipAtSample(track.clips, atSample);
|
|
2653
|
+
if (!clip) return false;
|
|
2654
|
+
const originalClipId = clip.id;
|
|
2655
|
+
const clipIdsBefore = new Set(track.clips.map((c) => c.id));
|
|
2656
|
+
engine.splitClip(selectedTrackId, originalClipId, atSample);
|
|
2657
|
+
const stateAfter = engine.getState();
|
|
2658
|
+
const trackAfter = stateAfter.tracks.find((t) => t.id === selectedTrackId);
|
|
2659
|
+
if (!trackAfter) {
|
|
2660
|
+
console.warn(
|
|
2661
|
+
'[dawcore] splitAtPlayhead: track "' + selectedTrackId + '" disappeared after split'
|
|
2662
|
+
);
|
|
2663
|
+
return false;
|
|
2664
|
+
}
|
|
2665
|
+
const newClips = trackAfter.clips.filter((c) => !clipIdsBefore.has(c.id));
|
|
2666
|
+
if (newClips.length !== 2) {
|
|
2667
|
+
if (newClips.length > 0) {
|
|
2668
|
+
console.warn(
|
|
2669
|
+
"[dawcore] splitAtPlayhead: expected 2 new clips after split but got " + newClips.length
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
return false;
|
|
2673
|
+
}
|
|
2674
|
+
const sorted = [...newClips].sort((a, b) => a.startSample - b.startSample);
|
|
2675
|
+
const leftClipId = sorted[0].id;
|
|
2676
|
+
const rightClipId = sorted[1].id;
|
|
2677
|
+
host.dispatchEvent(
|
|
2678
|
+
new CustomEvent("daw-clip-split", {
|
|
2679
|
+
bubbles: true,
|
|
2680
|
+
composed: true,
|
|
2681
|
+
detail: {
|
|
2682
|
+
trackId: selectedTrackId,
|
|
2683
|
+
originalClipId,
|
|
2684
|
+
leftClipId,
|
|
2685
|
+
rightClipId
|
|
2686
|
+
}
|
|
2687
|
+
})
|
|
2688
|
+
);
|
|
2689
|
+
return true;
|
|
2690
|
+
}
|
|
2691
|
+
function findClipAtSample(clips, atSample) {
|
|
2692
|
+
return clips.find(
|
|
2693
|
+
(c) => atSample > c.startSample && atSample < c.startSample + c.durationSamples
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// src/interactions/clip-peak-sync.ts
|
|
2698
|
+
function syncPeaksForChangedClips(host, tracks) {
|
|
2699
|
+
const currentClipIds = /* @__PURE__ */ new Set();
|
|
2700
|
+
for (const track of tracks) {
|
|
2701
|
+
for (const clip of track.clips) {
|
|
2702
|
+
currentClipIds.add(clip.id);
|
|
2703
|
+
const cached = host._clipOffsets.get(clip.id);
|
|
2704
|
+
const needsPeaks = !host._peaksData.has(clip.id) || !cached || cached.offsetSamples !== clip.offsetSamples || cached.durationSamples !== clip.durationSamples;
|
|
2705
|
+
if (!needsPeaks) continue;
|
|
2706
|
+
const audioBuffer = clip.audioBuffer ?? host._clipBuffers.get(clip.id) ?? findAudioBufferForClip(host, clip, track);
|
|
2707
|
+
if (!audioBuffer) {
|
|
2708
|
+
console.warn(
|
|
2709
|
+
"[dawcore] syncPeaksForChangedClips: no AudioBuffer for clip " + clip.id + " \u2014 waveform will be blank"
|
|
2710
|
+
);
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
host._clipBuffers = new Map(host._clipBuffers).set(clip.id, audioBuffer);
|
|
2714
|
+
host._clipOffsets.set(clip.id, {
|
|
2715
|
+
offsetSamples: clip.offsetSamples,
|
|
2716
|
+
durationSamples: clip.durationSamples
|
|
2717
|
+
});
|
|
2718
|
+
host._peakPipeline.generatePeaks(
|
|
2719
|
+
audioBuffer,
|
|
2720
|
+
host.samplesPerPixel,
|
|
2721
|
+
host.mono,
|
|
2722
|
+
clip.offsetSamples,
|
|
2723
|
+
clip.durationSamples
|
|
2724
|
+
).then((peakData) => {
|
|
2725
|
+
host._peaksData = new Map(host._peaksData).set(clip.id, peakData);
|
|
2726
|
+
}).catch((err) => {
|
|
2727
|
+
console.warn(
|
|
2728
|
+
"[dawcore] Failed to generate peaks for clip " + clip.id + ": " + String(err)
|
|
2729
|
+
);
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
cleanupOrphanedClipData(host, currentClipIds);
|
|
2734
|
+
}
|
|
2735
|
+
function cleanupOrphanedClipData(host, currentClipIds) {
|
|
2736
|
+
let buffersChanged = false;
|
|
2737
|
+
let peaksChanged = false;
|
|
2738
|
+
for (const id of host._clipBuffers.keys()) {
|
|
2739
|
+
if (!currentClipIds.has(id)) {
|
|
2740
|
+
host._clipBuffers.delete(id);
|
|
2741
|
+
buffersChanged = true;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
let offsetsChanged = false;
|
|
2745
|
+
for (const id of host._clipOffsets.keys()) {
|
|
2746
|
+
if (!currentClipIds.has(id)) {
|
|
2747
|
+
host._clipOffsets.delete(id);
|
|
2748
|
+
offsetsChanged = true;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
for (const id of host._peaksData.keys()) {
|
|
2752
|
+
if (!currentClipIds.has(id)) {
|
|
2753
|
+
host._peaksData.delete(id);
|
|
2754
|
+
peaksChanged = true;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (buffersChanged) {
|
|
2758
|
+
host._clipBuffers = new Map(host._clipBuffers);
|
|
2759
|
+
}
|
|
2760
|
+
if (offsetsChanged) {
|
|
2761
|
+
host._clipOffsets = new Map(host._clipOffsets);
|
|
2762
|
+
}
|
|
2763
|
+
if (peaksChanged) {
|
|
2764
|
+
host._peaksData = new Map(host._peaksData);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
function findAudioBufferForClip(host, clip, track) {
|
|
2768
|
+
for (const sibling of track.clips) {
|
|
2769
|
+
if (sibling.id === clip.id) continue;
|
|
2770
|
+
const buf = host._clipBuffers.get(sibling.id);
|
|
2771
|
+
if (buf) return buf;
|
|
2772
|
+
}
|
|
2773
|
+
return null;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2061
2776
|
// src/elements/daw-editor.ts
|
|
2062
2777
|
var DawEditorElement = class extends import_lit12.LitElement {
|
|
2063
2778
|
constructor() {
|
|
@@ -2069,6 +2784,9 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2069
2784
|
this.barWidth = 1;
|
|
2070
2785
|
this.barGap = 0;
|
|
2071
2786
|
this.fileDrop = false;
|
|
2787
|
+
this.clipHeaders = false;
|
|
2788
|
+
this.clipHeaderHeight = 20;
|
|
2789
|
+
this.interactiveClips = false;
|
|
2072
2790
|
this.sampleRate = 48e3;
|
|
2073
2791
|
/** Resolved sample rate — falls back to sampleRate property until first audio decode. */
|
|
2074
2792
|
this._resolvedSampleRate = null;
|
|
@@ -2085,14 +2803,15 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2085
2803
|
this._currentTime = 0;
|
|
2086
2804
|
this._engine = null;
|
|
2087
2805
|
this._enginePromise = null;
|
|
2088
|
-
this._audioInitialized = false;
|
|
2089
2806
|
this._audioCache = /* @__PURE__ */ new Map();
|
|
2090
2807
|
this._clipBuffers = /* @__PURE__ */ new Map();
|
|
2808
|
+
this._clipOffsets = /* @__PURE__ */ new Map();
|
|
2091
2809
|
this._peakPipeline = new PeakPipeline();
|
|
2092
2810
|
this._trackElements = /* @__PURE__ */ new Map();
|
|
2093
2811
|
this._childObserver = null;
|
|
2094
2812
|
this._audioResume = new AudioResumeController(this);
|
|
2095
2813
|
this._recordingController = new RecordingController(this);
|
|
2814
|
+
this._clipPointer = new ClipPointerHandler(this);
|
|
2096
2815
|
this._pointer = new PointerHandler(this);
|
|
2097
2816
|
this._viewport = (() => {
|
|
2098
2817
|
const v = new ViewportController(this);
|
|
@@ -2136,6 +2855,19 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2136
2855
|
this._onTrackControl = (e) => {
|
|
2137
2856
|
const { trackId, prop, value } = e.detail ?? {};
|
|
2138
2857
|
if (!trackId || !prop || !DawEditorElement._CONTROL_PROPS.has(prop)) return;
|
|
2858
|
+
if (this._selectedTrackId !== trackId) {
|
|
2859
|
+
this._setSelectedTrackId(trackId);
|
|
2860
|
+
if (this._engine) {
|
|
2861
|
+
this._engine.selectTrack(trackId);
|
|
2862
|
+
}
|
|
2863
|
+
this.dispatchEvent(
|
|
2864
|
+
new CustomEvent("daw-track-select", {
|
|
2865
|
+
bubbles: true,
|
|
2866
|
+
composed: true,
|
|
2867
|
+
detail: { trackId }
|
|
2868
|
+
})
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2139
2871
|
const oldDescriptor = this._tracks.get(trackId);
|
|
2140
2872
|
if (oldDescriptor) {
|
|
2141
2873
|
const descriptor = { ...oldDescriptor, [prop]: value };
|
|
@@ -2196,6 +2928,28 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2196
2928
|
// --- Recording ---
|
|
2197
2929
|
this.recordingStream = null;
|
|
2198
2930
|
}
|
|
2931
|
+
get _clipHandler() {
|
|
2932
|
+
return this.interactiveClips ? this._clipPointer : null;
|
|
2933
|
+
}
|
|
2934
|
+
get engine() {
|
|
2935
|
+
return this._engine;
|
|
2936
|
+
}
|
|
2937
|
+
/** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
|
|
2938
|
+
reextractClipPeaks(clipId, offsetSamples, durationSamples) {
|
|
2939
|
+
const buf = this._clipBuffers.get(clipId);
|
|
2940
|
+
if (!buf) return null;
|
|
2941
|
+
const singleClipBuffers = /* @__PURE__ */ new Map([[clipId, buf]]);
|
|
2942
|
+
const singleClipOffsets = /* @__PURE__ */ new Map([[clipId, { offsetSamples, durationSamples }]]);
|
|
2943
|
+
const result = this._peakPipeline.reextractPeaks(
|
|
2944
|
+
singleClipBuffers,
|
|
2945
|
+
this.samplesPerPixel,
|
|
2946
|
+
this.mono,
|
|
2947
|
+
singleClipOffsets
|
|
2948
|
+
);
|
|
2949
|
+
const peakData = result.get(clipId);
|
|
2950
|
+
if (!peakData) return null;
|
|
2951
|
+
return { data: peakData.data, length: peakData.length };
|
|
2952
|
+
}
|
|
2199
2953
|
get effectiveSampleRate() {
|
|
2200
2954
|
return this._resolvedSampleRate ?? this.sampleRate;
|
|
2201
2955
|
}
|
|
@@ -2270,6 +3024,7 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2270
3024
|
this._trackElements.clear();
|
|
2271
3025
|
this._audioCache.clear();
|
|
2272
3026
|
this._clipBuffers.clear();
|
|
3027
|
+
this._clipOffsets.clear();
|
|
2273
3028
|
this._peakPipeline.terminate();
|
|
2274
3029
|
try {
|
|
2275
3030
|
this._disposeEngine();
|
|
@@ -2281,17 +3036,19 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2281
3036
|
if (changedProperties.has("eagerResume")) {
|
|
2282
3037
|
this._audioResume.target = this.eagerResume;
|
|
2283
3038
|
}
|
|
3039
|
+
if (changedProperties.has("samplesPerPixel") && this._isPlaying) {
|
|
3040
|
+
this._startPlayhead();
|
|
3041
|
+
}
|
|
2284
3042
|
if (changedProperties.has("samplesPerPixel") && this._clipBuffers.size > 0) {
|
|
2285
|
-
const
|
|
3043
|
+
const re = this._peakPipeline.reextractPeaks(
|
|
2286
3044
|
this._clipBuffers,
|
|
2287
3045
|
this.samplesPerPixel,
|
|
2288
|
-
this.mono
|
|
3046
|
+
this.mono,
|
|
3047
|
+
this._clipOffsets
|
|
2289
3048
|
);
|
|
2290
|
-
if (
|
|
3049
|
+
if (re.size > 0) {
|
|
2291
3050
|
const next = new Map(this._peaksData);
|
|
2292
|
-
for (const [
|
|
2293
|
-
next.set(clipId, peakData);
|
|
2294
|
-
}
|
|
3051
|
+
for (const [id, pd] of re) next.set(id, pd);
|
|
2295
3052
|
this._peaksData = next;
|
|
2296
3053
|
}
|
|
2297
3054
|
}
|
|
@@ -2303,6 +3060,7 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2303
3060
|
const nextPeaks = new Map(this._peaksData);
|
|
2304
3061
|
for (const clip of removedTrack.clips) {
|
|
2305
3062
|
this._clipBuffers.delete(clip.id);
|
|
3063
|
+
this._clipOffsets.delete(clip.id);
|
|
2306
3064
|
nextPeaks.delete(clip.id);
|
|
2307
3065
|
}
|
|
2308
3066
|
this._peaksData = nextPeaks;
|
|
@@ -2381,10 +3139,16 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2381
3139
|
sourceDuration: audioBuffer.duration
|
|
2382
3140
|
});
|
|
2383
3141
|
this._clipBuffers = new Map(this._clipBuffers).set(clip.id, audioBuffer);
|
|
3142
|
+
this._clipOffsets.set(clip.id, {
|
|
3143
|
+
offsetSamples: clip.offsetSamples,
|
|
3144
|
+
durationSamples: clip.durationSamples
|
|
3145
|
+
});
|
|
2384
3146
|
const peakData = await this._peakPipeline.generatePeaks(
|
|
2385
3147
|
audioBuffer,
|
|
2386
3148
|
this.samplesPerPixel,
|
|
2387
|
-
this.mono
|
|
3149
|
+
this.mono,
|
|
3150
|
+
clip.offsetSamples,
|
|
3151
|
+
clip.durationSamples
|
|
2388
3152
|
);
|
|
2389
3153
|
this._peaksData = new Map(this._peaksData).set(clip.id, peakData);
|
|
2390
3154
|
clips.push(clip);
|
|
@@ -2476,10 +3240,20 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2476
3240
|
samplesPerPixel: this.samplesPerPixel,
|
|
2477
3241
|
zoomLevels: [256, 512, 1024, 2048, 4096, 8192, this.samplesPerPixel].filter((v, i, a) => a.indexOf(v) === i).sort((a, b) => a - b)
|
|
2478
3242
|
});
|
|
3243
|
+
let lastTracksVersion = -1;
|
|
2479
3244
|
engine.on("statechange", (engineState) => {
|
|
2480
3245
|
this._isPlaying = engineState.isPlaying;
|
|
2481
3246
|
this._duration = engineState.duration;
|
|
2482
3247
|
this._selectedTrackId = engineState.selectedTrackId;
|
|
3248
|
+
if (engineState.tracksVersion !== lastTracksVersion) {
|
|
3249
|
+
lastTracksVersion = engineState.tracksVersion;
|
|
3250
|
+
const nextTracks = /* @__PURE__ */ new Map();
|
|
3251
|
+
for (const track of engineState.tracks) {
|
|
3252
|
+
nextTracks.set(track.id, track);
|
|
3253
|
+
}
|
|
3254
|
+
this._engineTracks = nextTracks;
|
|
3255
|
+
syncPeaksForChangedClips(this, engineState.tracks);
|
|
3256
|
+
}
|
|
2483
3257
|
});
|
|
2484
3258
|
engine.on("timeupdate", (time) => {
|
|
2485
3259
|
this._currentTime = time;
|
|
@@ -2502,14 +3276,11 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2502
3276
|
return loadFiles(this, files);
|
|
2503
3277
|
}
|
|
2504
3278
|
// --- Playback ---
|
|
2505
|
-
async play() {
|
|
3279
|
+
async play(startTime) {
|
|
2506
3280
|
try {
|
|
2507
3281
|
const engine = await this._ensureEngine();
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
this._audioInitialized = true;
|
|
2511
|
-
}
|
|
2512
|
-
engine.play();
|
|
3282
|
+
await engine.init();
|
|
3283
|
+
engine.play(startTime);
|
|
2513
3284
|
this._startPlayhead();
|
|
2514
3285
|
this.dispatchEvent(new CustomEvent("daw-play", { bubbles: true, composed: true }));
|
|
2515
3286
|
} catch (err) {
|
|
@@ -2535,19 +3306,90 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2535
3306
|
this._stopPlayhead();
|
|
2536
3307
|
this.dispatchEvent(new CustomEvent("daw-stop", { bubbles: true, composed: true }));
|
|
2537
3308
|
}
|
|
3309
|
+
/** Toggle between play and pause. */
|
|
3310
|
+
togglePlayPause() {
|
|
3311
|
+
if (this._isPlaying) {
|
|
3312
|
+
this.pause();
|
|
3313
|
+
} else {
|
|
3314
|
+
this.play();
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
2538
3317
|
seekTo(time) {
|
|
2539
|
-
if (!this._engine)
|
|
2540
|
-
|
|
2541
|
-
|
|
3318
|
+
if (!this._engine) {
|
|
3319
|
+
console.warn("[dawcore] seekTo: engine not ready, call ignored");
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
if (this._isPlaying) {
|
|
3323
|
+
this.stop();
|
|
3324
|
+
this.play(time);
|
|
3325
|
+
} else {
|
|
3326
|
+
this._engine.seek(time);
|
|
3327
|
+
this._currentTime = time;
|
|
3328
|
+
this._stopPlayhead();
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
/** Undo the last structural edit. */
|
|
3332
|
+
undo() {
|
|
3333
|
+
if (!this._engine) {
|
|
3334
|
+
console.warn("[dawcore] undo: engine not ready, call ignored");
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
this._engine.undo();
|
|
3338
|
+
}
|
|
3339
|
+
/** Redo the last undone edit. */
|
|
3340
|
+
redo() {
|
|
3341
|
+
if (!this._engine) {
|
|
3342
|
+
console.warn("[dawcore] redo: engine not ready, call ignored");
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
this._engine.redo();
|
|
3346
|
+
}
|
|
3347
|
+
/** Whether undo is available. */
|
|
3348
|
+
get canUndo() {
|
|
3349
|
+
return this._engine?.canUndo ?? false;
|
|
3350
|
+
}
|
|
3351
|
+
/** Whether redo is available. */
|
|
3352
|
+
get canRedo() {
|
|
3353
|
+
return this._engine?.canRedo ?? false;
|
|
3354
|
+
}
|
|
3355
|
+
/** Split the clip under the playhead on the selected track. */
|
|
3356
|
+
splitAtPlayhead() {
|
|
3357
|
+
return splitAtPlayhead({
|
|
3358
|
+
effectiveSampleRate: this.effectiveSampleRate,
|
|
3359
|
+
currentTime: this._currentTime,
|
|
3360
|
+
isPlaying: this._isPlaying,
|
|
3361
|
+
engine: this._engine,
|
|
3362
|
+
dispatchEvent: (e) => this.dispatchEvent(e),
|
|
3363
|
+
stop: () => {
|
|
3364
|
+
this._engine?.stop();
|
|
3365
|
+
this._stopPlayhead();
|
|
3366
|
+
},
|
|
3367
|
+
// Call engine.play directly (synchronous) — not the async editor play()
|
|
3368
|
+
// which yields to microtask queue via await engine.init(). Engine is
|
|
3369
|
+
// already initialized at split time; the async gap causes audio desync.
|
|
3370
|
+
play: (time) => {
|
|
3371
|
+
this._engine?.play(time);
|
|
3372
|
+
this._startPlayhead();
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
get currentTime() {
|
|
3377
|
+
return this._currentTime;
|
|
2542
3378
|
}
|
|
2543
3379
|
get isRecording() {
|
|
2544
3380
|
return this._recordingController.isRecording;
|
|
2545
3381
|
}
|
|
3382
|
+
pauseRecording() {
|
|
3383
|
+
this._recordingController.pauseRecording();
|
|
3384
|
+
}
|
|
3385
|
+
resumeRecording() {
|
|
3386
|
+
this._recordingController.resumeRecording();
|
|
3387
|
+
}
|
|
2546
3388
|
stopRecording() {
|
|
2547
3389
|
this._recordingController.stopRecording();
|
|
2548
3390
|
}
|
|
2549
|
-
_addRecordedClip(trackId, buf, startSample, durSamples) {
|
|
2550
|
-
addRecordedClip(this, trackId, buf, startSample, durSamples);
|
|
3391
|
+
_addRecordedClip(trackId, buf, startSample, durSamples, offsetSamples = 0) {
|
|
3392
|
+
addRecordedClip(this, trackId, buf, startSample, durSamples, offsetSamples);
|
|
2551
3393
|
}
|
|
2552
3394
|
async startRecording(stream, options) {
|
|
2553
3395
|
const s = stream ?? this.recordingStream;
|
|
@@ -2560,15 +3402,19 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2560
3402
|
_renderRecordingPreview(trackId, chH) {
|
|
2561
3403
|
const rs = this._recordingController.getSession(trackId);
|
|
2562
3404
|
if (!rs) return "";
|
|
3405
|
+
const audibleSamples = Math.max(0, rs.totalSamples - rs.latencySamples);
|
|
3406
|
+
if (audibleSamples === 0) return "";
|
|
3407
|
+
const latencyPixels = Math.floor(rs.latencySamples / this.samplesPerPixel);
|
|
2563
3408
|
const left = Math.floor(rs.startSample / this.samplesPerPixel);
|
|
2564
|
-
const w = Math.floor(
|
|
2565
|
-
return rs.peaks.map(
|
|
2566
|
-
(
|
|
3409
|
+
const w = Math.floor(audibleSamples / this.samplesPerPixel);
|
|
3410
|
+
return rs.peaks.map((chPeaks, ch) => {
|
|
3411
|
+
const slicedPeaks = latencyPixels > 0 ? chPeaks.slice(latencyPixels * 2) : chPeaks;
|
|
3412
|
+
return import_lit12.html`
|
|
2567
3413
|
<daw-waveform
|
|
2568
3414
|
data-recording-track=${trackId}
|
|
2569
3415
|
data-recording-channel=${ch}
|
|
2570
3416
|
style="position:absolute;left:${left}px;top:${ch * chH}px;"
|
|
2571
|
-
.peaks=${
|
|
3417
|
+
.peaks=${slicedPeaks}
|
|
2572
3418
|
.length=${w}
|
|
2573
3419
|
.waveHeight=${chH}
|
|
2574
3420
|
.barWidth=${this.barWidth}
|
|
@@ -2577,8 +3423,8 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2577
3423
|
.visibleEnd=${this._viewport.visibleEnd}
|
|
2578
3424
|
.originX=${left}
|
|
2579
3425
|
></daw-waveform>
|
|
2580
|
-
|
|
2581
|
-
);
|
|
3426
|
+
`;
|
|
3427
|
+
});
|
|
2582
3428
|
}
|
|
2583
3429
|
// --- Playhead ---
|
|
2584
3430
|
_startPlayhead() {
|
|
@@ -2620,13 +3466,14 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2620
3466
|
const orderedTracks = this._getOrderedTracks().map(([trackId, track]) => {
|
|
2621
3467
|
const descriptor = this._tracks.get(trackId);
|
|
2622
3468
|
const firstPeaks = track.clips.map((c) => this._peaksData.get(c.id)).find((p) => p && p.data.length > 0);
|
|
2623
|
-
const
|
|
3469
|
+
const recSession = this._recordingController.getSession(trackId);
|
|
3470
|
+
const numChannels = firstPeaks ? firstPeaks.data.length : recSession ? recSession.channelCount : 1;
|
|
2624
3471
|
return {
|
|
2625
3472
|
trackId,
|
|
2626
3473
|
track,
|
|
2627
3474
|
descriptor,
|
|
2628
3475
|
numChannels,
|
|
2629
|
-
trackHeight: this.waveHeight * numChannels
|
|
3476
|
+
trackHeight: this.waveHeight * numChannels + (this.clipHeaders ? this.clipHeaderHeight : 0)
|
|
2630
3477
|
};
|
|
2631
3478
|
});
|
|
2632
3479
|
return import_lit12.html`
|
|
@@ -2680,21 +3527,47 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2680
3527
|
);
|
|
2681
3528
|
const clipLeft = Math.floor(clip.startSample / this.samplesPerPixel);
|
|
2682
3529
|
const channels = peakData?.data ?? [new Int16Array(0)];
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
3530
|
+
const hdrH = this.clipHeaders ? this.clipHeaderHeight : 0;
|
|
3531
|
+
const chH = this.waveHeight;
|
|
3532
|
+
return import_lit12.html` <div
|
|
3533
|
+
class="clip-container"
|
|
3534
|
+
style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
|
|
3535
|
+
data-clip-id=${clip.id}
|
|
3536
|
+
>
|
|
3537
|
+
${hdrH > 0 ? import_lit12.html`<div
|
|
3538
|
+
class="clip-header"
|
|
3539
|
+
data-clip-id=${clip.id}
|
|
3540
|
+
data-track-id=${t.trackId}
|
|
3541
|
+
?data-interactive=${this.interactiveClips}
|
|
3542
|
+
>
|
|
3543
|
+
<span>${clip.name || t.descriptor?.name || ""}</span>
|
|
3544
|
+
</div>` : ""}
|
|
3545
|
+
${channels.map(
|
|
3546
|
+
(chPeaks, chIdx) => import_lit12.html` <daw-waveform
|
|
3547
|
+
style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
|
|
3548
|
+
.peaks=${chPeaks}
|
|
3549
|
+
.length=${peakData?.length ?? width}
|
|
3550
|
+
.waveHeight=${chH}
|
|
3551
|
+
.barWidth=${this.barWidth}
|
|
3552
|
+
.barGap=${this.barGap}
|
|
3553
|
+
.visibleStart=${this._viewport.visibleStart}
|
|
3554
|
+
.visibleEnd=${this._viewport.visibleEnd}
|
|
3555
|
+
.originX=${clipLeft}
|
|
3556
|
+
></daw-waveform>`
|
|
3557
|
+
)}
|
|
3558
|
+
${this.interactiveClips ? import_lit12.html` <div
|
|
3559
|
+
class="clip-boundary"
|
|
3560
|
+
data-boundary-edge="left"
|
|
3561
|
+
data-clip-id=${clip.id}
|
|
3562
|
+
data-track-id=${t.trackId}
|
|
3563
|
+
></div>
|
|
3564
|
+
<div
|
|
3565
|
+
class="clip-boundary"
|
|
3566
|
+
data-boundary-edge="right"
|
|
3567
|
+
data-clip-id=${clip.id}
|
|
3568
|
+
data-track-id=${t.trackId}
|
|
3569
|
+
></div>` : ""}
|
|
3570
|
+
</div>`;
|
|
2698
3571
|
})}
|
|
2699
3572
|
${this._renderRecordingPreview(t.trackId, channelHeight)}
|
|
2700
3573
|
</div>
|
|
@@ -2742,7 +3615,8 @@ DawEditorElement.styles = [
|
|
|
2742
3615
|
outline: 2px dashed var(--daw-selection-color, rgba(99, 199, 95, 0.3));
|
|
2743
3616
|
outline-offset: -2px;
|
|
2744
3617
|
}
|
|
2745
|
-
|
|
3618
|
+
`,
|
|
3619
|
+
clipStyles
|
|
2746
3620
|
];
|
|
2747
3621
|
DawEditorElement._CONTROL_PROPS = /* @__PURE__ */ new Set(["volume", "pan", "muted", "soloed"]);
|
|
2748
3622
|
__decorateClass([
|
|
@@ -2766,6 +3640,15 @@ __decorateClass([
|
|
|
2766
3640
|
__decorateClass([
|
|
2767
3641
|
(0, import_decorators10.property)({ type: Boolean, attribute: "file-drop" })
|
|
2768
3642
|
], DawEditorElement.prototype, "fileDrop", 2);
|
|
3643
|
+
__decorateClass([
|
|
3644
|
+
(0, import_decorators10.property)({ type: Boolean, attribute: "clip-headers" })
|
|
3645
|
+
], DawEditorElement.prototype, "clipHeaders", 2);
|
|
3646
|
+
__decorateClass([
|
|
3647
|
+
(0, import_decorators10.property)({ type: Number, attribute: "clip-header-height" })
|
|
3648
|
+
], DawEditorElement.prototype, "clipHeaderHeight", 2);
|
|
3649
|
+
__decorateClass([
|
|
3650
|
+
(0, import_decorators10.property)({ type: Boolean, attribute: "interactive-clips" })
|
|
3651
|
+
], DawEditorElement.prototype, "interactiveClips", 2);
|
|
2769
3652
|
__decorateClass([
|
|
2770
3653
|
(0, import_decorators10.property)({ type: Number, attribute: "sample-rate" })
|
|
2771
3654
|
], DawEditorElement.prototype, "sampleRate", 2);
|
|
@@ -3030,7 +3913,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
|
|
|
3030
3913
|
}
|
|
3031
3914
|
connectedCallback() {
|
|
3032
3915
|
super.connectedCallback();
|
|
3033
|
-
this._listenToTarget();
|
|
3916
|
+
requestAnimationFrame(() => this._listenToTarget());
|
|
3034
3917
|
}
|
|
3035
3918
|
disconnectedCallback() {
|
|
3036
3919
|
super.disconnectedCallback();
|
|
@@ -3055,11 +3938,12 @@ var DawRecordButtonElement = class extends DawTransportButton {
|
|
|
3055
3938
|
render() {
|
|
3056
3939
|
return import_lit15.html`
|
|
3057
3940
|
<button part="button" ?data-recording=${this._isRecording} @click=${this._onClick}>
|
|
3058
|
-
<slot
|
|
3941
|
+
<slot>Record</slot>
|
|
3059
3942
|
</button>
|
|
3060
3943
|
`;
|
|
3061
3944
|
}
|
|
3062
3945
|
_onClick() {
|
|
3946
|
+
if (this._isRecording) return;
|
|
3063
3947
|
const target = this.target;
|
|
3064
3948
|
if (!target) {
|
|
3065
3949
|
console.warn(
|
|
@@ -3067,11 +3951,7 @@ var DawRecordButtonElement = class extends DawTransportButton {
|
|
|
3067
3951
|
);
|
|
3068
3952
|
return;
|
|
3069
3953
|
}
|
|
3070
|
-
|
|
3071
|
-
target.stopRecording();
|
|
3072
|
-
} else {
|
|
3073
|
-
target.startRecording(target.recordingStream);
|
|
3074
|
-
}
|
|
3954
|
+
target.startRecording(target.recordingStream);
|
|
3075
3955
|
}
|
|
3076
3956
|
};
|
|
3077
3957
|
DawRecordButtonElement.styles = [
|
|
@@ -3080,6 +3960,7 @@ DawRecordButtonElement.styles = [
|
|
|
3080
3960
|
button[data-recording] {
|
|
3081
3961
|
color: #d08070;
|
|
3082
3962
|
border-color: #d08070;
|
|
3963
|
+
background: rgba(208, 128, 112, 0.15);
|
|
3083
3964
|
}
|
|
3084
3965
|
`
|
|
3085
3966
|
];
|
|
@@ -3089,11 +3970,183 @@ __decorateClass([
|
|
|
3089
3970
|
DawRecordButtonElement = __decorateClass([
|
|
3090
3971
|
(0, import_decorators13.customElement)("daw-record-button")
|
|
3091
3972
|
], DawRecordButtonElement);
|
|
3973
|
+
|
|
3974
|
+
// src/elements/daw-keyboard-shortcuts.ts
|
|
3975
|
+
var import_lit16 = require("lit");
|
|
3976
|
+
var import_decorators14 = require("lit/decorators.js");
|
|
3977
|
+
var import_core5 = require("@waveform-playlist/core");
|
|
3978
|
+
var DawKeyboardShortcutsElement = class extends import_lit16.LitElement {
|
|
3979
|
+
constructor() {
|
|
3980
|
+
super(...arguments);
|
|
3981
|
+
this.playback = false;
|
|
3982
|
+
this.splitting = false;
|
|
3983
|
+
this.undo = false;
|
|
3984
|
+
// --- JS properties for remapping ---
|
|
3985
|
+
this.playbackShortcuts = null;
|
|
3986
|
+
this.splittingShortcuts = null;
|
|
3987
|
+
this.undoShortcuts = null;
|
|
3988
|
+
/** Additional custom shortcuts. */
|
|
3989
|
+
this.customShortcuts = [];
|
|
3990
|
+
this._editor = null;
|
|
3991
|
+
this._cachedShortcuts = null;
|
|
3992
|
+
// --- Event handler ---
|
|
3993
|
+
this._onKeyDown = (e) => {
|
|
3994
|
+
const shortcuts = this.shortcuts;
|
|
3995
|
+
if (shortcuts.length === 0) return;
|
|
3996
|
+
try {
|
|
3997
|
+
(0, import_core5.handleKeyboardEvent)(e, shortcuts, true);
|
|
3998
|
+
} catch (err) {
|
|
3999
|
+
console.warn("[dawcore] Keyboard shortcut failed (key=" + e.key + "): " + String(err));
|
|
4000
|
+
const target = this._editor ?? this;
|
|
4001
|
+
target.dispatchEvent(
|
|
4002
|
+
new CustomEvent("daw-error", {
|
|
4003
|
+
bubbles: true,
|
|
4004
|
+
composed: true,
|
|
4005
|
+
detail: { operation: "keyboard-shortcut", key: e.key, error: err }
|
|
4006
|
+
})
|
|
4007
|
+
);
|
|
4008
|
+
}
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
/** All active shortcuts (read-only, cached). */
|
|
4012
|
+
get shortcuts() {
|
|
4013
|
+
if (!this._cachedShortcuts) {
|
|
4014
|
+
this._cachedShortcuts = this._buildShortcuts();
|
|
4015
|
+
}
|
|
4016
|
+
return this._cachedShortcuts;
|
|
4017
|
+
}
|
|
4018
|
+
/** Invalidate cached shortcuts when Lit properties change. */
|
|
4019
|
+
updated() {
|
|
4020
|
+
this._cachedShortcuts = null;
|
|
4021
|
+
}
|
|
4022
|
+
// --- Lifecycle ---
|
|
4023
|
+
connectedCallback() {
|
|
4024
|
+
super.connectedCallback();
|
|
4025
|
+
this._editor = this.closest("daw-editor");
|
|
4026
|
+
if (!this._editor) {
|
|
4027
|
+
console.warn(
|
|
4028
|
+
"[dawcore] <daw-keyboard-shortcuts> must be placed inside a <daw-editor>. Preset shortcuts (playback, splitting, undo) will be inactive; only customShortcuts will fire."
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
document.addEventListener("keydown", this._onKeyDown);
|
|
4032
|
+
}
|
|
4033
|
+
disconnectedCallback() {
|
|
4034
|
+
super.disconnectedCallback();
|
|
4035
|
+
document.removeEventListener("keydown", this._onKeyDown);
|
|
4036
|
+
this._editor = null;
|
|
4037
|
+
}
|
|
4038
|
+
// No shadow DOM — render-less element
|
|
4039
|
+
createRenderRoot() {
|
|
4040
|
+
return this;
|
|
4041
|
+
}
|
|
4042
|
+
// --- Shortcut building ---
|
|
4043
|
+
_buildShortcuts() {
|
|
4044
|
+
const editor = this._editor;
|
|
4045
|
+
if (!editor) return this.customShortcuts;
|
|
4046
|
+
const result = [];
|
|
4047
|
+
if (this.playback) {
|
|
4048
|
+
const map = this.playbackShortcuts;
|
|
4049
|
+
result.push(
|
|
4050
|
+
this._makeShortcut(
|
|
4051
|
+
map?.playPause ?? { key: " ", ctrlKey: false, metaKey: false },
|
|
4052
|
+
() => editor.togglePlayPause(),
|
|
4053
|
+
"Play/Pause"
|
|
4054
|
+
),
|
|
4055
|
+
this._makeShortcut(
|
|
4056
|
+
map?.stop ?? { key: "Escape", ctrlKey: false, metaKey: false },
|
|
4057
|
+
() => editor.stop(),
|
|
4058
|
+
"Stop"
|
|
4059
|
+
),
|
|
4060
|
+
this._makeShortcut(
|
|
4061
|
+
map?.rewindToStart ?? { key: "0", ctrlKey: false, metaKey: false },
|
|
4062
|
+
() => editor.seekTo(0),
|
|
4063
|
+
"Rewind to start"
|
|
4064
|
+
)
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
if (this.splitting) {
|
|
4068
|
+
const map = this.splittingShortcuts;
|
|
4069
|
+
const binding = map?.splitAtPlayhead ?? {
|
|
4070
|
+
key: "s",
|
|
4071
|
+
ctrlKey: false,
|
|
4072
|
+
metaKey: false,
|
|
4073
|
+
altKey: false
|
|
4074
|
+
};
|
|
4075
|
+
result.push(this._makeShortcut(binding, () => editor.splitAtPlayhead(), "Split at playhead"));
|
|
4076
|
+
}
|
|
4077
|
+
if (this.undo) {
|
|
4078
|
+
const map = this.undoShortcuts;
|
|
4079
|
+
const undoBinding = map?.undo ?? { key: "z" };
|
|
4080
|
+
const redoBinding = map?.redo ?? { key: "z", shiftKey: true };
|
|
4081
|
+
if (undoBinding.ctrlKey === void 0 && undoBinding.metaKey === void 0) {
|
|
4082
|
+
const undoShift = undoBinding.shiftKey === void 0 ? { shiftKey: false } : {};
|
|
4083
|
+
result.push(
|
|
4084
|
+
this._makeShortcut(
|
|
4085
|
+
{ ...undoBinding, ctrlKey: true, ...undoShift },
|
|
4086
|
+
() => editor.undo(),
|
|
4087
|
+
"Undo"
|
|
4088
|
+
),
|
|
4089
|
+
this._makeShortcut(
|
|
4090
|
+
{ ...undoBinding, metaKey: true, ...undoShift },
|
|
4091
|
+
() => editor.undo(),
|
|
4092
|
+
"Undo"
|
|
4093
|
+
)
|
|
4094
|
+
);
|
|
4095
|
+
} else {
|
|
4096
|
+
result.push(this._makeShortcut(undoBinding, () => editor.undo(), "Undo"));
|
|
4097
|
+
}
|
|
4098
|
+
if (redoBinding.ctrlKey === void 0 && redoBinding.metaKey === void 0) {
|
|
4099
|
+
const redoShift = redoBinding.shiftKey === void 0 ? { shiftKey: true } : {};
|
|
4100
|
+
result.push(
|
|
4101
|
+
this._makeShortcut(
|
|
4102
|
+
{ ...redoBinding, ctrlKey: true, ...redoShift },
|
|
4103
|
+
() => editor.redo(),
|
|
4104
|
+
"Redo"
|
|
4105
|
+
),
|
|
4106
|
+
this._makeShortcut(
|
|
4107
|
+
{ ...redoBinding, metaKey: true, ...redoShift },
|
|
4108
|
+
() => editor.redo(),
|
|
4109
|
+
"Redo"
|
|
4110
|
+
)
|
|
4111
|
+
);
|
|
4112
|
+
} else {
|
|
4113
|
+
result.push(this._makeShortcut(redoBinding, () => editor.redo(), "Redo"));
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
result.push(...this.customShortcuts);
|
|
4117
|
+
return result;
|
|
4118
|
+
}
|
|
4119
|
+
_makeShortcut(binding, action, description) {
|
|
4120
|
+
return {
|
|
4121
|
+
key: binding.key,
|
|
4122
|
+
...binding.ctrlKey !== void 0 && { ctrlKey: binding.ctrlKey },
|
|
4123
|
+
...binding.shiftKey !== void 0 && { shiftKey: binding.shiftKey },
|
|
4124
|
+
...binding.metaKey !== void 0 && { metaKey: binding.metaKey },
|
|
4125
|
+
...binding.altKey !== void 0 && { altKey: binding.altKey },
|
|
4126
|
+
action,
|
|
4127
|
+
description
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
};
|
|
4131
|
+
__decorateClass([
|
|
4132
|
+
(0, import_decorators14.property)({ type: Boolean })
|
|
4133
|
+
], DawKeyboardShortcutsElement.prototype, "playback", 2);
|
|
4134
|
+
__decorateClass([
|
|
4135
|
+
(0, import_decorators14.property)({ type: Boolean })
|
|
4136
|
+
], DawKeyboardShortcutsElement.prototype, "splitting", 2);
|
|
4137
|
+
__decorateClass([
|
|
4138
|
+
(0, import_decorators14.property)({ type: Boolean })
|
|
4139
|
+
], DawKeyboardShortcutsElement.prototype, "undo", 2);
|
|
4140
|
+
DawKeyboardShortcutsElement = __decorateClass([
|
|
4141
|
+
(0, import_decorators14.customElement)("daw-keyboard-shortcuts")
|
|
4142
|
+
], DawKeyboardShortcutsElement);
|
|
3092
4143
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3093
4144
|
0 && (module.exports = {
|
|
3094
4145
|
AudioResumeController,
|
|
4146
|
+
ClipPointerHandler,
|
|
3095
4147
|
DawClipElement,
|
|
3096
4148
|
DawEditorElement,
|
|
4149
|
+
DawKeyboardShortcutsElement,
|
|
3097
4150
|
DawPauseButtonElement,
|
|
3098
4151
|
DawPlayButtonElement,
|
|
3099
4152
|
DawPlayheadElement,
|
|
@@ -3106,6 +4159,7 @@ DawRecordButtonElement = __decorateClass([
|
|
|
3106
4159
|
DawTransportButton,
|
|
3107
4160
|
DawTransportElement,
|
|
3108
4161
|
DawWaveformElement,
|
|
3109
|
-
RecordingController
|
|
4162
|
+
RecordingController,
|
|
4163
|
+
splitAtPlayhead
|
|
3110
4164
|
});
|
|
3111
4165
|
//# sourceMappingURL=index.js.map
|