xiaohan-tian's picture
feat: release S03 B01 W01 U01 models and inference helpers
345ccf6 verified
Raw History Blame Contribute Delete
6.4 kB
const RATE = 16;
const MAX_FRAMES = 2048;
const finite = (x) => typeof x === "number" && Number.isFinite(x);
const integer = (x) => finite(x) && Number.isInteger(x);
const cap = (x, k) => Math.min(Math.max(x, 0), k) / k;
function encodeRequest(r, categories, maxFrames = MAX_FRAMES) {
if (!r || !Array.isArray(r.notes) || !r.controls || !Array.isArray(r.time_signature) || r.time_signature.length !== 2) throw Error("Invalid note request");
const [num, den] = r.time_signature, c = r.controls;
if (!finite(r.tempo_bpm) || r.tempo_bpm <= 0 || !finite(r.bar_origin_beats)) throw Error("Invalid tempo/bar origin");
if (!integer(num) || !integer(den) || num <= 0 || den <= 0 || !Number.isInteger(Math.log2(den))) throw Error("Invalid meter");
if (!integer(c.cc_min) || !integer(c.cc_max) || c.cc_min < 0 || c.cc_max > 127 || c.cc_min > c.cc_max) throw Error("Invalid CC bounds");
if (!(categories.instruments ?? ["string"]).includes(c.instrument) || typeof c.use_velocity !== "boolean") throw Error("Instrument does not match selected model, or invalid velocity control");
const mood = categories.moods.indexOf(c.mood), role = categories.roles.indexOf(c.track_role);
if (mood < 0 || role < 0) throw Error("Unknown mood/role");
for (const n of r.notes) {
if (!n || !integer(n.pitch) || n.pitch < 0 || n.pitch > 127 || !finite(n.start) || !finite(n.end) || n.start < 0 || n.end <= n.start) throw Error("Invalid note");
if ("velocity" in n && (!integer(n.velocity) || n.velocity < 1 || n.velocity > 127)) throw Error("Invalid sounding velocity");
}
const notes = r.notes;
const enabled = c.use_velocity && notes.every((n) => "velocity" in n) && new Set(notes.map((n) => n.velocity)).size > 1;
const noteEnd = notes.reduce((v, n) => Math.max(v, n.end), 0), hardEnd = notes.length ? noteEnd + 1 : 0;
const length = Math.ceil(hardEnd * RATE);
if (!Number.isSafeInteger(length) || length > maxFrames) throw Error(`Request exceeds ${maxFrames} frames`);
const features = new Float32Array(length * 25), rolls = Array.from({ length: 3 }, () => new Float32Array(length * 128));
const lead = new BigInt64Array(length).fill(128n), mask = new Uint8Array(length).fill(1);
const onsets = [...new Set(notes.map((n) => n.start))].sort((a, b) => a - b);
const representatives = /* @__PURE__ */ new Map();
for (const n of notes) representatives.set(n.start, Math.max(representatives.get(n.start) ?? -1, n.pitch));
const group = new Map(onsets.map((s, i) => [s, i])), barLength = num * 4 / den, origin = r.bar_origin_beats;
for (let i = 0; i < length; i++) {
const t = i / RATE, end = Math.min((i + 1) / RATE, hardEnd);
const active = notes.filter((n) => n.start <= t && t < n.end);
const starts = notes.filter((n) => t <= n.start && n.start < end), stops = notes.filter((n) => t <= n.end && n.end < end);
[active, starts, stops].forEach((events, j) => {
for (const n of events) rolls[j][i * 128 + n.pitch] += 1;
});
const f = features.subarray(i * 25, (i + 1) * 25);
f[0] = Number(active.length > 0);
f[1] = Number(starts.length > 0);
f[2] = Number(stops.length > 0);
if (active.length) {
const n = active.reduce((best, v) => v.start > best.start || v.start === best.start && v.pitch > best.pitch ? v : best);
lead[i] = BigInt(n.pitch);
const g = group.get(n.start);
f.set([
enabled ? n.velocity / 127 : 0,
cap(t - n.start, 8),
cap(n.end - t, 8),
cap(n.end - n.start, 8),
(t - n.start) / (n.end - n.start),
g ? (n.pitch - representatives.get(onsets[g - 1])) / 12 : 0,
g + 1 < onsets.length ? (representatives.get(onsets[g + 1]) - n.pitch) / 12 : 0
], 3);
f[16] = active.length / 4;
f[17] = (active.reduce((v, n2) => Math.max(v, n2.pitch), -1) - active.reduce((v, n2) => Math.min(v, n2.pitch), 128)) / 24;
} else {
const previous = notes.filter((n) => n.end <= t), following = notes.filter((n) => n.start > t);
f[10] = previous.length ? cap(t - previous.reduce((v, n) => Math.max(v, n.end), 0), 8) : 0;
f[11] = following.length ? cap(following.reduce((v, n) => Math.min(v, n.start), Infinity) - t, 8) : 0;
}
f.set([
Math.sin(2 * Math.PI * (t - origin)),
Math.cos(2 * Math.PI * (t - origin)),
Math.sin(2 * Math.PI * (t - origin) / barLength),
Math.cos(2 * Math.PI * (t - origin) / barLength)
], 12);
f.set([cap(t, 16), cap(noteEnd - t, 16), cap(hardEnd - t, 16)], 18);
if (starts.length) {
f[21] = (starts.reduce((v, n) => Math.min(v, n.start), Infinity) - t) * RATE;
f[23] = cap(starts.reduce((v, n) => v + (n.end - n.start), 0) / starts.length, 8);
f[24] = enabled ? starts.reduce((v, n) => v + n.velocity, 0) / starts.length / 127 : 0;
}
if (stops.length) f[22] = (stops.reduce((v, n) => Math.min(v, n.end), Infinity) - t) * RATE;
}
const floats = (data, dims) => ({ type: "float32", data, dims });
const category = (v) => ({ type: "int64", data: BigInt64Array.of(BigInt(v)), dims: [1] });
const inputs = {
lead_pitch: { type: "int64", data: lead, dims: [1, length] },
pitch_roll: floats(rolls[0], [1, length, 128]),
onset_roll: floats(rolls[1], [1, length, 128]),
offset_roll: floats(rolls[2], [1, length, 128]),
frame_numeric: floats(features, [1, length, 25]),
mood_id: category(mood),
role_id: category(role),
instrument_id: category((categories.instruments ?? ["string"]).indexOf(c.instrument)),
ctrl_numeric: floats(Float32Array.of(c.cc_min / 127, c.cc_max / 127, r.tempo_bpm / 200, num / 8, den / 8, Number(enabled)), [1, 6]),
mask: { type: "bool", data: mask, dims: [1, length] }
};
for (const t of Object.values(inputs)) if (t.type === "float32" && !t.data.every(Number.isFinite)) throw Error("Nonfinite encoded feature/control");
return { inputs, hardEnd, length, effectiveVelocity: enabled };
}
function expression(r, normalized) {
const lo = r.controls.cc_min, hi = r.controls.cc_max;
return Array.from(normalized, (y, i) => {
if (!Number.isFinite(y) || y < 0 || y > 1) throw Error("Invalid network output");
const value = lo === hi ? lo : Math.fround(Math.fround(y * (hi - lo)) + lo);
return { beat: i / RATE, value, cc1: Math.min(127, Math.max(0, Math.floor(value + 0.5))) };
});
}
export {
MAX_FRAMES,
RATE,
encodeRequest,
expression
};