@dawcore/components 0.0.5 → 0.0.8
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 +292 -0
- package/dist/index.d.mts +16 -10
- package/dist/index.d.ts +16 -10
- package/dist/index.js +71 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1668,18 +1668,22 @@ var ViewportController = class {
|
|
|
1668
1668
|
};
|
|
1669
1669
|
|
|
1670
1670
|
// src/controllers/audio-resume-controller.ts
|
|
1671
|
-
var import_playout = require("@waveform-playlist/playout");
|
|
1672
1671
|
var AudioResumeController = class {
|
|
1673
1672
|
constructor(host) {
|
|
1674
1673
|
this._target = null;
|
|
1675
1674
|
this._attached = false;
|
|
1676
1675
|
this._generation = 0;
|
|
1677
1676
|
this._onGesture = (e) => {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1677
|
+
const ctx = this._host.audioContext;
|
|
1678
|
+
if (ctx.state === "closed") {
|
|
1679
|
+
console.warn("[dawcore] AudioResumeController: AudioContext is closed, cannot resume.");
|
|
1680
|
+
} else if (ctx.state === "suspended") {
|
|
1681
|
+
ctx.resume().catch((err) => {
|
|
1682
|
+
console.warn(
|
|
1683
|
+
"[dawcore] AudioResumeController: eager resume failed, will retry on play: " + String(err)
|
|
1684
|
+
);
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1683
1687
|
const otherType = e.type === "pointerdown" ? "keydown" : "pointerdown";
|
|
1684
1688
|
this._target?.removeEventListener(otherType, this._onGesture, {
|
|
1685
1689
|
capture: true
|
|
@@ -1747,13 +1751,12 @@ var AudioResumeController = class {
|
|
|
1747
1751
|
};
|
|
1748
1752
|
|
|
1749
1753
|
// src/controllers/recording-controller.ts
|
|
1750
|
-
var import_playout2 = require("@waveform-playlist/playout");
|
|
1751
1754
|
var import_worklets = require("@waveform-playlist/worklets");
|
|
1752
1755
|
var import_recording = require("@waveform-playlist/recording");
|
|
1753
1756
|
var RecordingController = class {
|
|
1754
1757
|
constructor(host) {
|
|
1755
1758
|
this._sessions = /* @__PURE__ */ new Map();
|
|
1756
|
-
this.
|
|
1759
|
+
this._workletLoadedCtx = null;
|
|
1757
1760
|
this._host = host;
|
|
1758
1761
|
host.addController(this);
|
|
1759
1762
|
}
|
|
@@ -1763,6 +1766,7 @@ var RecordingController = class {
|
|
|
1763
1766
|
for (const trackId of [...this._sessions.keys()]) {
|
|
1764
1767
|
this._cleanupSession(trackId);
|
|
1765
1768
|
}
|
|
1769
|
+
this._workletLoadedCtx = null;
|
|
1766
1770
|
}
|
|
1767
1771
|
get isRecording() {
|
|
1768
1772
|
return this._sessions.size > 0;
|
|
@@ -1781,21 +1785,25 @@ var RecordingController = class {
|
|
|
1781
1785
|
return;
|
|
1782
1786
|
}
|
|
1783
1787
|
const bits = options.bits ?? 16;
|
|
1784
|
-
const context = (0, import_playout2.getGlobalContext)();
|
|
1785
|
-
const rawCtx = context.rawContext;
|
|
1786
|
-
this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
|
|
1787
1788
|
try {
|
|
1788
|
-
|
|
1789
|
+
const rawCtx = this._host.audioContext;
|
|
1790
|
+
this._host.resolveAudioContextSampleRate(rawCtx.sampleRate);
|
|
1791
|
+
if (!this._workletLoadedCtx || this._workletLoadedCtx !== rawCtx) {
|
|
1789
1792
|
await rawCtx.audioWorklet.addModule(import_worklets.recordingProcessorUrl);
|
|
1790
|
-
this.
|
|
1793
|
+
this._workletLoadedCtx = rawCtx;
|
|
1791
1794
|
}
|
|
1792
|
-
const
|
|
1795
|
+
const detectedChannelCount = stream.getAudioTracks()[0]?.getSettings()?.channelCount;
|
|
1796
|
+
if (detectedChannelCount === void 0 && options.channelCount !== void 0) {
|
|
1797
|
+
console.warn(
|
|
1798
|
+
"[dawcore] Could not detect stream channel count, using fallback: " + options.channelCount
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
const channelCount = detectedChannelCount ?? options.channelCount ?? 1;
|
|
1793
1802
|
const startSample = options.startSample ?? Math.floor(this._host._currentTime * this._host.effectiveSampleRate);
|
|
1794
1803
|
const outputLatency = rawCtx.outputLatency ?? 0;
|
|
1795
|
-
const
|
|
1796
|
-
const
|
|
1797
|
-
const
|
|
1798
|
-
const workletNode = context.createAudioWorkletNode("recording-processor", {
|
|
1804
|
+
const latencySamples = Math.floor(outputLatency * rawCtx.sampleRate);
|
|
1805
|
+
const source = rawCtx.createMediaStreamSource(stream);
|
|
1806
|
+
const workletNode = new AudioWorkletNode(rawCtx, "recording-processor", {
|
|
1799
1807
|
channelCount,
|
|
1800
1808
|
channelCountMode: "explicit"
|
|
1801
1809
|
});
|
|
@@ -1896,8 +1904,7 @@ var RecordingController = class {
|
|
|
1896
1904
|
);
|
|
1897
1905
|
return;
|
|
1898
1906
|
}
|
|
1899
|
-
const
|
|
1900
|
-
const stopCtx = context.rawContext;
|
|
1907
|
+
const stopCtx = this._host.audioContext;
|
|
1901
1908
|
const channelData = session.chunks.map((chunkArr) => (0, import_recording.concatenateAudioData)(chunkArr));
|
|
1902
1909
|
const audioBuffer = (0, import_recording.createAudioBuffer)(
|
|
1903
1910
|
stopCtx,
|
|
@@ -2869,6 +2876,9 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2869
2876
|
this._selectionStartTime = 0;
|
|
2870
2877
|
this._selectionEndTime = 0;
|
|
2871
2878
|
this._currentTime = 0;
|
|
2879
|
+
/** Consumer-provided AudioContext. When set, used for decode, playback, and recording. */
|
|
2880
|
+
this._externalAudioContext = null;
|
|
2881
|
+
this._ownedAudioContext = null;
|
|
2872
2882
|
this._engine = null;
|
|
2873
2883
|
this._enginePromise = null;
|
|
2874
2884
|
this._audioCache = /* @__PURE__ */ new Map();
|
|
@@ -2963,7 +2973,6 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2963
2973
|
this._onTrackRemoved(trackId);
|
|
2964
2974
|
}
|
|
2965
2975
|
};
|
|
2966
|
-
this._contextConfigurePromise = null;
|
|
2967
2976
|
// --- File Drop ---
|
|
2968
2977
|
this._onDragOver = (e) => {
|
|
2969
2978
|
if (!this.fileDrop) return;
|
|
@@ -3015,6 +3024,31 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3015
3024
|
this._samplesPerPixel = clamped;
|
|
3016
3025
|
this.requestUpdate("samplesPerPixel", old);
|
|
3017
3026
|
}
|
|
3027
|
+
/** Set an AudioContext to use for all audio operations. Must be set before tracks load. */
|
|
3028
|
+
set audioContext(ctx) {
|
|
3029
|
+
if (ctx && ctx.state === "closed") {
|
|
3030
|
+
console.warn("[dawcore] Provided AudioContext is already closed. Ignoring.");
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
if (this._engine) {
|
|
3034
|
+
console.warn(
|
|
3035
|
+
"[dawcore] audioContext set after engine is built. The engine will continue using the previous context."
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
3038
|
+
this._externalAudioContext = ctx;
|
|
3039
|
+
}
|
|
3040
|
+
get audioContext() {
|
|
3041
|
+
if (this._externalAudioContext) return this._externalAudioContext;
|
|
3042
|
+
if (!this._ownedAudioContext) {
|
|
3043
|
+
this._ownedAudioContext = new AudioContext({ sampleRate: this.sampleRate });
|
|
3044
|
+
if (this._ownedAudioContext.sampleRate !== this.sampleRate) {
|
|
3045
|
+
console.warn(
|
|
3046
|
+
"[dawcore] Requested sampleRate " + this.sampleRate + " but AudioContext is running at " + this._ownedAudioContext.sampleRate
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
return this._ownedAudioContext;
|
|
3051
|
+
}
|
|
3018
3052
|
get _clipHandler() {
|
|
3019
3053
|
return this.interactiveClips ? this._clipPointer : null;
|
|
3020
3054
|
}
|
|
@@ -3115,12 +3149,17 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3115
3149
|
this._clipOffsets.clear();
|
|
3116
3150
|
this._peakPipeline.terminate();
|
|
3117
3151
|
this._minSamplesPerPixel = 0;
|
|
3118
|
-
this._contextConfigurePromise = null;
|
|
3119
3152
|
try {
|
|
3120
3153
|
this._disposeEngine();
|
|
3121
3154
|
} catch (err) {
|
|
3122
3155
|
console.warn("[dawcore] Error disposing engine: " + String(err));
|
|
3123
3156
|
}
|
|
3157
|
+
if (this._ownedAudioContext) {
|
|
3158
|
+
this._ownedAudioContext.close().catch((err) => {
|
|
3159
|
+
console.warn("[dawcore] Error closing AudioContext: " + String(err));
|
|
3160
|
+
});
|
|
3161
|
+
this._ownedAudioContext = null;
|
|
3162
|
+
}
|
|
3124
3163
|
}
|
|
3125
3164
|
willUpdate(changedProperties) {
|
|
3126
3165
|
if (changedProperties.has("eagerResume")) {
|
|
@@ -3225,9 +3264,7 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3225
3264
|
if (waveformDataPromise) {
|
|
3226
3265
|
try {
|
|
3227
3266
|
const wd = await waveformDataPromise;
|
|
3228
|
-
|
|
3229
|
-
const { getGlobalAudioContext } = await import("@waveform-playlist/playout");
|
|
3230
|
-
const contextRate = getGlobalAudioContext().sampleRate;
|
|
3267
|
+
const contextRate = this.audioContext.sampleRate;
|
|
3231
3268
|
if (wd.sample_rate === contextRate) {
|
|
3232
3269
|
waveformData = wd;
|
|
3233
3270
|
} else {
|
|
@@ -3359,30 +3396,6 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3359
3396
|
);
|
|
3360
3397
|
}
|
|
3361
3398
|
}
|
|
3362
|
-
/**
|
|
3363
|
-
* Ensure the global AudioContext is configured with the editor's sample-rate hint
|
|
3364
|
-
* before the first audio operation. Idempotent — concurrent callers await the
|
|
3365
|
-
* same promise so no one proceeds to getGlobalAudioContext() before configuration.
|
|
3366
|
-
*/
|
|
3367
|
-
_ensureContextConfigured() {
|
|
3368
|
-
if (!this._contextConfigurePromise) {
|
|
3369
|
-
this._contextConfigurePromise = (async () => {
|
|
3370
|
-
const { configureGlobalContext } = await import("@waveform-playlist/playout");
|
|
3371
|
-
const actualRate = configureGlobalContext({
|
|
3372
|
-
sampleRate: this.sampleRate
|
|
3373
|
-
});
|
|
3374
|
-
if (actualRate !== this.sampleRate) {
|
|
3375
|
-
console.warn(
|
|
3376
|
-
"[dawcore] Requested sampleRate " + this.sampleRate + " but AudioContext is running at " + actualRate
|
|
3377
|
-
);
|
|
3378
|
-
}
|
|
3379
|
-
})().catch((err) => {
|
|
3380
|
-
this._contextConfigurePromise = null;
|
|
3381
|
-
throw err;
|
|
3382
|
-
});
|
|
3383
|
-
}
|
|
3384
|
-
return this._contextConfigurePromise;
|
|
3385
|
-
}
|
|
3386
3399
|
async _fetchAndDecode(src) {
|
|
3387
3400
|
if (this._audioCache.has(src)) {
|
|
3388
3401
|
return this._audioCache.get(src);
|
|
@@ -3395,9 +3408,7 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3395
3408
|
);
|
|
3396
3409
|
}
|
|
3397
3410
|
const arrayBuffer = await response.arrayBuffer();
|
|
3398
|
-
|
|
3399
|
-
const { getGlobalAudioContext } = await import("@waveform-playlist/playout");
|
|
3400
|
-
return getGlobalAudioContext().decodeAudioData(arrayBuffer);
|
|
3411
|
+
return this.audioContext.decodeAudioData(arrayBuffer);
|
|
3401
3412
|
})();
|
|
3402
3413
|
this._audioCache.set(src, promise);
|
|
3403
3414
|
try {
|
|
@@ -3438,11 +3449,11 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3438
3449
|
return this._enginePromise;
|
|
3439
3450
|
}
|
|
3440
3451
|
async _buildEngine() {
|
|
3441
|
-
const [{ PlaylistEngine }, {
|
|
3452
|
+
const [{ PlaylistEngine }, { NativePlayoutAdapter }] = await Promise.all([
|
|
3442
3453
|
import("@waveform-playlist/engine"),
|
|
3443
|
-
import("@
|
|
3454
|
+
import("@dawcore/transport")
|
|
3444
3455
|
]);
|
|
3445
|
-
const adapter =
|
|
3456
|
+
const adapter = new NativePlayoutAdapter(this.audioContext);
|
|
3446
3457
|
const engine = new PlaylistEngine({
|
|
3447
3458
|
adapter,
|
|
3448
3459
|
sampleRate: this.effectiveSampleRate,
|
|
@@ -3640,8 +3651,12 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3640
3651
|
const playhead = this._getPlayhead();
|
|
3641
3652
|
if (!playhead || !this._engine) return;
|
|
3642
3653
|
const engine = this._engine;
|
|
3654
|
+
const ctx = this.audioContext;
|
|
3643
3655
|
playhead.startAnimation(
|
|
3644
|
-
() =>
|
|
3656
|
+
() => {
|
|
3657
|
+
const latency = "outputLatency" in ctx ? ctx.outputLatency : 0;
|
|
3658
|
+
return Math.max(0, engine.getCurrentTime() - latency);
|
|
3659
|
+
},
|
|
3645
3660
|
this.effectiveSampleRate,
|
|
3646
3661
|
this.samplesPerPixel
|
|
3647
3662
|
);
|