@dawcore/components 0.0.10 → 0.0.12
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 +68 -31
- package/dist/index.d.mts +25 -13
- package/dist/index.d.ts +25 -13
- package/dist/index.js +71 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -26,12 +26,19 @@ npm install @dawcore/components
|
|
|
26
26
|
|
|
27
27
|
Peer dependencies:
|
|
28
28
|
```bash
|
|
29
|
-
npm install @waveform-playlist/core @waveform-playlist/engine
|
|
29
|
+
npm install @waveform-playlist/core @waveform-playlist/engine
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Audio backend (choose one — see [Choosing an Audio Backend](#choosing-an-audio-backend)):
|
|
33
|
+
```bash
|
|
34
|
+
npm install @dawcore/transport # Native Web Audio (recommended)
|
|
35
|
+
# or
|
|
36
|
+
npm install @waveform-playlist/playout tone # Tone.js
|
|
30
37
|
```
|
|
31
38
|
|
|
32
39
|
Optional (for recording):
|
|
33
40
|
```bash
|
|
34
|
-
npm install @waveform-playlist/
|
|
41
|
+
npm install @waveform-playlist/worklets
|
|
35
42
|
```
|
|
36
43
|
|
|
37
44
|
## Quick Start
|
|
@@ -39,6 +46,11 @@ npm install @waveform-playlist/recording @waveform-playlist/worklets
|
|
|
39
46
|
```html
|
|
40
47
|
<script type="module">
|
|
41
48
|
import '@dawcore/components';
|
|
49
|
+
import { NativePlayoutAdapter } from '@dawcore/transport';
|
|
50
|
+
|
|
51
|
+
const editor = document.querySelector('daw-editor');
|
|
52
|
+
const adapter = new NativePlayoutAdapter(new AudioContext());
|
|
53
|
+
editor.adapter = adapter;
|
|
42
54
|
</script>
|
|
43
55
|
|
|
44
56
|
<daw-editor id="editor" samples-per-pixel="1024" wave-height="100" timescale>
|
|
@@ -54,7 +66,44 @@ npm install @waveform-playlist/recording @waveform-playlist/worklets
|
|
|
54
66
|
</daw-transport>
|
|
55
67
|
```
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
The editor loads audio, generates waveforms, and handles playback.
|
|
70
|
+
|
|
71
|
+
## Choosing an Audio Backend
|
|
72
|
+
|
|
73
|
+
### Native Web Audio (recommended for most use cases)
|
|
74
|
+
|
|
75
|
+
No Tone.js dependency. Supports multi-tempo, multi-meter, metronome, count-in, and effects hooks.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install @dawcore/transport
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
import { NativePlayoutAdapter } from '@dawcore/transport';
|
|
83
|
+
|
|
84
|
+
const ctx = new AudioContext({ sampleRate: 48000 });
|
|
85
|
+
const adapter = new NativePlayoutAdapter(ctx);
|
|
86
|
+
editor.adapter = adapter;
|
|
87
|
+
|
|
88
|
+
// Transport-specific features via adapter reference
|
|
89
|
+
adapter.transport.setMetronomeEnabled(true);
|
|
90
|
+
adapter.transport.setCountIn(true);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Tone.js (effects, MIDI synths)
|
|
94
|
+
|
|
95
|
+
Uses Tone.js for audio processing. Single tempo/meter only.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install @waveform-playlist/playout tone
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
import { createToneAdapter } from '@waveform-playlist/playout';
|
|
103
|
+
|
|
104
|
+
const adapter = createToneAdapter();
|
|
105
|
+
editor.adapter = adapter;
|
|
106
|
+
```
|
|
58
107
|
|
|
59
108
|
## Multi-Clip Timeline
|
|
60
109
|
|
|
@@ -92,33 +141,20 @@ The `.dat` file renders the waveform immediately. Audio decodes in the backgroun
|
|
|
92
141
|
|
|
93
142
|
## Transport Access
|
|
94
143
|
|
|
95
|
-
|
|
144
|
+
Transport-specific APIs are on the `NativePlayoutAdapter` reference:
|
|
96
145
|
|
|
97
146
|
```javascript
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
transport.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Metronome (default click sounds built in)
|
|
109
|
-
transport.setMetronomeEnabled(true);
|
|
110
|
-
|
|
111
|
-
// Count-in
|
|
112
|
-
transport.setCountIn(true);
|
|
113
|
-
transport.setCountInBars(1);
|
|
114
|
-
transport.setCountInMode('always');
|
|
115
|
-
|
|
116
|
-
transport.on('countIn', ({ beat, totalBeats }) => {
|
|
117
|
-
console.log(beat + '/' + totalBeats);
|
|
147
|
+
// Transport-specific APIs are on the NativePlayoutAdapter
|
|
148
|
+
adapter.transport.setTempo(140);
|
|
149
|
+
adapter.transport.setMeter(3, 4);
|
|
150
|
+
adapter.transport.setMetronomeEnabled(true);
|
|
151
|
+
adapter.transport.setCountIn(true);
|
|
152
|
+
adapter.transport.setCountInBars(1);
|
|
153
|
+
adapter.transport.setCountInMode('always');
|
|
154
|
+
adapter.transport.on('countIn', ({ beat, totalBeats }) => {
|
|
155
|
+
console.log('Count-in: ' + beat + '/' + totalBeats);
|
|
118
156
|
});
|
|
119
|
-
|
|
120
|
-
// Effects hook — insert any AudioNode chain
|
|
121
|
-
transport.connectTrackOutput('track-id', reverbNode);
|
|
157
|
+
adapter.transport.connectTrackOutput('track-id', reverbNode);
|
|
122
158
|
```
|
|
123
159
|
|
|
124
160
|
## Programmatic File Loading
|
|
@@ -276,14 +312,15 @@ editor.addEventListener('daw-files-load-error', (e) => console.error(e.detail));
|
|
|
276
312
|
|
|
277
313
|
## Custom AudioContext
|
|
278
314
|
|
|
279
|
-
|
|
315
|
+
Pass a custom `AudioContext` via the adapter:
|
|
280
316
|
|
|
281
317
|
```javascript
|
|
282
|
-
const
|
|
283
|
-
|
|
318
|
+
const ctx = new AudioContext({ sampleRate: 48000, latencyHint: 0 });
|
|
319
|
+
const adapter = new NativePlayoutAdapter(ctx);
|
|
320
|
+
editor.adapter = adapter;
|
|
284
321
|
```
|
|
285
322
|
|
|
286
|
-
Set
|
|
323
|
+
Set the adapter before tracks load. The provided context is used for decoding, playback, and recording.
|
|
287
324
|
|
|
288
325
|
## License
|
|
289
326
|
|
package/dist/index.d.mts
CHANGED
|
@@ -2,8 +2,7 @@ import * as lit from 'lit';
|
|
|
2
2
|
import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
|
|
3
3
|
import { Peaks, Bits, FadeType, PeakData, MeterEntry, SnapTo, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
|
|
4
4
|
import WaveformData from 'waveform-data';
|
|
5
|
-
import { PlaylistEngine } from '@waveform-playlist/engine';
|
|
6
|
-
import { Transport } from '@dawcore/transport';
|
|
5
|
+
import { PlayoutAdapter, PlaylistEngine } from '@waveform-playlist/engine';
|
|
7
6
|
|
|
8
7
|
declare class DawClipElement extends LitElement {
|
|
9
8
|
src: string;
|
|
@@ -393,6 +392,22 @@ interface RecordingHost extends ReactiveControllerHost {
|
|
|
393
392
|
play?(startTime?: number): Promise<void>;
|
|
394
393
|
stop?(): void;
|
|
395
394
|
dispatchEvent(event: Event): boolean;
|
|
395
|
+
/**
|
|
396
|
+
* Register a worklet module URL on the adapter's AudioContext.
|
|
397
|
+
* Abstracts native vs standardized-audio-context differences.
|
|
398
|
+
* When absent, falls back to native audioContext.audioWorklet.addModule().
|
|
399
|
+
*/
|
|
400
|
+
addWorkletModule?(url: string): Promise<void>;
|
|
401
|
+
/**
|
|
402
|
+
* Create an AudioWorkletNode on the adapter's context.
|
|
403
|
+
* When absent, falls back to new AudioWorkletNode(audioContext, ...).
|
|
404
|
+
*/
|
|
405
|
+
createAudioWorkletNode?(name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode;
|
|
406
|
+
/**
|
|
407
|
+
* Create a MediaStreamSource on the adapter's context.
|
|
408
|
+
* When absent, falls back to audioContext.createMediaStreamSource().
|
|
409
|
+
*/
|
|
410
|
+
createMediaStreamSource?(stream: MediaStream): MediaStreamAudioSourceNode;
|
|
396
411
|
}
|
|
397
412
|
declare class RecordingController implements ReactiveController {
|
|
398
413
|
private _host;
|
|
@@ -653,9 +668,8 @@ declare class DawEditorElement extends LitElement {
|
|
|
653
668
|
secondsToTicks?: (seconds: number) => number;
|
|
654
669
|
/** Optional tempo-aware conversion: PPQN ticks → seconds. Required alongside secondsToTicks. */
|
|
655
670
|
ticksToSeconds?: (ticks: number) => number;
|
|
656
|
-
/**
|
|
657
|
-
|
|
658
|
-
sampleRate: number;
|
|
671
|
+
/** Sample rate — reads from adapter's AudioContext when available, otherwise falls back to 48000. */
|
|
672
|
+
get sampleRate(): number;
|
|
659
673
|
/** Resolved sample rate — falls back to sampleRate property until first audio decode. */
|
|
660
674
|
_resolvedSampleRate: number | null;
|
|
661
675
|
_tracks: Map<string, TrackDescriptor>;
|
|
@@ -668,14 +682,11 @@ declare class DawEditorElement extends LitElement {
|
|
|
668
682
|
_selectionStartTime: number;
|
|
669
683
|
_selectionEndTime: number;
|
|
670
684
|
_currentTime: number;
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
private
|
|
674
|
-
/** Set an AudioContext to use for all audio operations. Must be set before tracks load. */
|
|
675
|
-
set audioContext(ctx: AudioContext | null);
|
|
685
|
+
set adapter(value: PlayoutAdapter | null);
|
|
686
|
+
get adapter(): PlayoutAdapter | null;
|
|
687
|
+
private _externalAdapter;
|
|
676
688
|
get audioContext(): AudioContext;
|
|
677
689
|
_engine: PlaylistEngine | null;
|
|
678
|
-
private _adapter;
|
|
679
690
|
private _warnedMissingTicksToSeconds;
|
|
680
691
|
private _warnedMissingSecondsToTicks;
|
|
681
692
|
private _enginePromise;
|
|
@@ -697,8 +708,6 @@ declare class DawEditorElement extends LitElement {
|
|
|
697
708
|
private _clipPointer;
|
|
698
709
|
get _clipHandler(): ClipPointerHandler | null;
|
|
699
710
|
get engine(): PlaylistEngine | null;
|
|
700
|
-
/** The adapter's Transport — use for tempo, metronome, and effects. */
|
|
701
|
-
get transport(): Transport | null;
|
|
702
711
|
get renderSamplesPerPixel(): number;
|
|
703
712
|
/** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
|
|
704
713
|
reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
|
|
@@ -774,6 +783,9 @@ declare class DawEditorElement extends LitElement {
|
|
|
774
783
|
resumeRecording(): void;
|
|
775
784
|
stopRecording(): void;
|
|
776
785
|
_addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
|
|
786
|
+
addWorkletModule(url: string): Promise<void>;
|
|
787
|
+
createAudioWorkletNode(name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode;
|
|
788
|
+
createMediaStreamSource(stream: MediaStream): MediaStreamAudioSourceNode;
|
|
777
789
|
startRecording(stream?: MediaStream, options?: RecordingOptions): Promise<void>;
|
|
778
790
|
private _renderRecordingPreview;
|
|
779
791
|
_startPlayhead(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,7 @@ import * as lit from 'lit';
|
|
|
2
2
|
import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
|
|
3
3
|
import { Peaks, Bits, FadeType, PeakData, MeterEntry, SnapTo, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
|
|
4
4
|
import WaveformData from 'waveform-data';
|
|
5
|
-
import { PlaylistEngine } from '@waveform-playlist/engine';
|
|
6
|
-
import { Transport } from '@dawcore/transport';
|
|
5
|
+
import { PlayoutAdapter, PlaylistEngine } from '@waveform-playlist/engine';
|
|
7
6
|
|
|
8
7
|
declare class DawClipElement extends LitElement {
|
|
9
8
|
src: string;
|
|
@@ -393,6 +392,22 @@ interface RecordingHost extends ReactiveControllerHost {
|
|
|
393
392
|
play?(startTime?: number): Promise<void>;
|
|
394
393
|
stop?(): void;
|
|
395
394
|
dispatchEvent(event: Event): boolean;
|
|
395
|
+
/**
|
|
396
|
+
* Register a worklet module URL on the adapter's AudioContext.
|
|
397
|
+
* Abstracts native vs standardized-audio-context differences.
|
|
398
|
+
* When absent, falls back to native audioContext.audioWorklet.addModule().
|
|
399
|
+
*/
|
|
400
|
+
addWorkletModule?(url: string): Promise<void>;
|
|
401
|
+
/**
|
|
402
|
+
* Create an AudioWorkletNode on the adapter's context.
|
|
403
|
+
* When absent, falls back to new AudioWorkletNode(audioContext, ...).
|
|
404
|
+
*/
|
|
405
|
+
createAudioWorkletNode?(name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode;
|
|
406
|
+
/**
|
|
407
|
+
* Create a MediaStreamSource on the adapter's context.
|
|
408
|
+
* When absent, falls back to audioContext.createMediaStreamSource().
|
|
409
|
+
*/
|
|
410
|
+
createMediaStreamSource?(stream: MediaStream): MediaStreamAudioSourceNode;
|
|
396
411
|
}
|
|
397
412
|
declare class RecordingController implements ReactiveController {
|
|
398
413
|
private _host;
|
|
@@ -653,9 +668,8 @@ declare class DawEditorElement extends LitElement {
|
|
|
653
668
|
secondsToTicks?: (seconds: number) => number;
|
|
654
669
|
/** Optional tempo-aware conversion: PPQN ticks → seconds. Required alongside secondsToTicks. */
|
|
655
670
|
ticksToSeconds?: (ticks: number) => number;
|
|
656
|
-
/**
|
|
657
|
-
|
|
658
|
-
sampleRate: number;
|
|
671
|
+
/** Sample rate — reads from adapter's AudioContext when available, otherwise falls back to 48000. */
|
|
672
|
+
get sampleRate(): number;
|
|
659
673
|
/** Resolved sample rate — falls back to sampleRate property until first audio decode. */
|
|
660
674
|
_resolvedSampleRate: number | null;
|
|
661
675
|
_tracks: Map<string, TrackDescriptor>;
|
|
@@ -668,14 +682,11 @@ declare class DawEditorElement extends LitElement {
|
|
|
668
682
|
_selectionStartTime: number;
|
|
669
683
|
_selectionEndTime: number;
|
|
670
684
|
_currentTime: number;
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
private
|
|
674
|
-
/** Set an AudioContext to use for all audio operations. Must be set before tracks load. */
|
|
675
|
-
set audioContext(ctx: AudioContext | null);
|
|
685
|
+
set adapter(value: PlayoutAdapter | null);
|
|
686
|
+
get adapter(): PlayoutAdapter | null;
|
|
687
|
+
private _externalAdapter;
|
|
676
688
|
get audioContext(): AudioContext;
|
|
677
689
|
_engine: PlaylistEngine | null;
|
|
678
|
-
private _adapter;
|
|
679
690
|
private _warnedMissingTicksToSeconds;
|
|
680
691
|
private _warnedMissingSecondsToTicks;
|
|
681
692
|
private _enginePromise;
|
|
@@ -697,8 +708,6 @@ declare class DawEditorElement extends LitElement {
|
|
|
697
708
|
private _clipPointer;
|
|
698
709
|
get _clipHandler(): ClipPointerHandler | null;
|
|
699
710
|
get engine(): PlaylistEngine | null;
|
|
700
|
-
/** The adapter's Transport — use for tempo, metronome, and effects. */
|
|
701
|
-
get transport(): Transport | null;
|
|
702
711
|
get renderSamplesPerPixel(): number;
|
|
703
712
|
/** Re-extract peaks for a clip at new offset/duration from cached WaveformData. */
|
|
704
713
|
reextractClipPeaks(clipId: string, offsetSamples: number, durationSamples: number): {
|
|
@@ -774,6 +783,9 @@ declare class DawEditorElement extends LitElement {
|
|
|
774
783
|
resumeRecording(): void;
|
|
775
784
|
stopRecording(): void;
|
|
776
785
|
_addRecordedClip(trackId: string, buf: AudioBuffer, startSample: number, durSamples: number, offsetSamples?: number): void;
|
|
786
|
+
addWorkletModule(url: string): Promise<void>;
|
|
787
|
+
createAudioWorkletNode(name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode;
|
|
788
|
+
createMediaStreamSource(stream: MediaStream): MediaStreamAudioSourceNode;
|
|
777
789
|
startRecording(stream?: MediaStream, options?: RecordingOptions): Promise<void>;
|
|
778
790
|
private _renderRecordingPreview;
|
|
779
791
|
_startPlayhead(): void;
|
package/dist/index.js
CHANGED
|
@@ -1950,7 +1950,20 @@ var AudioResumeController = class {
|
|
|
1950
1950
|
this._attached = false;
|
|
1951
1951
|
this._generation = 0;
|
|
1952
1952
|
this._onGesture = (e) => {
|
|
1953
|
-
|
|
1953
|
+
let ctx;
|
|
1954
|
+
try {
|
|
1955
|
+
ctx = this._host.audioContext;
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
if (err instanceof Error && err.message.includes("No PlayoutAdapter set")) {
|
|
1958
|
+
this._removeListeners();
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
console.warn(
|
|
1962
|
+
"[dawcore] AudioResumeController: unexpected error accessing audioContext: " + String(err)
|
|
1963
|
+
);
|
|
1964
|
+
this._removeListeners();
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1954
1967
|
if (ctx.state === "closed") {
|
|
1955
1968
|
console.warn("[dawcore] AudioResumeController: AudioContext is closed, cannot resume.");
|
|
1956
1969
|
} else if (ctx.state === "suspended") {
|
|
@@ -2064,15 +2077,16 @@ var RecordingController = class {
|
|
|
2064
2077
|
const rawCtx = this._host.audioContext;
|
|
2065
2078
|
this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
|
|
2066
2079
|
if (!this._workletLoadedCtx || this._workletLoadedCtx !== rawCtx) {
|
|
2067
|
-
let
|
|
2080
|
+
let addRecordingWorkletModule;
|
|
2068
2081
|
try {
|
|
2069
|
-
({
|
|
2070
|
-
} catch {
|
|
2082
|
+
({ addRecordingWorkletModule } = await import("@waveform-playlist/worklets"));
|
|
2083
|
+
} catch (importErr) {
|
|
2071
2084
|
throw new Error(
|
|
2072
|
-
"Recording requires @waveform-playlist/worklets. Install it: npm install @waveform-playlist/worklets"
|
|
2085
|
+
"Recording requires @waveform-playlist/worklets. Install it: npm install @waveform-playlist/worklets (cause: " + String(importErr) + ")"
|
|
2073
2086
|
);
|
|
2074
2087
|
}
|
|
2075
|
-
|
|
2088
|
+
const addModule = this._host.addWorkletModule ? (url) => this._host.addWorkletModule(url) : (url) => rawCtx.audioWorklet.addModule(url);
|
|
2089
|
+
await addRecordingWorkletModule(addModule);
|
|
2076
2090
|
this._workletLoadedCtx = rawCtx;
|
|
2077
2091
|
}
|
|
2078
2092
|
const detectedChannelCount = stream.getAudioTracks()[0]?.getSettings()?.channelCount;
|
|
@@ -2085,8 +2099,11 @@ var RecordingController = class {
|
|
|
2085
2099
|
const startSample = options.startSample ?? Math.floor(this._host._currentTime * this._host.effectiveSampleRate);
|
|
2086
2100
|
const outputLatency = rawCtx.outputLatency ?? 0;
|
|
2087
2101
|
const latencySamples = Math.floor(outputLatency * rawCtx.sampleRate);
|
|
2088
|
-
const source = rawCtx.createMediaStreamSource(stream);
|
|
2089
|
-
const workletNode =
|
|
2102
|
+
const source = this._host.createMediaStreamSource ? this._host.createMediaStreamSource(stream) : rawCtx.createMediaStreamSource(stream);
|
|
2103
|
+
const workletNode = this._host.createAudioWorkletNode ? this._host.createAudioWorkletNode("recording-processor", {
|
|
2104
|
+
channelCount,
|
|
2105
|
+
channelCountMode: "explicit"
|
|
2106
|
+
}) : new AudioWorkletNode(rawCtx, "recording-processor", {
|
|
2090
2107
|
channelCount,
|
|
2091
2108
|
channelCountMode: "explicit"
|
|
2092
2109
|
});
|
|
@@ -3188,6 +3205,7 @@ async function loadWaveformDataFromUrl(src) {
|
|
|
3188
3205
|
}
|
|
3189
3206
|
|
|
3190
3207
|
// src/elements/daw-editor.ts
|
|
3208
|
+
var NO_ADAPTER_ERROR = "No PlayoutAdapter set on <daw-editor>. Set editor.adapter before use.\n\n // Option 1: Native Web Audio (no Tone.js)\n npm install @dawcore/transport\n import { NativePlayoutAdapter } from '@dawcore/transport';\n editor.adapter = new NativePlayoutAdapter(new AudioContext());\n\n // Option 2: Tone.js (effects, MIDI synths)\n npm install @waveform-playlist/playout\n import { createToneAdapter } from '@waveform-playlist/playout';\n editor.adapter = createToneAdapter();";
|
|
3191
3209
|
var DawEditorElement = class extends import_lit13.LitElement {
|
|
3192
3210
|
constructor() {
|
|
3193
3211
|
super(...arguments);
|
|
@@ -3207,7 +3225,6 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3207
3225
|
this.timeSignature = [4, 4];
|
|
3208
3226
|
this._ppqn = 960;
|
|
3209
3227
|
this.snapTo = "off";
|
|
3210
|
-
this.sampleRate = 48e3;
|
|
3211
3228
|
/** Resolved sample rate — falls back to sampleRate property until first audio decode. */
|
|
3212
3229
|
this._resolvedSampleRate = null;
|
|
3213
3230
|
this._tracks = /* @__PURE__ */ new Map();
|
|
@@ -3221,11 +3238,8 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3221
3238
|
this._selectionStartTime = 0;
|
|
3222
3239
|
this._selectionEndTime = 0;
|
|
3223
3240
|
this._currentTime = 0;
|
|
3224
|
-
|
|
3225
|
-
this._externalAudioContext = null;
|
|
3226
|
-
this._ownedAudioContext = null;
|
|
3241
|
+
this._externalAdapter = null;
|
|
3227
3242
|
this._engine = null;
|
|
3228
|
-
this._adapter = null;
|
|
3229
3243
|
this._warnedMissingTicksToSeconds = false;
|
|
3230
3244
|
this._warnedMissingSecondsToTicks = false;
|
|
3231
3245
|
this._enginePromise = null;
|
|
@@ -3407,30 +3421,30 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3407
3421
|
this._ppqn = value;
|
|
3408
3422
|
this.requestUpdate("ppqn", old);
|
|
3409
3423
|
}
|
|
3410
|
-
/**
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3424
|
+
/** Sample rate — reads from adapter's AudioContext when available, otherwise falls back to 48000. */
|
|
3425
|
+
get sampleRate() {
|
|
3426
|
+
return this._resolvedSampleRate ?? this._externalAdapter?.audioContext.sampleRate ?? 48e3;
|
|
3427
|
+
}
|
|
3428
|
+
set adapter(value) {
|
|
3429
|
+
if (value && value.audioContext.state === "closed") {
|
|
3430
|
+
console.warn("[dawcore] Adapter AudioContext is already closed. Ignoring.");
|
|
3414
3431
|
return;
|
|
3415
3432
|
}
|
|
3416
3433
|
if (this._engine) {
|
|
3417
3434
|
console.warn(
|
|
3418
|
-
"[dawcore]
|
|
3435
|
+
"[dawcore] adapter set after engine is built. The engine will continue using the previous adapter."
|
|
3419
3436
|
);
|
|
3420
3437
|
}
|
|
3421
|
-
this.
|
|
3438
|
+
this._externalAdapter = value;
|
|
3439
|
+
}
|
|
3440
|
+
get adapter() {
|
|
3441
|
+
return this._externalAdapter;
|
|
3422
3442
|
}
|
|
3423
3443
|
get audioContext() {
|
|
3424
|
-
if (this.
|
|
3425
|
-
|
|
3426
|
-
this._ownedAudioContext = new AudioContext({ sampleRate: this.sampleRate });
|
|
3427
|
-
if (this._ownedAudioContext.sampleRate !== this.sampleRate) {
|
|
3428
|
-
console.warn(
|
|
3429
|
-
"[dawcore] Requested sampleRate " + this.sampleRate + " but AudioContext is running at " + this._ownedAudioContext.sampleRate
|
|
3430
|
-
);
|
|
3431
|
-
}
|
|
3444
|
+
if (!this._externalAdapter) {
|
|
3445
|
+
throw new Error(NO_ADAPTER_ERROR);
|
|
3432
3446
|
}
|
|
3433
|
-
return this.
|
|
3447
|
+
return this._externalAdapter.audioContext;
|
|
3434
3448
|
}
|
|
3435
3449
|
get _clipHandler() {
|
|
3436
3450
|
return this.interactiveClips ? this._clipPointer : null;
|
|
@@ -3438,10 +3452,6 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3438
3452
|
get engine() {
|
|
3439
3453
|
return this._engine;
|
|
3440
3454
|
}
|
|
3441
|
-
/** The adapter's Transport — use for tempo, metronome, and effects. */
|
|
3442
|
-
get transport() {
|
|
3443
|
-
return this._adapter?.transport ?? null;
|
|
3444
|
-
}
|
|
3445
3455
|
get renderSamplesPerPixel() {
|
|
3446
3456
|
return this._renderSpp;
|
|
3447
3457
|
}
|
|
@@ -3594,12 +3604,6 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3594
3604
|
} catch (err) {
|
|
3595
3605
|
console.warn("[dawcore] Error disposing engine: " + String(err));
|
|
3596
3606
|
}
|
|
3597
|
-
if (this._ownedAudioContext) {
|
|
3598
|
-
this._ownedAudioContext.close().catch((err) => {
|
|
3599
|
-
console.warn("[dawcore] Error closing AudioContext: " + String(err));
|
|
3600
|
-
});
|
|
3601
|
-
this._ownedAudioContext = null;
|
|
3602
|
-
}
|
|
3603
3607
|
}
|
|
3604
3608
|
willUpdate(changedProperties) {
|
|
3605
3609
|
if (changedProperties.has("eagerResume")) {
|
|
@@ -3890,19 +3894,25 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3890
3894
|
return this._enginePromise;
|
|
3891
3895
|
}
|
|
3892
3896
|
async _buildEngine() {
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
const adapter =
|
|
3898
|
-
|
|
3899
|
-
|
|
3897
|
+
if (!this._externalAdapter) {
|
|
3898
|
+
throw new Error(NO_ADAPTER_ERROR);
|
|
3899
|
+
}
|
|
3900
|
+
const { PlaylistEngine } = await import("@waveform-playlist/engine");
|
|
3901
|
+
const adapter = this._externalAdapter;
|
|
3902
|
+
if (adapter.setTempo) {
|
|
3903
|
+
adapter.setTempo(this._bpm);
|
|
3904
|
+
} else if (this._bpm !== 120) {
|
|
3905
|
+
console.warn(
|
|
3906
|
+
"[dawcore] Adapter does not implement setTempo. Initial BPM " + this._bpm + " will not be applied \u2014 clips may use wrong tempo."
|
|
3907
|
+
);
|
|
3908
|
+
}
|
|
3909
|
+
adapter.setPpqn?.(this._ppqn);
|
|
3910
|
+
this.ppqn = adapter.ppqn;
|
|
3900
3911
|
const engine = new PlaylistEngine({
|
|
3901
3912
|
adapter,
|
|
3902
3913
|
sampleRate: this.effectiveSampleRate,
|
|
3903
3914
|
samplesPerPixel: this.samplesPerPixel,
|
|
3904
3915
|
bpm: this._bpm,
|
|
3905
|
-
ppqn: this._ppqn,
|
|
3906
3916
|
zoomLevels: [256, 512, 1024, 2048, 4096, 8192, this.samplesPerPixel].filter((v, i, a) => a.indexOf(v) === i).sort((a, b) => a - b)
|
|
3907
3917
|
});
|
|
3908
3918
|
let lastTracksVersion = -1;
|
|
@@ -3935,7 +3945,6 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
3935
3945
|
this._engine.dispose();
|
|
3936
3946
|
this._engine = null;
|
|
3937
3947
|
}
|
|
3938
|
-
this._adapter = null;
|
|
3939
3948
|
this._enginePromise = null;
|
|
3940
3949
|
}
|
|
3941
3950
|
async loadFiles(files) {
|
|
@@ -4060,6 +4069,18 @@ var DawEditorElement = class extends import_lit13.LitElement {
|
|
|
4060
4069
|
_addRecordedClip(trackId, buf, startSample, durSamples, offsetSamples = 0) {
|
|
4061
4070
|
addRecordedClip(this, trackId, buf, startSample, durSamples, offsetSamples);
|
|
4062
4071
|
}
|
|
4072
|
+
// --- RecordingHost bridge methods for cross-context worklet support ---
|
|
4073
|
+
// These delegate to the adapter's context type (native or standardized-audio-context).
|
|
4074
|
+
// The RecordingController calls these when available, falling back to native APIs.
|
|
4075
|
+
addWorkletModule(url) {
|
|
4076
|
+
return this._externalAdapter?.addWorkletModule?.(url) ?? this.audioContext.audioWorklet.addModule(url);
|
|
4077
|
+
}
|
|
4078
|
+
createAudioWorkletNode(name, options) {
|
|
4079
|
+
return this._externalAdapter?.createAudioWorkletNode?.(name, options) ?? new AudioWorkletNode(this.audioContext, name, options);
|
|
4080
|
+
}
|
|
4081
|
+
createMediaStreamSource(stream) {
|
|
4082
|
+
return this._externalAdapter?.createMediaStreamSource?.(stream) ?? this.audioContext.createMediaStreamSource(stream);
|
|
4083
|
+
}
|
|
4063
4084
|
async startRecording(stream, options) {
|
|
4064
4085
|
const s = stream ?? this.recordingStream;
|
|
4065
4086
|
if (!s) {
|
|
@@ -4451,9 +4472,6 @@ __decorateClass([
|
|
|
4451
4472
|
__decorateClass([
|
|
4452
4473
|
(0, import_decorators11.property)({ attribute: false })
|
|
4453
4474
|
], DawEditorElement.prototype, "ticksToSeconds", 2);
|
|
4454
|
-
__decorateClass([
|
|
4455
|
-
(0, import_decorators11.property)({ type: Number, attribute: "sample-rate" })
|
|
4456
|
-
], DawEditorElement.prototype, "sampleRate", 2);
|
|
4457
4475
|
__decorateClass([
|
|
4458
4476
|
(0, import_decorators11.state)()
|
|
4459
4477
|
], DawEditorElement.prototype, "_tracks", 2);
|
|
@@ -4475,6 +4493,9 @@ __decorateClass([
|
|
|
4475
4493
|
__decorateClass([
|
|
4476
4494
|
(0, import_decorators11.state)()
|
|
4477
4495
|
], DawEditorElement.prototype, "_dragOver", 2);
|
|
4496
|
+
__decorateClass([
|
|
4497
|
+
(0, import_decorators11.property)({ attribute: false })
|
|
4498
|
+
], DawEditorElement.prototype, "adapter", 1);
|
|
4478
4499
|
__decorateClass([
|
|
4479
4500
|
(0, import_decorators11.property)({ attribute: "eager-resume" })
|
|
4480
4501
|
], DawEditorElement.prototype, "eagerResume", 2);
|