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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 | // The instrument layer's fixed tables and the two bits of arithmetic that
// decide what note you hear: the theremin's pentatonic snap and the
// computer-keyboard map (with its octave/fret clamps). Fixtures recorded off
// the real src/game/instruments.ts — the keyboard cases by calling the actual
// onKeyDown and capturing the "nt" frame it broadcast.
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";
const v = JSON.parse(readFileSync(new URL("./vectors/instruments.json", import.meta.url), "utf8"));
test("the tables are byte-identical to the TS ones", () => {
assert.deepEqual([...lib.noteNames], v.noteNames);
assert.deepEqual([...lib.blackKeys], v.black);
assert.deepEqual([...lib.minPent], v.minPent);
assert.deepEqual([...lib.keyrow], v.keyrow);
assert.deepEqual([...lib.padkeys], v.padkeys);
assert.deepEqual([...lib.padNames], v.padNames);
assert.equal(lib.therLo, v.therLo);
assert.equal(lib.therHi, v.therHi);
});
test("pad names are never translated (they are music notation)", () => {
assert.equal(lib.padNames.length, 16);
assert.equal(lib.padNames[0], "KICK");
assert.equal(lib.padNames[15], "CRSH");
});
test("pentatonic snap matches TS across the theremin's whole field", () => {
for (const [midi, want] of v.snapPent) {
assert.equal(lib.snapPent(midi), want, `snapPent(${midi})`);
}
});
test("snapped notes are always minor-pentatonic degrees", () => {
for (const [midi] of v.snapPent) {
const n = lib.snapPent(midi);
assert.ok([...lib.minPent].includes(((n % 12) + 12) % 12), `${midi} -> ${n}`);
}
});
test("the theremin field spans C3..C6 linearly", () => {
assert.equal(lib.therMidi(0), 48);
assert.equal(lib.therMidi(1), 84);
assert.equal(lib.therMidi(0.5), 66);
});
test("the melodic keyboard maps KEYROW onto semitones from the instrument base", () => {
const keyrow = [...lib.keyrow];
for (const c of v.keyboard) {
if (c.myInst !== 0 && c.myInst !== 3) continue;
const idx = keyrow.indexOf(c.key.toLowerCase()); // onKeyDown lowercases
if (idx < 0) {
assert.equal(c.handled, false, `${c.key} is not a melodic key`);
assert.deepEqual(c.emitted, []);
continue;
}
const base = c.myInst === 0 ? lib.grandBase : lib.bassBase;
assert.equal(c.handled, true, c.key);
assert.deepEqual(
c.emitted,
[JSON.stringify({ t: "nt", i: c.myInst, n: base + idx, v: 0.85 })],
`${c.key} at inst ${c.myInst}`,
);
}
});
test("the pad keyboard maps PADKEYS onto pads, one-shot (nt then nf)", () => {
const padkeys = [...lib.padkeys];
for (const c of v.keyboard) {
if (c.myInst !== 1) continue;
const idx = padkeys.indexOf(c.key.toLowerCase()); // onKeyDown lowercases
if (idx < 0) {
assert.equal(c.handled, false, c.key);
continue;
}
assert.equal(c.handled, true, c.key);
assert.deepEqual(
c.emitted,
[JSON.stringify({ t: "nt", i: 1, n: idx, v: 1 }), JSON.stringify({ t: "nf", i: 1, n: idx })],
c.key,
);
}
});
test("the theremin and the audience have no computer keyboard", () => {
for (const c of v.keyboard) {
if (c.myInst === 2 || c.myInst === 4) {
assert.equal(c.handled, false, `inst ${c.myInst} key ${c.key}`);
assert.deepEqual(c.emitted, []);
}
}
});
test("z / x shift the octave (grand) or the fret position (bass), and clamp", () => {
const by = Object.fromEntries(v.octaveShift.map((c) => [c.name, c]));
const noteOf = (c) => JSON.parse(c.lowest[0]).n;
assert.equal(noteOf(by["grand-default"]), lib.grandBase);
assert.equal(noteOf(by["grand-down-one"]), lib.clampGrandBase(lib.grandBase - 12));
assert.equal(noteOf(by["grand-up-one"]), lib.clampGrandBase(lib.grandBase + 12));
assert.equal(noteOf(by["grand-up-two"]), lib.clampGrandBase(lib.grandBase + 24));
assert.equal(noteOf(by["grand-clamps-low"]), 24);
assert.equal(noteOf(by["grand-clamps-high"]), 84);
assert.equal(noteOf(by["bass-default"]), lib.bassBase);
assert.equal(noteOf(by["bass-down-one"]), lib.clampBassBase(lib.bassBase - 12));
assert.equal(noteOf(by["bass-clamps-low"]), 16);
assert.equal(noteOf(by["bass-clamps-high"]), 40);
});
test("the clamps themselves match the TS bounds", () => {
assert.deepEqual([12, 24, 60, 84, 96].map(lib.clampGrandBase), [24, 24, 60, 84, 84]);
assert.deepEqual([0, 16, 28, 40, 52].map(lib.clampBassBase), [16, 16, 28, 40, 40]);
});
test("key repeat is ignored, and a key up releases exactly its own note once", () => {
assert.equal(v.repeatIgnored.handled, false);
assert.equal(v.repeatIgnored.emitted, 0);
assert.deepEqual(v.keyUp, [
JSON.stringify({ t: "nf", i: 0, n: 60 }),
JSON.stringify({ t: "nf", i: 0, n: 64 }),
]);
});
test("the audience seat never puts a note on the wire", () => {
assert.deepEqual(v.audienceSilent, []);
});
|