@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 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 @dawcore/transport
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/recording @waveform-playlist/worklets
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
- That's it. The editor loads audio, generates waveforms, and handles playback.
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
- Access the native transport for tempo, metronome, count-in, meter, and effects:
144
+ Transport-specific APIs are on the `NativePlayoutAdapter` reference:
96
145
 
97
146
  ```javascript
98
- const editor = document.getElementById('editor');
99
-
100
- // Build engine eagerly so transport is available immediately
101
- await editor._ensureEngine();
102
- const transport = editor.transport;
103
-
104
- // Tempo & meter
105
- transport.setTempo(140);
106
- transport.setMeter(3, 4);
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
- By default, `<daw-editor>` creates its own `AudioContext` using the `sample-rate` attribute. To provide your own:
315
+ Pass a custom `AudioContext` via the adapter:
280
316
 
281
317
  ```javascript
282
- const editor = document.getElementById('editor');
283
- editor.audioContext = new AudioContext({ sampleRate: 48000, latencyHint: 0 });
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 this before tracks load. The provided context is used for decoding, playback, and recording.
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
- /** Desired sample rate. Creates a cross-browser AudioContext at this rate.
657
- * Pre-computed .dat peaks render instantly when they match. */
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
- /** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
672
- private _externalAudioContext;
673
- private _ownedAudioContext;
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
- /** Desired sample rate. Creates a cross-browser AudioContext at this rate.
657
- * Pre-computed .dat peaks render instantly when they match. */
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
- /** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
672
- private _externalAudioContext;
673
- private _ownedAudioContext;
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
- const ctx = this._host.audioContext;
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 recordingProcessorUrl;
2080
+ let addRecordingWorkletModule;
2068
2081
  try {
2069
- ({ recordingProcessorUrl } = await import("@waveform-playlist/worklets"));
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
- await rawCtx.audioWorklet.addModule(recordingProcessorUrl);
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 = new AudioWorkletNode(rawCtx, "recording-processor", {
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
- /** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
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
- /** Set an AudioContext to use for all audio operations. Must be set before tracks load. */
3411
- set audioContext(ctx) {
3412
- if (ctx && ctx.state === "closed") {
3413
- console.warn("[dawcore] Provided AudioContext is already closed. Ignoring.");
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] audioContext set after engine is built. The engine will continue using the previous context."
3435
+ "[dawcore] adapter set after engine is built. The engine will continue using the previous adapter."
3419
3436
  );
3420
3437
  }
3421
- this._externalAudioContext = ctx;
3438
+ this._externalAdapter = value;
3439
+ }
3440
+ get adapter() {
3441
+ return this._externalAdapter;
3422
3442
  }
3423
3443
  get audioContext() {
3424
- if (this._externalAudioContext) return this._externalAudioContext;
3425
- if (!this._ownedAudioContext) {
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._ownedAudioContext;
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
- const [{ PlaylistEngine }, { NativePlayoutAdapter }] = await Promise.all([
3894
- import("@waveform-playlist/engine"),
3895
- import("@dawcore/transport")
3896
- ]);
3897
- const adapter = new NativePlayoutAdapter(this.audioContext);
3898
- this._adapter = adapter;
3899
- adapter.setTempo(this._bpm);
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);