# steelseries-live-scale Light the notes of a musical scale on a **SteelSeries Apex 7** using the GameSense SDK, mapped to Ableton Live's computer MIDI keyboard layout. This is **build step 1** of [`apex7-ableton-scale-lighting.md`](./apex7-ableton-scale-lighting.md): a standalone script that talks to GameSense directly. Live/Max for Live is not involved yet — you pass the scale on the command line. TypeScript, no runtime dependencies. Node 16+. ## Quick start (Windows, with SteelSeries GG running) ```powershell npm install # also compiles src\*.ts to dist\ node bin\apex7-scale.js --root C --scale major ``` `npm install` builds via the `prepare` script; after editing any `.ts` file run `npm run build` (or `npm run typecheck` for types only). The board dims, the C-major keys light up (`A S D F G H J K`), the root is orange. The process stays resident and heartbeats so the lighting sticks; you can type new scales at the prompt: ``` F# dorian Bb minor-pentatonic quit ``` Ctrl+C blanks the board and hands lighting back to SteelSeries GG. ### Verify the key mapping on hardware ```powershell node bin\apex7-scale.js --key-test ``` Lights one note key at a time, in order, printing the note it should be. Walk it against the table in the brief — `A`=C, `W`=C#, … `J`=B, `K`=C (octave up). ### Other modes ```powershell node bin\apex7-scale.js --demo 2 # cycle scales, 2s each node bin\apex7-scale.js --intervals 0,3,5,6,7,10 --root A # raw intervals (blues) node bin\apex7-scale.js --once # one frame, then exit node bin\apex7-scale.js --off # blank + deregister node bin\apex7-scale.js --list # known scale names node bin\apex7-scale.js --help ``` Colors: `--bg`, `--color`, `--root-color`, `--off-color`, all `#rrggbb`. ```powershell node bin\apex7-scale.js --root D --scale minor --bg "#020208" --color "#ff00d0" --root-color "#ffffff" ``` ## How the lighting works One GameSense event (`SCALE`) with **14 handlers**, all in `context-color` mode: | Handler | Zone | Frame key | |---|---|---| | background | every key *except* the 13 note keys (93 keys) | `background` | | 13 × note key | one key each, by HID code | `note-a` … `note-k` | Because every handler pulls its color from the event's `frame`, the handlers are bound **once** at startup and a scale change is a single POST. That is what step 3 needs when the scale changes mid-set. Two design notes: - **Zones, not `bitmap`.** The brief suggested bitmap mode. Bitmap's 22×6 grid has no documented index→key table per keyboard model, whereas HID usage codes are exact. Painting *all* keys with the background zone solves the same problem bitmap was suggested for: the moment a GameSense event arrives the board enters GameSense mode and any key you don't address goes black. - **The octave key `K`** follows pitch class 0 (same as `A`) but is only tinted as the root when C actually is the root. ## Testing without hardware `tools/fake-gamesense.ts` stands in for the GameSense server: it accepts the real endpoints, resolves the bound handlers against each frame, and renders the resulting keyboard as ANSI color in the terminal. ```bash npm run stub -- --port 51000 # terminal 1 node bin/apex7-scale.js --address 127.0.0.1:51000 --root F# --scale dorian # terminal 2 ``` `--address` (or `GAMESENSE_ADDRESS`) skips `coreProps.json` discovery entirely, so this works on Linux too. `--dry-run` prints the payloads without sending. Unit tests for the scale math, key mapping, handler shape and frame colors: ```bash npm test ``` ## Layout ``` bin/apex7-scale.js launcher (plain JS) — runs dist/src/cli.js src/cli.ts argument parsing, output, the resident stdin loop src/protocol.ts GameSense wire types (Rgb, Frame, Handler, …) src/hid.ts USB HID usage codes; the full-board key list src/scale.ts scale presets, root/interval parsing, pitch class -> QWERTY key src/gamesense.ts GameSense REST client (coreProps discovery, heartbeat, cleanup) src/lighting.ts handler + frame construction src/errors.ts `catch (err: unknown)` helpers src/index.ts ScaleLighting — the API step 3 will call from Node for Max tools/fake-gamesense.ts terminal simulator of the GameSense server test/logic.test.ts unit tests dist/ compiled CommonJS + .d.ts (gitignored) ``` `tsconfig.json` emits CommonJS at ES2020, because Node for Max loads CJS on a Node version we do not control. ## Reusing this from Max for Live (step 3) `src/index.ts` is the seam — no CLI concerns in it, and it ships type declarations alongside the compiled JS: ```ts import { ScaleLighting } from 'steelseries-live-scale'; // or, from a Node for Max script: require('/dist/src') const lights = new ScaleLighting(); await lights.start(); // register + bind + heartbeat await lights.showScale(0, [0,2,4,5,7,9,11]); // root_note + scale_intervals from the LOM // ... await lights.stop(); ``` `showScale(root, intervals)` takes exactly what Live 12's `Song` object exposes as `root_note` and `scale_intervals`. ## Troubleshooting - **`coreProps.json not found`** — SteelSeries GG isn't running, or is installed somewhere unusual. Expected at `%PROGRAMDATA%\SteelSeries\SteelSeries Engine 3\coreProps.json`. - **Nothing lights up** — check GG's Engine is enabled and that no other app holds an exclusive lighting profile. - **Lighting reverts after ~15s** — that's GameSense's deactivation timeout; it only happens with `--once`, since resident mode heartbeats every 5s. - **A key stays dark** — it may not be in `src/hid.ts`. Add its HID code there; keys not addressed by any handler go black in GameSense mode. - **`apex7-scale is not built yet`** — run `npm run build`.