Download inference/encoder.mjs from KGAudioLab/instrument-aire-models: direct link, hf CLI and curl.
- Browser
- Download file 6.4 kB
-
https://huggingface.co/KGAudioLab/instrument-aire-models/resolve/main/inference/encoder.mjs
- Command line
-
hf download hf://KGAudioLab/instrument-aire-models/inference/encoder.mjs
-
curl -L -o encoder.mjs https://huggingface.co/KGAudioLab/instrument-aire-models/resolve/main/inference/encoder.mjs
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 | |
| }; | |