File size: 6,397 Bytes
345ccf6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
};