@dawcore/components 0.0.17 → 0.0.19
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.d.mts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +132 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +132 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -5
package/dist/index.d.mts
CHANGED
|
@@ -3,11 +3,18 @@ import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost
|
|
|
3
3
|
import { MidiNoteData, SpectrogramConfig, FadeType, Peaks, Bits, PeakData, MeterEntry, SnapTo, ColorMapValue, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
|
|
4
4
|
import WaveformData from 'waveform-data';
|
|
5
5
|
import { PlayoutAdapter, PlaylistEngine } from '@waveform-playlist/engine';
|
|
6
|
+
import { MidiLoadOptions, MidiLoadResult } from '@dawcore/midi';
|
|
6
7
|
import { ClipRegistration, CanvasRegistration, ViewportState } from '@dawcore/spectrogram';
|
|
7
8
|
|
|
8
9
|
declare class DawClipElement extends LitElement {
|
|
9
10
|
src: string;
|
|
10
11
|
peaksSrc: string;
|
|
12
|
+
/**
|
|
13
|
+
* Timeline position in seconds. JS property only — NOT reflected to the
|
|
14
|
+
* `start` attribute (Lit's `reflect` defaults to false). Tests that need
|
|
15
|
+
* to assert clip position must read this property directly; reading
|
|
16
|
+
* `el.getAttribute('start')` returns `null` regardless of correctness.
|
|
17
|
+
*/
|
|
11
18
|
start: number;
|
|
12
19
|
duration: number;
|
|
13
20
|
offset: number;
|
|
@@ -824,7 +831,29 @@ interface LoadFilesResult {
|
|
|
824
831
|
}>;
|
|
825
832
|
}
|
|
826
833
|
|
|
827
|
-
|
|
834
|
+
/**
|
|
835
|
+
* MIDI loading logic extracted from daw-editor. Operates on the editor via a
|
|
836
|
+
* narrow host interface (`addTrack` + `querySelectorAll`) — `<daw-editor>`
|
|
837
|
+
* satisfies it without any new public surface.
|
|
838
|
+
*
|
|
839
|
+
* Numbered steps below match the Data Flow diagram in
|
|
840
|
+
* `docs/specs/2026-05-23-dawcore-load-midi-design.md`.
|
|
841
|
+
*/
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Minimal host surface needed by `loadMidiImpl`. `<daw-editor>` satisfies this
|
|
845
|
+
* structurally. `querySelectorAll` is needed for cleanup-on-failure so the
|
|
846
|
+
* loader can identify `<daw-track>` elements appended during this call (both
|
|
847
|
+
* those whose `addTrack` resolved and those that rejected after `_loadTrack`
|
|
848
|
+
* fired `daw-track-error` — the latter aren't in the `addTrack` resolution
|
|
849
|
+
* value, so we need DOM observation to find them).
|
|
850
|
+
*/
|
|
851
|
+
interface MidiLoaderHost {
|
|
852
|
+
addTrack(config: TrackConfig): Promise<DawTrackElement>;
|
|
853
|
+
querySelectorAll(selector: string): NodeListOf<Element>;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
declare class DawEditorElement extends LitElement implements MidiLoaderHost {
|
|
828
857
|
get samplesPerPixel(): number;
|
|
829
858
|
set samplesPerPixel(value: number);
|
|
830
859
|
private _samplesPerPixel;
|
|
@@ -979,6 +1008,15 @@ declare class DawEditorElement extends LitElement {
|
|
|
979
1008
|
connectedCallback(): void;
|
|
980
1009
|
disconnectedCallback(): void;
|
|
981
1010
|
willUpdate(changedProperties: Map<string, unknown>): void;
|
|
1011
|
+
/**
|
|
1012
|
+
* Cache of the last ViewportState forwarded to the spectrogram controller.
|
|
1013
|
+
* Lit's `updated()` fires on every reactive state change (`_isPlaying`,
|
|
1014
|
+
* `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
|
|
1015
|
+
* viewport. Skip the cross-controller call when nothing changed.
|
|
1016
|
+
*
|
|
1017
|
+
* The orchestrator dedupes too, but this avoids the call entirely.
|
|
1018
|
+
*/
|
|
1019
|
+
private _lastSpectrogramViewport;
|
|
982
1020
|
protected updated(_changed: Map<string, unknown>): void;
|
|
983
1021
|
private _onTrackConnected;
|
|
984
1022
|
private _onTrackRemoved;
|
|
@@ -1057,6 +1095,20 @@ declare class DawEditorElement extends LitElement {
|
|
|
1057
1095
|
private _onDragLeave;
|
|
1058
1096
|
private _onDrop;
|
|
1059
1097
|
loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
|
|
1098
|
+
/**
|
|
1099
|
+
* Imperatively load a `.mid` file (URL or File) and create N `<daw-track>`
|
|
1100
|
+
* elements — one per note-bearing MIDI track. On any per-track failure,
|
|
1101
|
+
* every `<daw-track>` appended during the call is removed (both successful
|
|
1102
|
+
* and failed) so the editor returns to its pre-call state.
|
|
1103
|
+
*
|
|
1104
|
+
* `options.signal` is forwarded to `fetch()` only for URL sources; aborting
|
|
1105
|
+
* after parsing does not cancel in-flight `addTrack` calls.
|
|
1106
|
+
*
|
|
1107
|
+
* Requires the optional `@dawcore/midi` peer dep — throws with an install
|
|
1108
|
+
* hint (and `console.warn`s the original error) when the dynamic import
|
|
1109
|
+
* fails for any reason.
|
|
1110
|
+
*/
|
|
1111
|
+
loadMidi(source: string | File, options?: MidiLoadOptions): Promise<MidiLoadResult>;
|
|
1060
1112
|
/**
|
|
1061
1113
|
* Build the engine if it hasn't been built yet. Lets consumers obtain a
|
|
1062
1114
|
* non-null `editor.engine` before any track has been loaded — useful for
|
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,18 @@ import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost
|
|
|
3
3
|
import { MidiNoteData, SpectrogramConfig, FadeType, Peaks, Bits, PeakData, MeterEntry, SnapTo, ColorMapValue, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
|
|
4
4
|
import WaveformData from 'waveform-data';
|
|
5
5
|
import { PlayoutAdapter, PlaylistEngine } from '@waveform-playlist/engine';
|
|
6
|
+
import { MidiLoadOptions, MidiLoadResult } from '@dawcore/midi';
|
|
6
7
|
import { ClipRegistration, CanvasRegistration, ViewportState } from '@dawcore/spectrogram';
|
|
7
8
|
|
|
8
9
|
declare class DawClipElement extends LitElement {
|
|
9
10
|
src: string;
|
|
10
11
|
peaksSrc: string;
|
|
12
|
+
/**
|
|
13
|
+
* Timeline position in seconds. JS property only — NOT reflected to the
|
|
14
|
+
* `start` attribute (Lit's `reflect` defaults to false). Tests that need
|
|
15
|
+
* to assert clip position must read this property directly; reading
|
|
16
|
+
* `el.getAttribute('start')` returns `null` regardless of correctness.
|
|
17
|
+
*/
|
|
11
18
|
start: number;
|
|
12
19
|
duration: number;
|
|
13
20
|
offset: number;
|
|
@@ -824,7 +831,29 @@ interface LoadFilesResult {
|
|
|
824
831
|
}>;
|
|
825
832
|
}
|
|
826
833
|
|
|
827
|
-
|
|
834
|
+
/**
|
|
835
|
+
* MIDI loading logic extracted from daw-editor. Operates on the editor via a
|
|
836
|
+
* narrow host interface (`addTrack` + `querySelectorAll`) — `<daw-editor>`
|
|
837
|
+
* satisfies it without any new public surface.
|
|
838
|
+
*
|
|
839
|
+
* Numbered steps below match the Data Flow diagram in
|
|
840
|
+
* `docs/specs/2026-05-23-dawcore-load-midi-design.md`.
|
|
841
|
+
*/
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Minimal host surface needed by `loadMidiImpl`. `<daw-editor>` satisfies this
|
|
845
|
+
* structurally. `querySelectorAll` is needed for cleanup-on-failure so the
|
|
846
|
+
* loader can identify `<daw-track>` elements appended during this call (both
|
|
847
|
+
* those whose `addTrack` resolved and those that rejected after `_loadTrack`
|
|
848
|
+
* fired `daw-track-error` — the latter aren't in the `addTrack` resolution
|
|
849
|
+
* value, so we need DOM observation to find them).
|
|
850
|
+
*/
|
|
851
|
+
interface MidiLoaderHost {
|
|
852
|
+
addTrack(config: TrackConfig): Promise<DawTrackElement>;
|
|
853
|
+
querySelectorAll(selector: string): NodeListOf<Element>;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
declare class DawEditorElement extends LitElement implements MidiLoaderHost {
|
|
828
857
|
get samplesPerPixel(): number;
|
|
829
858
|
set samplesPerPixel(value: number);
|
|
830
859
|
private _samplesPerPixel;
|
|
@@ -979,6 +1008,15 @@ declare class DawEditorElement extends LitElement {
|
|
|
979
1008
|
connectedCallback(): void;
|
|
980
1009
|
disconnectedCallback(): void;
|
|
981
1010
|
willUpdate(changedProperties: Map<string, unknown>): void;
|
|
1011
|
+
/**
|
|
1012
|
+
* Cache of the last ViewportState forwarded to the spectrogram controller.
|
|
1013
|
+
* Lit's `updated()` fires on every reactive state change (`_isPlaying`,
|
|
1014
|
+
* `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
|
|
1015
|
+
* viewport. Skip the cross-controller call when nothing changed.
|
|
1016
|
+
*
|
|
1017
|
+
* The orchestrator dedupes too, but this avoids the call entirely.
|
|
1018
|
+
*/
|
|
1019
|
+
private _lastSpectrogramViewport;
|
|
982
1020
|
protected updated(_changed: Map<string, unknown>): void;
|
|
983
1021
|
private _onTrackConnected;
|
|
984
1022
|
private _onTrackRemoved;
|
|
@@ -1057,6 +1095,20 @@ declare class DawEditorElement extends LitElement {
|
|
|
1057
1095
|
private _onDragLeave;
|
|
1058
1096
|
private _onDrop;
|
|
1059
1097
|
loadFiles(files: FileList | File[]): Promise<LoadFilesResult>;
|
|
1098
|
+
/**
|
|
1099
|
+
* Imperatively load a `.mid` file (URL or File) and create N `<daw-track>`
|
|
1100
|
+
* elements — one per note-bearing MIDI track. On any per-track failure,
|
|
1101
|
+
* every `<daw-track>` appended during the call is removed (both successful
|
|
1102
|
+
* and failed) so the editor returns to its pre-call state.
|
|
1103
|
+
*
|
|
1104
|
+
* `options.signal` is forwarded to `fetch()` only for URL sources; aborting
|
|
1105
|
+
* after parsing does not cancel in-flight `addTrack` calls.
|
|
1106
|
+
*
|
|
1107
|
+
* Requires the optional `@dawcore/midi` peer dep — throws with an install
|
|
1108
|
+
* hint (and `console.warn`s the original error) when the dynamic import
|
|
1109
|
+
* fails for any reason.
|
|
1110
|
+
*/
|
|
1111
|
+
loadMidi(source: string | File, options?: MidiLoadOptions): Promise<MidiLoadResult>;
|
|
1060
1112
|
/**
|
|
1061
1113
|
* Build the engine if it hasn't been built yet. Lets consumers obtain a
|
|
1062
1114
|
* non-null `editor.engine` before any track has been loaded — useful for
|
package/dist/index.js
CHANGED
|
@@ -3495,6 +3495,108 @@ async function loadFiles(host, files) {
|
|
|
3495
3495
|
return { loaded, failed };
|
|
3496
3496
|
}
|
|
3497
3497
|
|
|
3498
|
+
// src/interactions/midi-loader.ts
|
|
3499
|
+
var INSTALL_HINT = "@dawcore/midi is required for loadMidi(). Install with: npm install @dawcore/midi";
|
|
3500
|
+
async function loadMidiImpl(host, source, options = {}) {
|
|
3501
|
+
const startTime = options.startTime ?? 0;
|
|
3502
|
+
if (!Number.isFinite(startTime) || startTime < 0) {
|
|
3503
|
+
throw new RangeError(
|
|
3504
|
+
"loadMidi: startTime must be a non-negative finite number (got " + String(options.startTime) + ")"
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
let midiModule;
|
|
3508
|
+
try {
|
|
3509
|
+
midiModule = await import("@dawcore/midi");
|
|
3510
|
+
} catch (originalErr) {
|
|
3511
|
+
console.warn("[dawcore] @dawcore/midi dynamic import failed: " + String(originalErr));
|
|
3512
|
+
throw new Error(INSTALL_HINT);
|
|
3513
|
+
}
|
|
3514
|
+
const { parseMidiUrl, parseMidiFile } = midiModule;
|
|
3515
|
+
let parsed;
|
|
3516
|
+
if (typeof source === "string") {
|
|
3517
|
+
parsed = await parseMidiUrl(source, void 0, options.signal);
|
|
3518
|
+
} else {
|
|
3519
|
+
let buffer;
|
|
3520
|
+
try {
|
|
3521
|
+
buffer = await source.arrayBuffer();
|
|
3522
|
+
} catch (err) {
|
|
3523
|
+
throw new Error(
|
|
3524
|
+
'loadMidi: failed to read File "' + source.name + '" (' + source.size + " bytes): " + String(err)
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
parsed = parseMidiFile(buffer);
|
|
3528
|
+
}
|
|
3529
|
+
const childrenBefore = new Set(host.querySelectorAll("daw-track"));
|
|
3530
|
+
const settlements = await Promise.allSettled(
|
|
3531
|
+
parsed.tracks.map(
|
|
3532
|
+
(t) => host.addTrack({
|
|
3533
|
+
name: t.name,
|
|
3534
|
+
renderMode: "piano-roll",
|
|
3535
|
+
clips: [
|
|
3536
|
+
{
|
|
3537
|
+
midiNotes: t.notes,
|
|
3538
|
+
midiChannel: t.channel,
|
|
3539
|
+
midiProgram: t.programNumber,
|
|
3540
|
+
start: startTime
|
|
3541
|
+
}
|
|
3542
|
+
]
|
|
3543
|
+
})
|
|
3544
|
+
)
|
|
3545
|
+
);
|
|
3546
|
+
const succeeded = [];
|
|
3547
|
+
const rejections = [];
|
|
3548
|
+
for (const s of settlements) {
|
|
3549
|
+
if (s.status === "fulfilled") {
|
|
3550
|
+
succeeded.push(s.value);
|
|
3551
|
+
} else {
|
|
3552
|
+
rejections.push(s.reason);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
if (rejections.length > 0) {
|
|
3556
|
+
const appendedTracks = Array.from(host.querySelectorAll("daw-track")).filter(
|
|
3557
|
+
(el) => !childrenBefore.has(el)
|
|
3558
|
+
);
|
|
3559
|
+
for (const el of appendedTracks) {
|
|
3560
|
+
try {
|
|
3561
|
+
el.remove();
|
|
3562
|
+
} catch (cleanupErr) {
|
|
3563
|
+
console.warn("[dawcore] loadMidi cleanup failed for a track: " + String(cleanupErr));
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
await Promise.resolve();
|
|
3567
|
+
for (let i = 1; i < rejections.length; i++) {
|
|
3568
|
+
console.warn(
|
|
3569
|
+
"[dawcore] loadMidi: additional track failure (" + i + "): " + stringifyReason(rejections[i])
|
|
3570
|
+
);
|
|
3571
|
+
}
|
|
3572
|
+
const first = rejections[0];
|
|
3573
|
+
if (rejections.length > 1) {
|
|
3574
|
+
const message = "loadMidi: " + rejections.length + " of " + settlements.length + " tracks failed; first: " + (first instanceof Error ? first.message : stringifyReason(first));
|
|
3575
|
+
throw new Error(message);
|
|
3576
|
+
}
|
|
3577
|
+
throw first instanceof Error ? first : new Error(stringifyReason(first));
|
|
3578
|
+
}
|
|
3579
|
+
return {
|
|
3580
|
+
trackIds: succeeded.map((el) => el.trackId),
|
|
3581
|
+
bpm: parsed.bpm,
|
|
3582
|
+
timeSignature: parsed.timeSignature,
|
|
3583
|
+
duration: parsed.duration,
|
|
3584
|
+
name: parsed.name
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
function stringifyReason(reason) {
|
|
3588
|
+
if (reason === null) return "null";
|
|
3589
|
+
if (reason === void 0) return "undefined";
|
|
3590
|
+
if (typeof reason === "object") {
|
|
3591
|
+
try {
|
|
3592
|
+
return JSON.stringify(reason);
|
|
3593
|
+
} catch {
|
|
3594
|
+
return Object.prototype.toString.call(reason);
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
return String(reason);
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3498
3600
|
// src/interactions/recording-clip.ts
|
|
3499
3601
|
var import_core7 = require("@waveform-playlist/core");
|
|
3500
3602
|
function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamples = 0) {
|
|
@@ -3828,6 +3930,15 @@ var DawEditorElement = class extends import_lit14.LitElement {
|
|
|
3828
3930
|
v.scrollSelector = ".scroll-area";
|
|
3829
3931
|
return v;
|
|
3830
3932
|
})();
|
|
3933
|
+
/**
|
|
3934
|
+
* Cache of the last ViewportState forwarded to the spectrogram controller.
|
|
3935
|
+
* Lit's `updated()` fires on every reactive state change (`_isPlaying`,
|
|
3936
|
+
* `_selectedTrackId`, etc.) — most of which don't affect the spectrogram
|
|
3937
|
+
* viewport. Skip the cross-controller call when nothing changed.
|
|
3938
|
+
*
|
|
3939
|
+
* The orchestrator dedupes too, but this avoids the call entirely.
|
|
3940
|
+
*/
|
|
3941
|
+
this._lastSpectrogramViewport = null;
|
|
3831
3942
|
// --- Track Events ---
|
|
3832
3943
|
this._onTrackConnected = (e) => {
|
|
3833
3944
|
const trackId = e.detail?.trackId;
|
|
@@ -4364,7 +4475,11 @@ var DawEditorElement = class extends import_lit14.LitElement {
|
|
|
4364
4475
|
if (this._spectrogramController) {
|
|
4365
4476
|
const vs = this._viewport.visibleStart;
|
|
4366
4477
|
const ve = this._viewport.visibleEnd;
|
|
4478
|
+
const spp = this._renderSpp;
|
|
4367
4479
|
if (Number.isFinite(vs) && Number.isFinite(ve)) {
|
|
4480
|
+
const prev = this._lastSpectrogramViewport;
|
|
4481
|
+
if (prev && prev.vs === vs && prev.ve === ve && prev.spp === spp) return;
|
|
4482
|
+
this._lastSpectrogramViewport = { vs, ve, spp };
|
|
4368
4483
|
const span = ve - vs;
|
|
4369
4484
|
const bufferPad = span * 0.25;
|
|
4370
4485
|
this._spectrogramController.setViewport({
|
|
@@ -4372,7 +4487,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
|
|
|
4372
4487
|
visibleEndPx: ve,
|
|
4373
4488
|
bufferStartPx: Math.max(0, vs - bufferPad),
|
|
4374
4489
|
bufferEndPx: ve + bufferPad,
|
|
4375
|
-
samplesPerPixel:
|
|
4490
|
+
samplesPerPixel: spp
|
|
4376
4491
|
});
|
|
4377
4492
|
}
|
|
4378
4493
|
}
|
|
@@ -5054,6 +5169,22 @@ var DawEditorElement = class extends import_lit14.LitElement {
|
|
|
5054
5169
|
async loadFiles(files) {
|
|
5055
5170
|
return loadFiles(this, files);
|
|
5056
5171
|
}
|
|
5172
|
+
/**
|
|
5173
|
+
* Imperatively load a `.mid` file (URL or File) and create N `<daw-track>`
|
|
5174
|
+
* elements — one per note-bearing MIDI track. On any per-track failure,
|
|
5175
|
+
* every `<daw-track>` appended during the call is removed (both successful
|
|
5176
|
+
* and failed) so the editor returns to its pre-call state.
|
|
5177
|
+
*
|
|
5178
|
+
* `options.signal` is forwarded to `fetch()` only for URL sources; aborting
|
|
5179
|
+
* after parsing does not cancel in-flight `addTrack` calls.
|
|
5180
|
+
*
|
|
5181
|
+
* Requires the optional `@dawcore/midi` peer dep — throws with an install
|
|
5182
|
+
* hint (and `console.warn`s the original error) when the dynamic import
|
|
5183
|
+
* fails for any reason.
|
|
5184
|
+
*/
|
|
5185
|
+
async loadMidi(source, options) {
|
|
5186
|
+
return loadMidiImpl(this, source, options);
|
|
5187
|
+
}
|
|
5057
5188
|
// --- Programmatic Track API ---
|
|
5058
5189
|
/**
|
|
5059
5190
|
* Build the engine if it hasn't been built yet. Lets consumers obtain a
|