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>
140 lines
5.0 KiB
Markdown
140 lines
5.0 KiB
Markdown
# 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.
|
||
|
||
No dependencies. Node 14+.
|
||
|
||
## Quick start (Windows, with SteelSeries GG running)
|
||
|
||
```powershell
|
||
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
|
||
|
||
```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.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.
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```js
|
||
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.
|