@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/dist/index.mjs CHANGED
@@ -1903,7 +1903,20 @@ var AudioResumeController = class {
1903
1903
  this._attached = false;
1904
1904
  this._generation = 0;
1905
1905
  this._onGesture = (e) => {
1906
- const ctx = this._host.audioContext;
1906
+ let ctx;
1907
+ try {
1908
+ ctx = this._host.audioContext;
1909
+ } catch (err) {
1910
+ if (err instanceof Error && err.message.includes("No PlayoutAdapter set")) {
1911
+ this._removeListeners();
1912
+ return;
1913
+ }
1914
+ console.warn(
1915
+ "[dawcore] AudioResumeController: unexpected error accessing audioContext: " + String(err)
1916
+ );
1917
+ this._removeListeners();
1918
+ return;
1919
+ }
1907
1920
  if (ctx.state === "closed") {
1908
1921
  console.warn("[dawcore] AudioResumeController: AudioContext is closed, cannot resume.");
1909
1922
  } else if (ctx.state === "suspended") {
@@ -2017,15 +2030,16 @@ var RecordingController = class {
2017
2030
  const rawCtx = this._host.audioContext;
2018
2031
  this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
2019
2032
  if (!this._workletLoadedCtx || this._workletLoadedCtx !== rawCtx) {
2020
- let recordingProcessorUrl;
2033
+ let addRecordingWorkletModule;
2021
2034
  try {
2022
- ({ recordingProcessorUrl } = await import("@waveform-playlist/worklets"));
2023
- } catch {
2035
+ ({ addRecordingWorkletModule } = await import("@waveform-playlist/worklets"));
2036
+ } catch (importErr) {
2024
2037
  throw new Error(
2025
- "Recording requires @waveform-playlist/worklets. Install it: npm install @waveform-playlist/worklets"
2038
+ "Recording requires @waveform-playlist/worklets. Install it: npm install @waveform-playlist/worklets (cause: " + String(importErr) + ")"
2026
2039
  );
2027
2040
  }
2028
- await rawCtx.audioWorklet.addModule(recordingProcessorUrl);
2041
+ const addModule = this._host.addWorkletModule ? (url) => this._host.addWorkletModule(url) : (url) => rawCtx.audioWorklet.addModule(url);
2042
+ await addRecordingWorkletModule(addModule);
2029
2043
  this._workletLoadedCtx = rawCtx;
2030
2044
  }
2031
2045
  const detectedChannelCount = stream.getAudioTracks()[0]?.getSettings()?.channelCount;
@@ -2038,8 +2052,11 @@ var RecordingController = class {
2038
2052
  const startSample = options.startSample ?? Math.floor(this._host._currentTime * this._host.effectiveSampleRate);
2039
2053
  const outputLatency = rawCtx.outputLatency ?? 0;
2040
2054
  const latencySamples = Math.floor(outputLatency * rawCtx.sampleRate);
2041
- const source = rawCtx.createMediaStreamSource(stream);
2042
- const workletNode = new AudioWorkletNode(rawCtx, "recording-processor", {
2055
+ const source = this._host.createMediaStreamSource ? this._host.createMediaStreamSource(stream) : rawCtx.createMediaStreamSource(stream);
2056
+ const workletNode = this._host.createAudioWorkletNode ? this._host.createAudioWorkletNode("recording-processor", {
2057
+ channelCount,
2058
+ channelCountMode: "explicit"
2059
+ }) : new AudioWorkletNode(rawCtx, "recording-processor", {
2043
2060
  channelCount,
2044
2061
  channelCountMode: "explicit"
2045
2062
  });
@@ -3141,6 +3158,7 @@ async function loadWaveformDataFromUrl(src) {
3141
3158
  }
3142
3159
 
3143
3160
  // src/elements/daw-editor.ts
3161
+ 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();";
3144
3162
  var DawEditorElement = class extends LitElement9 {
3145
3163
  constructor() {
3146
3164
  super(...arguments);
@@ -3160,7 +3178,6 @@ var DawEditorElement = class extends LitElement9 {
3160
3178
  this.timeSignature = [4, 4];
3161
3179
  this._ppqn = 960;
3162
3180
  this.snapTo = "off";
3163
- this.sampleRate = 48e3;
3164
3181
  /** Resolved sample rate — falls back to sampleRate property until first audio decode. */
3165
3182
  this._resolvedSampleRate = null;
3166
3183
  this._tracks = /* @__PURE__ */ new Map();
@@ -3174,11 +3191,8 @@ var DawEditorElement = class extends LitElement9 {
3174
3191
  this._selectionStartTime = 0;
3175
3192
  this._selectionEndTime = 0;
3176
3193
  this._currentTime = 0;
3177
- /** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
3178
- this._externalAudioContext = null;
3179
- this._ownedAudioContext = null;
3194
+ this._externalAdapter = null;
3180
3195
  this._engine = null;
3181
- this._adapter = null;
3182
3196
  this._warnedMissingTicksToSeconds = false;
3183
3197
  this._warnedMissingSecondsToTicks = false;
3184
3198
  this._enginePromise = null;
@@ -3360,30 +3374,30 @@ var DawEditorElement = class extends LitElement9 {
3360
3374
  this._ppqn = value;
3361
3375
  this.requestUpdate("ppqn", old);
3362
3376
  }
3363
- /** Set an AudioContext to use for all audio operations. Must be set before tracks load. */
3364
- set audioContext(ctx) {
3365
- if (ctx && ctx.state === "closed") {
3366
- console.warn("[dawcore] Provided AudioContext is already closed. Ignoring.");
3377
+ /** Sample rate reads from adapter's AudioContext when available, otherwise falls back to 48000. */
3378
+ get sampleRate() {
3379
+ return this._resolvedSampleRate ?? this._externalAdapter?.audioContext.sampleRate ?? 48e3;
3380
+ }
3381
+ set adapter(value) {
3382
+ if (value && value.audioContext.state === "closed") {
3383
+ console.warn("[dawcore] Adapter AudioContext is already closed. Ignoring.");
3367
3384
  return;
3368
3385
  }
3369
3386
  if (this._engine) {
3370
3387
  console.warn(
3371
- "[dawcore] audioContext set after engine is built. The engine will continue using the previous context."
3388
+ "[dawcore] adapter set after engine is built. The engine will continue using the previous adapter."
3372
3389
  );
3373
3390
  }
3374
- this._externalAudioContext = ctx;
3391
+ this._externalAdapter = value;
3392
+ }
3393
+ get adapter() {
3394
+ return this._externalAdapter;
3375
3395
  }
3376
3396
  get audioContext() {
3377
- if (this._externalAudioContext) return this._externalAudioContext;
3378
- if (!this._ownedAudioContext) {
3379
- this._ownedAudioContext = new AudioContext({ sampleRate: this.sampleRate });
3380
- if (this._ownedAudioContext.sampleRate !== this.sampleRate) {
3381
- console.warn(
3382
- "[dawcore] Requested sampleRate " + this.sampleRate + " but AudioContext is running at " + this._ownedAudioContext.sampleRate
3383
- );
3384
- }
3397
+ if (!this._externalAdapter) {
3398
+ throw new Error(NO_ADAPTER_ERROR);
3385
3399
  }
3386
- return this._ownedAudioContext;
3400
+ return this._externalAdapter.audioContext;
3387
3401
  }
3388
3402
  get _clipHandler() {
3389
3403
  return this.interactiveClips ? this._clipPointer : null;
@@ -3391,10 +3405,6 @@ var DawEditorElement = class extends LitElement9 {
3391
3405
  get engine() {
3392
3406
  return this._engine;
3393
3407
  }
3394
- /** The adapter's Transport — use for tempo, metronome, and effects. */
3395
- get transport() {
3396
- return this._adapter?.transport ?? null;
3397
- }
3398
3408
  get renderSamplesPerPixel() {
3399
3409
  return this._renderSpp;
3400
3410
  }
@@ -3547,12 +3557,6 @@ var DawEditorElement = class extends LitElement9 {
3547
3557
  } catch (err) {
3548
3558
  console.warn("[dawcore] Error disposing engine: " + String(err));
3549
3559
  }
3550
- if (this._ownedAudioContext) {
3551
- this._ownedAudioContext.close().catch((err) => {
3552
- console.warn("[dawcore] Error closing AudioContext: " + String(err));
3553
- });
3554
- this._ownedAudioContext = null;
3555
- }
3556
3560
  }
3557
3561
  willUpdate(changedProperties) {
3558
3562
  if (changedProperties.has("eagerResume")) {
@@ -3843,19 +3847,25 @@ var DawEditorElement = class extends LitElement9 {
3843
3847
  return this._enginePromise;
3844
3848
  }
3845
3849
  async _buildEngine() {
3846
- const [{ PlaylistEngine }, { NativePlayoutAdapter }] = await Promise.all([
3847
- import("@waveform-playlist/engine"),
3848
- import("@dawcore/transport")
3849
- ]);
3850
- const adapter = new NativePlayoutAdapter(this.audioContext);
3851
- this._adapter = adapter;
3852
- adapter.setTempo(this._bpm);
3850
+ if (!this._externalAdapter) {
3851
+ throw new Error(NO_ADAPTER_ERROR);
3852
+ }
3853
+ const { PlaylistEngine } = await import("@waveform-playlist/engine");
3854
+ const adapter = this._externalAdapter;
3855
+ if (adapter.setTempo) {
3856
+ adapter.setTempo(this._bpm);
3857
+ } else if (this._bpm !== 120) {
3858
+ console.warn(
3859
+ "[dawcore] Adapter does not implement setTempo. Initial BPM " + this._bpm + " will not be applied \u2014 clips may use wrong tempo."
3860
+ );
3861
+ }
3862
+ adapter.setPpqn?.(this._ppqn);
3863
+ this.ppqn = adapter.ppqn;
3853
3864
  const engine = new PlaylistEngine({
3854
3865
  adapter,
3855
3866
  sampleRate: this.effectiveSampleRate,
3856
3867
  samplesPerPixel: this.samplesPerPixel,
3857
3868
  bpm: this._bpm,
3858
- ppqn: this._ppqn,
3859
3869
  zoomLevels: [256, 512, 1024, 2048, 4096, 8192, this.samplesPerPixel].filter((v, i, a) => a.indexOf(v) === i).sort((a, b) => a - b)
3860
3870
  });
3861
3871
  let lastTracksVersion = -1;
@@ -3888,7 +3898,6 @@ var DawEditorElement = class extends LitElement9 {
3888
3898
  this._engine.dispose();
3889
3899
  this._engine = null;
3890
3900
  }
3891
- this._adapter = null;
3892
3901
  this._enginePromise = null;
3893
3902
  }
3894
3903
  async loadFiles(files) {
@@ -4013,6 +4022,18 @@ var DawEditorElement = class extends LitElement9 {
4013
4022
  _addRecordedClip(trackId, buf, startSample, durSamples, offsetSamples = 0) {
4014
4023
  addRecordedClip(this, trackId, buf, startSample, durSamples, offsetSamples);
4015
4024
  }
4025
+ // --- RecordingHost bridge methods for cross-context worklet support ---
4026
+ // These delegate to the adapter's context type (native or standardized-audio-context).
4027
+ // The RecordingController calls these when available, falling back to native APIs.
4028
+ addWorkletModule(url) {
4029
+ return this._externalAdapter?.addWorkletModule?.(url) ?? this.audioContext.audioWorklet.addModule(url);
4030
+ }
4031
+ createAudioWorkletNode(name, options) {
4032
+ return this._externalAdapter?.createAudioWorkletNode?.(name, options) ?? new AudioWorkletNode(this.audioContext, name, options);
4033
+ }
4034
+ createMediaStreamSource(stream) {
4035
+ return this._externalAdapter?.createMediaStreamSource?.(stream) ?? this.audioContext.createMediaStreamSource(stream);
4036
+ }
4016
4037
  async startRecording(stream, options) {
4017
4038
  const s = stream ?? this.recordingStream;
4018
4039
  if (!s) {
@@ -4404,9 +4425,6 @@ __decorateClass([
4404
4425
  __decorateClass([
4405
4426
  property7({ attribute: false })
4406
4427
  ], DawEditorElement.prototype, "ticksToSeconds", 2);
4407
- __decorateClass([
4408
- property7({ type: Number, attribute: "sample-rate" })
4409
- ], DawEditorElement.prototype, "sampleRate", 2);
4410
4428
  __decorateClass([
4411
4429
  state3()
4412
4430
  ], DawEditorElement.prototype, "_tracks", 2);
@@ -4428,6 +4446,9 @@ __decorateClass([
4428
4446
  __decorateClass([
4429
4447
  state3()
4430
4448
  ], DawEditorElement.prototype, "_dragOver", 2);
4449
+ __decorateClass([
4450
+ property7({ attribute: false })
4451
+ ], DawEditorElement.prototype, "adapter", 1);
4431
4452
  __decorateClass([
4432
4453
  property7({ attribute: "eager-resume" })
4433
4454
  ], DawEditorElement.prototype, "eagerResume", 2);