@dawcore/components 0.0.5 → 0.0.7

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
@@ -1622,18 +1622,22 @@ var ViewportController = class {
1622
1622
  };
1623
1623
 
1624
1624
  // src/controllers/audio-resume-controller.ts
1625
- import { resumeGlobalAudioContext } from "@waveform-playlist/playout";
1626
1625
  var AudioResumeController = class {
1627
1626
  constructor(host) {
1628
1627
  this._target = null;
1629
1628
  this._attached = false;
1630
1629
  this._generation = 0;
1631
1630
  this._onGesture = (e) => {
1632
- resumeGlobalAudioContext().catch((err) => {
1633
- console.warn(
1634
- "[dawcore] AudioResumeController: eager resume failed, will retry on play: " + String(err)
1635
- );
1636
- });
1631
+ const ctx = this._host.audioContext;
1632
+ if (ctx.state === "closed") {
1633
+ console.warn("[dawcore] AudioResumeController: AudioContext is closed, cannot resume.");
1634
+ } else if (ctx.state === "suspended") {
1635
+ ctx.resume().catch((err) => {
1636
+ console.warn(
1637
+ "[dawcore] AudioResumeController: eager resume failed, will retry on play: " + String(err)
1638
+ );
1639
+ });
1640
+ }
1637
1641
  const otherType = e.type === "pointerdown" ? "keydown" : "pointerdown";
1638
1642
  this._target?.removeEventListener(otherType, this._onGesture, {
1639
1643
  capture: true
@@ -1701,13 +1705,12 @@ var AudioResumeController = class {
1701
1705
  };
1702
1706
 
1703
1707
  // src/controllers/recording-controller.ts
1704
- import { getGlobalContext } from "@waveform-playlist/playout";
1705
1708
  import { recordingProcessorUrl } from "@waveform-playlist/worklets";
1706
1709
  import { appendPeaks, concatenateAudioData, createAudioBuffer } from "@waveform-playlist/recording";
1707
1710
  var RecordingController = class {
1708
1711
  constructor(host) {
1709
1712
  this._sessions = /* @__PURE__ */ new Map();
1710
- this._workletLoaded = false;
1713
+ this._workletLoadedCtx = null;
1711
1714
  this._host = host;
1712
1715
  host.addController(this);
1713
1716
  }
@@ -1717,6 +1720,7 @@ var RecordingController = class {
1717
1720
  for (const trackId of [...this._sessions.keys()]) {
1718
1721
  this._cleanupSession(trackId);
1719
1722
  }
1723
+ this._workletLoadedCtx = null;
1720
1724
  }
1721
1725
  get isRecording() {
1722
1726
  return this._sessions.size > 0;
@@ -1735,21 +1739,25 @@ var RecordingController = class {
1735
1739
  return;
1736
1740
  }
1737
1741
  const bits = options.bits ?? 16;
1738
- const context = getGlobalContext();
1739
- const rawCtx = context.rawContext;
1740
- this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
1741
1742
  try {
1742
- if (!this._workletLoaded) {
1743
+ const rawCtx = this._host.audioContext;
1744
+ this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
1745
+ if (!this._workletLoadedCtx || this._workletLoadedCtx !== rawCtx) {
1743
1746
  await rawCtx.audioWorklet.addModule(recordingProcessorUrl);
1744
- this._workletLoaded = true;
1747
+ this._workletLoadedCtx = rawCtx;
1745
1748
  }
1746
- const channelCount = stream.getAudioTracks()[0]?.getSettings()?.channelCount ?? 1;
1749
+ const detectedChannelCount = stream.getAudioTracks()[0]?.getSettings()?.channelCount;
1750
+ if (detectedChannelCount === void 0 && options.channelCount !== void 0) {
1751
+ console.warn(
1752
+ "[dawcore] Could not detect stream channel count, using fallback: " + options.channelCount
1753
+ );
1754
+ }
1755
+ const channelCount = detectedChannelCount ?? options.channelCount ?? 1;
1747
1756
  const startSample = options.startSample ?? Math.floor(this._host._currentTime * this._host.effectiveSampleRate);
1748
1757
  const outputLatency = rawCtx.outputLatency ?? 0;
1749
- const lookAhead = context.lookAhead ?? 0;
1750
- const latencySamples = Math.floor((outputLatency + lookAhead) * rawCtx.sampleRate);
1751
- const source = context.createMediaStreamSource(stream);
1752
- const workletNode = context.createAudioWorkletNode("recording-processor", {
1758
+ const latencySamples = Math.floor(outputLatency * rawCtx.sampleRate);
1759
+ const source = rawCtx.createMediaStreamSource(stream);
1760
+ const workletNode = new AudioWorkletNode(rawCtx, "recording-processor", {
1753
1761
  channelCount,
1754
1762
  channelCountMode: "explicit"
1755
1763
  });
@@ -1850,8 +1858,7 @@ var RecordingController = class {
1850
1858
  );
1851
1859
  return;
1852
1860
  }
1853
- const context = getGlobalContext();
1854
- const stopCtx = context.rawContext;
1861
+ const stopCtx = this._host.audioContext;
1855
1862
  const channelData = session.chunks.map((chunkArr) => concatenateAudioData(chunkArr));
1856
1863
  const audioBuffer = createAudioBuffer(
1857
1864
  stopCtx,
@@ -2823,6 +2830,9 @@ var DawEditorElement = class extends LitElement8 {
2823
2830
  this._selectionStartTime = 0;
2824
2831
  this._selectionEndTime = 0;
2825
2832
  this._currentTime = 0;
2833
+ /** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
2834
+ this._externalAudioContext = null;
2835
+ this._ownedAudioContext = null;
2826
2836
  this._engine = null;
2827
2837
  this._enginePromise = null;
2828
2838
  this._audioCache = /* @__PURE__ */ new Map();
@@ -2917,7 +2927,6 @@ var DawEditorElement = class extends LitElement8 {
2917
2927
  this._onTrackRemoved(trackId);
2918
2928
  }
2919
2929
  };
2920
- this._contextConfigurePromise = null;
2921
2930
  // --- File Drop ---
2922
2931
  this._onDragOver = (e) => {
2923
2932
  if (!this.fileDrop) return;
@@ -2969,6 +2978,31 @@ var DawEditorElement = class extends LitElement8 {
2969
2978
  this._samplesPerPixel = clamped;
2970
2979
  this.requestUpdate("samplesPerPixel", old);
2971
2980
  }
2981
+ /** Set an AudioContext to use for all audio operations. Must be set before tracks load. */
2982
+ set audioContext(ctx) {
2983
+ if (ctx && ctx.state === "closed") {
2984
+ console.warn("[dawcore] Provided AudioContext is already closed. Ignoring.");
2985
+ return;
2986
+ }
2987
+ if (this._engine) {
2988
+ console.warn(
2989
+ "[dawcore] audioContext set after engine is built. The engine will continue using the previous context."
2990
+ );
2991
+ }
2992
+ this._externalAudioContext = ctx;
2993
+ }
2994
+ get audioContext() {
2995
+ if (this._externalAudioContext) return this._externalAudioContext;
2996
+ if (!this._ownedAudioContext) {
2997
+ this._ownedAudioContext = new AudioContext({ sampleRate: this.sampleRate });
2998
+ if (this._ownedAudioContext.sampleRate !== this.sampleRate) {
2999
+ console.warn(
3000
+ "[dawcore] Requested sampleRate " + this.sampleRate + " but AudioContext is running at " + this._ownedAudioContext.sampleRate
3001
+ );
3002
+ }
3003
+ }
3004
+ return this._ownedAudioContext;
3005
+ }
2972
3006
  get _clipHandler() {
2973
3007
  return this.interactiveClips ? this._clipPointer : null;
2974
3008
  }
@@ -3069,12 +3103,17 @@ var DawEditorElement = class extends LitElement8 {
3069
3103
  this._clipOffsets.clear();
3070
3104
  this._peakPipeline.terminate();
3071
3105
  this._minSamplesPerPixel = 0;
3072
- this._contextConfigurePromise = null;
3073
3106
  try {
3074
3107
  this._disposeEngine();
3075
3108
  } catch (err) {
3076
3109
  console.warn("[dawcore] Error disposing engine: " + String(err));
3077
3110
  }
3111
+ if (this._ownedAudioContext) {
3112
+ this._ownedAudioContext.close().catch((err) => {
3113
+ console.warn("[dawcore] Error closing AudioContext: " + String(err));
3114
+ });
3115
+ this._ownedAudioContext = null;
3116
+ }
3078
3117
  }
3079
3118
  willUpdate(changedProperties) {
3080
3119
  if (changedProperties.has("eagerResume")) {
@@ -3179,9 +3218,7 @@ var DawEditorElement = class extends LitElement8 {
3179
3218
  if (waveformDataPromise) {
3180
3219
  try {
3181
3220
  const wd = await waveformDataPromise;
3182
- await this._ensureContextConfigured();
3183
- const { getGlobalAudioContext } = await import("@waveform-playlist/playout");
3184
- const contextRate = getGlobalAudioContext().sampleRate;
3221
+ const contextRate = this.audioContext.sampleRate;
3185
3222
  if (wd.sample_rate === contextRate) {
3186
3223
  waveformData = wd;
3187
3224
  } else {
@@ -3313,30 +3350,6 @@ var DawEditorElement = class extends LitElement8 {
3313
3350
  );
3314
3351
  }
3315
3352
  }
3316
- /**
3317
- * Ensure the global AudioContext is configured with the editor's sample-rate hint
3318
- * before the first audio operation. Idempotent — concurrent callers await the
3319
- * same promise so no one proceeds to getGlobalAudioContext() before configuration.
3320
- */
3321
- _ensureContextConfigured() {
3322
- if (!this._contextConfigurePromise) {
3323
- this._contextConfigurePromise = (async () => {
3324
- const { configureGlobalContext } = await import("@waveform-playlist/playout");
3325
- const actualRate = configureGlobalContext({
3326
- sampleRate: this.sampleRate
3327
- });
3328
- if (actualRate !== this.sampleRate) {
3329
- console.warn(
3330
- "[dawcore] Requested sampleRate " + this.sampleRate + " but AudioContext is running at " + actualRate
3331
- );
3332
- }
3333
- })().catch((err) => {
3334
- this._contextConfigurePromise = null;
3335
- throw err;
3336
- });
3337
- }
3338
- return this._contextConfigurePromise;
3339
- }
3340
3353
  async _fetchAndDecode(src) {
3341
3354
  if (this._audioCache.has(src)) {
3342
3355
  return this._audioCache.get(src);
@@ -3349,9 +3362,7 @@ var DawEditorElement = class extends LitElement8 {
3349
3362
  );
3350
3363
  }
3351
3364
  const arrayBuffer = await response.arrayBuffer();
3352
- await this._ensureContextConfigured();
3353
- const { getGlobalAudioContext } = await import("@waveform-playlist/playout");
3354
- return getGlobalAudioContext().decodeAudioData(arrayBuffer);
3365
+ return this.audioContext.decodeAudioData(arrayBuffer);
3355
3366
  })();
3356
3367
  this._audioCache.set(src, promise);
3357
3368
  try {
@@ -3392,11 +3403,11 @@ var DawEditorElement = class extends LitElement8 {
3392
3403
  return this._enginePromise;
3393
3404
  }
3394
3405
  async _buildEngine() {
3395
- const [{ PlaylistEngine }, { createToneAdapter }] = await Promise.all([
3406
+ const [{ PlaylistEngine }, { NativePlayoutAdapter }] = await Promise.all([
3396
3407
  import("@waveform-playlist/engine"),
3397
- import("@waveform-playlist/playout")
3408
+ import("@dawcore/transport")
3398
3409
  ]);
3399
- const adapter = createToneAdapter();
3410
+ const adapter = new NativePlayoutAdapter(this.audioContext);
3400
3411
  const engine = new PlaylistEngine({
3401
3412
  adapter,
3402
3413
  sampleRate: this.effectiveSampleRate,