@ericdisero/aurora-shared 0.1.0 → 0.2.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/dist/db.js +25 -2
- package/dist/extract-catalog.d.ts +81 -0
- package/dist/extract-catalog.js +351 -0
- package/dist/extract.d.ts +35 -0
- package/dist/extract.js +133 -0
- package/dist/jobs.d.ts +4 -1
- package/dist/jobs.js +56 -0
- package/dist/key-detect.d.ts +3 -0
- package/dist/key-detect.js +154 -0
- package/dist/operations/index.js +422 -45
- package/dist/providers/suno.d.ts +57 -0
- package/dist/providers/suno.js +89 -10
- package/dist/skills/content.js +2 -2
- package/dist/storage/assets.d.ts +3 -0
- package/dist/storage/assets.js +7 -0
- package/dist/storage/extractions.d.ts +11 -0
- package/dist/storage/extractions.js +42 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/skills/aurora-cost-discipline.md +4 -2
- package/skills/aurora-suno-prompting.md +47 -16
package/dist/jobs.js
CHANGED
|
@@ -12,6 +12,7 @@ import { downloadTo, fetchGenerationRecord, isGenerationFailure } from './provid
|
|
|
12
12
|
import { fetchSeparationStatus, resolveSeparationStatus } from './providers/mvsep.js';
|
|
13
13
|
import { ensureKindDir, getAsset, insertAsset, uniqueDestPath } from './storage/assets.js';
|
|
14
14
|
import { landSplitJob, finalizeSplit } from './split.js';
|
|
15
|
+
import { failExtractCall, finalizeExtract, landExtractCall, submitNextExtractCall } from './extract.js';
|
|
15
16
|
function jobPath(jobId) {
|
|
16
17
|
return join(getJobsDir(), `${jobId}.json`);
|
|
17
18
|
}
|
|
@@ -121,6 +122,9 @@ export async function advanceJob(m) {
|
|
|
121
122
|
if (fresh.kind === 'split') {
|
|
122
123
|
await advanceSplit(fresh);
|
|
123
124
|
}
|
|
125
|
+
else if (fresh.kind === 'extract') {
|
|
126
|
+
await advanceExtract(fresh);
|
|
127
|
+
}
|
|
124
128
|
else {
|
|
125
129
|
await advanceGeneration(fresh);
|
|
126
130
|
}
|
|
@@ -217,4 +221,56 @@ async function advanceSplit(m) {
|
|
|
217
221
|
const landedCount = jobNames.filter((j) => m.landed[j]).length;
|
|
218
222
|
m.stage = `separating (${landedCount}/3 jobs landed; ${pendingStates.join(', ')})`;
|
|
219
223
|
}
|
|
224
|
+
/** Advance the extract state machine by ONE provider interaction: submit the
|
|
225
|
+
* next planned call, or poll the in-flight one and land its files. A failed
|
|
226
|
+
* call is recorded and skipped (partial results survive — prism behavior);
|
|
227
|
+
* after the last call, EE synthesis + DB persistence finalize the run. */
|
|
228
|
+
async function advanceExtract(m) {
|
|
229
|
+
const state = m.provider.extract;
|
|
230
|
+
if (!state)
|
|
231
|
+
throw new Error('Extract job manifest is incomplete');
|
|
232
|
+
const asset = getAsset(state.assetId);
|
|
233
|
+
if (!asset)
|
|
234
|
+
throw new Error(`Extract source asset no longer exists: ${state.assetId}`);
|
|
235
|
+
const total = state.calls.length;
|
|
236
|
+
// All calls settled → finalize once.
|
|
237
|
+
if (state.callIndex >= total) {
|
|
238
|
+
m.stage = 'building everything-else';
|
|
239
|
+
const rows = await finalizeExtract(asset, state);
|
|
240
|
+
m.stems.push(...rows.map((r) => ({ stemType: r.stemId, path: r.path })));
|
|
241
|
+
m.status = 'done';
|
|
242
|
+
const failNote = state.failures.length > 0 ? `; ${state.failures.length} call(s) failed` : '';
|
|
243
|
+
m.stage = `complete — ${rows.length} stems landed${failNote}`;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const call = state.calls[state.callIndex];
|
|
247
|
+
// No in-flight hash → submit the next call.
|
|
248
|
+
if (!state.currentHash) {
|
|
249
|
+
try {
|
|
250
|
+
await submitNextExtractCall(state);
|
|
251
|
+
m.stage = `submitted ${call.outputType} (${state.callIndex + 1}/${total})`;
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
failExtractCall(state, err instanceof Error ? err.message : String(err));
|
|
255
|
+
m.stage = `${call.outputType} failed at create (${state.callIndex}/${total} done) — continuing`;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Poll the in-flight call once.
|
|
260
|
+
try {
|
|
261
|
+
const status = await fetchSeparationStatus(state.currentHash);
|
|
262
|
+
const files = resolveSeparationStatus(state.currentHash, status);
|
|
263
|
+
if (files) {
|
|
264
|
+
await landExtractCall(state, files);
|
|
265
|
+
m.stage = `${call.outputType} landed (${state.callIndex}/${total} calls)`;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
m.stage = `separating ${call.outputType} (${state.callIndex + 1}/${total}): ${status.status}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
failExtractCall(state, err instanceof Error ? err.message : String(err));
|
|
273
|
+
m.stage = `${call.outputType} failed (${state.callIndex}/${total} done) — continuing`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
220
276
|
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Musical key detection — Krumhansl-Schmuckler, ported from prism's
|
|
2
|
+
// sample_worker.py detect_key (lines 134-215). Pure TS: Hann-windowed STFT
|
|
3
|
+
// (radix-2 FFT, n_fft 4096, hop 1024) over the first 60 seconds, chroma
|
|
4
|
+
// accumulation across 27.5 Hz–4186 Hz (A0–C8), Pearson correlation against the
|
|
5
|
+
// rotated Krumhansl-Kessler major profile. Returns "C major / A minor" form.
|
|
6
|
+
//
|
|
7
|
+
// LOCKSTEP: identical copy of aurora's src/main/extract/key-detect.ts.
|
|
8
|
+
import { decodeWavFile } from './audio/wav.js';
|
|
9
|
+
// Krumhansl-Kessler major key profile (1990).
|
|
10
|
+
const KRUMHANSL_MAJOR = [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88];
|
|
11
|
+
const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
|
12
|
+
const N_FFT = 4096;
|
|
13
|
+
const HOP = N_FFT / 4;
|
|
14
|
+
/** In-place iterative radix-2 complex FFT (re/im of length n, power of 2). */
|
|
15
|
+
function fft(re, im) {
|
|
16
|
+
const n = re.length;
|
|
17
|
+
// Bit-reversal permutation.
|
|
18
|
+
for (let i = 1, j = 0; i < n; i++) {
|
|
19
|
+
let bit = n >> 1;
|
|
20
|
+
for (; j & bit; bit >>= 1)
|
|
21
|
+
j ^= bit;
|
|
22
|
+
j ^= bit;
|
|
23
|
+
if (i < j) {
|
|
24
|
+
const tr = re[i];
|
|
25
|
+
re[i] = re[j];
|
|
26
|
+
re[j] = tr;
|
|
27
|
+
const ti = im[i];
|
|
28
|
+
im[i] = im[j];
|
|
29
|
+
im[j] = ti;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (let len = 2; len <= n; len <<= 1) {
|
|
33
|
+
const ang = (-2 * Math.PI) / len;
|
|
34
|
+
const wr = Math.cos(ang);
|
|
35
|
+
const wi = Math.sin(ang);
|
|
36
|
+
for (let i = 0; i < n; i += len) {
|
|
37
|
+
let cwr = 1;
|
|
38
|
+
let cwi = 0;
|
|
39
|
+
for (let k = 0; k < len / 2; k++) {
|
|
40
|
+
const ur = re[i + k];
|
|
41
|
+
const ui = im[i + k];
|
|
42
|
+
const vr = re[i + k + len / 2] * cwr - im[i + k + len / 2] * cwi;
|
|
43
|
+
const vi = re[i + k + len / 2] * cwi + im[i + k + len / 2] * cwr;
|
|
44
|
+
re[i + k] = ur + vr;
|
|
45
|
+
im[i + k] = ui + vi;
|
|
46
|
+
re[i + k + len / 2] = ur - vr;
|
|
47
|
+
im[i + k + len / 2] = ui - vi;
|
|
48
|
+
const nwr = cwr * wr - cwi * wi;
|
|
49
|
+
cwi = cwr * wi + cwi * wr;
|
|
50
|
+
cwr = nwr;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function pearson(a, b) {
|
|
56
|
+
const n = a.length;
|
|
57
|
+
let ma = 0;
|
|
58
|
+
let mb = 0;
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
ma += a[i];
|
|
61
|
+
mb += b[i];
|
|
62
|
+
}
|
|
63
|
+
ma /= n;
|
|
64
|
+
mb /= n;
|
|
65
|
+
let num = 0;
|
|
66
|
+
let da = 0;
|
|
67
|
+
let db = 0;
|
|
68
|
+
for (let i = 0; i < n; i++) {
|
|
69
|
+
const xa = a[i] - ma;
|
|
70
|
+
const xb = b[i] - mb;
|
|
71
|
+
num += xa * xb;
|
|
72
|
+
da += xa * xa;
|
|
73
|
+
db += xb * xb;
|
|
74
|
+
}
|
|
75
|
+
const den = Math.sqrt(da * db);
|
|
76
|
+
return den > 0 ? num / den : 0;
|
|
77
|
+
}
|
|
78
|
+
/** Detect the musical key of an audio file. Returns "C major / A minor" or
|
|
79
|
+
* null when detection fails (never throws — key is a nice-to-have). */
|
|
80
|
+
export async function detectKey(audioPath) {
|
|
81
|
+
try {
|
|
82
|
+
const wav = await decodeWavFile(audioPath);
|
|
83
|
+
const sr = wav.sampleRate;
|
|
84
|
+
// Mono mix of the first 60 seconds.
|
|
85
|
+
const maxSamples = Math.min(60 * sr, wav.channels[0].length);
|
|
86
|
+
const mono = new Float64Array(maxSamples);
|
|
87
|
+
const chCount = wav.channels.length;
|
|
88
|
+
for (let i = 0; i < maxSamples; i++) {
|
|
89
|
+
let s = 0;
|
|
90
|
+
for (let c = 0; c < chCount; c++)
|
|
91
|
+
s += wav.channels[c][i];
|
|
92
|
+
mono[i] = s / chCount;
|
|
93
|
+
}
|
|
94
|
+
if (maxSamples < N_FFT)
|
|
95
|
+
return null;
|
|
96
|
+
// Hann window.
|
|
97
|
+
const window = new Float64Array(N_FFT);
|
|
98
|
+
for (let i = 0; i < N_FFT; i++)
|
|
99
|
+
window[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / (N_FFT - 1)));
|
|
100
|
+
// Precompute the pitch class per usable bin (27.5 Hz–4186 Hz, A0–C8).
|
|
101
|
+
const binPitchClass = new Int8Array(N_FFT / 2 + 1).fill(-1);
|
|
102
|
+
for (let bin = 0; bin <= N_FFT / 2; bin++) {
|
|
103
|
+
const freq = (bin * sr) / N_FFT;
|
|
104
|
+
if (freq < 27.5 || freq > 4186)
|
|
105
|
+
continue;
|
|
106
|
+
const midi = 12 * Math.log2(freq / 440) + 69;
|
|
107
|
+
binPitchClass[bin] = ((Math.round(midi) % 12) + 12) % 12;
|
|
108
|
+
}
|
|
109
|
+
const chroma = new Float64Array(12);
|
|
110
|
+
const re = new Float64Array(N_FFT);
|
|
111
|
+
const im = new Float64Array(N_FFT);
|
|
112
|
+
for (let start = 0; start + N_FFT <= maxSamples; start += HOP) {
|
|
113
|
+
for (let i = 0; i < N_FFT; i++) {
|
|
114
|
+
re[i] = mono[start + i] * window[i];
|
|
115
|
+
im[i] = 0;
|
|
116
|
+
}
|
|
117
|
+
fft(re, im);
|
|
118
|
+
for (let bin = 0; bin <= N_FFT / 2; bin++) {
|
|
119
|
+
const pc = binPitchClass[bin];
|
|
120
|
+
if (pc < 0)
|
|
121
|
+
continue;
|
|
122
|
+
chroma[pc] += Math.hypot(re[bin], im[bin]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let total = 0;
|
|
126
|
+
for (let i = 0; i < 12; i++)
|
|
127
|
+
total += chroma[i];
|
|
128
|
+
if (total <= 0)
|
|
129
|
+
return null;
|
|
130
|
+
const normalized = [];
|
|
131
|
+
for (let i = 0; i < 12; i++)
|
|
132
|
+
normalized.push(chroma[i] / total);
|
|
133
|
+
// Correlate against the major profile rotated to each key.
|
|
134
|
+
let bestKey = 0;
|
|
135
|
+
let bestCorr = -Infinity;
|
|
136
|
+
for (let shift = 0; shift < 12; shift++) {
|
|
137
|
+
const rotated = [];
|
|
138
|
+
for (let i = 0; i < 12; i++)
|
|
139
|
+
rotated.push(KRUMHANSL_MAJOR[(i - shift + 12) % 12]);
|
|
140
|
+
const corr = pearson(normalized, rotated);
|
|
141
|
+
if (corr > bestCorr) {
|
|
142
|
+
bestCorr = corr;
|
|
143
|
+
bestKey = shift;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const major = NOTE_NAMES[bestKey];
|
|
147
|
+
const minor = NOTE_NAMES[(bestKey + 9) % 12];
|
|
148
|
+
return `${major} major / ${minor} minor`;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=key-detect.js.map
|