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 };