@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,1029 @@
1
+ import { AEV3Config, extractBareSessionStationId, resolveSingleBeatsAndKeysWithBeatsLink, replaceMp4ForLocalUse, calculateTempoOrigSzFromBeatDownBeat, calculateQuantisedTrackActualDurationFromBeatGrid, getFullDurationFromBeatsLinkSegmentInfo, getDurationFromBeatsLinkUpToLastDownbeat0, resolveMultipleBeatsAndKeysWithBeatsLink } from './chunk-TBXCZFAY.js';
2
+
3
+ // src/services/dropped-mix-ids.ts
4
+ var NEXT_MIX_ID_DROPPED_ON_RESOLVING_NEXT_MIX = [];
5
+
6
+ // src/services/fetchMix.tsx
7
+ var TRACK_CREATOR_ENDPOINT = `${import.meta.env?.VITE_KB_STEMIFY_API_URL || "https://api.stemplayer.com"}/track-creator`;
8
+ var LOGIN_LINK = `${import.meta.env?.VITE_KB_STEMIFY_API_URL || "https://api.stemplayer.com"}/accounts/user/login`;
9
+ var APPLY_BEAT_GRID_SMOOTHING = true;
10
+ var FETCH_MIX_CONSOLE_TIME = "";
11
+ var TRACK_ITEM_FRAGMENT = `fragment TrackItem on Track {
12
+ artistDisplayName
13
+ colors
14
+ name
15
+ trackId
16
+ trackMixType
17
+ trackStatus
18
+ isClaimed
19
+ duration
20
+ artwork: image
21
+ artworkGlowColor
22
+ url: url(codec: aac, container: mp4, profile: raw)
23
+ stems {
24
+ aac: url(codec: aac)
25
+ hls: url(codec: hls)
26
+ wav: url(codec: wav)
27
+ mp3: url(codec: mp3)
28
+ stemId
29
+ stemPosition
30
+ }
31
+ }`;
32
+ var MIX_ITEM_FRAGMENT = `fragment MixItem on Mix {
33
+ mixId
34
+ name
35
+ colors
36
+ mixPosition
37
+ tracks {
38
+ ...TrackItem
39
+ }
40
+ popularSelectedStems {
41
+ vocals
42
+ other
43
+ drums
44
+ bass
45
+ }
46
+ }`;
47
+ var _FM_log = (...data) => {
48
+ return;
49
+ };
50
+ var _FM_log_table = (...data) => {
51
+ return;
52
+ };
53
+ var login = async () => {
54
+ const loginUrl = LOGIN_LINK;
55
+ try {
56
+ const cachedToken = localStorage.getItem("accessToken");
57
+ const tokenExpiry = localStorage.getItem("tokenExpiry");
58
+ if (cachedToken && tokenExpiry && (/* @__PURE__ */ new Date()).getTime() < parseInt(tokenExpiry, 10)) {
59
+ const cachedTokenObj = JSON.parse(cachedToken);
60
+ _FM_log("Using cached access token:", cachedTokenObj.accessToken.value);
61
+ return cachedTokenObj.accessToken.value;
62
+ }
63
+ let loginPayload;
64
+ try {
65
+ const username = import.meta.env?.VITE_STEM_API_USERNAME;
66
+ const password = import.meta.env?.VITE_STEM_API_PASSWORD;
67
+ if (!username || !password) {
68
+ throw new Error("Username or password not found in environment variables");
69
+ }
70
+ loginPayload = {
71
+ email: username,
72
+ password
73
+ };
74
+ } catch (error) {
75
+ _FM_log(error);
76
+ throw new Error("Username or password not found");
77
+ }
78
+ _FM_log("Starting login request...");
79
+ const loginResponse = await fetch(loginUrl, {
80
+ method: "POST",
81
+ headers: {
82
+ "Content-Type": "application/json"
83
+ },
84
+ body: JSON.stringify(loginPayload)
85
+ });
86
+ if (!loginResponse.ok) {
87
+ throw new Error("Login failed");
88
+ }
89
+ const loginData = await loginResponse.json();
90
+ const accessTokenObj = loginData.data;
91
+ const expiryTime = (/* @__PURE__ */ new Date()).getTime() + 20 * 60 * 1e3;
92
+ _FM_log("Login successful. Access token:", accessTokenObj.accessToken.value);
93
+ localStorage.setItem("accessToken", JSON.stringify(accessTokenObj));
94
+ localStorage.setItem("tokenExpiry", expiryTime.toString());
95
+ return accessTokenObj.accessToken.value;
96
+ } catch (error) {
97
+ console.error("Error during login or fetching mix data:", error.message);
98
+ return null;
99
+ }
100
+ };
101
+ var getMixMetadata = async (authToken, mixId, stationId, smoothBeats = APPLY_BEAT_GRID_SMOOTHING, abortController) => {
102
+ const query = `query GetMix($id: ID!) {
103
+ mix(id: $id) {
104
+ ...MixItem
105
+ }
106
+ }
107
+
108
+ ${TRACK_ITEM_FRAGMENT}
109
+
110
+ ${MIX_ITEM_FRAGMENT}`;
111
+ const variables = { "id": mixId };
112
+ const queryParams = new URLSearchParams({
113
+ query: query.trim(),
114
+ variables: JSON.stringify(variables),
115
+ operationName: "GetMix"
116
+ });
117
+ const url = `${TRACK_CREATOR_ENDPOINT}/graphql?${queryParams.toString()}`;
118
+ const headers = {
119
+ Authorization: `Bearer ${authToken}`
120
+ };
121
+ try {
122
+ const response = await fetch(url, {
123
+ method: "GET",
124
+ headers,
125
+ signal: abortController?.signal
126
+ });
127
+ const responseData = await response.json();
128
+ if (response.ok) {
129
+ _FM_log("Successfully fetched the mix details.");
130
+ _FM_log(responseData);
131
+ if (!responseData?.data?.mix) {
132
+ console.error("Mix not found or returned null for this ID");
133
+ return null;
134
+ }
135
+ responseData.data.mix.remixes = [];
136
+ const trackIds = responseData.data.mix.tracks.map((t) => {
137
+ return { id: t.trackId, url: t.url };
138
+ });
139
+ const trackIds2 = responseData.data.mix.remixes?.length > 0 ? responseData.data.mix.remixes[0].tracksmap((t) => {
140
+ return { id: t.trackId, url: t.url };
141
+ }) : [];
142
+ const tracks = removeDuplicatesById([...trackIds, ...trackIds2]);
143
+ const trackBeatsCollection2 = await resolveMultipleBeatsAndKeysWithBeatsLink(tracks, authToken, smoothBeats, window.$currentBeatGridVersion);
144
+ responseData.data.mix.tracks.map((t) => {
145
+ const m_t = t;
146
+ if (trackBeatsCollection2.data[t.trackId]) {
147
+ replaceMp4Url(m_t);
148
+ addBeatsRelatedParam(m_t, trackBeatsCollection2.data[t.trackId]);
149
+ m_t.__falseTrack = false;
150
+ m_t.__durationForValidation1 = getFullDurationFromBeatsLinkSegmentInfo(m_t.segmentInfo);
151
+ m_t.__durationForValidation2 = getDurationFromBeatsLinkUpToLastDownbeat0(-1, m_t.beats);
152
+ } else {
153
+ m_t.__falseTrack = true;
154
+ m_t.__durationForValidation1 = 0;
155
+ m_t.__durationForValidation2 = 0;
156
+ }
157
+ return m_t;
158
+ });
159
+ return responseData;
160
+ } else {
161
+ console.error(`Failed to fetch mix details. ${FETCH_MIX_CONSOLE_TIME}`);
162
+ return null;
163
+ }
164
+ } catch (e) {
165
+ console.timeEnd(FETCH_MIX_CONSOLE_TIME);
166
+ console.error(`Error during mix detail retrieval: ${e}`);
167
+ return null;
168
+ }
169
+ };
170
+ var getNextMixMetadata = async (authToken, mixId, stationId, direction, currentMixPosition = -1, smoothBeats = APPLY_BEAT_GRID_SMOOTHING, abortController) => {
171
+ const query = `query MixesToPlay($id: [ID]!, $first: Int, $afterId: ID, $sort: Sort) {
172
+ stations(ids: $id) {
173
+ stationId
174
+ lastModified
175
+ mixCount
176
+ mixes(first: $first, afterId: $afterId, sort: $sort) {
177
+ ...MixItem
178
+ mixPosition
179
+ }
180
+ }
181
+ }
182
+
183
+ ${TRACK_ITEM_FRAGMENT}
184
+
185
+ ${MIX_ITEM_FRAGMENT}`;
186
+ const queryArtist = `query MixesToPlay($id: [ID]!, $first: Int, $afterId: ID) {
187
+ stations: libraryArtists(ids: $id) {
188
+ artist {
189
+ artistId
190
+ stationId: artistId
191
+ }
192
+ lastModified
193
+ mixCount
194
+ mixes(first: $first, afterId: $afterId) {
195
+ ...MixItem
196
+ mixPosition
197
+ }
198
+ }
199
+ }
200
+
201
+ ${TRACK_ITEM_FRAGMENT}
202
+
203
+ ${MIX_ITEM_FRAGMENT}`;
204
+ const variables = { "afterId": mixId, "id": [stationId], "first": 3, "sort": direction === "nextMixId" ? "asc" : "desc" };
205
+ const queryParams = new URLSearchParams({
206
+ query: isStationIdLibraryArtistStation(stationId) ? queryArtist.trim() : query.trim(),
207
+ variables: JSON.stringify(variables),
208
+ operationName: "MixesToPlay"
209
+ });
210
+ const url = `${TRACK_CREATOR_ENDPOINT}/graphql?${queryParams.toString()}`;
211
+ const headers = {
212
+ Authorization: `Bearer ${authToken}`
213
+ };
214
+ FETCH_MIX_CONSOLE_TIME = `Getting mix details... ${stationId}/${mixId}`;
215
+ try {
216
+ const response = await fetch(url, {
217
+ method: "GET",
218
+ headers,
219
+ signal: abortController?.signal
220
+ });
221
+ const responseData = await response.json();
222
+ _FM_log("%cnext mix json", "color: magenta", responseData);
223
+ if (response.ok || responseData.data?.stations?.[0]?.mixes && responseData.data?.stations?.[0]?.mixes?.length === 0) {
224
+ _FM_log("Successfully fetched the mix details.");
225
+ _FM_log(responseData);
226
+ responseData.data?.stations?.[0]?.mixes.forEach((m) => m.remixes = []);
227
+ const trackIds = responseData.data?.stations?.[0]?.mixes.map((m) => m.tracks.map((t) => {
228
+ return { id: t.trackId, url: t.url };
229
+ }));
230
+ const tracks = removeDuplicatesById([...trackIds].flat(Infinity));
231
+ const trackBeatsCollection2 = await resolveMultipleBeatsAndKeysWithBeatsLink(tracks, authToken, smoothBeats, window.$currentBeatGridVersion);
232
+ responseData.data?.stations?.[0]?.mixes.forEach((m) => m.tracks.map((t) => {
233
+ const m_t = t;
234
+ if (trackBeatsCollection2.data[t.trackId]) {
235
+ replaceMp4Url(m_t);
236
+ addBeatsRelatedParam(m_t, trackBeatsCollection2.data[t.trackId]);
237
+ m_t.__falseTrack = false;
238
+ m_t.__durationForValidation1 = getFullDurationFromBeatsLinkSegmentInfo(m_t.segmentInfo);
239
+ m_t.__durationForValidation2 = getDurationFromBeatsLinkUpToLastDownbeat0(-1, m_t.beats);
240
+ } else {
241
+ m_t.__falseTrack = true;
242
+ m_t.__durationForValidation1 = 0;
243
+ m_t.__durationForValidation2 = 0;
244
+ }
245
+ return m_t;
246
+ }));
247
+ if (!responseData.data?.stations?.[0]?.stationId) {
248
+ if (responseData.data?.stations?.[0]?.artist?.stationId) {
249
+ responseData.data.stations[0].stationId = responseData.data?.stations?.[0]?.artist?.stationId;
250
+ } else {
251
+ responseData.data.stations[0].stationId = "";
252
+ }
253
+ }
254
+ const stationMixCount = responseData.data?.stations?.[0]?.mixCount || 0;
255
+ const mixesLength = responseData.data?.stations?.[0]?.mixes?.length;
256
+ _FM_log_table({
257
+ id: "autoplay",
258
+ stationId,
259
+ mixId,
260
+ currentMixPosition,
261
+ stationMixCount,
262
+ mixesLength,
263
+ "isNaN(currentMixPosition*1)": isNaN(currentMixPosition * 1)
264
+ });
265
+ if (responseData.data?.stations?.[0]?.mixes?.length === 0 || NEXT_MIX_ID_DROPPED_ON_RESOLVING_NEXT_MIX.includes(responseData.data?.stations?.[0]?.mixes?.[0]?.mixId)) {
266
+ if (isNaN(currentMixPosition * 1)) {
267
+ return getFirstOrLastMixMetadata(authToken, stationId, direction, smoothBeats);
268
+ } else {
269
+ if (currentMixPosition + 1 < stationMixCount) {
270
+ return getMixesAfterNMetadata(authToken, stationId, direction, currentMixPosition + 1, smoothBeats);
271
+ } else {
272
+ return getFirstOrLastMixMetadata(authToken, stationId, direction, smoothBeats);
273
+ }
274
+ }
275
+ }
276
+ return responseData;
277
+ } else {
278
+ console.error(`Failed to fetch mix details. ${FETCH_MIX_CONSOLE_TIME}`);
279
+ return null;
280
+ }
281
+ } catch (e) {
282
+ console.error(`Error during mix detail retrieval: ${e}`);
283
+ return null;
284
+ }
285
+ };
286
+ var getFirstOrLastMixMetadata = async (authToken, stationId, direction, smoothBeats = APPLY_BEAT_GRID_SMOOTHING, abortController) => {
287
+ return getMixesAfterNMetadata(authToken, stationId, direction, 0, smoothBeats, abortController);
288
+ };
289
+ var getMixesAfterNMetadata = async (authToken, stationId, direction, mixPosition = 0, smoothBeats = APPLY_BEAT_GRID_SMOOTHING, abortController) => {
290
+ const query = `query MixesToPlay($id: [ID]!, $first: Int, $after: Int, $sort: Sort) {
291
+ stations(ids: $id) {
292
+ stationId
293
+ lastModified
294
+ mixCount
295
+ mixes(first: $first, after: $after, sort: $sort) {
296
+ ...MixItem
297
+ mixPosition
298
+ }
299
+ }
300
+ }
301
+
302
+ ${TRACK_ITEM_FRAGMENT}
303
+
304
+ ${MIX_ITEM_FRAGMENT}`;
305
+ const queryArtist = `query MixesToPlay($id: [ID]!, $first: Int, $after: Int) {
306
+ stations: libraryArtists(ids: $id) {
307
+ artist {
308
+ artistId
309
+ stationId: artistId
310
+ }
311
+ lastModified
312
+ mixCount
313
+ mixes(first: $first, after: $after) {
314
+ ...MixItem
315
+ mixPosition
316
+ }
317
+ }
318
+ }
319
+
320
+ ${TRACK_ITEM_FRAGMENT}
321
+
322
+ ${MIX_ITEM_FRAGMENT}`;
323
+ const variables = { "after": mixPosition, "id": [stationId], "first": 3, "sort": direction === "nextMixId" ? "asc" : "desc" };
324
+ const queryParams = new URLSearchParams({
325
+ query: isStationIdLibraryArtistStation(stationId) ? queryArtist.trim() : query.trim(),
326
+ variables: JSON.stringify(variables),
327
+ operationName: "MixesToPlay"
328
+ });
329
+ const url = `${TRACK_CREATOR_ENDPOINT}/graphql?${queryParams.toString()}`;
330
+ const headers = {
331
+ Authorization: `Bearer ${authToken}`
332
+ };
333
+ FETCH_MIX_CONSOLE_TIME = `Getting mix details... ${stationId}/first_item}`;
334
+ try {
335
+ const response = await fetch(url, {
336
+ method: "GET",
337
+ headers,
338
+ signal: abortController?.signal
339
+ });
340
+ const responseData = await response.json();
341
+ _FM_log("%cnext mix json", "color: magenta", responseData);
342
+ if (response.ok || responseData.data?.stations?.[0]?.mixes && responseData.data?.stations?.[0]?.mixes?.length === 0) {
343
+ _FM_log("Successfully fetched the mix details.");
344
+ _FM_log(responseData);
345
+ responseData.data?.stations?.[0]?.mixes.forEach((m) => m.remixes = []);
346
+ const trackIds = responseData.data?.stations?.[0]?.mixes.map((m) => m.tracks.map((t) => {
347
+ return { id: t.trackId, url: t.url };
348
+ }));
349
+ const tracks = removeDuplicatesById([...trackIds].flat(Infinity));
350
+ const trackBeatsCollection2 = await resolveMultipleBeatsAndKeysWithBeatsLink(tracks, authToken, smoothBeats, window.$currentBeatGridVersion);
351
+ responseData.data?.stations?.[0]?.mixes.forEach((m) => m.tracks.map((t) => {
352
+ const m_t = t;
353
+ if (trackBeatsCollection2.data[t.trackId]) {
354
+ replaceMp4Url(m_t);
355
+ addBeatsRelatedParam(m_t, trackBeatsCollection2.data[t.trackId]);
356
+ m_t.__falseTrack = false;
357
+ m_t.__durationForValidation1 = getFullDurationFromBeatsLinkSegmentInfo(m_t.segmentInfo);
358
+ m_t.__durationForValidation2 = getDurationFromBeatsLinkUpToLastDownbeat0(-1, m_t.beats);
359
+ } else {
360
+ m_t.__falseTrack = true;
361
+ m_t.__durationForValidation1 = 0;
362
+ m_t.__durationForValidation2 = 0;
363
+ }
364
+ return m_t;
365
+ }));
366
+ if (!responseData.data?.stations?.[0]?.stationId) {
367
+ if (responseData.data?.stations?.[0]?.artist?.stationId) {
368
+ responseData.data.stations[0].stationId = responseData.data?.stations?.[0]?.artist?.stationId;
369
+ } else {
370
+ responseData.data.stations[0].stationId = "";
371
+ }
372
+ }
373
+ return responseData;
374
+ } else {
375
+ console.error(`Failed to fetch mix details. ${FETCH_MIX_CONSOLE_TIME}`);
376
+ return null;
377
+ }
378
+ } catch (e) {
379
+ console.timeEnd(FETCH_MIX_CONSOLE_TIME);
380
+ console.error(`Error during mix detail retrieval: ${e}`);
381
+ return null;
382
+ }
383
+ };
384
+ var extractMixIdVer2 = (url) => {
385
+ const pattern = /^https?:\/\/[^/]+\/(?:set|artist)\/([^/]+)\/mix\/([^/?#]+)(?:\/|$|\?)/i;
386
+ const match = url.match(pattern);
387
+ if (match) {
388
+ const mixValue = match[2];
389
+ return mixValue;
390
+ }
391
+ };
392
+ var extractStationIdVer2 = (url) => {
393
+ const pattern = /^https?:\/\/[^/]+\/(?:set|artist)\/([^/]+)\/mix\/([^/?#]+)(?:\/|$|\?)/i;
394
+ const match = url.match(pattern);
395
+ if (match) {
396
+ const mixValue = match[1];
397
+ return mixValue;
398
+ }
399
+ };
400
+ var fetchMixDataWithSharedURL = async (url, smoothBeats = APPLY_BEAT_GRID_SMOOTHING, abortController) => {
401
+ if (!url) {
402
+ return;
403
+ }
404
+ const mixId = extractMixIdVer2(url);
405
+ const stationId = extractStationIdVer2(url);
406
+ if (mixId) {
407
+ const token = await login();
408
+ AEV3Config.getInstance().setAccessToken(token);
409
+ if (token) {
410
+ const fetchedData = await getMixMetadata(token, mixId, stationId, smoothBeats, abortController);
411
+ if (fetchedData) {
412
+ return JSON.stringify(fetchedData);
413
+ } else {
414
+ console.error("Failed to fetch mix data");
415
+ }
416
+ }
417
+ } else {
418
+ console.error("Invalid URL or mixId not found");
419
+ }
420
+ };
421
+ var removeDuplicatesById = (arr) => {
422
+ const seen = /* @__PURE__ */ new Set();
423
+ return arr.filter((item) => {
424
+ if (seen.has(item.id)) {
425
+ return false;
426
+ }
427
+ seen.add(item.id);
428
+ return true;
429
+ });
430
+ };
431
+ var isStationIdLibraryArtistStation = (stationId) => {
432
+ return stationId?.includes("00000014-");
433
+ };
434
+ var replaceMp4Url = (m_t) => {
435
+ const mp4 = m_t.url;
436
+ m_t.url = replaceMp4ForLocalUse(mp4);
437
+ };
438
+ var addBeatsRelatedParam = (m_t, beats) => {
439
+ m_t.beats = beats.beats;
440
+ m_t.tempoOrigSz = calculateTempoOrigSzFromBeatDownBeat(m_t.beats.map((b) => b.timestamp));
441
+ m_t.duration = calculateQuantisedTrackActualDurationFromBeatGrid(-1, m_t.beats);
442
+ m_t.indices = beats.indices;
443
+ m_t.segmentInfo = beats.segmentInfo;
444
+ m_t.key = beats.key;
445
+ m_t.tonality = beats.tonality;
446
+ m_t.beatSource = beats.beatSource;
447
+ return m_t;
448
+ };
449
+
450
+ // src/daw/services/track-search-api.ts
451
+ var API_BASE = import.meta.env?.VITE_KB_STEMIFY_API_URL || "https://api.stemplayer.com";
452
+ var GQL_URL = `${API_BASE}/track-creator/graphql`;
453
+ var FEAT_API_URL = import.meta.env?.VITE_FEAT_API_URL || "https://173i5juztg.execute-api.us-west-2.amazonaws.com";
454
+ var SIMILARITY_MODES = [
455
+ { id: "feature", label: "Audio DNA", description: "Audio fingerprint / text similarity" },
456
+ { id: "remix", label: "Remix Match", description: "Server-side key/BPM/feature matching" }
457
+ ];
458
+ var CAMELOT_MAP = {
459
+ "A:minor": "8A",
460
+ "A:major": "11B",
461
+ "A#:minor": "3A",
462
+ "A#:major": "6B",
463
+ "B:minor": "10A",
464
+ "B:major": "1B",
465
+ "C:minor": "5A",
466
+ "C:major": "8B",
467
+ "C#:minor": "12A",
468
+ "C#:major": "3B",
469
+ "D:minor": "7A",
470
+ "D:major": "10B",
471
+ "D#:minor": "2A",
472
+ "D#:major": "5B",
473
+ "E:minor": "9A",
474
+ "E:major": "12B",
475
+ "F:minor": "4A",
476
+ "F:major": "7B",
477
+ "F#:minor": "11A",
478
+ "F#:major": "2B",
479
+ "G:minor": "6A",
480
+ "G:major": "9B",
481
+ "G#:minor": "1A",
482
+ "G#:major": "4B"
483
+ };
484
+ function getCamelotTag(key, tonality) {
485
+ return CAMELOT_MAP[`${key}:${tonality}`] || `${key}${tonality === "minor" ? "m" : ""}`;
486
+ }
487
+ function parseCamelot(tag) {
488
+ const m = tag.match(/^(\d+)([AB])$/);
489
+ if (!m) return null;
490
+ return { num: parseInt(m[1], 10), letter: m[2] };
491
+ }
492
+ function scoreKeyCompatibility(refKey, refTonality, trackKey, trackTonality) {
493
+ const refTag = getCamelotTag(refKey, refTonality);
494
+ const trkTag = getCamelotTag(trackKey, trackTonality);
495
+ if (refTag === trkTag) return 1;
496
+ const ref = parseCamelot(refTag);
497
+ const trk = parseCamelot(trkTag);
498
+ if (!ref || !trk) return 0;
499
+ const numDist = Math.min(
500
+ Math.abs(ref.num - trk.num),
501
+ 12 - Math.abs(ref.num - trk.num)
502
+ );
503
+ if (numDist === 0 && ref.letter !== trk.letter) return 0.9;
504
+ if (numDist === 1 && ref.letter === trk.letter) return 0.85;
505
+ if (numDist === 1 && ref.letter !== trk.letter) return 0.7;
506
+ if (numDist === 2 && ref.letter === trk.letter) return 0.5;
507
+ return Math.max(0, 0.4 - numDist * 0.05);
508
+ }
509
+ function scoreBpmCompatibility(refBpm, trackBpm) {
510
+ const candidates = [trackBpm, trackBpm * 2, trackBpm / 2];
511
+ let bestPct = Infinity;
512
+ let bestLabel = "1x";
513
+ for (const bpm of candidates) {
514
+ const pct = Math.abs(bpm - refBpm) / refBpm;
515
+ if (pct < bestPct) {
516
+ bestPct = pct;
517
+ bestLabel = bpm === trackBpm ? "1x" : bpm > trackBpm ? "2x" : "\xBDx";
518
+ }
519
+ }
520
+ const score = Math.max(0, 1 - bestPct / 0.15);
521
+ return { score, ratio: bestLabel };
522
+ }
523
+ function computeCompatibility(ref, track) {
524
+ const keyScore = scoreKeyCompatibility(ref.key, ref.tonality, track.key, track.tonality);
525
+ const { score: bpmScore, ratio } = scoreBpmCompatibility(ref.bpm, track.bpm);
526
+ const bpmPct = Math.round((1 - Math.abs(ref.bpm - track.bpm) / ref.bpm) * 100);
527
+ const overall = keyScore * 0.5 + bpmScore * 0.5;
528
+ return {
529
+ bpmPct,
530
+ keyScore: Math.round(keyScore * 100),
531
+ overall: Math.round(overall * 100),
532
+ camelotTag: getCamelotTag(track.key, track.tonality),
533
+ bpmLabel: `${Math.round(track.bpm)} ${ratio !== "1x" ? `(${ratio})` : ""}`.trim()
534
+ };
535
+ }
536
+ var beatsCache = /* @__PURE__ */ new Map();
537
+ async function fetchBeatsRaw(trackId) {
538
+ const cached = beatsCache.get(trackId);
539
+ if (cached) return cached;
540
+ try {
541
+ const token = await getToken();
542
+ const url = `${API_BASE}/track-creator/tracks/${trackId}/beats?min_bars=16`;
543
+ const res = await fetch(url, {
544
+ headers: { Authorization: `Bearer ${token}` }
545
+ });
546
+ if (!res.ok) return null;
547
+ const json = await res.json();
548
+ const d = json?.data;
549
+ if (!d?.bpm || !d?.key) return null;
550
+ const segments = Array.isArray(d.segmentInfo) ? d.segmentInfo : [];
551
+ const durationSec = typeof d.duration === "number" ? d.duration : segments.length > 0 ? segments[segments.length - 1][1] : null;
552
+ const record = {
553
+ info: { bpm: d.bpm, key: d.key, tonality: d.tonality || "minor" },
554
+ segmentInfo: segments,
555
+ durationSec
556
+ };
557
+ beatsCache.set(trackId, record);
558
+ return record;
559
+ } catch {
560
+ return null;
561
+ }
562
+ }
563
+ async function fetchTrackMusicalInfo(trackId) {
564
+ const record = await fetchBeatsRaw(trackId);
565
+ return record ? record.info : null;
566
+ }
567
+ async function fetchTrackSegmentInfo(trackId) {
568
+ const record = await fetchBeatsRaw(trackId);
569
+ if (!record) return null;
570
+ return { segmentInfo: record.segmentInfo, durationSec: record.durationSec };
571
+ }
572
+ async function getToken() {
573
+ const token = await login();
574
+ if (!token) throw new Error("Authentication failed \u2014 check .env credentials");
575
+ AEV3Config.getInstance().setAccessToken(token);
576
+ return token;
577
+ }
578
+ var MIX_URL_PATTERN = /^https?:\/\/[^/]+\/(?:set|artist)\/([^/]+)\/mix\/([^/?#]+)/i;
579
+ var SESSION_TRACK_URL_PATTERN = /^https?:\/\/[^/]+\/session\/([^/]+)\/track\/([^/?#]+)/i;
580
+ function isMixUrl(input) {
581
+ return MIX_URL_PATTERN.test(input.trim());
582
+ }
583
+ function isSessionTrackUrl(input) {
584
+ const t = input.trim();
585
+ return SESSION_TRACK_URL_PATTERN.test(t) || extractBareSessionStationId(t) !== null;
586
+ }
587
+ function isStemUrl(input) {
588
+ const trimmed = input.trim();
589
+ return MIX_URL_PATTERN.test(trimmed) || SESSION_TRACK_URL_PATTERN.test(trimmed) || extractBareSessionStationId(trimmed) !== null;
590
+ }
591
+ var MIX_TRACK_FRAGMENT = `fragment TrackItem on Track {
592
+ trackId
593
+ name
594
+ artistDisplayName
595
+ artwork: image
596
+ artworkGlowColor
597
+ url: url(codec: aac, container: mp4, profile: raw)
598
+ }`;
599
+ var MIX_QUERY = `query GetMix($id: ID!) {
600
+ mix(id: $id) {
601
+ mixId
602
+ name
603
+ tracks { ...TrackItem }
604
+ }
605
+ }
606
+ ${MIX_TRACK_FRAGMENT}`;
607
+ async function fetchTracksFromMixUrl(url) {
608
+ const mixId = extractMixIdVer2(url);
609
+ if (!mixId) throw new Error("Could not extract mix ID from URL");
610
+ const token = await getToken();
611
+ const params = new URLSearchParams({
612
+ query: MIX_QUERY.trim(),
613
+ variables: JSON.stringify({ id: mixId }),
614
+ operationName: "GetMix"
615
+ });
616
+ const res = await fetch(`${GQL_URL}?${params}`, {
617
+ headers: { Authorization: `Bearer ${token}` }
618
+ });
619
+ if (!res.ok) throw new Error(`Mix fetch failed (${res.status})`);
620
+ const json = await res.json();
621
+ const tracks = json?.data?.mix?.tracks;
622
+ if (!Array.isArray(tracks) || tracks.length === 0) {
623
+ throw new Error("Mix contains no tracks");
624
+ }
625
+ return tracks.map((t) => ({
626
+ id: t.trackId,
627
+ title: t.name || "Untitled",
628
+ artist: t.artistDisplayName || "Unknown",
629
+ artwork: t.artwork || null,
630
+ artworkGlowColor: t.artworkGlowColor || null,
631
+ type: "MixTrack"
632
+ }));
633
+ }
634
+ async function fetchTracksFromSessionUrl(url) {
635
+ const { fetchStationTrackDataWithSharedURL } = await import('./fetchStationTracks-SKFT4V3U.js');
636
+ const jsonString = await fetchStationTrackDataWithSharedURL(url);
637
+ if (!jsonString) throw new Error("Could not fetch station track data from session URL");
638
+ const stObj = JSON.parse(jsonString);
639
+ const currentTracks = stObj?.data?.stations?.[0]?.currentTrack ?? [];
640
+ const nextTracks = stObj?.data?.stations?.[0]?.nextTracks ?? [];
641
+ const allEntries = [...currentTracks, ...nextTracks];
642
+ if (allEntries.length === 0) throw new Error("No tracks found for this session link");
643
+ return allEntries.filter((e) => e?.track && !e.track.__falseTrack).map((e) => ({
644
+ id: e.track.trackId,
645
+ title: e.track.name || "Untitled",
646
+ artist: e.track.artistDisplayName || "Unknown",
647
+ artwork: e.track.artwork || e.track.image || null,
648
+ artworkGlowColor: e.track.artworkGlowColor || null,
649
+ type: "StationTrack"
650
+ }));
651
+ }
652
+ var SEARCH_QUERY = `
653
+ query SearchTracks($name: String, $first: Int, $after: Int) {
654
+ searchItems(filter: { name: $name, searchMode: all }, first: $first, after: $after) {
655
+ tracks {
656
+ ... on Track {
657
+ title: name
658
+ id: trackId
659
+ artist: artistDisplayName
660
+ artwork: image
661
+ artworkGlowColor: artworkHighlightColor
662
+ type: __typename
663
+ }
664
+ ... on ExternalSearchTrack {
665
+ title: name
666
+ id: trackId
667
+ artist: artistDisplayName
668
+ artwork: image
669
+ artworkGlowColor
670
+ type: __typename
671
+ }
672
+ }
673
+ }
674
+ }`;
675
+ async function searchTracks(query, limit = 20, offset = 0) {
676
+ const token = await getToken();
677
+ const params = new URLSearchParams({
678
+ query: SEARCH_QUERY.trim(),
679
+ variables: JSON.stringify({ name: query, first: limit, after: offset }),
680
+ operationName: "SearchTracks"
681
+ });
682
+ const res = await fetch(`${GQL_URL}?${params}`, {
683
+ headers: { Authorization: `Bearer ${token}` }
684
+ });
685
+ if (!res.ok) throw new Error(`Search failed (${res.status})`);
686
+ const json = await res.json();
687
+ const tracks = json?.data?.searchItems?.tracks ?? [];
688
+ return tracks.map((t) => ({
689
+ id: t.id,
690
+ title: t.title || "Untitled",
691
+ artist: t.artist || "Unknown",
692
+ artwork: t.artwork || null,
693
+ artworkGlowColor: t.artworkGlowColor || null,
694
+ type: t.type || "Track"
695
+ }));
696
+ }
697
+ var TRACK_PREVIEW_QUERY = `
698
+ query GetTrackPreview($id: ID!) {
699
+ track(id: $id) {
700
+ trackId
701
+ duration
702
+ url: url(codec: aac, container: mp4, profile: raw)
703
+ }
704
+ }`;
705
+ var previewSourcesCache = /* @__PURE__ */ new Map();
706
+ async function fetchTrackPreviewSources(trackId) {
707
+ const cached = previewSourcesCache.get(trackId);
708
+ if (cached) return cached;
709
+ try {
710
+ const token = await getToken();
711
+ const params = new URLSearchParams({
712
+ query: TRACK_PREVIEW_QUERY.trim(),
713
+ variables: JSON.stringify({ id: trackId }),
714
+ operationName: "GetTrackPreview"
715
+ });
716
+ const res = await fetch(`${GQL_URL}?${params}`, {
717
+ headers: { Authorization: `Bearer ${token}` }
718
+ });
719
+ if (!res.ok) return null;
720
+ const json = await res.json();
721
+ const t = json?.data?.track;
722
+ if (!t) return null;
723
+ const record = {
724
+ durationSec: typeof t.duration === "number" ? t.duration : null,
725
+ mp4Url: typeof t.url === "string" ? t.url : null
726
+ };
727
+ previewSourcesCache.set(trackId, record);
728
+ return record;
729
+ } catch {
730
+ return null;
731
+ }
732
+ }
733
+ var TRACK_QUERY = `
734
+ query GetTrack($id: ID!) {
735
+ track(id: $id) {
736
+ trackId
737
+ name
738
+ artistDisplayName
739
+ artwork: image
740
+ artworkGlowColor: artworkHighlightColor
741
+ duration
742
+ url: url(codec: aac, container: mp4, profile: raw)
743
+ colors
744
+ trackMixType
745
+ trackStatus
746
+ isClaimed
747
+ stems {
748
+ aac: url(codec: aac)
749
+ hls: url(codec: hls)
750
+ wav: url(codec: wav)
751
+ mp3: url(codec: mp3)
752
+ stemId
753
+ stemPosition
754
+ }
755
+ }
756
+ }`;
757
+ async function fetchTrackForDAW(trackId) {
758
+ const token = await getToken();
759
+ const params = new URLSearchParams({
760
+ query: TRACK_QUERY.trim(),
761
+ variables: JSON.stringify({ id: trackId }),
762
+ operationName: "GetTrack"
763
+ });
764
+ const res = await fetch(`${GQL_URL}?${params}`, {
765
+ headers: { Authorization: `Bearer ${token}` }
766
+ });
767
+ if (!res.ok) throw new Error(`Track fetch failed (${res.status})`);
768
+ const json = await res.json();
769
+ const t = json?.data?.track;
770
+ if (!t) throw new Error("Track not found");
771
+ const beatsResolved = await resolveSingleBeatsAndKeysWithBeatsLink(
772
+ trackId,
773
+ token,
774
+ true,
775
+ // smooth beats
776
+ null,
777
+ // beatVersion — always server default, never the debug global
778
+ t.url
779
+ );
780
+ const beats = beatsResolved.data[trackId];
781
+ if (!beats) throw new Error(`Beat analysis unavailable for track ${trackId}`);
782
+ const track = t;
783
+ track.trackDisplayName = t.name;
784
+ const mp4Url = t.url;
785
+ if (!mp4Url) {
786
+ throw new Error(`Track ${trackId} has no MP4 URL`);
787
+ }
788
+ track.url = replaceMp4ForLocalUse(mp4Url) ?? mp4Url;
789
+ track.trackMixType = track.trackMixType || "seed";
790
+ track.color1Hex = t.colors?.[0] || t.artworkGlowColor || "";
791
+ track.color2Hex = t.colors?.[1] || "";
792
+ track.webMixChunkUrl = "";
793
+ track.webMixChunkCount = 0;
794
+ track.switchInTimes = [];
795
+ track.beats = beats.beats;
796
+ track.tempoOrigSz = calculateTempoOrigSzFromBeatDownBeat(track.beats.map((b) => b.timestamp));
797
+ track.duration = calculateQuantisedTrackActualDurationFromBeatGrid(-1, track.beats);
798
+ track.indices = beats.indices;
799
+ track.segmentInfo = beats.segmentInfo;
800
+ track.key = beats.key;
801
+ track.tonality = beats.tonality;
802
+ track.beatSource = beats.beatSource;
803
+ track.__falseTrack = false;
804
+ track.__durationForValidation1 = getFullDurationFromBeatsLinkSegmentInfo(track.segmentInfo);
805
+ track.__durationForValidation2 = getDurationFromBeatsLinkUpToLastDownbeat0(-1, track.beats);
806
+ return track;
807
+ }
808
+ function parseFeatureResults(data) {
809
+ if (!Array.isArray(data)) return [];
810
+ return data.filter((t) => t.uuid || t.id).map((t) => ({
811
+ id: t.uuid || t.id,
812
+ title: t.title || "Untitled",
813
+ artist: t.artist || "Unknown",
814
+ artwork: t.artwork || null,
815
+ artworkGlowColor: null,
816
+ type: "SimilarTrack"
817
+ }));
818
+ }
819
+ async function fetchFeaturesByQuery(query, limit) {
820
+ const body = new FormData();
821
+ body.append("query", query);
822
+ body.append("num_results", String(limit));
823
+ body.append("include_pca", "true");
824
+ const res = await fetch(`${FEAT_API_URL}/searchfeatures`, {
825
+ method: "POST",
826
+ body
827
+ });
828
+ if (!res.ok) return [];
829
+ return parseFeatureResults(await res.json());
830
+ }
831
+ async function fetchFeaturesByUUID(uuid, limit) {
832
+ const body = new FormData();
833
+ body.append("uuid", uuid);
834
+ body.append("num_results", String(limit));
835
+ body.append("include_pca", "true");
836
+ const res = await fetch(`${FEAT_API_URL}/searchfeatures`, {
837
+ method: "POST",
838
+ body
839
+ });
840
+ if (!res.ok) return [];
841
+ return parseFeatureResults(await res.json());
842
+ }
843
+ async function fetchSimilarTracksByFeature(trackId, limit = 12) {
844
+ try {
845
+ const uuidResults = await fetchFeaturesByUUID(trackId, limit);
846
+ if (uuidResults.length > 0) return uuidResults;
847
+ const { tracks: sessionTracks } = (await import('./store/daw-session-store.js')).useDAWSessionStore.getState();
848
+ const sessionTrack = sessionTracks.find(
849
+ (t) => (t.trackData?.trackId || t.id) === trackId
850
+ );
851
+ if (sessionTrack) {
852
+ const parts = [sessionTrack.displayName, sessionTrack.artistName].filter(Boolean);
853
+ if (parts.length > 0) {
854
+ return await fetchFeaturesByQuery(parts.join(" "), limit);
855
+ }
856
+ }
857
+ return [];
858
+ } catch (err) {
859
+ console.error("[fetchSimilarTracksByFeature]", err);
860
+ return [];
861
+ }
862
+ }
863
+ var STRICT_POOL_BBOX = {
864
+ maxSemitoneDiff: 1,
865
+ maxBarSizePctDiff: 8,
866
+ allowOppositeTonality: false
867
+ };
868
+ var SIMILAR_TRACKS_QUERY = `
869
+ query SimilarTracks(
870
+ $trackId: ID!,
871
+ $first: Int,
872
+ $after: Int,
873
+ $excludeTrackIds: [ID],
874
+ $strategy: SimilarTracksStrategy,
875
+ $ranking: SimilarTracksRankingInput
876
+ ) {
877
+ similarTracks(
878
+ trackId: $trackId,
879
+ first: $first,
880
+ after: $after,
881
+ excludeTrackIds: $excludeTrackIds,
882
+ strategy: $strategy,
883
+ ranking: $ranking
884
+ ) {
885
+ trackId
886
+ name
887
+ artistDisplayName
888
+ colors
889
+ trackStatus
890
+ duration
891
+ isClaimed
892
+ artwork: image
893
+ artworkGlowColor
894
+ }
895
+ }`;
896
+ function parseSimilarTracksResults(json) {
897
+ const tracks = json?.data?.similarTracks ?? [];
898
+ return tracks.filter((t) => t.trackId).map((t) => ({
899
+ id: t.trackId,
900
+ title: t.name || "Untitled",
901
+ artist: t.artistDisplayName || "Unknown",
902
+ artwork: t.artwork || null,
903
+ artworkGlowColor: t.artworkGlowColor || null,
904
+ type: "RemixMatch"
905
+ }));
906
+ }
907
+ function buildRankingInput(ranking, strategy) {
908
+ const out = {};
909
+ if (ranking) {
910
+ if (ranking.collaborativeStrength !== void 0) {
911
+ out.collaborativeStrength = ranking.collaborativeStrength;
912
+ }
913
+ if (ranking.genreWeight !== void 0) {
914
+ out.genreWeight = ranking.genreWeight;
915
+ }
916
+ if (ranking.prioritizeUserLikes !== void 0) {
917
+ out.prioritizeUserLikes = ranking.prioritizeUserLikes;
918
+ }
919
+ }
920
+ if (strategy === "embedding_rank_with_bbox") {
921
+ Object.assign(out, STRICT_POOL_BBOX);
922
+ }
923
+ return Object.keys(out).length > 0 ? out : void 0;
924
+ }
925
+ async function fetchSimilarTracksByRemix(trackId, limit = 12, excludeTrackIds, offset = 0, args) {
926
+ try {
927
+ const token = await getToken();
928
+ const variables = {
929
+ trackId,
930
+ first: limit,
931
+ after: offset
932
+ };
933
+ if (excludeTrackIds?.length) {
934
+ variables.excludeTrackIds = excludeTrackIds;
935
+ }
936
+ if (args?.strategy) {
937
+ variables.strategy = args.strategy;
938
+ }
939
+ const ranking = buildRankingInput(args?.ranking, args?.strategy);
940
+ if (ranking) {
941
+ variables.ranking = ranking;
942
+ }
943
+ const params = new URLSearchParams({
944
+ query: SIMILAR_TRACKS_QUERY.trim(),
945
+ variables: JSON.stringify(variables),
946
+ operationName: "SimilarTracks"
947
+ });
948
+ const res = await fetch(`${GQL_URL}?${params}`, {
949
+ headers: { Authorization: `Bearer ${token}` }
950
+ });
951
+ if (!res.ok) {
952
+ console.warn(`[fetchSimilarTracksByRemix] similarTracks returned ${res.status}`);
953
+ return [];
954
+ }
955
+ return parseSimilarTracksResults(await res.json());
956
+ } catch (err) {
957
+ console.error("[fetchSimilarTracksByRemix]", err);
958
+ return [];
959
+ }
960
+ }
961
+ async function fetchSimilarTracksFromStation(mixUrl, limit = 8) {
962
+ const stationId = extractStationIdVer2(mixUrl);
963
+ const mixId = extractMixIdVer2(mixUrl);
964
+ if (!stationId || !mixId) return [];
965
+ const token = await login();
966
+ if (!token) return [];
967
+ const seen = /* @__PURE__ */ new Set();
968
+ const results = [];
969
+ const addTracksFromMixes = (mixes) => {
970
+ if (!mixes) return;
971
+ for (const mix of mixes) {
972
+ const tracks = mix?.tracks ?? [];
973
+ for (const t of tracks) {
974
+ if (t?.trackId && !seen.has(t.trackId) && !t.__falseTrack) {
975
+ seen.add(t.trackId);
976
+ results.push({
977
+ id: t.trackId,
978
+ title: t.name || t.trackDisplayName || "Untitled",
979
+ artist: t.artistDisplayName || "Unknown",
980
+ artwork: t.artwork || t.image || null,
981
+ artworkGlowColor: t.artworkGlowColor || null,
982
+ type: t.trackMixType || "Track"
983
+ });
984
+ if (results.length >= limit) return;
985
+ }
986
+ }
987
+ }
988
+ };
989
+ try {
990
+ const nextData = await getNextMixMetadata(
991
+ token,
992
+ mixId,
993
+ stationId,
994
+ "nextMixId",
995
+ -1,
996
+ true
997
+ );
998
+ addTracksFromMixes(nextData?.data?.stations?.[0]?.mixes);
999
+ if (results.length < limit) {
1000
+ const prevData = await getNextMixMetadata(
1001
+ token,
1002
+ mixId,
1003
+ stationId,
1004
+ "prevMixId",
1005
+ -1,
1006
+ true
1007
+ );
1008
+ addTracksFromMixes(prevData?.data?.stations?.[0]?.mixes);
1009
+ }
1010
+ if (results.length < limit) {
1011
+ const fallbackData = await getMixesAfterNMetadata(
1012
+ token,
1013
+ stationId,
1014
+ "nextMixId",
1015
+ 0,
1016
+ true
1017
+ );
1018
+ addTracksFromMixes(fallbackData?.data?.stations?.[0]?.mixes);
1019
+ }
1020
+ return results.slice(0, limit);
1021
+ } catch (err) {
1022
+ console.error("[fetchSimilarTracksFromStation]", err);
1023
+ return [];
1024
+ }
1025
+ }
1026
+
1027
+ export { SIMILARITY_MODES, computeCompatibility, fetchMixDataWithSharedURL, fetchSimilarTracksByFeature, fetchSimilarTracksByRemix, fetchSimilarTracksFromStation, fetchTrackForDAW, fetchTrackMusicalInfo, fetchTrackPreviewSources, fetchTrackSegmentInfo, fetchTracksFromMixUrl, fetchTracksFromSessionUrl, getCamelotTag, isMixUrl, isSessionTrackUrl, isStemUrl, login, searchTracks };
1028
+ //# sourceMappingURL=chunk-56PWIP7O.js.map
1029
+ //# sourceMappingURL=chunk-56PWIP7O.js.map