@dawcore/components 0.0.4 → 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 +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +81 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +87 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -755,7 +755,12 @@ DawStopButtonElement = __decorateClass([
|
|
|
755
755
|
// src/elements/daw-editor.ts
|
|
756
756
|
import { LitElement as LitElement8, html as html7, css as css7 } from "lit";
|
|
757
757
|
import { customElement as customElement10, property as property6, state as state3 } from "lit/decorators.js";
|
|
758
|
-
import {
|
|
758
|
+
import {
|
|
759
|
+
createClip as createClip2,
|
|
760
|
+
createClipFromSeconds as createClipFromSeconds2,
|
|
761
|
+
createTrack as createTrack2,
|
|
762
|
+
clipPixelWidth
|
|
763
|
+
} from "@waveform-playlist/core";
|
|
759
764
|
|
|
760
765
|
// src/workers/peaksWorker.ts
|
|
761
766
|
import WaveformData from "waveform-data";
|
|
@@ -1617,18 +1622,22 @@ var ViewportController = class {
|
|
|
1617
1622
|
};
|
|
1618
1623
|
|
|
1619
1624
|
// src/controllers/audio-resume-controller.ts
|
|
1620
|
-
import { resumeGlobalAudioContext } from "@waveform-playlist/playout";
|
|
1621
1625
|
var AudioResumeController = class {
|
|
1622
1626
|
constructor(host) {
|
|
1623
1627
|
this._target = null;
|
|
1624
1628
|
this._attached = false;
|
|
1625
1629
|
this._generation = 0;
|
|
1626
1630
|
this._onGesture = (e) => {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
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
|
+
}
|
|
1632
1641
|
const otherType = e.type === "pointerdown" ? "keydown" : "pointerdown";
|
|
1633
1642
|
this._target?.removeEventListener(otherType, this._onGesture, {
|
|
1634
1643
|
capture: true
|
|
@@ -1696,13 +1705,12 @@ var AudioResumeController = class {
|
|
|
1696
1705
|
};
|
|
1697
1706
|
|
|
1698
1707
|
// src/controllers/recording-controller.ts
|
|
1699
|
-
import { getGlobalContext } from "@waveform-playlist/playout";
|
|
1700
1708
|
import { recordingProcessorUrl } from "@waveform-playlist/worklets";
|
|
1701
1709
|
import { appendPeaks, concatenateAudioData, createAudioBuffer } from "@waveform-playlist/recording";
|
|
1702
1710
|
var RecordingController = class {
|
|
1703
1711
|
constructor(host) {
|
|
1704
1712
|
this._sessions = /* @__PURE__ */ new Map();
|
|
1705
|
-
this.
|
|
1713
|
+
this._workletLoadedCtx = null;
|
|
1706
1714
|
this._host = host;
|
|
1707
1715
|
host.addController(this);
|
|
1708
1716
|
}
|
|
@@ -1712,6 +1720,7 @@ var RecordingController = class {
|
|
|
1712
1720
|
for (const trackId of [...this._sessions.keys()]) {
|
|
1713
1721
|
this._cleanupSession(trackId);
|
|
1714
1722
|
}
|
|
1723
|
+
this._workletLoadedCtx = null;
|
|
1715
1724
|
}
|
|
1716
1725
|
get isRecording() {
|
|
1717
1726
|
return this._sessions.size > 0;
|
|
@@ -1730,21 +1739,25 @@ var RecordingController = class {
|
|
|
1730
1739
|
return;
|
|
1731
1740
|
}
|
|
1732
1741
|
const bits = options.bits ?? 16;
|
|
1733
|
-
const context = getGlobalContext();
|
|
1734
|
-
const rawCtx = context.rawContext;
|
|
1735
|
-
this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
|
|
1736
1742
|
try {
|
|
1737
|
-
|
|
1743
|
+
const rawCtx = this._host.audioContext;
|
|
1744
|
+
this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
|
|
1745
|
+
if (!this._workletLoadedCtx || this._workletLoadedCtx !== rawCtx) {
|
|
1738
1746
|
await rawCtx.audioWorklet.addModule(recordingProcessorUrl);
|
|
1739
|
-
this.
|
|
1747
|
+
this._workletLoadedCtx = rawCtx;
|
|
1740
1748
|
}
|
|
1741
|
-
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;
|
|
1742
1756
|
const startSample = options.startSample ?? Math.floor(this._host._currentTime * this._host.effectiveSampleRate);
|
|
1743
1757
|
const outputLatency = rawCtx.outputLatency ?? 0;
|
|
1744
|
-
const
|
|
1745
|
-
const
|
|
1746
|
-
const
|
|
1747
|
-
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", {
|
|
1748
1761
|
channelCount,
|
|
1749
1762
|
channelCountMode: "explicit"
|
|
1750
1763
|
});
|
|
@@ -1845,8 +1858,7 @@ var RecordingController = class {
|
|
|
1845
1858
|
);
|
|
1846
1859
|
return;
|
|
1847
1860
|
}
|
|
1848
|
-
const
|
|
1849
|
-
const stopCtx = context.rawContext;
|
|
1861
|
+
const stopCtx = this._host.audioContext;
|
|
1850
1862
|
const channelData = session.chunks.map((chunkArr) => concatenateAudioData(chunkArr));
|
|
1851
1863
|
const audioBuffer = createAudioBuffer(
|
|
1852
1864
|
stopCtx,
|
|
@@ -2818,6 +2830,9 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
2818
2830
|
this._selectionStartTime = 0;
|
|
2819
2831
|
this._selectionEndTime = 0;
|
|
2820
2832
|
this._currentTime = 0;
|
|
2833
|
+
/** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
|
|
2834
|
+
this._externalAudioContext = null;
|
|
2835
|
+
this._ownedAudioContext = null;
|
|
2821
2836
|
this._engine = null;
|
|
2822
2837
|
this._enginePromise = null;
|
|
2823
2838
|
this._audioCache = /* @__PURE__ */ new Map();
|
|
@@ -2963,6 +2978,31 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
2963
2978
|
this._samplesPerPixel = clamped;
|
|
2964
2979
|
this.requestUpdate("samplesPerPixel", old);
|
|
2965
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
|
+
}
|
|
2966
3006
|
get _clipHandler() {
|
|
2967
3007
|
return this.interactiveClips ? this._clipPointer : null;
|
|
2968
3008
|
}
|
|
@@ -3068,6 +3108,12 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
3068
3108
|
} catch (err) {
|
|
3069
3109
|
console.warn("[dawcore] Error disposing engine: " + String(err));
|
|
3070
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
|
+
}
|
|
3071
3117
|
}
|
|
3072
3118
|
willUpdate(changedProperties) {
|
|
3073
3119
|
if (changedProperties.has("eagerResume")) {
|
|
@@ -3171,7 +3217,15 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
3171
3217
|
let waveformData = null;
|
|
3172
3218
|
if (waveformDataPromise) {
|
|
3173
3219
|
try {
|
|
3174
|
-
|
|
3220
|
+
const wd = await waveformDataPromise;
|
|
3221
|
+
const contextRate = this.audioContext.sampleRate;
|
|
3222
|
+
if (wd.sample_rate === contextRate) {
|
|
3223
|
+
waveformData = wd;
|
|
3224
|
+
} else {
|
|
3225
|
+
console.warn(
|
|
3226
|
+
"[dawcore] Pre-computed peaks at " + wd.sample_rate + " Hz do not match AudioContext at " + contextRate + " Hz \u2014 ignoring " + clipDesc.peaksSrc + ", generating from audio"
|
|
3227
|
+
);
|
|
3228
|
+
}
|
|
3175
3229
|
} catch (err) {
|
|
3176
3230
|
console.warn(
|
|
3177
3231
|
"[dawcore] Failed to load peaks from " + clipDesc.peaksSrc + ": " + String(err) + " \u2014 falling back to AudioBuffer generation"
|
|
@@ -3179,15 +3233,16 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
3179
3233
|
}
|
|
3180
3234
|
}
|
|
3181
3235
|
if (waveformData) {
|
|
3182
|
-
const
|
|
3236
|
+
const wdRate = waveformData.sample_rate;
|
|
3237
|
+
const clip2 = createClip2({
|
|
3183
3238
|
waveformData,
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3239
|
+
startSample: Math.round(clipDesc.start * wdRate),
|
|
3240
|
+
durationSamples: Math.round((clipDesc.duration || waveformData.duration) * wdRate),
|
|
3241
|
+
offsetSamples: Math.round(clipDesc.offset * wdRate),
|
|
3187
3242
|
gain: clipDesc.gain,
|
|
3188
3243
|
name: clipDesc.name,
|
|
3189
|
-
sampleRate:
|
|
3190
|
-
|
|
3244
|
+
sampleRate: wdRate,
|
|
3245
|
+
sourceDurationSamples: Math.ceil(waveformData.duration * wdRate)
|
|
3191
3246
|
});
|
|
3192
3247
|
const effectiveScale = Math.max(this.samplesPerPixel, waveformData.scale);
|
|
3193
3248
|
const peakData2 = extractPeaks(
|
|
@@ -3307,8 +3362,7 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
3307
3362
|
);
|
|
3308
3363
|
}
|
|
3309
3364
|
const arrayBuffer = await response.arrayBuffer();
|
|
3310
|
-
|
|
3311
|
-
return getGlobalAudioContext().decodeAudioData(arrayBuffer);
|
|
3365
|
+
return this.audioContext.decodeAudioData(arrayBuffer);
|
|
3312
3366
|
})();
|
|
3313
3367
|
this._audioCache.set(src, promise);
|
|
3314
3368
|
try {
|
|
@@ -3349,11 +3403,11 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
3349
3403
|
return this._enginePromise;
|
|
3350
3404
|
}
|
|
3351
3405
|
async _buildEngine() {
|
|
3352
|
-
const [{ PlaylistEngine }, {
|
|
3406
|
+
const [{ PlaylistEngine }, { NativePlayoutAdapter }] = await Promise.all([
|
|
3353
3407
|
import("@waveform-playlist/engine"),
|
|
3354
|
-
import("@
|
|
3408
|
+
import("@dawcore/transport")
|
|
3355
3409
|
]);
|
|
3356
|
-
const adapter =
|
|
3410
|
+
const adapter = new NativePlayoutAdapter(this.audioContext);
|
|
3357
3411
|
const engine = new PlaylistEngine({
|
|
3358
3412
|
adapter,
|
|
3359
3413
|
sampleRate: this.effectiveSampleRate,
|