@dawcore/components 0.0.7 → 0.0.9
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/README.md +157 -35
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -9,9 +9,12 @@ Framework-agnostic Web Components for multi-track audio editing. Drop `<daw-edit
|
|
|
9
9
|
- **Canvas waveforms** — Chunked rendering with virtual scrolling for large timelines
|
|
10
10
|
- **Drag interactions** — Move clips, trim boundaries, split at playhead
|
|
11
11
|
- **Keyboard shortcuts** — Play/pause, split, undo/redo via `<daw-keyboard-shortcuts>`
|
|
12
|
+
- **Undo/redo** — Full transaction-based undo with Cmd/Ctrl+Z and Cmd/Ctrl+Shift+Z
|
|
12
13
|
- **File drop** — Drag audio files onto the editor to add tracks
|
|
13
|
-
- **Recording** — Live mic recording with waveform preview
|
|
14
|
+
- **Recording** — Live mic recording with waveform preview, pause/resume, cancelable clip creation
|
|
14
15
|
- **Pre-computed peaks** — Instant waveform rendering from `.dat` files before audio decodes
|
|
16
|
+
- **Track controls** — Volume, pan, mute, solo per track via `<daw-track-controls>`
|
|
17
|
+
- **Transport access** — Tempo, metronome, count-in, meter, effects via `@dawcore/transport`
|
|
15
18
|
- **CSS theming** — Dark mode by default, fully customizable via CSS custom properties
|
|
16
19
|
- **Native Web Audio** — Uses `@dawcore/transport` for playback scheduling. No Tone.js dependency.
|
|
17
20
|
|
|
@@ -87,6 +90,96 @@ For instant waveform rendering before audio finishes decoding:
|
|
|
87
90
|
|
|
88
91
|
The `.dat` file renders the waveform immediately. Audio decodes in the background for playback.
|
|
89
92
|
|
|
93
|
+
## Transport Access
|
|
94
|
+
|
|
95
|
+
Access the native transport for tempo, metronome, count-in, meter, and effects:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
const editor = document.getElementById('editor');
|
|
99
|
+
|
|
100
|
+
// Transport is available after first track loads
|
|
101
|
+
editor.addEventListener('daw-play', () => {
|
|
102
|
+
const transport = editor.engine?.adapter?.transport;
|
|
103
|
+
if (!transport) return;
|
|
104
|
+
|
|
105
|
+
// Tempo & meter
|
|
106
|
+
transport.setTempo(140);
|
|
107
|
+
transport.setMeter(3, 4);
|
|
108
|
+
|
|
109
|
+
// Metronome (default click sounds built in)
|
|
110
|
+
transport.setMetronomeEnabled(true);
|
|
111
|
+
|
|
112
|
+
// Count-in
|
|
113
|
+
transport.setCountIn(true);
|
|
114
|
+
transport.setCountInBars(1);
|
|
115
|
+
transport.setCountInMode('always');
|
|
116
|
+
|
|
117
|
+
transport.on('countIn', ({ beat, totalBeats }) => {
|
|
118
|
+
console.log(beat + '/' + totalBeats);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Effects hook — insert any AudioNode chain
|
|
122
|
+
transport.connectTrackOutput('track-id', reverbNode);
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Programmatic File Loading
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const editor = document.getElementById('editor');
|
|
130
|
+
const result = await editor.loadFiles(fileList);
|
|
131
|
+
// result: { loaded: string[], failed: Array<{ file, error }> }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Recording
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<daw-editor id="editor" samples-per-pixel="1024" wave-height="100">
|
|
138
|
+
<daw-track name="Recording"></daw-track>
|
|
139
|
+
</daw-editor>
|
|
140
|
+
|
|
141
|
+
<daw-transport for="editor">
|
|
142
|
+
<daw-play-button></daw-play-button>
|
|
143
|
+
<daw-pause-button></daw-pause-button>
|
|
144
|
+
<daw-stop-button></daw-stop-button>
|
|
145
|
+
<daw-record-button></daw-record-button>
|
|
146
|
+
</daw-transport>
|
|
147
|
+
|
|
148
|
+
<script type="module">
|
|
149
|
+
const editor = document.getElementById('editor');
|
|
150
|
+
// Consumer provides the mic stream
|
|
151
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
152
|
+
audio: { channelCount: { ideal: 2 } } // request stereo when available
|
|
153
|
+
});
|
|
154
|
+
editor.recordingStream = stream;
|
|
155
|
+
|
|
156
|
+
// Cancelable — prevent default to handle the AudioBuffer yourself
|
|
157
|
+
editor.addEventListener('daw-recording-complete', (e) => {
|
|
158
|
+
// e.preventDefault(); // skip automatic clip creation
|
|
159
|
+
console.log('recorded:', e.detail.audioBuffer);
|
|
160
|
+
});
|
|
161
|
+
</script>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Keyboard Shortcuts
|
|
165
|
+
|
|
166
|
+
Add `<daw-keyboard-shortcuts>` as a child of `<daw-editor>`:
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<daw-editor id="editor">
|
|
170
|
+
<daw-keyboard-shortcuts playback splitting undo></daw-keyboard-shortcuts>
|
|
171
|
+
<!-- ... tracks ... -->
|
|
172
|
+
</daw-editor>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Attribute | Shortcuts |
|
|
176
|
+
|-----------|-----------|
|
|
177
|
+
| `playback` | Space (play/pause), Enter (stop) |
|
|
178
|
+
| `splitting` | S (split at playhead) |
|
|
179
|
+
| `undo` | Cmd/Ctrl+Z (undo), Cmd/Ctrl+Shift+Z (redo) |
|
|
180
|
+
|
|
181
|
+
Custom shortcuts via JS properties: `playbackShortcuts`, `splittingShortcuts`, `undoShortcuts`, `customShortcuts`.
|
|
182
|
+
|
|
90
183
|
## CSS Theming
|
|
91
184
|
|
|
92
185
|
Style with CSS custom properties on `<daw-editor>` or any ancestor:
|
|
@@ -109,45 +202,78 @@ daw-editor {
|
|
|
109
202
|
}
|
|
110
203
|
```
|
|
111
204
|
|
|
112
|
-
##
|
|
205
|
+
## Elements
|
|
113
206
|
|
|
114
|
-
|
|
115
|
-
<daw-editor id="editor" samples-per-pixel="1024" wave-height="100">
|
|
116
|
-
<daw-track name="Recording"></daw-track>
|
|
117
|
-
</daw-editor>
|
|
207
|
+
### `<daw-editor>`
|
|
118
208
|
|
|
119
|
-
|
|
120
|
-
<daw-play-button></daw-play-button>
|
|
121
|
-
<daw-pause-button></daw-pause-button>
|
|
122
|
-
<daw-stop-button></daw-stop-button>
|
|
123
|
-
<daw-record-button></daw-record-button>
|
|
124
|
-
</daw-transport>
|
|
209
|
+
Core orchestrator. Attributes:
|
|
125
210
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
211
|
+
| Attribute | Type | Default | Description |
|
|
212
|
+
|-----------|------|---------|-------------|
|
|
213
|
+
| `samples-per-pixel` | number | `1024` | Zoom level |
|
|
214
|
+
| `sample-rate` | number | `48000` | AudioContext sample rate hint |
|
|
215
|
+
| `wave-height` | number | `100` | Track waveform height in pixels |
|
|
216
|
+
| `timescale` | boolean | `false` | Show time ruler |
|
|
217
|
+
| `clip-headers` | boolean | `false` | Show clip name headers |
|
|
218
|
+
| `interactive-clips` | boolean | `false` | Enable drag/trim/split |
|
|
219
|
+
| `mono` | boolean | `false` | Merge stereo to mono display |
|
|
220
|
+
| `eager-resume` | boolean | `false` | Resume AudioContext on first user gesture |
|
|
133
221
|
|
|
134
|
-
|
|
222
|
+
JS properties: `audioContext`, `recordingStream`, `engine`.
|
|
223
|
+
|
|
224
|
+
Methods: `loadFiles(fileList)`, `splitAtPlayhead()`.
|
|
225
|
+
|
|
226
|
+
### `<daw-track>`
|
|
227
|
+
|
|
228
|
+
Declarative track data. Attributes: `src`, `name`, `volume`, `pan`, `muted`, `soloed`, `mono`.
|
|
229
|
+
|
|
230
|
+
### `<daw-clip>`
|
|
135
231
|
|
|
136
|
-
|
|
232
|
+
Declarative clip data. Attributes: `src`, `peaks-src`, `start`, `duration`, `offset`, `gain`.
|
|
233
|
+
|
|
234
|
+
### `<daw-transport for="editor-id">`
|
|
235
|
+
|
|
236
|
+
Container that resolves target editor. Children: `<daw-play-button>`, `<daw-pause-button>`, `<daw-stop-button>`, `<daw-record-button>`.
|
|
237
|
+
|
|
238
|
+
### `<daw-track-controls>`
|
|
239
|
+
|
|
240
|
+
Per-track UI for volume, pan, mute, solo. Receives state from editor, dispatches `daw-track-control` and `daw-track-remove` events.
|
|
241
|
+
|
|
242
|
+
### `<daw-keyboard-shortcuts>`
|
|
243
|
+
|
|
244
|
+
Render-less child of `<daw-editor>`. Boolean attributes: `playback`, `splitting`, `undo`.
|
|
245
|
+
|
|
246
|
+
## Events
|
|
137
247
|
|
|
138
248
|
```javascript
|
|
139
249
|
const editor = document.getElementById('editor');
|
|
140
250
|
|
|
141
|
-
|
|
142
|
-
editor.addEventListener('daw-
|
|
143
|
-
editor.addEventListener('daw-
|
|
144
|
-
editor.addEventListener('daw-
|
|
145
|
-
editor.addEventListener('daw-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
editor.addEventListener('daw-
|
|
149
|
-
editor.addEventListener('daw-
|
|
150
|
-
|
|
251
|
+
// Playback
|
|
252
|
+
editor.addEventListener('daw-play', () => {});
|
|
253
|
+
editor.addEventListener('daw-pause', () => {});
|
|
254
|
+
editor.addEventListener('daw-stop', () => {});
|
|
255
|
+
editor.addEventListener('daw-seek', (e) => console.log(e.detail.time));
|
|
256
|
+
|
|
257
|
+
// Selection & tracks
|
|
258
|
+
editor.addEventListener('daw-selection', (e) => console.log(e.detail));
|
|
259
|
+
editor.addEventListener('daw-track-select', (e) => console.log(e.detail.trackId));
|
|
260
|
+
|
|
261
|
+
// Clip interactions
|
|
262
|
+
editor.addEventListener('daw-clip-move', (e) => console.log(e.detail));
|
|
263
|
+
editor.addEventListener('daw-clip-trim', (e) => console.log(e.detail));
|
|
264
|
+
editor.addEventListener('daw-clip-split', (e) => console.log(e.detail));
|
|
265
|
+
|
|
266
|
+
// Recording
|
|
267
|
+
editor.addEventListener('daw-recording-start', (e) => console.log(e.detail));
|
|
268
|
+
editor.addEventListener('daw-recording-complete', (e) => {
|
|
269
|
+
// e.preventDefault() to skip automatic clip creation
|
|
270
|
+
console.log(e.detail.audioBuffer);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Errors
|
|
274
|
+
editor.addEventListener('daw-track-error', (e) => console.error(e.detail));
|
|
275
|
+
editor.addEventListener('daw-error', (e) => console.error(e.detail));
|
|
276
|
+
editor.addEventListener('daw-files-load-error', (e) => console.error(e.detail));
|
|
151
277
|
```
|
|
152
278
|
|
|
153
279
|
## Custom AudioContext
|
|
@@ -161,10 +287,6 @@ editor.audioContext = new AudioContext({ sampleRate: 48000, latencyHint: 0 });
|
|
|
161
287
|
|
|
162
288
|
Set this before tracks load. The provided context is used for decoding, playback, and recording.
|
|
163
289
|
|
|
164
|
-
## API
|
|
165
|
-
|
|
166
|
-
See [COMPONENTS.md](./COMPONENTS.md) for the full element and attribute reference.
|
|
167
|
-
|
|
168
290
|
## License
|
|
169
291
|
|
|
170
292
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -3475,8 +3475,8 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3475
3475
|
syncPeaksForChangedClips(this, engineState.tracks);
|
|
3476
3476
|
}
|
|
3477
3477
|
});
|
|
3478
|
-
engine.on("
|
|
3479
|
-
this._currentTime =
|
|
3478
|
+
engine.on("pause", () => {
|
|
3479
|
+
this._currentTime = engine.getCurrentTime();
|
|
3480
3480
|
});
|
|
3481
3481
|
engine.on("stop", () => {
|
|
3482
3482
|
this._currentTime = engine.getCurrentTime();
|
|
@@ -3576,7 +3576,7 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3576
3576
|
splitAtPlayhead() {
|
|
3577
3577
|
return splitAtPlayhead({
|
|
3578
3578
|
effectiveSampleRate: this.effectiveSampleRate,
|
|
3579
|
-
currentTime: this.
|
|
3579
|
+
currentTime: this.currentTime,
|
|
3580
3580
|
isPlaying: this._isPlaying,
|
|
3581
3581
|
engine: this._engine,
|
|
3582
3582
|
dispatchEvent: (e) => this.dispatchEvent(e),
|
|
@@ -3594,6 +3594,9 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3594
3594
|
});
|
|
3595
3595
|
}
|
|
3596
3596
|
get currentTime() {
|
|
3597
|
+
if (this._isPlaying && this._engine) {
|
|
3598
|
+
return this._engine.getCurrentTime();
|
|
3599
|
+
}
|
|
3597
3600
|
return this._currentTime;
|
|
3598
3601
|
}
|
|
3599
3602
|
get isRecording() {
|
|
@@ -3651,8 +3654,12 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3651
3654
|
const playhead = this._getPlayhead();
|
|
3652
3655
|
if (!playhead || !this._engine) return;
|
|
3653
3656
|
const engine = this._engine;
|
|
3657
|
+
const ctx = this.audioContext;
|
|
3654
3658
|
playhead.startAnimation(
|
|
3655
|
-
() =>
|
|
3659
|
+
() => {
|
|
3660
|
+
const latency = "outputLatency" in ctx ? ctx.outputLatency : 0;
|
|
3661
|
+
return Math.max(0, engine.getCurrentTime() - latency);
|
|
3662
|
+
},
|
|
3656
3663
|
this.effectiveSampleRate,
|
|
3657
3664
|
this.samplesPerPixel
|
|
3658
3665
|
);
|