game6 / client / test / instruments.test.mjs
  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, []);
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/game6.git