@kano/stem-daw 0.1.0
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 +253 -0
- package/dist/chat-actions-54Z6URC4.js +7 -0
- package/dist/chat-actions-54Z6URC4.js.map +1 -0
- package/dist/chunk-56PWIP7O.js +1029 -0
- package/dist/chunk-56PWIP7O.js.map +1 -0
- package/dist/chunk-AAVC7KUW.js +145 -0
- package/dist/chunk-AAVC7KUW.js.map +1 -0
- package/dist/chunk-KCOOE2OP.js +1764 -0
- package/dist/chunk-KCOOE2OP.js.map +1 -0
- package/dist/chunk-LO74ZJ4H.js +23923 -0
- package/dist/chunk-LO74ZJ4H.js.map +1 -0
- package/dist/chunk-OFGZURP6.js +247 -0
- package/dist/chunk-OFGZURP6.js.map +1 -0
- package/dist/chunk-OYNES5W3.js +3085 -0
- package/dist/chunk-OYNES5W3.js.map +1 -0
- package/dist/chunk-QQ5NZTHT.js +336 -0
- package/dist/chunk-QQ5NZTHT.js.map +1 -0
- package/dist/chunk-TBXCZFAY.js +13713 -0
- package/dist/chunk-TBXCZFAY.js.map +1 -0
- package/dist/chunk-U44X6QP5.js +281 -0
- package/dist/chunk-U44X6QP5.js.map +1 -0
- package/dist/chunk-UKMELGZL.js +27 -0
- package/dist/chunk-UKMELGZL.js.map +1 -0
- package/dist/components/DAWView.d.ts +19 -0
- package/dist/components/DAWView.js +11 -0
- package/dist/components/DAWView.js.map +1 -0
- package/dist/daw-controller-BjRWcTol.d.ts +339 -0
- package/dist/engine/daw-controller.d.ts +3 -0
- package/dist/engine/daw-controller.js +5 -0
- package/dist/engine/daw-controller.js.map +1 -0
- package/dist/engine/daw-import-stem-fm-config.d.ts +224 -0
- package/dist/engine/daw-import-stem-fm-config.js +7 -0
- package/dist/engine/daw-import-stem-fm-config.js.map +1 -0
- package/dist/fetchStationTracks-SKFT4V3U.js +3 -0
- package/dist/fetchStationTracks-SKFT4V3U.js.map +1 -0
- package/dist/index.d.ts +308 -0
- package/dist/index.js +332 -0
- package/dist/index.js.map +1 -0
- package/dist/interface-DaRj7RkY.d.ts +66 -0
- package/dist/interfaces-5ZlG0Y4Y.d.ts +549 -0
- package/dist/media-session-XTP6PP7Q.js +3 -0
- package/dist/media-session-XTP6PP7Q.js.map +1 -0
- package/dist/note-detection-PPLM7R2H.js +148 -0
- package/dist/note-detection-PPLM7R2H.js.map +1 -0
- package/dist/sampler-audio-B7MBG3YN.js +3 -0
- package/dist/sampler-audio-B7MBG3YN.js.map +1 -0
- package/dist/sampler-store-QPHANXYP.js +3 -0
- package/dist/sampler-store-QPHANXYP.js.map +1 -0
- package/dist/services/track-search-api.d.ts +152 -0
- package/dist/services/track-search-api.js +4 -0
- package/dist/services/track-search-api.js.map +1 -0
- package/dist/store/daw-auth-store.d.ts +31 -0
- package/dist/store/daw-auth-store.js +3 -0
- package/dist/store/daw-auth-store.js.map +1 -0
- package/dist/store/daw-session-store.d.ts +255 -0
- package/dist/store/daw-session-store.js +3 -0
- package/dist/store/daw-session-store.js.map +1 -0
- package/dist/vite/index.d.ts +46 -0
- package/dist/vite/index.js +94 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/workers/analysis-worker.js +379 -0
- package/dist/workers/buffer-player-processor-202602.lavv8e32-ts.js +1 -0
- package/dist/workers/daw-stem-processor.js +228 -0
- package/dist/workers/manifest.json +10 -0
- package/dist/workers/phase-vocoder3.js +920 -0
- package/dist/workers/realtime-pitch-shift-processor.js +2 -0
- package/package.json +151 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { pauseInMusicControl, resumeInMusicControl, setupMediaSession, setupMediaSessionModeAware, updateMediaSessionForModeChange, updateMediaSessionPrevNextTrackRef } from './chunk-TBXCZFAY.js';
|
|
2
|
+
//# sourceMappingURL=media-session-XTP6PP7Q.js.map
|
|
3
|
+
//# sourceMappingURL=media-session-XTP6PP7Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"media-session-XTP6PP7Q.js"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// src/daw/utils/note-detection.ts
|
|
2
|
+
var BASIC_PITCH_SR = 22050;
|
|
3
|
+
var BP_FFT_HOP = 256;
|
|
4
|
+
var BP_ANNOTATIONS_FPS = Math.floor(BASIC_PITCH_SR / BP_FFT_HOP);
|
|
5
|
+
var BP_CONTOURS_BINS_PER_SEMITONE = 3;
|
|
6
|
+
var BP_MIDI_OFFSET = 21;
|
|
7
|
+
var BP_BASE_FREQ = 27.5;
|
|
8
|
+
function resampleBuffer(buffer, targetSR) {
|
|
9
|
+
const offlineCtx = new OfflineAudioContext(1, Math.ceil(buffer.duration * targetSR), targetSR);
|
|
10
|
+
const source = offlineCtx.createBufferSource();
|
|
11
|
+
source.buffer = buffer;
|
|
12
|
+
source.connect(offlineCtx.destination);
|
|
13
|
+
source.start();
|
|
14
|
+
return offlineCtx.startRendering();
|
|
15
|
+
}
|
|
16
|
+
function mergeSamePitchNotes(notes, maxGapSec = 0.05) {
|
|
17
|
+
if (notes.length === 0) return notes;
|
|
18
|
+
const byPitch = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const n of notes) {
|
|
20
|
+
let arr = byPitch.get(n.pitchMidi);
|
|
21
|
+
if (!arr) {
|
|
22
|
+
arr = [];
|
|
23
|
+
byPitch.set(n.pitchMidi, arr);
|
|
24
|
+
}
|
|
25
|
+
arr.push({ ...n });
|
|
26
|
+
}
|
|
27
|
+
const result = [];
|
|
28
|
+
for (const [, group] of byPitch) {
|
|
29
|
+
group.sort((a, b) => a.startTimeSeconds - b.startTimeSeconds);
|
|
30
|
+
let cur = group[0];
|
|
31
|
+
for (let i = 1; i < group.length; i++) {
|
|
32
|
+
const next = group[i];
|
|
33
|
+
const curEnd = cur.startTimeSeconds + cur.durationSeconds;
|
|
34
|
+
if (next.startTimeSeconds <= curEnd + maxGapSec) {
|
|
35
|
+
const nextEnd = next.startTimeSeconds + next.durationSeconds;
|
|
36
|
+
cur.durationSeconds = Math.max(curEnd, nextEnd) - cur.startTimeSeconds;
|
|
37
|
+
cur.amplitude = Math.max(cur.amplitude, next.amplitude);
|
|
38
|
+
} else {
|
|
39
|
+
result.push(cur);
|
|
40
|
+
cur = next;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
result.push(cur);
|
|
44
|
+
}
|
|
45
|
+
result.sort((a, b) => a.startTimeSeconds - b.startTimeSeconds);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
async function runBasicPitchModel(buffer) {
|
|
49
|
+
const { BasicPitch } = await import('@spotify/basic-pitch');
|
|
50
|
+
const inputBuffer = buffer.sampleRate !== BASIC_PITCH_SR ? await resampleBuffer(buffer, BASIC_PITCH_SR) : buffer;
|
|
51
|
+
const modelUrl = "/models/basic-pitch/model.json";
|
|
52
|
+
const basicPitch = new BasicPitch(modelUrl);
|
|
53
|
+
const frames = [];
|
|
54
|
+
const onsets = [];
|
|
55
|
+
const contours = [];
|
|
56
|
+
await basicPitch.evaluateModel(
|
|
57
|
+
inputBuffer,
|
|
58
|
+
(f, o, c) => {
|
|
59
|
+
frames.push(...f);
|
|
60
|
+
onsets.push(...o);
|
|
61
|
+
contours.push(...c);
|
|
62
|
+
},
|
|
63
|
+
() => {
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
return { frames, onsets, contours };
|
|
67
|
+
}
|
|
68
|
+
async function detectNotes(buffer) {
|
|
69
|
+
const {
|
|
70
|
+
addPitchBendsToNoteEvents,
|
|
71
|
+
noteFramesToTime,
|
|
72
|
+
outputToNotesPoly
|
|
73
|
+
} = await import('@spotify/basic-pitch');
|
|
74
|
+
const { frames, onsets, contours } = await runBasicPitchModel(buffer);
|
|
75
|
+
const rawNotes = outputToNotesPoly(frames, onsets);
|
|
76
|
+
const noteEvents = noteFramesToTime(
|
|
77
|
+
addPitchBendsToNoteEvents(contours, rawNotes)
|
|
78
|
+
);
|
|
79
|
+
return mergeSamePitchNotes(noteEvents, 0.05);
|
|
80
|
+
}
|
|
81
|
+
async function detectPitchContourML(buffer, targetHop = 1024, onProgress) {
|
|
82
|
+
onProgress?.(0.05, "Loading Basic Pitch model\u2026");
|
|
83
|
+
const { frames, contours } = await runBasicPitchModel(buffer);
|
|
84
|
+
onProgress?.(0.6, `Model complete \u2014 ${contours.length} contour frames`);
|
|
85
|
+
const ACTIVATION_THRESHOLD = 0.12;
|
|
86
|
+
const bpFrames = [];
|
|
87
|
+
for (let t = 0; t < contours.length; t++) {
|
|
88
|
+
const row = contours[t];
|
|
89
|
+
const frameRow = frames[t];
|
|
90
|
+
let peakBin = -1;
|
|
91
|
+
let peakVal = ACTIVATION_THRESHOLD;
|
|
92
|
+
for (let b = 0; b < row.length; b++) {
|
|
93
|
+
if (row[b] > peakVal) {
|
|
94
|
+
peakVal = row[b];
|
|
95
|
+
peakBin = b;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (peakBin < 0) {
|
|
99
|
+
bpFrames.push({ timeSec: t / BP_ANNOTATIONS_FPS, midi: -1, freq: -1, confidence: 0 });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const semitoneIdx = Math.floor(peakBin / BP_CONTOURS_BINS_PER_SEMITONE);
|
|
103
|
+
const frameActivation = semitoneIdx < frameRow.length ? frameRow[semitoneIdx] : 0;
|
|
104
|
+
if (frameActivation < 0.15) {
|
|
105
|
+
bpFrames.push({ timeSec: t / BP_ANNOTATIONS_FPS, midi: -1, freq: -1, confidence: 0 });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let refinedBin = peakBin;
|
|
109
|
+
if (peakBin > 0 && peakBin < row.length - 1) {
|
|
110
|
+
const s0 = row[peakBin - 1], s1 = row[peakBin], s2 = row[peakBin + 1];
|
|
111
|
+
const denom = 2 * (s0 - 2 * s1 + s2);
|
|
112
|
+
if (Math.abs(denom) > 1e-8) {
|
|
113
|
+
refinedBin = peakBin + (s0 - s2) / denom;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const midi = BP_MIDI_OFFSET + refinedBin / BP_CONTOURS_BINS_PER_SEMITONE;
|
|
117
|
+
const freq = BP_BASE_FREQ * Math.pow(2, refinedBin / (12 * BP_CONTOURS_BINS_PER_SEMITONE));
|
|
118
|
+
bpFrames.push({ timeSec: t / BP_ANNOTATIONS_FPS, midi, freq, confidence: peakVal });
|
|
119
|
+
}
|
|
120
|
+
onProgress?.(0.8, "Resampling to target hop rate\u2026");
|
|
121
|
+
const origSR = buffer.sampleRate;
|
|
122
|
+
const totalSamples = buffer.length;
|
|
123
|
+
const yinWindow = 1024;
|
|
124
|
+
const numTargetFrames = Math.floor((totalSamples - yinWindow) / targetHop);
|
|
125
|
+
const result = [];
|
|
126
|
+
for (let f = 0; f < numTargetFrames; f++) {
|
|
127
|
+
const timeSec = f * targetHop / origSR;
|
|
128
|
+
const bpIdx = Math.round(timeSec * BP_ANNOTATIONS_FPS);
|
|
129
|
+
if (bpIdx >= 0 && bpIdx < bpFrames.length) {
|
|
130
|
+
const bp = bpFrames[bpIdx];
|
|
131
|
+
result.push({
|
|
132
|
+
time: timeSec,
|
|
133
|
+
freq: bp.freq,
|
|
134
|
+
rms: bp.confidence,
|
|
135
|
+
midi: bp.midi,
|
|
136
|
+
frameIndex: f
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
result.push({ time: timeSec, freq: -1, rms: 0, midi: -1, frameIndex: f });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
onProgress?.(1, `${result.length} frames (${result.filter((f) => f.midi > 0).length} voiced)`);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { detectNotes, detectPitchContourML };
|
|
147
|
+
//# sourceMappingURL=note-detection-PPLM7R2H.js.map
|
|
148
|
+
//# sourceMappingURL=note-detection-PPLM7R2H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daw/utils/note-detection.ts"],"names":[],"mappings":"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","file":"note-detection-PPLM7R2H.js","sourcesContent":["/**\n * Note detection utility — wraps @spotify/basic-pitch for polyphonic\n * audio-to-MIDI transcription. Handles resampling to the required 22050 Hz.\n *\n * Two modes:\n * detectNotes() — quantised NoteEvent[] (sampler, piano roll)\n * detectPitchContourML() — continuous sub-semitone PitchFrame[] from the\n * raw 264-bin contour matrix (~86 fps), resampled\n * to match YIN hop rate for the autotune pipeline.\n *\n * Post-processing: consecutive notes of the EXACT same pitch that are\n * back-to-back (overlapping or tiny gap) get merged into one longer note.\n * Everything else is left alone so polyphonic content stays intact.\n */\n\nimport type { NoteEvent } from '../store/sampler-store';\n\nconst BASIC_PITCH_SR = 22050;\nconst BP_FFT_HOP = 256;\nconst BP_ANNOTATIONS_FPS = Math.floor(BASIC_PITCH_SR / BP_FFT_HOP); // ~86\nconst BP_CONTOURS_BINS_PER_SEMITONE = 3;\nconst BP_ANNOTATIONS_N_SEMITONES = 88;\nconst BP_N_FREQ_BINS = BP_ANNOTATIONS_N_SEMITONES * BP_CONTOURS_BINS_PER_SEMITONE; // 264\nconst BP_MIDI_OFFSET = 21; // A0\nconst BP_BASE_FREQ = 27.5;\n\nexport interface PitchFrameML {\n time: number;\n freq: number;\n rms: number;\n midi: number;\n frameIndex: number;\n}\n\nfunction resampleBuffer(buffer: AudioBuffer, targetSR: number): Promise<AudioBuffer> {\n const offlineCtx = new OfflineAudioContext(1, Math.ceil(buffer.duration * targetSR), targetSR);\n const source = offlineCtx.createBufferSource();\n source.buffer = buffer;\n source.connect(offlineCtx.destination);\n source.start();\n return offlineCtx.startRendering();\n}\n\n/**\n * Merge consecutive notes of the EXACT same pitch when they overlap\n * or the gap between them is ≤ maxGapSec. Different pitches are never merged.\n */\nfunction mergeSamePitchNotes(notes: NoteEvent[], maxGapSec: number = 0.05): NoteEvent[] {\n if (notes.length === 0) return notes;\n\n const byPitch = new Map<number, NoteEvent[]>();\n for (const n of notes) {\n let arr = byPitch.get(n.pitchMidi);\n if (!arr) { arr = []; byPitch.set(n.pitchMidi, arr); }\n arr.push({ ...n });\n }\n\n const result: NoteEvent[] = [];\n\n for (const [, group] of byPitch) {\n group.sort((a, b) => a.startTimeSeconds - b.startTimeSeconds);\n\n let cur = group[0];\n for (let i = 1; i < group.length; i++) {\n const next = group[i];\n const curEnd = cur.startTimeSeconds + cur.durationSeconds;\n\n if (next.startTimeSeconds <= curEnd + maxGapSec) {\n const nextEnd = next.startTimeSeconds + next.durationSeconds;\n cur.durationSeconds = Math.max(curEnd, nextEnd) - cur.startTimeSeconds;\n cur.amplitude = Math.max(cur.amplitude, next.amplitude);\n } else {\n result.push(cur);\n cur = next;\n }\n }\n result.push(cur);\n }\n\n result.sort((a, b) => a.startTimeSeconds - b.startTimeSeconds);\n return result;\n}\n\nasync function runBasicPitchModel(buffer: AudioBuffer): Promise<{ frames: number[][]; onsets: number[][]; contours: number[][] }> {\n const { BasicPitch } = await import('@spotify/basic-pitch');\n\n const inputBuffer = buffer.sampleRate !== BASIC_PITCH_SR\n ? await resampleBuffer(buffer, BASIC_PITCH_SR)\n : buffer;\n\n const modelUrl = '/models/basic-pitch/model.json';\n const basicPitch = new BasicPitch(modelUrl);\n\n const frames: number[][] = [];\n const onsets: number[][] = [];\n const contours: number[][] = [];\n\n await basicPitch.evaluateModel(\n inputBuffer,\n (f: number[][], o: number[][], c: number[][]) => {\n frames.push(...f);\n onsets.push(...o);\n contours.push(...c);\n },\n () => {},\n );\n\n return { frames, onsets, contours };\n}\n\nexport async function detectNotes(buffer: AudioBuffer): Promise<NoteEvent[]> {\n const {\n addPitchBendsToNoteEvents,\n noteFramesToTime,\n outputToNotesPoly,\n } = await import('@spotify/basic-pitch');\n\n const { frames, onsets, contours } = await runBasicPitchModel(buffer);\n\n const rawNotes = outputToNotesPoly(frames, onsets);\n\n const noteEvents = noteFramesToTime(\n addPitchBendsToNoteEvents(contours, rawNotes),\n );\n\n return mergeSamePitchNotes(noteEvents, 0.05);\n}\n\n/**\n * Run Basic Pitch and extract a continuous pitch contour from the raw\n * contour activation matrix. Each contour frame has 264 bins (3 per\n * semitone × 88 piano keys). We find the peak bin, refine with\n * parabolic interpolation for sub-bin accuracy, and convert to Hz/MIDI.\n *\n * The result is resampled to match the YIN hop rate (targetHop samples\n * at the original sampleRate) so it plugs directly into the autotune\n * correction pipeline.\n */\nexport async function detectPitchContourML(\n buffer: AudioBuffer,\n targetHop: number = 1024,\n onProgress?: (pct: number, msg: string) => void,\n): Promise<PitchFrameML[]> {\n onProgress?.(0.05, 'Loading Basic Pitch model…');\n const { frames, contours } = await runBasicPitchModel(buffer);\n onProgress?.(0.6, `Model complete — ${contours.length} contour frames`);\n\n const ACTIVATION_THRESHOLD = 0.12;\n\n // Extract dominant pitch per contour frame at ~86fps\n const bpFrames: { timeSec: number; midi: number; freq: number; confidence: number }[] = [];\n for (let t = 0; t < contours.length; t++) {\n const row = contours[t];\n const frameRow = frames[t];\n\n // Find peak bin in contour row\n let peakBin = -1;\n let peakVal = ACTIVATION_THRESHOLD;\n for (let b = 0; b < row.length; b++) {\n if (row[b] > peakVal) { peakVal = row[b]; peakBin = b; }\n }\n\n if (peakBin < 0) {\n bpFrames.push({ timeSec: t / BP_ANNOTATIONS_FPS, midi: -1, freq: -1, confidence: 0 });\n continue;\n }\n\n // Check that the corresponding frames activation is also reasonably high\n const semitoneIdx = Math.floor(peakBin / BP_CONTOURS_BINS_PER_SEMITONE);\n const frameActivation = semitoneIdx < frameRow.length ? frameRow[semitoneIdx] : 0;\n if (frameActivation < 0.15) {\n bpFrames.push({ timeSec: t / BP_ANNOTATIONS_FPS, midi: -1, freq: -1, confidence: 0 });\n continue;\n }\n\n // Parabolic interpolation for sub-bin accuracy\n let refinedBin = peakBin;\n if (peakBin > 0 && peakBin < row.length - 1) {\n const s0 = row[peakBin - 1], s1 = row[peakBin], s2 = row[peakBin + 1];\n const denom = 2 * (s0 - 2 * s1 + s2);\n if (Math.abs(denom) > 1e-8) {\n refinedBin = peakBin + (s0 - s2) / denom;\n }\n }\n\n // Convert bin → MIDI (continuous, sub-semitone)\n const midi = BP_MIDI_OFFSET + refinedBin / BP_CONTOURS_BINS_PER_SEMITONE;\n const freq = BP_BASE_FREQ * Math.pow(2, refinedBin / (12 * BP_CONTOURS_BINS_PER_SEMITONE));\n\n bpFrames.push({ timeSec: t / BP_ANNOTATIONS_FPS, midi, freq, confidence: peakVal });\n }\n\n onProgress?.(0.8, 'Resampling to target hop rate…');\n\n // Resample from ~86fps to the target hop rate at the original sample rate\n const origSR = buffer.sampleRate;\n const totalSamples = buffer.length;\n const yinWindow = 1024;\n const numTargetFrames = Math.floor((totalSamples - yinWindow) / targetHop);\n const result: PitchFrameML[] = [];\n\n for (let f = 0; f < numTargetFrames; f++) {\n const timeSec = (f * targetHop) / origSR;\n // Find the closest Basic Pitch frame\n const bpIdx = Math.round(timeSec * BP_ANNOTATIONS_FPS);\n if (bpIdx >= 0 && bpIdx < bpFrames.length) {\n const bp = bpFrames[bpIdx];\n result.push({\n time: timeSec,\n freq: bp.freq,\n rms: bp.confidence,\n midi: bp.midi,\n frameIndex: f,\n });\n } else {\n result.push({ time: timeSec, freq: -1, rms: 0, midi: -1, frameIndex: f });\n }\n }\n\n onProgress?.(1.0, `${result.length} frames (${result.filter(f => f.midi > 0).length} voiced)`);\n return result;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"sampler-audio-B7MBG3YN.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"sampler-store-QPHANXYP.js"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { a as IMusicKey, b as IMusicTonality, I as ITrackForSequence } from '../interface-DaRj7RkY.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Track search & single-track loading for the DAW.
|
|
5
|
+
*
|
|
6
|
+
* Uses the same GraphQL endpoint as the streaming player but provides
|
|
7
|
+
* a simplified interface for searching and loading individual tracks
|
|
8
|
+
* (not full mixes).
|
|
9
|
+
*
|
|
10
|
+
* Also provides "similar tracks" from the same station (refresh-mix style)
|
|
11
|
+
* for the suggested stems sidebar.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type SimilarityMode = 'feature' | 'remix';
|
|
15
|
+
declare const SIMILARITY_MODES: {
|
|
16
|
+
id: SimilarityMode;
|
|
17
|
+
label: string;
|
|
18
|
+
description: string;
|
|
19
|
+
}[];
|
|
20
|
+
interface SearchResult {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
artist: string;
|
|
24
|
+
artwork: string | null;
|
|
25
|
+
artworkGlowColor: string | null;
|
|
26
|
+
type: string;
|
|
27
|
+
}
|
|
28
|
+
interface TrackMusicalInfo {
|
|
29
|
+
bpm: number;
|
|
30
|
+
key: IMusicKey;
|
|
31
|
+
tonality: IMusicTonality;
|
|
32
|
+
}
|
|
33
|
+
interface CompatibilityScore {
|
|
34
|
+
bpmPct: number;
|
|
35
|
+
keyScore: number;
|
|
36
|
+
overall: number;
|
|
37
|
+
camelotTag: string;
|
|
38
|
+
bpmLabel: string;
|
|
39
|
+
}
|
|
40
|
+
declare function getCamelotTag(key: IMusicKey, tonality: IMusicTonality): string;
|
|
41
|
+
declare function computeCompatibility(ref: TrackMusicalInfo, track: TrackMusicalInfo): CompatibilityScore;
|
|
42
|
+
declare function fetchTrackMusicalInfo(trackId: string): Promise<TrackMusicalInfo | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Read-through accessor for the segment + duration metadata captured
|
|
45
|
+
* alongside bpm/key. Returns `null` if the beats endpoint hasn't yet
|
|
46
|
+
* resolved for this track; callers (e.g. the song-preview controller)
|
|
47
|
+
* should either await `fetchTrackMusicalInfo` first or treat `null`
|
|
48
|
+
* as "fall back to a default preview offset".
|
|
49
|
+
*/
|
|
50
|
+
declare function fetchTrackSegmentInfo(trackId: string): Promise<{
|
|
51
|
+
segmentInfo: [number, number, number][];
|
|
52
|
+
durationSec: number | null;
|
|
53
|
+
} | null>;
|
|
54
|
+
declare function isMixUrl(input: string): boolean;
|
|
55
|
+
declare function isSessionTrackUrl(input: string): boolean;
|
|
56
|
+
declare function isStemUrl(input: string): boolean;
|
|
57
|
+
declare function fetchTracksFromMixUrl(url: string): Promise<SearchResult[]>;
|
|
58
|
+
declare function fetchTracksFromSessionUrl(url: string): Promise<SearchResult[]>;
|
|
59
|
+
declare function searchTracks(query: string, limit?: number, offset?: number): Promise<SearchResult[]>;
|
|
60
|
+
interface TrackPreviewSources {
|
|
61
|
+
durationSec: number | null;
|
|
62
|
+
/** Multi-track fMP4 URL — the same source the DAW loader uses. We
|
|
63
|
+
* byte-range a few segments out of this for the chorus preview. */
|
|
64
|
+
mp4Url: string | null;
|
|
65
|
+
}
|
|
66
|
+
declare function fetchTrackPreviewSources(trackId: string): Promise<TrackPreviewSources | null>;
|
|
67
|
+
declare function fetchTrackForDAW(trackId: string): Promise<ITrackForSequence>;
|
|
68
|
+
/**
|
|
69
|
+
* Fetches sonically similar tracks for a given track ID using the
|
|
70
|
+
* Feature API's /searchfeatures endpoint (PCA-based audio similarity).
|
|
71
|
+
*
|
|
72
|
+
* Tries UUID-based fingerprint lookup first. If the track doesn't have
|
|
73
|
+
* pre-indexed features (common for freshly loaded tracks), falls back
|
|
74
|
+
* to a text query using the track's title/artist from the DAW session.
|
|
75
|
+
*/
|
|
76
|
+
declare function fetchSimilarTracksByFeature(trackId: string, limit?: number): Promise<SearchResult[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Backend candidate-selection strategy. Maps 1:1 to the
|
|
79
|
+
* `SimilarTracksStrategy` enum on the GraphQL server.
|
|
80
|
+
*
|
|
81
|
+
* • `cube_rank` (default, "Loose pool")
|
|
82
|
+
* No hard filter on key/tempo. Ranks by torus cube distance with
|
|
83
|
+
* optional embedding-cosine blend (`embeddingWeight`). Today's
|
|
84
|
+
* behaviour when no `strategy` arg is passed.
|
|
85
|
+
*
|
|
86
|
+
* • `embedding_rank_with_bbox` ("Strict pool")
|
|
87
|
+
* Filters candidates to a ±N-semitone / ±M% bar-size bbox around
|
|
88
|
+
* the seed, then ranks survivors by audio-embedding cosine. The
|
|
89
|
+
* bbox knobs and `cubeWeight` only do anything in this mode.
|
|
90
|
+
*/
|
|
91
|
+
type SimilarTracksStrategy = 'cube_rank' | 'embedding_rank_with_bbox';
|
|
92
|
+
/**
|
|
93
|
+
* Slim subset of `SimilarTracksRankingInput` we actually surface in the
|
|
94
|
+
* UI. The full input has 13 fields; the rest either redistribute the
|
|
95
|
+
* engagement budget internally (the five `*Share` fields) or have
|
|
96
|
+
* strategy-aware server defaults that are hard to expose meaningfully
|
|
97
|
+
* (`cubeWeight`, `embeddingWeight`, the bbox knobs). Anything not in
|
|
98
|
+
* this type stays on the server-side default.
|
|
99
|
+
*
|
|
100
|
+
* EVERY field is optional. The service strips unset fields before
|
|
101
|
+
* serialising so an untouched request remains byte-identical to the
|
|
102
|
+
* pre-tuning behaviour — important because production GQL introspection
|
|
103
|
+
* is disabled and we can't be 100% certain it's on the same schema rev
|
|
104
|
+
* as staging.
|
|
105
|
+
*/
|
|
106
|
+
interface SimilarTracksRanking {
|
|
107
|
+
/**
|
|
108
|
+
* Master gain on the engagement bonus. 0 = pure musical similarity,
|
|
109
|
+
* 1 = server default, 2 = strongly favour popular tracks. Clamped
|
|
110
|
+
* server-side to [0, 2].
|
|
111
|
+
*/
|
|
112
|
+
collaborativeStrength?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Absolute weight on the binary genre-overlap signal. 0 = ignore
|
|
115
|
+
* genre, server default ~0.5 in cube_rank.
|
|
116
|
+
*/
|
|
117
|
+
genreWeight?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Inject the caller's liked tracks into the candidate pool. Server
|
|
120
|
+
* default is `true`.
|
|
121
|
+
*/
|
|
122
|
+
prioritizeUserLikes?: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Aggregate args passed to `fetchSimilarTracksByRemix`. Anything left
|
|
126
|
+
* `undefined` is omitted from the GraphQL variables list so the server
|
|
127
|
+
* picks its own defaults.
|
|
128
|
+
*/
|
|
129
|
+
interface SimilarTracksQueryArgs {
|
|
130
|
+
strategy?: SimilarTracksStrategy;
|
|
131
|
+
ranking?: SimilarTracksRanking;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fetches similar tracks by key/BPM/features using the similarTracks
|
|
135
|
+
* query. No pseudo-mix creation needed — queries directly from a target track.
|
|
136
|
+
*
|
|
137
|
+
* `excludeTrackIds` filters out tracks already in the session.
|
|
138
|
+
*
|
|
139
|
+
* `args` is optional and lets callers pass strategy / ranking overrides
|
|
140
|
+
* sourced from the session store (see `selectSimilarTracksQueryArgs`).
|
|
141
|
+
* When `args` is omitted entirely, the outgoing GraphQL variables are
|
|
142
|
+
* byte-identical to the pre-tuning behaviour.
|
|
143
|
+
*/
|
|
144
|
+
declare function fetchSimilarTracksByRemix(trackId: string, limit?: number, excludeTrackIds?: string[], offset?: number, args?: SimilarTracksQueryArgs): Promise<SearchResult[]>;
|
|
145
|
+
/**
|
|
146
|
+
* Fetches similar tracks from the same station as the given mix URL.
|
|
147
|
+
* Uses the same "refresh mix" logic: next/prev mixes in the station.
|
|
148
|
+
* Returns lightweight previews suitable for the suggested stems sidebar.
|
|
149
|
+
*/
|
|
150
|
+
declare function fetchSimilarTracksFromStation(mixUrl: string, limit?: number): Promise<SearchResult[]>;
|
|
151
|
+
|
|
152
|
+
export { type CompatibilityScore, SIMILARITY_MODES, type SearchResult, type SimilarTracksQueryArgs, type SimilarTracksRanking, type SimilarTracksStrategy, type SimilarityMode, type TrackMusicalInfo, type TrackPreviewSources, computeCompatibility, fetchSimilarTracksByFeature, fetchSimilarTracksByRemix, fetchSimilarTracksFromStation, fetchTrackForDAW, fetchTrackMusicalInfo, fetchTrackPreviewSources, fetchTrackSegmentInfo, fetchTracksFromMixUrl, fetchTracksFromSessionUrl, getCamelotTag, isMixUrl, isSessionTrackUrl, isStemUrl, searchTracks };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { SIMILARITY_MODES, computeCompatibility, fetchSimilarTracksByFeature, fetchSimilarTracksByRemix, fetchSimilarTracksFromStation, fetchTrackForDAW, fetchTrackMusicalInfo, fetchTrackPreviewSources, fetchTrackSegmentInfo, fetchTracksFromMixUrl, fetchTracksFromSessionUrl, getCamelotTag, isMixUrl, isSessionTrackUrl, isStemUrl, searchTracks } from '../chunk-56PWIP7O.js';
|
|
2
|
+
import '../chunk-TBXCZFAY.js';
|
|
3
|
+
//# sourceMappingURL=track-search-api.js.map
|
|
4
|
+
//# sourceMappingURL=track-search-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"track-search-api.js"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as zustand_middleware from 'zustand/middleware';
|
|
2
|
+
import * as zustand from 'zustand';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DAW Auth Store — NDA gate state.
|
|
6
|
+
*
|
|
7
|
+
* Tracks whether the user has accepted the beta NDA.
|
|
8
|
+
* Persisted to localStorage so they only see the gate once per browser.
|
|
9
|
+
*/
|
|
10
|
+
interface DAWAuthState {
|
|
11
|
+
ndaAccepted: boolean;
|
|
12
|
+
acceptNDA: () => void;
|
|
13
|
+
logout: () => void;
|
|
14
|
+
}
|
|
15
|
+
declare const useDAWAuthStore: zustand.UseBoundStore<Omit<zustand.StoreApi<DAWAuthState>, "persist"> & {
|
|
16
|
+
persist: {
|
|
17
|
+
setOptions: (options: Partial<zustand_middleware.PersistOptions<DAWAuthState, {
|
|
18
|
+
ndaAccepted: boolean;
|
|
19
|
+
}>>) => void;
|
|
20
|
+
clearStorage: () => void;
|
|
21
|
+
rehydrate: () => Promise<void> | void;
|
|
22
|
+
hasHydrated: () => boolean;
|
|
23
|
+
onHydrate: (fn: (state: DAWAuthState) => void) => () => void;
|
|
24
|
+
onFinishHydration: (fn: (state: DAWAuthState) => void) => () => void;
|
|
25
|
+
getOptions: () => Partial<zustand_middleware.PersistOptions<DAWAuthState, {
|
|
26
|
+
ndaAccepted: boolean;
|
|
27
|
+
}>>;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export { useDAWAuthStore };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"daw-auth-store.js"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as zustand from 'zustand';
|
|
2
|
+
import { P as PitchEngineSettings, ac as StemEffects, d as DAWSession, b as DAWToolMode, S as StemId, c as StemSelectionRange, i as ClipboardEntry, D as DAWTrack, f as SessionNote, I as IBeat, M as MuteRegion, R as ResampledRegion, k as DAWChannel } from '../interfaces-5ZlG0Y4Y.js';
|
|
3
|
+
import { I as ITrackForSequence } from '../interface-DaRj7RkY.js';
|
|
4
|
+
import { SimilarTracksQueryArgs } from '../services/track-search-api.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DAW Cache Layer
|
|
8
|
+
*
|
|
9
|
+
* Two tiers:
|
|
10
|
+
* 1. localStorage — lightweight session state (track layout, positions, mute regions, etc.)
|
|
11
|
+
* 2. IndexedDB — heavy audio data (raw fMP4 ArrayBuffers) keyed by URL
|
|
12
|
+
*
|
|
13
|
+
* Normal refresh restores from cache. Hard refresh (Ctrl+Shift+R) clears the
|
|
14
|
+
* in-memory singleton and falls through to a fresh load when localStorage is
|
|
15
|
+
* still present. Call `clearAll()` to nuke everything.
|
|
16
|
+
*
|
|
17
|
+
* Project files (.stem-project) wrap PersistedSession with version + timestamps
|
|
18
|
+
* for portable save/load across machines.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
interface PersistedTrack {
|
|
22
|
+
/** The original `trackData` needed to re-add via controller */
|
|
23
|
+
trackDataJson: string;
|
|
24
|
+
/** Channel layout to restore after audio is loaded */
|
|
25
|
+
channels: PersistedChannel[];
|
|
26
|
+
volume: number;
|
|
27
|
+
muted: boolean;
|
|
28
|
+
soloed: boolean;
|
|
29
|
+
expanded: boolean;
|
|
30
|
+
/** Display metadata (persisted so project files are self-describing) */
|
|
31
|
+
color?: string;
|
|
32
|
+
artwork?: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
artistName?: string;
|
|
35
|
+
/** User transpose offset in semitones */
|
|
36
|
+
transposeSemitones?: number;
|
|
37
|
+
/** Pitch engine configuration */
|
|
38
|
+
pitchSettings?: PitchEngineSettings;
|
|
39
|
+
/** Per-bar pitch compensation for quantized speed changes */
|
|
40
|
+
perBarPitchCompensation?: boolean;
|
|
41
|
+
/** Beat grid offset in beats (fractional) */
|
|
42
|
+
beatGridOffsetBeats?: number;
|
|
43
|
+
}
|
|
44
|
+
interface PersistedChannel {
|
|
45
|
+
stemIndex: number;
|
|
46
|
+
volume: number;
|
|
47
|
+
muted: boolean;
|
|
48
|
+
soloed: boolean;
|
|
49
|
+
visible: boolean;
|
|
50
|
+
timelineStartBar: number;
|
|
51
|
+
trimStartBar: number;
|
|
52
|
+
trimEndBar: number;
|
|
53
|
+
muteRegions: {
|
|
54
|
+
startBar: number;
|
|
55
|
+
endBar: number;
|
|
56
|
+
}[];
|
|
57
|
+
resampledRegions?: {
|
|
58
|
+
startBar: number;
|
|
59
|
+
endBar: number;
|
|
60
|
+
sliceCount: number;
|
|
61
|
+
}[];
|
|
62
|
+
effects?: StemEffects;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The portion of state that we snapshot for undo/redo. */
|
|
66
|
+
interface UndoableSnapshot {
|
|
67
|
+
tracks: DAWTrack[];
|
|
68
|
+
masterTempo: number;
|
|
69
|
+
originalTempo: number;
|
|
70
|
+
masterBarLength48000: number;
|
|
71
|
+
loopRegion: {
|
|
72
|
+
startBar: number;
|
|
73
|
+
endBar: number;
|
|
74
|
+
} | null;
|
|
75
|
+
mixName: string;
|
|
76
|
+
notes: SessionNote[];
|
|
77
|
+
}
|
|
78
|
+
interface DAWSessionState extends DAWSession {
|
|
79
|
+
setMixName: (name: string) => void;
|
|
80
|
+
toolMode: DAWToolMode;
|
|
81
|
+
setToolMode: (mode: DAWToolMode) => void;
|
|
82
|
+
/**
|
|
83
|
+
* "Composite View" — purely a render flag. When true, every track's
|
|
84
|
+
* channels collapse to a single shared 4-stem strip on the timeline:
|
|
85
|
+
* all stem-0 lanes share one Y, all stem-1 lanes the next, etc. The
|
|
86
|
+
* canvas naturally layers their waveforms so the user can see the
|
|
87
|
+
* whole arrangement at a glance. Audio routing, mute/solo/transpose
|
|
88
|
+
* state, and the underlying tracks list are completely untouched.
|
|
89
|
+
*
|
|
90
|
+
* Interactive editing (drag-trim, marquee select, slice, drag-move)
|
|
91
|
+
* is disabled while composite view is on — switch it off to edit.
|
|
92
|
+
*/
|
|
93
|
+
compositeView: boolean;
|
|
94
|
+
setCompositeView: (on: boolean) => void;
|
|
95
|
+
toggleCompositeView: () => void;
|
|
96
|
+
/**
|
|
97
|
+
* User-facing tuning for the `similarTracks` GraphQL query, shared
|
|
98
|
+
* by the Add-Track modal and the Similar-Tracks sidebar so changes
|
|
99
|
+
* in one re-fire the fetch in the other.
|
|
100
|
+
*
|
|
101
|
+
* Two independent axes, each shown as a DJ-style tick fader:
|
|
102
|
+
*
|
|
103
|
+
* • `balance` — 0..1, default 0.5 (centre = "use both equally").
|
|
104
|
+
* Drives `genreWeight` as `1 - balance`, so:
|
|
105
|
+
* balance 0 → genreWeight 1 (all musicality, tight genre)
|
|
106
|
+
* balance 0.5 → no override (server default)
|
|
107
|
+
* balance 1 → genreWeight 0 (all vibe, ignore genre)
|
|
108
|
+
*
|
|
109
|
+
* • `popularity` — 0..2, default 1 (centre = server default).
|
|
110
|
+
* Drives `collaborativeStrength` directly:
|
|
111
|
+
* popularity 0 → no engagement boost (deep cuts)
|
|
112
|
+
* popularity 1 → no override
|
|
113
|
+
* popularity 2 → max engagement boost (mainstream)
|
|
114
|
+
*
|
|
115
|
+
* When both knobs are at their defaults the outgoing GraphQL
|
|
116
|
+
* variables are byte-identical to the pre-tuning shape (no
|
|
117
|
+
* `ranking` arg at all).
|
|
118
|
+
*/
|
|
119
|
+
similarTracksBalance: number;
|
|
120
|
+
similarTracksPopularity: number;
|
|
121
|
+
/** Clamped internally to [0, 1]. */
|
|
122
|
+
setSimilarTracksBalance: (v: number) => void;
|
|
123
|
+
/** Clamped internally to [0, 2]. */
|
|
124
|
+
setSimilarTracksPopularity: (v: number) => void;
|
|
125
|
+
/** Restore both knobs to their untouched/default position. */
|
|
126
|
+
resetSimilarTracksTuning: () => void;
|
|
127
|
+
/** Build the GraphQL args object — `undefined` fields are dropped server-side. */
|
|
128
|
+
getSimilarTracksQueryArgs: () => SimilarTracksQueryArgs;
|
|
129
|
+
selectedStem: StemId | null;
|
|
130
|
+
setSelectedStem: (stem: StemId | null) => void;
|
|
131
|
+
/**
|
|
132
|
+
* Multi-stem selections. All entries must share the same `trackId`
|
|
133
|
+
* (selections are scoped to a single track at a time). Adding a range on
|
|
134
|
+
* a different track replaces the entire set.
|
|
135
|
+
*/
|
|
136
|
+
selections: StemSelectionRange[];
|
|
137
|
+
setSelections: (ranges: StemSelectionRange[]) => void;
|
|
138
|
+
addSelection: (range: StemSelectionRange) => void;
|
|
139
|
+
clearSelections: () => void;
|
|
140
|
+
/**
|
|
141
|
+
* Compatibility shim: the "primary" (first) selection. Reading this
|
|
142
|
+
* surfaces the first selection range; writing replaces the entire set
|
|
143
|
+
* with either zero or one range. Prefer `selections` for new code.
|
|
144
|
+
*/
|
|
145
|
+
selectionRange: StemSelectionRange | null;
|
|
146
|
+
setSelectionRange: (range: StemSelectionRange | null) => void;
|
|
147
|
+
/**
|
|
148
|
+
* Multi-stem clipboard. All entries are expected to share a `trackId`
|
|
149
|
+
* (copies a snapshot of one or more stems from a single track).
|
|
150
|
+
*/
|
|
151
|
+
clipboardEntries: ClipboardEntry[];
|
|
152
|
+
setClipboardEntries: (entries: ClipboardEntry[]) => void;
|
|
153
|
+
/** Compatibility shim: the first clipboard entry, or null. */
|
|
154
|
+
clipboard: ClipboardEntry | null;
|
|
155
|
+
setClipboard: (entry: ClipboardEntry | null) => void;
|
|
156
|
+
_undoStack: UndoableSnapshot[];
|
|
157
|
+
_redoStack: UndoableSnapshot[];
|
|
158
|
+
/** Push current state onto the undo stack (call BEFORE making the change). */
|
|
159
|
+
pushUndo: () => void;
|
|
160
|
+
undo: () => void;
|
|
161
|
+
redo: () => void;
|
|
162
|
+
canUndo: () => boolean;
|
|
163
|
+
canRedo: () => boolean;
|
|
164
|
+
addTrack: (trackData: ITrackForSequence) => DAWTrack;
|
|
165
|
+
removeTrack: (trackId: string) => void;
|
|
166
|
+
reorderTrack: (trackId: string, newIndex: number) => void;
|
|
167
|
+
/**
|
|
168
|
+
* Tracks that exist in the persisted session but haven't been
|
|
169
|
+
* decoded into the live timeline yet. Populated by
|
|
170
|
+
* `restoreSession` when `eagerLoadCount` is smaller than the
|
|
171
|
+
* persisted tracks list; surfaced in the timeline so the user
|
|
172
|
+
* can opt-in to load them one-by-one (or all at once) instead of
|
|
173
|
+
* paying the full multi-track decode cost on refresh.
|
|
174
|
+
*
|
|
175
|
+
* These are NOT included in undo snapshots — promoting a
|
|
176
|
+
* pending track to a live track is a side-effecting load, not a
|
|
177
|
+
* structural edit, so the undo stack stays focused on
|
|
178
|
+
* user-driven timeline state.
|
|
179
|
+
*/
|
|
180
|
+
pendingTracks: PersistedTrack[];
|
|
181
|
+
/** Replace the entire pending list (used by restoreSession). */
|
|
182
|
+
setPendingTracks: (tracks: PersistedTrack[]) => void;
|
|
183
|
+
/** Append more pending tracks to the existing queue. */
|
|
184
|
+
addPendingTracks: (tracks: PersistedTrack[]) => void;
|
|
185
|
+
/** Remove a single pending track by index — called after the
|
|
186
|
+
* promote-to-live action has finished loading audio. */
|
|
187
|
+
removePendingTrackAt: (index: number) => void;
|
|
188
|
+
/** Drop all pending tracks (used when the user clears the session). */
|
|
189
|
+
clearPendingTracks: () => void;
|
|
190
|
+
setStemTimelineStart: (trackId: string, stemIndex: number, bar: number) => void;
|
|
191
|
+
setStemTrim: (trackId: string, stemIndex: number, trimStart: number, trimEnd: number) => void;
|
|
192
|
+
/** Move all stems of a track together */
|
|
193
|
+
setTrackTimelineStart: (trackId: string, bar: number) => void;
|
|
194
|
+
/** Trim all stems of a track together */
|
|
195
|
+
setTrackTrim: (trackId: string, trimStart: number, trimEnd: number) => void;
|
|
196
|
+
setPlaying: (playing: boolean) => void;
|
|
197
|
+
setPlayheadBar: (bar: number) => void;
|
|
198
|
+
setMasterTempo: (bpm: number) => void;
|
|
199
|
+
setGlobalPlaybackRate: (rate: number) => void;
|
|
200
|
+
setLoopRegion: (region: {
|
|
201
|
+
startBar: number;
|
|
202
|
+
endBar: number;
|
|
203
|
+
} | null) => void;
|
|
204
|
+
setBarSubdivisions: (n: number) => void;
|
|
205
|
+
/** Add a fully-formed note. Returns its id. Pushes undo. */
|
|
206
|
+
addNote: (note: Omit<SessionNote, 'id' | 'createdAt'>) => string;
|
|
207
|
+
/** Patch a subset of a note's fields. No-op if id is unknown. Pushes undo. */
|
|
208
|
+
updateNote: (id: string, patch: Partial<Omit<SessionNote, 'id' | 'createdAt'>>) => void;
|
|
209
|
+
/** Remove a note by id. No-op if id is unknown. Pushes undo. */
|
|
210
|
+
removeNote: (id: string) => void;
|
|
211
|
+
setTrackVolume: (trackId: string, volume: number) => void;
|
|
212
|
+
setTrackMuted: (trackId: string, muted: boolean) => void;
|
|
213
|
+
setTrackSoloed: (trackId: string, soloed: boolean) => void;
|
|
214
|
+
setTrackExpanded: (trackId: string, expanded: boolean) => void;
|
|
215
|
+
setChannelVolume: (trackId: string, stemIndex: number, volume: number) => void;
|
|
216
|
+
setChannelMuted: (trackId: string, stemIndex: number, muted: boolean) => void;
|
|
217
|
+
setChannelSoloed: (trackId: string, stemIndex: number, soloed: boolean) => void;
|
|
218
|
+
setChannelVisible: (trackId: string, stemIndex: number, visible: boolean) => void;
|
|
219
|
+
setChannelEffects: (trackId: string, stemIndex: number, effects: StemEffects) => void;
|
|
220
|
+
setTrackTranspose: (trackId: string, semitones: number) => void;
|
|
221
|
+
setTrackPitchSettings: (trackId: string, settings: PitchEngineSettings) => void;
|
|
222
|
+
setPerBarPitchCompensation: (trackId: string, enabled: boolean) => void;
|
|
223
|
+
/** Shift a track's beat grid by a fractional beat amount (recomputes barMapping) */
|
|
224
|
+
setTrackBeatGridOffset: (trackId: string, offsetBeats: number) => void;
|
|
225
|
+
/** Replace a track's raw beats array and recompute its bar mapping (used by Beats Lab model switching) */
|
|
226
|
+
replaceTrackBeats: (trackId: string, newBeats: IBeat[]) => void;
|
|
227
|
+
addMuteRegion: (trackId: string, stemIndex: number, region: MuteRegion) => void;
|
|
228
|
+
removeMuteRegion: (trackId: string, stemIndex: number, regionIndex: number) => void;
|
|
229
|
+
clearMuteRegions: (trackId: string, stemIndex: number) => void;
|
|
230
|
+
/** Replace all mute regions at once without pushing undo (for live drag previews). */
|
|
231
|
+
setMuteRegionsBatch: (trackId: string, stemIndex: number, regions: MuteRegion[]) => void;
|
|
232
|
+
/** Split a stem at a bar position — creates two mute-region-free halves by inserting a mute at the cut point */
|
|
233
|
+
splitStemAtBar: (trackId: string, stemIndex: number, bar: number) => void;
|
|
234
|
+
/**
|
|
235
|
+
* Add a single split point at the given local bar (relative to track
|
|
236
|
+
* barMapping). Pushes undo. No-op if the bar is outside the trim window
|
|
237
|
+
* or already a split point (within 0.001-bar tolerance).
|
|
238
|
+
*/
|
|
239
|
+
addSplitPoint: (trackId: string, stemIndex: number, localBar: number) => void;
|
|
240
|
+
/**
|
|
241
|
+
* Replace the entire splitPoints array. Used for batch slicing
|
|
242
|
+
* (every-bar / every-beat / every-section) and for undo restore.
|
|
243
|
+
* Pushes undo when `pushUndo` is true.
|
|
244
|
+
*/
|
|
245
|
+
setSplitPointsBatch: (trackId: string, stemIndex: number, points: number[], pushUndo?: boolean) => void;
|
|
246
|
+
/** Clear all split points on a stem. Pushes undo. */
|
|
247
|
+
clearSplitPoints: (trackId: string, stemIndex: number) => void;
|
|
248
|
+
addResampledRegion: (trackId: string, stemIndex: number, region: ResampledRegion) => void;
|
|
249
|
+
getTimelineLength: () => number;
|
|
250
|
+
getTrackById: (trackId: string) => DAWTrack | undefined;
|
|
251
|
+
getChannel: (trackId: string, stemIndex: number) => DAWChannel | undefined;
|
|
252
|
+
}
|
|
253
|
+
declare const useDAWSessionStore: zustand.UseBoundStore<zustand.StoreApi<DAWSessionState>>;
|
|
254
|
+
|
|
255
|
+
export { useDAWSessionStore };
|