@dawcore/components 0.0.11 → 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/README.md +67 -30
- package/dist/index.d.mts +25 -13
- package/dist/index.d.ts +25 -13
- package/dist/index.js +71 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
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
|
-
|
|
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
|
|
2033
|
+
let addRecordingWorkletModule;
|
|
2021
2034
|
try {
|
|
2022
|
-
({
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
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]
|
|
3388
|
+
"[dawcore] adapter set after engine is built. The engine will continue using the previous adapter."
|
|
3372
3389
|
);
|
|
3373
3390
|
}
|
|
3374
|
-
this.
|
|
3391
|
+
this._externalAdapter = value;
|
|
3392
|
+
}
|
|
3393
|
+
get adapter() {
|
|
3394
|
+
return this._externalAdapter;
|
|
3375
3395
|
}
|
|
3376
3396
|
get audioContext() {
|
|
3377
|
-
if (this.
|
|
3378
|
-
|
|
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.
|
|
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
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
const adapter =
|
|
3851
|
-
|
|
3852
|
-
|
|
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);
|