@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/README.md +170 -0
- package/dist/index.d.mts +16 -10
- package/dist/index.d.ts +16 -10
- package/dist/index.js +66 -55
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
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
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1747
|
+
this._workletLoadedCtx = rawCtx;
|
|
1745
1748
|
}
|
|
1746
|
-
const
|
|
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
|
|
1750
|
-
const
|
|
1751
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 }, {
|
|
3406
|
+
const [{ PlaylistEngine }, { NativePlayoutAdapter }] = await Promise.all([
|
|
3396
3407
|
import("@waveform-playlist/engine"),
|
|
3397
|
-
import("@
|
|
3408
|
+
import("@dawcore/transport")
|
|
3398
3409
|
]);
|
|
3399
|
-
const adapter =
|
|
3410
|
+
const adapter = new NativePlayoutAdapter(this.audioContext);
|
|
3400
3411
|
const engine = new PlaylistEngine({
|
|
3401
3412
|
adapter,
|
|
3402
3413
|
sampleRate: this.effectiveSampleRate,
|