Files
steelseries-live-scale/README.md
khannurien 425eb86327 Add standalone GameSense scale lighting for the Apex 7
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>
2026-08-12 13:10:35 +00:00

5.0 KiB
Raw Blame History

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-anote-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.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.