@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.
Files changed (67) hide show
  1. package/README.md +253 -0
  2. package/dist/chat-actions-54Z6URC4.js +7 -0
  3. package/dist/chat-actions-54Z6URC4.js.map +1 -0
  4. package/dist/chunk-56PWIP7O.js +1029 -0
  5. package/dist/chunk-56PWIP7O.js.map +1 -0
  6. package/dist/chunk-AAVC7KUW.js +145 -0
  7. package/dist/chunk-AAVC7KUW.js.map +1 -0
  8. package/dist/chunk-KCOOE2OP.js +1764 -0
  9. package/dist/chunk-KCOOE2OP.js.map +1 -0
  10. package/dist/chunk-LO74ZJ4H.js +23923 -0
  11. package/dist/chunk-LO74ZJ4H.js.map +1 -0
  12. package/dist/chunk-OFGZURP6.js +247 -0
  13. package/dist/chunk-OFGZURP6.js.map +1 -0
  14. package/dist/chunk-OYNES5W3.js +3085 -0
  15. package/dist/chunk-OYNES5W3.js.map +1 -0
  16. package/dist/chunk-QQ5NZTHT.js +336 -0
  17. package/dist/chunk-QQ5NZTHT.js.map +1 -0
  18. package/dist/chunk-TBXCZFAY.js +13713 -0
  19. package/dist/chunk-TBXCZFAY.js.map +1 -0
  20. package/dist/chunk-U44X6QP5.js +281 -0
  21. package/dist/chunk-U44X6QP5.js.map +1 -0
  22. package/dist/chunk-UKMELGZL.js +27 -0
  23. package/dist/chunk-UKMELGZL.js.map +1 -0
  24. package/dist/components/DAWView.d.ts +19 -0
  25. package/dist/components/DAWView.js +11 -0
  26. package/dist/components/DAWView.js.map +1 -0
  27. package/dist/daw-controller-BjRWcTol.d.ts +339 -0
  28. package/dist/engine/daw-controller.d.ts +3 -0
  29. package/dist/engine/daw-controller.js +5 -0
  30. package/dist/engine/daw-controller.js.map +1 -0
  31. package/dist/engine/daw-import-stem-fm-config.d.ts +224 -0
  32. package/dist/engine/daw-import-stem-fm-config.js +7 -0
  33. package/dist/engine/daw-import-stem-fm-config.js.map +1 -0
  34. package/dist/fetchStationTracks-SKFT4V3U.js +3 -0
  35. package/dist/fetchStationTracks-SKFT4V3U.js.map +1 -0
  36. package/dist/index.d.ts +308 -0
  37. package/dist/index.js +332 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/interface-DaRj7RkY.d.ts +66 -0
  40. package/dist/interfaces-5ZlG0Y4Y.d.ts +549 -0
  41. package/dist/media-session-XTP6PP7Q.js +3 -0
  42. package/dist/media-session-XTP6PP7Q.js.map +1 -0
  43. package/dist/note-detection-PPLM7R2H.js +148 -0
  44. package/dist/note-detection-PPLM7R2H.js.map +1 -0
  45. package/dist/sampler-audio-B7MBG3YN.js +3 -0
  46. package/dist/sampler-audio-B7MBG3YN.js.map +1 -0
  47. package/dist/sampler-store-QPHANXYP.js +3 -0
  48. package/dist/sampler-store-QPHANXYP.js.map +1 -0
  49. package/dist/services/track-search-api.d.ts +152 -0
  50. package/dist/services/track-search-api.js +4 -0
  51. package/dist/services/track-search-api.js.map +1 -0
  52. package/dist/store/daw-auth-store.d.ts +31 -0
  53. package/dist/store/daw-auth-store.js +3 -0
  54. package/dist/store/daw-auth-store.js.map +1 -0
  55. package/dist/store/daw-session-store.d.ts +255 -0
  56. package/dist/store/daw-session-store.js +3 -0
  57. package/dist/store/daw-session-store.js.map +1 -0
  58. package/dist/vite/index.d.ts +46 -0
  59. package/dist/vite/index.js +94 -0
  60. package/dist/vite/index.js.map +1 -0
  61. package/dist/workers/analysis-worker.js +379 -0
  62. package/dist/workers/buffer-player-processor-202602.lavv8e32-ts.js +1 -0
  63. package/dist/workers/daw-stem-processor.js +228 -0
  64. package/dist/workers/manifest.json +10 -0
  65. package/dist/workers/phase-vocoder3.js +920 -0
  66. package/dist/workers/realtime-pitch-shift-processor.js +2 -0
  67. 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,3 @@
1
+ import '../interfaces-5ZlG0Y4Y.js';
2
+ export { D as DAWController } from '../daw-controller-BjRWcTol.js';
3
+ import '../interface-DaRj7RkY.js';
@@ -0,0 +1,5 @@
1
+ export { DAWController } from '../chunk-OYNES5W3.js';
2
+ import '../chunk-TBXCZFAY.js';
3
+ import '../chunk-KCOOE2OP.js';
4
+ //# sourceMappingURL=daw-controller.js.map
5
+ //# sourceMappingURL=daw-controller.js.map
@@ -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"}