@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 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 (optional)
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
- ## Recording
205
+ ## Elements
113
206
 
114
- ```html
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
- <daw-transport for="editor">
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
- <script type="module">
127
- const editor = document.getElementById('editor');
128
- // Consumer provides the mic stream
129
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
130
- editor.recordingStream = stream;
131
- </script>
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
- ## Events
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
- Listen for editor events on the `<daw-editor>` element:
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
- editor.addEventListener('daw-play', () => console.log('playing'));
142
- editor.addEventListener('daw-pause', () => console.log('paused'));
143
- editor.addEventListener('daw-stop', () => console.log('stopped'));
144
- editor.addEventListener('daw-seek', (e) => console.log('seek:', e.detail.time));
145
- editor.addEventListener('daw-selection', (e) => console.log('selection:', e.detail));
146
- editor.addEventListener('daw-track-select', (e) => console.log('track:', e.detail.trackId));
147
- editor.addEventListener('daw-clip-move', (e) => console.log('move:', e.detail));
148
- editor.addEventListener('daw-clip-trim', (e) => console.log('trim:', e.detail));
149
- editor.addEventListener('daw-clip-split', (e) => console.log('split:', e.detail));
150
- editor.addEventListener('daw-track-error', (e) => console.error('error:', e.detail));
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("timeupdate", (time) => {
3479
- this._currentTime = time;
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._currentTime,
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
- () => engine.getCurrentTime(),
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
  );