Build step 1 of the Ableton scale-lighting brief: a dependency-free Node script that drives the keyboard directly, with the scale passed on the command line. Live and Max for Live come later. Lighting uses one event with 14 context-color handlers — a background zone covering every key except the 13 note keys, plus one handler per note key addressed by USB HID code. Colors travel in the event frame, so handlers are bound once and a scale change is a single POST, which is what the M4L step will need. Chose custom zones over bitmap mode: bitmap's 22x6 grid has no documented index-to-key table per model, while HID codes are exact. Painting the whole board with the background zone covers the same blackout caveat bitmap was suggested for. tools/fake-gamesense.js stands in for the GameSense server and renders the resolved frame as ANSI colour, so the mapping is verifiable without hardware or Windows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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:
a standalone script that talks to GameSense directly. Live/Max for Live is not
involved yet — you pass the scale on the command line.
No dependencies. Node 14+.
Quick start (Windows, with SteelSeries GG running)
node bin\apex7-scale.js --root C --scale major
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
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
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.
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
Kfollows pitch class 0 (same asA) but is only tinted as the root when C actually is the root.
Testing without hardware
tools/fake-gamesense.js 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.
node tools/fake-gamesense.js --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:
npm test
Layout
bin/apex7-scale.js CLI
src/hid.js USB HID usage codes; the full-board key list
src/scale.js scale presets, root/interval parsing, pitch class -> QWERTY key
src/gamesense.js GameSense REST client (coreProps discovery, heartbeat, cleanup)
src/lighting.js handler + frame construction
src/index.js ScaleLighting — the API step 3 will call from Node for Max
tools/fake-gamesense.js terminal simulator of the GameSense server
test/logic.test.js unit tests
Reusing this from Max for Live (step 3)
src/index.js is the seam — no CLI concerns in it:
const { ScaleLighting } = require('./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.js. Add its HID code there; keys not addressed by any handler go black in GameSense mode.