@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,339 @@
|
|
|
1
|
+
import { D as DAWTrack, ab as StemEffectChain, ac as StemEffects, P as PitchEngineSettings, c as StemSelectionRange } from './interfaces-5ZlG0Y4Y.js';
|
|
2
|
+
import { I as ITrackForSequence } from './interface-DaRj7RkY.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimal browser-compatible EventEmitter replacement.
|
|
6
|
+
* Avoids importing Node.js "events" module which Vite externalises.
|
|
7
|
+
*/
|
|
8
|
+
type Listener = (...args: any[]) => void;
|
|
9
|
+
declare class BrowserEventEmitter {
|
|
10
|
+
private _listeners;
|
|
11
|
+
on(event: string, listener: Listener): this;
|
|
12
|
+
off(event: string, listener: Listener): this;
|
|
13
|
+
once(event: string, listener: Listener): this;
|
|
14
|
+
emit(event: string, ...args: any[]): boolean;
|
|
15
|
+
removeAllListeners(event?: string): this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* DAWTrackLoader – Downloads a full fMP4 file and decodes all 4 stems
|
|
20
|
+
* into separate AudioBuffers for offline/DAW-style manipulation.
|
|
21
|
+
*
|
|
22
|
+
* This intentionally avoids the streaming AudioSourceSequence pipeline.
|
|
23
|
+
* The trade-off is a longer initial load, but after that every stem is
|
|
24
|
+
* instantly accessible at any sample position.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
interface StemAudioBuffers {
|
|
28
|
+
other: AudioBuffer;
|
|
29
|
+
vocals: AudioBuffer;
|
|
30
|
+
bass: AudioBuffer;
|
|
31
|
+
drums: AudioBuffer;
|
|
32
|
+
}
|
|
33
|
+
type LoadProgress = {
|
|
34
|
+
phase: 'fetching' | 'parsing' | 'decoding';
|
|
35
|
+
percent: number;
|
|
36
|
+
detail?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Download an fMP4 track and decode each stem into a standalone AudioBuffer.
|
|
40
|
+
*/
|
|
41
|
+
declare function loadTrackStems(mp4Url: string, onProgress?: (p: LoadProgress) => void): Promise<StemAudioBuffers>;
|
|
42
|
+
|
|
43
|
+
declare class DAWController extends BrowserEventEmitter {
|
|
44
|
+
private ctx;
|
|
45
|
+
private masterGain;
|
|
46
|
+
private tracks;
|
|
47
|
+
/** URL → shared decode entry. See `SourceCacheEntry`. */
|
|
48
|
+
private sourceCache;
|
|
49
|
+
private isInitialised;
|
|
50
|
+
private _pitchWorkletAvailable;
|
|
51
|
+
private _playing;
|
|
52
|
+
private _playStartCtxTime;
|
|
53
|
+
private _playStartOffset;
|
|
54
|
+
private playheadAnimationId;
|
|
55
|
+
private _seekGen;
|
|
56
|
+
/**
|
|
57
|
+
* Max number of tracks allowed to hold their decoded PCM concurrently.
|
|
58
|
+
* Beyond this we evict LRU tracks via `dumpTrackAudio`. See
|
|
59
|
+
* `DEFAULT_MAX_DECODED_TRACKS` for the rationale.
|
|
60
|
+
*/
|
|
61
|
+
private _maxDecodedTracks;
|
|
62
|
+
private _stemActive;
|
|
63
|
+
private _metronomeOn;
|
|
64
|
+
private _metronomeDownbeatOnly;
|
|
65
|
+
private _metronomeGain;
|
|
66
|
+
private _metronomeScheduleId;
|
|
67
|
+
private _metronomeNextBeat;
|
|
68
|
+
private _cpuPollId;
|
|
69
|
+
private _cpuUsage;
|
|
70
|
+
private _nativeModeTrackId;
|
|
71
|
+
private _nativeModeOriginalBarMap;
|
|
72
|
+
/** Native-time offset (samples) at the moment play/seek was last called in native mode */
|
|
73
|
+
private _nativeStartSample;
|
|
74
|
+
private static instance;
|
|
75
|
+
static getInstance(): DAWController;
|
|
76
|
+
private constructor();
|
|
77
|
+
init(): Promise<void>;
|
|
78
|
+
isReady(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Resolve when the named track's audio has fully decoded. If the
|
|
81
|
+
* decode is already complete (common with the source-cache when
|
|
82
|
+
* multiple clips share one source), resolve immediately on the next
|
|
83
|
+
* microtask. Rejects if the track is removed or no such track exists.
|
|
84
|
+
*
|
|
85
|
+
* Use this instead of `controller.on('trackLoaded', …)` from
|
|
86
|
+
* importers / batch loaders — the event fires once and a late
|
|
87
|
+
* subscriber would miss it. This helper closes that race.
|
|
88
|
+
*/
|
|
89
|
+
whenTrackLoaded(trackId: string): Promise<void>;
|
|
90
|
+
getAudioContext(): AudioContext;
|
|
91
|
+
getMasterGainNode(): GainNode;
|
|
92
|
+
addTrack(trackData: ITrackForSequence, onProgress?: (p: LoadProgress) => void): Promise<DAWTrack | null>;
|
|
93
|
+
/**
|
|
94
|
+
* Read the current cap. See `DEFAULT_MAX_DECODED_TRACKS` for context.
|
|
95
|
+
*/
|
|
96
|
+
get maxDecodedTracks(): number;
|
|
97
|
+
/**
|
|
98
|
+
* Update the soft cap on simultaneously-decoded tracks. If the new cap
|
|
99
|
+
* is lower than the current decoded count, the surplus LRU tracks are
|
|
100
|
+
* dumped immediately.
|
|
101
|
+
*/
|
|
102
|
+
setMaxDecodedTracks(n: number): void;
|
|
103
|
+
/** Tracks currently holding their decoded PCM in memory. */
|
|
104
|
+
private decodedTrackCount;
|
|
105
|
+
/** True if the named track is currently audible (playing into its range). */
|
|
106
|
+
private isTrackAudible;
|
|
107
|
+
/**
|
|
108
|
+
* Free PCM from the least-recently-used undumped tracks until at most
|
|
109
|
+
* `keep` tracks remain decoded. Audible tracks (containing the playhead)
|
|
110
|
+
* and currently-transitioning tracks are excluded from eviction so
|
|
111
|
+
* playback never goes silent unexpectedly.
|
|
112
|
+
*/
|
|
113
|
+
private evictLRUDecodedTracks;
|
|
114
|
+
/**
|
|
115
|
+
* Release the decoded PCM held by a track to free memory without
|
|
116
|
+
* removing the track from the timeline. The worklets, gains, and
|
|
117
|
+
* effect chain stay wired so rehydration is a single message away.
|
|
118
|
+
*
|
|
119
|
+
* - Worklet stem buffers: `dump-buffer` clears the Float32Arrays.
|
|
120
|
+
* - Main-thread mirror (`rt.stems`): replaced with 1-sample silent
|
|
121
|
+
* AudioBuffers so legacy consumers (BarSampler, paste-audio) don't
|
|
122
|
+
* null-deref. Re-allocated to full length on rehydrate.
|
|
123
|
+
* - Waveform mipmaps: cleared so the timeline shows a flat
|
|
124
|
+
* centerline until the track is rehydrated.
|
|
125
|
+
* - In-flight decode: aborted (refcount on shared source decremented).
|
|
126
|
+
*/
|
|
127
|
+
dumpTrackAudio(trackId: string): void;
|
|
128
|
+
/**
|
|
129
|
+
* Re-decode a previously-dumped track from IndexedDB (the encoded fMP4
|
|
130
|
+
* stays cached even after dump). Reuses the existing worklet/audio
|
|
131
|
+
* graph — no node recreation — so rehydration is just a fresh
|
|
132
|
+
* progressive decode that streams patches back into the same
|
|
133
|
+
* worklets and mipmap cache.
|
|
134
|
+
*
|
|
135
|
+
* Returns true once `loadHandle.playable` resolves (enough audio for
|
|
136
|
+
* playback to start). Background batches continue filling the buffer.
|
|
137
|
+
*/
|
|
138
|
+
rehydrateTrackAudio(trackId: string): Promise<boolean>;
|
|
139
|
+
/** True if a track's decoded PCM has been freed. */
|
|
140
|
+
isTrackDumped(trackId: string): boolean;
|
|
141
|
+
removeTrack(trackId: string): void;
|
|
142
|
+
getStemBuffers(trackId: string): StemAudioBuffers | undefined;
|
|
143
|
+
getStemEffectChain(trackId: string, stemIndex: number): StemEffectChain | null;
|
|
144
|
+
updateStemEffects(trackId: string, stemIndex: number, fx: StemEffects): void;
|
|
145
|
+
/**
|
|
146
|
+
* Compute the native buffer sample position for a stem at a given
|
|
147
|
+
* global timeline bar, using the track's barMapping for precision.
|
|
148
|
+
*
|
|
149
|
+
* Returns null if the stem is outside its active region.
|
|
150
|
+
*/
|
|
151
|
+
private computeStemSeekSamples;
|
|
152
|
+
play(): Promise<void>;
|
|
153
|
+
pause(): void;
|
|
154
|
+
stop(): void;
|
|
155
|
+
seekToBar(bar: number): Promise<void>;
|
|
156
|
+
setMasterTempo(bpm: number): void;
|
|
157
|
+
/**
|
|
158
|
+
* Reorder a track and, if it becomes the new first track, promote it as master.
|
|
159
|
+
* Handles worklet bar map / pitch resync the same way setMasterTempo does.
|
|
160
|
+
*/
|
|
161
|
+
reorderTrack(trackId: string, newIndex: number): void;
|
|
162
|
+
setGlobalPlaybackRate(rate: number): void;
|
|
163
|
+
private broadcastGlobalRate;
|
|
164
|
+
syncGainsForTrack(trackId: string): void;
|
|
165
|
+
syncAllGains(): void;
|
|
166
|
+
/** Send mute regions for a specific stem to its worklet (bar-based → sample-based) */
|
|
167
|
+
syncMuteRegions(trackId: string, stemIndex: number): void;
|
|
168
|
+
/** Sync mute regions for all stems of a track */
|
|
169
|
+
syncAllMuteRegionsForTrack(trackId: string): void;
|
|
170
|
+
/**
|
|
171
|
+
* Rebuild and re-send the bar map for a track to all its stem worklets.
|
|
172
|
+
* Call after the track's barMapping has been modified (e.g. beat grid nudge).
|
|
173
|
+
*/
|
|
174
|
+
reloadTrackBarMap(trackId: string): void;
|
|
175
|
+
/**
|
|
176
|
+
* Enable or disable native playback for a track. When enabled, the track
|
|
177
|
+
* plays at its original recorded tempo (speed = 1.0 for every bar) with no
|
|
178
|
+
* pitch compensation. This allows the Beat Lab to display beat markers at
|
|
179
|
+
* their true sample positions and have them match the audible audio exactly.
|
|
180
|
+
*/
|
|
181
|
+
setNativePlaybackMode(trackId: string, enabled: boolean): void;
|
|
182
|
+
isNativePlaybackMode(): boolean;
|
|
183
|
+
get nativeModeTrackId(): string | null;
|
|
184
|
+
/**
|
|
185
|
+
* Get the current playback position in native (unquantized) seconds.
|
|
186
|
+
* Only meaningful when native playback mode is active.
|
|
187
|
+
*/
|
|
188
|
+
getNativePositionSec(): number;
|
|
189
|
+
/**
|
|
190
|
+
* Seek to a native sample position (used by beat lab scrubbing).
|
|
191
|
+
* Only works when native playback mode is active.
|
|
192
|
+
*/
|
|
193
|
+
seekToNativeSample(sample: number): void;
|
|
194
|
+
/**
|
|
195
|
+
* Send the combined pitch factor (correction + transpose) to a track's pitch worklet.
|
|
196
|
+
* When perBarPitchCompensation is enabled, the per-bar correction is added on top
|
|
197
|
+
* via updatePerBarPitch() during playback — this method sends the base correction.
|
|
198
|
+
*/
|
|
199
|
+
syncPitchForTrack(trackId: string): void;
|
|
200
|
+
/**
|
|
201
|
+
* Check if we've crossed a bar boundary and send an updated pitch factor
|
|
202
|
+
* that includes the per-bar pitch compensation for that specific bar.
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* rAF-driven per-bar pitch update (fallback for seeks / playback start).
|
|
206
|
+
* handleWorkletBarChange is the primary path — this catches missed bars.
|
|
207
|
+
*/
|
|
208
|
+
private updatePerBarPitch;
|
|
209
|
+
/** One-shot pitch send for play/seek — sends the bar's pitch and sets
|
|
210
|
+
* lastPitchStr so the rAF path won't duplicate it. */
|
|
211
|
+
private sendInitialBarPitch;
|
|
212
|
+
/** Set user transpose in semitones and update the worklet. */
|
|
213
|
+
setTrackTranspose(trackId: string, semitones: number): void;
|
|
214
|
+
/** Toggle per-bar pitch compensation for a track. */
|
|
215
|
+
setPerBarPitchCompensation(trackId: string, enabled: boolean): void;
|
|
216
|
+
/**
|
|
217
|
+
* Send the per-bar pitch map to the first stem worklet so it can apply a
|
|
218
|
+
* short crossfade at bar boundaries to mask the ~3ms gap between the
|
|
219
|
+
* speed change (instant, audio-thread) and the pitch change (next render quantum).
|
|
220
|
+
* Also sends the pitch map so the worklet can forward pitch commands to
|
|
221
|
+
* Rubberband via the main thread at the exact bar crossing moment.
|
|
222
|
+
*/
|
|
223
|
+
private syncWorkletPitchMap;
|
|
224
|
+
/** Update rubberband settings for a track. Requires worklet recreation. */
|
|
225
|
+
setTrackPitchSettings(trackId: string, settings: PitchEngineSettings): void;
|
|
226
|
+
/**
|
|
227
|
+
* Apply an autotune pitch offset on top of the current pitch chain.
|
|
228
|
+
* Called from the autotune panel at ~60fps during playback.
|
|
229
|
+
* Set autoSemitones=0 to clear the override.
|
|
230
|
+
*/
|
|
231
|
+
applyAutotuneCorrection(trackId: string, autoSemitones: number): void;
|
|
232
|
+
/** Send a pitch factor to the Rubberband pitch worklet. */
|
|
233
|
+
private sendPitchToWorklet;
|
|
234
|
+
/**
|
|
235
|
+
* Configure a Rubberband pitch worklet via port messages.
|
|
236
|
+
* The worklet ignores processorOptions — all config must be sent as messages.
|
|
237
|
+
*
|
|
238
|
+
* pitchDelayMs controls how many ms the worklet waits after receiving a pitch
|
|
239
|
+
* command before applying it to Rubberband. This is the single control for
|
|
240
|
+
* aligning speed changes (instant) with pitch changes (delayed by FFT pipeline).
|
|
241
|
+
*/
|
|
242
|
+
private configurePitchWorklet;
|
|
243
|
+
get pitchWorkletAvailable(): boolean;
|
|
244
|
+
get audioContext(): AudioContext | null;
|
|
245
|
+
/** Get the total semitones currently being applied to a track's pitch worklet. */
|
|
246
|
+
getCurrentPitchSemitones(trackId: string): number;
|
|
247
|
+
/**
|
|
248
|
+
* Get the pitch artifact (semitones) that the current bar's speed change introduces.
|
|
249
|
+
* This is the inverse of the compensation: if speed > 1, pitch goes up, etc.
|
|
250
|
+
* Returns 0 if per-bar compensation isn't active or no bar is current.
|
|
251
|
+
*/
|
|
252
|
+
getCurrentSpeedPitchArtifact(trackId: string): number;
|
|
253
|
+
/** Update pitchDelayMs for a track and push it to the Rubberband worklet immediately. */
|
|
254
|
+
setTrackPitchDelay(trackId: string, ms: number): void;
|
|
255
|
+
/** Change bar subdivision and reload all bar maps + pitch maps. */
|
|
256
|
+
setBarSubdivisions(n: number): void;
|
|
257
|
+
/** Start polling AudioContext render capacity (if supported). */
|
|
258
|
+
startCpuMonitor(intervalMs?: number): void;
|
|
259
|
+
stopCpuMonitor(): void;
|
|
260
|
+
get cpuUsage(): number;
|
|
261
|
+
private startPlayheadAnimation;
|
|
262
|
+
/**
|
|
263
|
+
* Authoritative pitch update from the worklet's bar-changed message.
|
|
264
|
+
* Fires at audio-thread timing. The worklet's internal pitchDelayMs
|
|
265
|
+
* handles the alignment between speed (instant) and pitch (delayed).
|
|
266
|
+
*/
|
|
267
|
+
private handleWorkletBarChange;
|
|
268
|
+
/** Update per-bar pitch compensation for all tracks that have it enabled. */
|
|
269
|
+
private updateAllPerBarPitch;
|
|
270
|
+
/** Start/stop individual stem worklets as the playhead crosses their timeline boundaries */
|
|
271
|
+
private updateStemPlayState;
|
|
272
|
+
private stopPlayheadAnimation;
|
|
273
|
+
private getCurrentPositionSec;
|
|
274
|
+
barToSeconds(bar: number): number;
|
|
275
|
+
secondsToBar(sec: number): number;
|
|
276
|
+
get metronomeEnabled(): boolean;
|
|
277
|
+
get metronomeVolume(): number;
|
|
278
|
+
get metronomeDownbeatOnly(): boolean;
|
|
279
|
+
setMetronomeDownbeatOnly(downbeatOnly: boolean): void;
|
|
280
|
+
toggleMetronome(on?: boolean): void;
|
|
281
|
+
setMetronomeVolume(volume: number): void;
|
|
282
|
+
private startMetronomeScheduler;
|
|
283
|
+
private stopMetronomeScheduler;
|
|
284
|
+
private scheduleMetronomeClicks;
|
|
285
|
+
private playClick;
|
|
286
|
+
/**
|
|
287
|
+
* Copy audio samples from one bar range and overwrite another bar range
|
|
288
|
+
* inside a track's stem buffers. Source and destination stems may be the
|
|
289
|
+
* same (default) or different stems on the same track. After writing, the
|
|
290
|
+
* modified destination buffer is sent to the worklet so playback reflects
|
|
291
|
+
* the change immediately.
|
|
292
|
+
*
|
|
293
|
+
* Both `sourceStart/End` and `targetStartBar` are in local bars (relative
|
|
294
|
+
* to the track's barMapping, not the global timeline).
|
|
295
|
+
*
|
|
296
|
+
* `targetStemIndex` defaults to `stemIndex` (source) for backwards-compatible
|
|
297
|
+
* same-stem behaviour.
|
|
298
|
+
*/
|
|
299
|
+
pasteAudioRegion(trackId: string, stemIndex: number, sourceStartBar: number, sourceEndBar: number, targetStartBar: number, targetStemIndex?: number): void;
|
|
300
|
+
/**
|
|
301
|
+
* Copy each selection's bar range into the clipboard. No mutation of
|
|
302
|
+
* the audio buffers — just snapshots the source bar ranges. No undo step
|
|
303
|
+
* (copy is non-destructive).
|
|
304
|
+
*/
|
|
305
|
+
copySelections(selections: StemSelectionRange[]): void;
|
|
306
|
+
/**
|
|
307
|
+
* Mute (silence) each selection's bar range. Single undo step covers all
|
|
308
|
+
* ranges. After muting, clears the selection set.
|
|
309
|
+
*/
|
|
310
|
+
muteSelections(selections: StemSelectionRange[]): void;
|
|
311
|
+
/**
|
|
312
|
+
* Cut: copy each selection's bar range to clipboard AND mute it. Single
|
|
313
|
+
* undo step. Clears the selection set after.
|
|
314
|
+
*/
|
|
315
|
+
cutSelections(selections: StemSelectionRange[]): void;
|
|
316
|
+
/**
|
|
317
|
+
* Paste the current clipboard at a global timeline bar. Each clipboard
|
|
318
|
+
* entry pastes into its own stemIndex on the focused track. Cross-track
|
|
319
|
+
* paste is supported as same-stemIndex-different-track when the source
|
|
320
|
+
* track no longer exists.
|
|
321
|
+
*
|
|
322
|
+
* For a SINGLE-entry clipboard, the optional `targetStemIndex` allows
|
|
323
|
+
* cross-stem paste (e.g. paste bass clip into drums lane).
|
|
324
|
+
*/
|
|
325
|
+
pasteClipboardAtBar(targetGlobalBar: number, targetTrackId: string, targetStemIndex?: number): void;
|
|
326
|
+
/**
|
|
327
|
+
* Duplicate each selection: copy its audio in-buffer to immediately after
|
|
328
|
+
* the selection end on the same stem. Single undo step.
|
|
329
|
+
*/
|
|
330
|
+
duplicateSelections(selections: StemSelectionRange[]): void;
|
|
331
|
+
/**
|
|
332
|
+
* Insert a resampled AudioBuffer into a stem, replacing the specified bar range.
|
|
333
|
+
* Rebuilds the worklet buffer and bar map, and records the resampled region in the store.
|
|
334
|
+
*/
|
|
335
|
+
insertResampledRegion(trackId: string, stemIndex: number, startBar: number, endBar: number, resampledBuffer: AudioBuffer): void;
|
|
336
|
+
destroy(): void;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export { DAWController as D, type LoadProgress as L, type StemAudioBuffers as S, loadTrackStems as l };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"daw-controller.js"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import a stem.fm mix-config JSON into the DAW.
|
|
3
|
+
*
|
|
4
|
+
* The mix-config is a flat list of *sessions* — each one a "scene" on the
|
|
5
|
+
* master timeline of length `length.bars`. The model is:
|
|
6
|
+
*
|
|
7
|
+
* • `trackChannels[]` — which tracks are present in this scene (canonical).
|
|
8
|
+
* • `originAnchors[]` — parallel array; where each track starts in its
|
|
9
|
+
* source for this scene (originalBarIndex / sample).
|
|
10
|
+
* • modifier events — `volume` (per-stem), `rate`, `pitch`. They describe
|
|
11
|
+
* how the present tracks are *played* within the scene
|
|
12
|
+
* (per-bar tempo + pitch + per-stem volume automation).
|
|
13
|
+
* • effect events — `reverb` (and friends). Currently ignored —
|
|
14
|
+
* they don't affect clip placement, only sound.
|
|
15
|
+
*
|
|
16
|
+
* High-level mapping to the DAW (initial implementation):
|
|
17
|
+
*
|
|
18
|
+
* ONE clip per (session, trackChannel) pair, length = session.length.bars,
|
|
19
|
+
* source position = anchor.originalBarIndex.
|
|
20
|
+
*
|
|
21
|
+
* Per-stem channel volume + mute regions are derived from the session's
|
|
22
|
+
* volume events. A track that's "loaded but silent" (all volumes 0) still
|
|
23
|
+
* gets a clip on the timeline — it just renders muted, ready to come alive
|
|
24
|
+
* in a later session.
|
|
25
|
+
*
|
|
26
|
+
* Rate/pitch automation within a session is collapsed: a single average
|
|
27
|
+
* rate per clip and the first pitch event's value as a constant transpose.
|
|
28
|
+
* This is the right trade-off until the DAW grows per-section variable
|
|
29
|
+
* tempo + clip-level volume automation; until then a session imports as
|
|
30
|
+
* a clean structural skeleton, and we layer the richer automation on top.
|
|
31
|
+
*
|
|
32
|
+
* Track ids are resolved once each via `fetchTrackForDAW` — a track that
|
|
33
|
+
* appears in many sessions is fetched once and the same `ITrackForSequence`
|
|
34
|
+
* is reused for every clip referencing it.
|
|
35
|
+
*
|
|
36
|
+
* Public entry point: `importStemFmMixConfig`.
|
|
37
|
+
*/
|
|
38
|
+
/** Stems live in this order in the DAW (0..3). Must match `STEM_TYPES` /
|
|
39
|
+
* `STEM_LABELS` in `interfaces.ts`. */
|
|
40
|
+
declare const STEM_NAMES: readonly ["other", "vocals", "bass", "drums"];
|
|
41
|
+
type StemName = (typeof STEM_NAMES)[number];
|
|
42
|
+
/** Modifier events shape clip playback (rate / pitch / per-stem volume).
|
|
43
|
+
* Effect events (`reverb`, …) come as a separate `category: 'effect'`
|
|
44
|
+
* shape — see `StemFmEffectEvent`. We currently parse modifiers only. */
|
|
45
|
+
type StemFmModifierType = 'rate' | 'pitch' | 'volume';
|
|
46
|
+
type StemFmCurve = 'instant' | 'linear';
|
|
47
|
+
interface StemFmTriggerOrigin {
|
|
48
|
+
/** 0-based bar index in the original (source) audio. */
|
|
49
|
+
originalBarIndex: number;
|
|
50
|
+
/** Wall-clock position of the bar in the source audio (seconds). */
|
|
51
|
+
originalStartTimeSec: number;
|
|
52
|
+
/** Sample position of the bar in the source audio (48 kHz). */
|
|
53
|
+
originalSampleStart: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Modifier event (rate / pitch / volume). Carries `startValue` / `endValue`
|
|
57
|
+
* in `behavior` — for `instant` events these are equal; for `linear` events
|
|
58
|
+
* they describe a ramp of length `schedule.length.bars` master bars.
|
|
59
|
+
*/
|
|
60
|
+
interface StemFmModifierEvent {
|
|
61
|
+
category: 'modifier';
|
|
62
|
+
type: StemFmModifierType;
|
|
63
|
+
schedule: {
|
|
64
|
+
/** `bar` is 1-based in the session timeline. */
|
|
65
|
+
triggerTime: {
|
|
66
|
+
bar: number;
|
|
67
|
+
position: string;
|
|
68
|
+
};
|
|
69
|
+
/** Ramp length. `bars: 0` ⇒ instant. */
|
|
70
|
+
length: {
|
|
71
|
+
bars: number;
|
|
72
|
+
offset: string;
|
|
73
|
+
};
|
|
74
|
+
triggerOrigin?: StemFmTriggerOrigin;
|
|
75
|
+
};
|
|
76
|
+
target: {
|
|
77
|
+
trackId: string;
|
|
78
|
+
/** Volume events target a specific stem; rate / pitch events
|
|
79
|
+
* target the whole track and omit this field. */
|
|
80
|
+
stem?: StemName;
|
|
81
|
+
};
|
|
82
|
+
behavior: {
|
|
83
|
+
startValue: number;
|
|
84
|
+
endValue: number;
|
|
85
|
+
curve: StemFmCurve;
|
|
86
|
+
signatureLabel?: string | null;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Effect events (e.g. reverb) carry an entirely different `behavior` shape
|
|
91
|
+
* (`startEnabled / endEnabled / startMix / endMix / startDecay / endDecay`)
|
|
92
|
+
* and are not yet wired through to the DAW's `StemEffectChain`. We type
|
|
93
|
+
* them loosely here so they can flow through the validator without
|
|
94
|
+
* crashing the importer; consumers should ignore unknown event types.
|
|
95
|
+
*/
|
|
96
|
+
interface StemFmEffectEvent {
|
|
97
|
+
category: 'effect';
|
|
98
|
+
type: string;
|
|
99
|
+
schedule: StemFmModifierEvent['schedule'];
|
|
100
|
+
target: {
|
|
101
|
+
trackId: string;
|
|
102
|
+
stem?: StemName;
|
|
103
|
+
};
|
|
104
|
+
behavior: Record<string, unknown>;
|
|
105
|
+
}
|
|
106
|
+
type StemFmEvent = StemFmModifierEvent | StemFmEffectEvent;
|
|
107
|
+
interface StemFmSession {
|
|
108
|
+
type: 'playback' | 'transition';
|
|
109
|
+
trackChannels: {
|
|
110
|
+
id: string;
|
|
111
|
+
}[];
|
|
112
|
+
length: {
|
|
113
|
+
bars: number;
|
|
114
|
+
offset: string;
|
|
115
|
+
};
|
|
116
|
+
events: StemFmEvent[];
|
|
117
|
+
/** Convenience duplicate of `length.bars` (newer exports include this). */
|
|
118
|
+
bars?: number;
|
|
119
|
+
/**
|
|
120
|
+
* Where each track in `trackChannels` starts in its source audio for
|
|
121
|
+
* this session. Same length & order as `trackChannels`. Newer exports
|
|
122
|
+
* include this; older ones don't, and we fall back to the first rate
|
|
123
|
+
* event's `triggerOrigin` for the track.
|
|
124
|
+
*/
|
|
125
|
+
originAnchors?: StemFmTriggerOrigin[];
|
|
126
|
+
}
|
|
127
|
+
type StemFmMixConfig = StemFmSession[];
|
|
128
|
+
/**
|
|
129
|
+
* One scheduled clip on the DAW timeline — corresponds 1:1 with a
|
|
130
|
+
* (session, trackChannel) pair from the mix-config.
|
|
131
|
+
*/
|
|
132
|
+
interface PlannedClip {
|
|
133
|
+
/** Which session this clip came from (0-based). For diagnostics. */
|
|
134
|
+
sessionIndex: number;
|
|
135
|
+
/** stem.fm track id (source). */
|
|
136
|
+
sourceTrackId: string;
|
|
137
|
+
/** Where on the global DAW timeline this clip starts. */
|
|
138
|
+
timelineStartBar: number;
|
|
139
|
+
/** Inclusive first source bar (relative to the original track). */
|
|
140
|
+
sourceStartBar: number;
|
|
141
|
+
/** Exclusive last source bar (relative to the original track). */
|
|
142
|
+
sourceEndBar: number;
|
|
143
|
+
/** Per-stem volume to apply on this clip (length 4, [other, vocals, bass, drums]). */
|
|
144
|
+
stemVolumes: [number, number, number, number];
|
|
145
|
+
/** Per-stem mute regions inside the clip (local bars relative to
|
|
146
|
+
* `sourceStartBar`). Each entry shadows a bar range where the
|
|
147
|
+
* stem volume was 0 in the session. */
|
|
148
|
+
stemMuteRegions: [
|
|
149
|
+
Array<{
|
|
150
|
+
startBar: number;
|
|
151
|
+
endBar: number;
|
|
152
|
+
}>,
|
|
153
|
+
Array<{
|
|
154
|
+
startBar: number;
|
|
155
|
+
endBar: number;
|
|
156
|
+
}>,
|
|
157
|
+
Array<{
|
|
158
|
+
startBar: number;
|
|
159
|
+
endBar: number;
|
|
160
|
+
}>,
|
|
161
|
+
Array<{
|
|
162
|
+
startBar: number;
|
|
163
|
+
endBar: number;
|
|
164
|
+
}>
|
|
165
|
+
];
|
|
166
|
+
/** Pitch shift in semitones for this clip (constant across the clip). */
|
|
167
|
+
transposeSemitones: number;
|
|
168
|
+
/** Average rate over the clip (informational; the DAW computes a per-bar
|
|
169
|
+
* speed map on its own from `barMapping` vs master tempo). */
|
|
170
|
+
avgRate: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Build the full list of planned DAW clips from a mix-config.
|
|
174
|
+
*
|
|
175
|
+
* Initial-implementation rule: emit ONE clip per (session, trackChannel)
|
|
176
|
+
* pair. Source position comes from `originAnchors` (or the first rate
|
|
177
|
+
* event's `triggerOrigin` as a fallback). Length is `session.length.bars`.
|
|
178
|
+
* Per-stem volumes & mute regions come from the session's volume events.
|
|
179
|
+
*
|
|
180
|
+
* No run-splitting on rate-event jumps, no skipping silent placeholders —
|
|
181
|
+
* a track that's "loaded silent" appears on the timeline as a muted clip,
|
|
182
|
+
* ready to come alive in a later session.
|
|
183
|
+
*
|
|
184
|
+
* Pure / synchronous so it can be unit-tested without the audio engine.
|
|
185
|
+
*/
|
|
186
|
+
declare function planClipsFromStemFmConfig(config: StemFmMixConfig): {
|
|
187
|
+
clips: PlannedClip[];
|
|
188
|
+
totalTimelineBars: number;
|
|
189
|
+
trackIds: string[];
|
|
190
|
+
};
|
|
191
|
+
interface ImportStemFmCallbacks {
|
|
192
|
+
onStatus?: (message: string) => void;
|
|
193
|
+
onPercent?: (percent: number) => void;
|
|
194
|
+
/**
|
|
195
|
+
* Limit the import to the first N sessions of the mix-config. Useful
|
|
196
|
+
* while the DAW doesn't yet de-duplicate repeated source tracks — a
|
|
197
|
+
* long playback session that jumps backwards in its source track
|
|
198
|
+
* (e.g. session 3 of the test file revisits "What Goes Around" 7
|
|
199
|
+
* times) explodes into many independently-decoded clips of the same
|
|
200
|
+
* audio. Defaults to `Infinity` (import everything).
|
|
201
|
+
*/
|
|
202
|
+
maxSessions?: number;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Validate that a parsed object looks like a stem.fm mix-config (an array of
|
|
206
|
+
* sessions). Throws a `TypeError` with a human-friendly message when the
|
|
207
|
+
* shape doesn't match, so callers can show the error directly.
|
|
208
|
+
*/
|
|
209
|
+
declare function isStemFmMixConfig(value: unknown): value is StemFmMixConfig;
|
|
210
|
+
/**
|
|
211
|
+
* Import a stem.fm mix-config JSON, replacing the current DAW session.
|
|
212
|
+
*
|
|
213
|
+
* Caller is expected to have already cleared the previous session (matches
|
|
214
|
+
* the `restoreSession` pattern in `daw-session-restore.ts`).
|
|
215
|
+
*
|
|
216
|
+
* Returns the planned clips so the caller can show a summary to the user.
|
|
217
|
+
*/
|
|
218
|
+
declare function importStemFmMixConfig(config: StemFmMixConfig, callbacks?: ImportStemFmCallbacks): Promise<{
|
|
219
|
+
clips: PlannedClip[];
|
|
220
|
+
totalTimelineBars: number;
|
|
221
|
+
addedTrackInstances: number;
|
|
222
|
+
}>;
|
|
223
|
+
|
|
224
|
+
export { type ImportStemFmCallbacks, type StemFmCurve, type StemFmEffectEvent, type StemFmEvent, type StemFmMixConfig, type StemFmModifierEvent, type StemFmModifierType, type StemFmSession, type StemFmTriggerOrigin, importStemFmMixConfig, isStemFmMixConfig, planClipsFromStemFmConfig };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { importStemFmMixConfig, isStemFmMixConfig, planClipsFromStemFmConfig } from '../chunk-U44X6QP5.js';
|
|
2
|
+
import '../chunk-OYNES5W3.js';
|
|
3
|
+
import '../chunk-56PWIP7O.js';
|
|
4
|
+
import '../chunk-TBXCZFAY.js';
|
|
5
|
+
import '../chunk-KCOOE2OP.js';
|
|
6
|
+
//# sourceMappingURL=daw-import-stem-fm-config.js.map
|
|
7
|
+
//# sourceMappingURL=daw-import-stem-fm-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"daw-import-stem-fm-config.js"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { BARE_SESSION_STATION_TRACK_LIMIT, extractBareSessionStationId, extractStationIdFromUrl, extractStationIdVer2, extractStationTrackIdVer2, fetchStationTrackDataWithSharedURL, getStationTracksGeneric, getStationTracksPageFirstN, isStationIdLibraryArtistStation, loadStationTrackFromJsonForPrototype, login, removeDuplicatesById, uniqueTrackIds } from './chunk-TBXCZFAY.js';
|
|
2
|
+
//# sourceMappingURL=fetchStationTracks-SKFT4V3U.js.map
|
|
3
|
+
//# sourceMappingURL=fetchStationTracks-SKFT4V3U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"fetchStationTracks-SKFT4V3U.js"}
|