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>
This commit is contained in:
khannurien
2026-08-12 13:10:35 +00:00
commit 425eb86327
13 changed files with 1823 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
# SteelSeries Apex 7 × Ableton Live — Scale-Aware Key Lighting
> Handoff brief for a Claude Code agent. Translated and structured from a
> planning conversation. Goal: build the tooling to highlight the notes of the
> currently selected Ableton scale on the physical keyboard's RGB backlight.
## Project goal
Use the per-key RGB backlight of a **SteelSeries Apex 7** to highlight, in real
time, the keys that correspond to the notes of the scale currently selected in
**Ableton Live 12**. The physical keyboard is fitted with transparent keycaps so
the light reads clearly as note highlighting.
The full desired flow:
```
Live 12 (Live API: root_note + scale_intervals)
→ Max for Live device (computes the pitch classes in the scale)
→ Node for Max (POST JSON over HTTP)
→ GameSense server (127.0.0.1)
→ Apex 7 per-key RGB
```
## Hardware notes
- **Keyboard:** SteelSeries Apex 7 — per-key RGB, MX-compatible (cross-stem)
switches, so standard MX keycaps fit.
- **Keycaps:** transparent / "pudding" MX keycaps for light bleed. (Already
swapped; not part of the software build.)
- Requires **SteelSeries GG** running in the background for GameSense to work.
## Key ↔ note mapping (Ableton computer MIDI keyboard)
When Ableton's computer MIDI keyboard is enabled, one octave maps like a piano.
White keys on the bottom row, black keys on the row above:
| Note | Key | Note | Key |
|------|-----|------|-----|
| C | A | F# | T |
| C# | W | G | G |
| D | S | G# | Y |
| D# | E | A | H |
| E | D | A# | U |
| F | F | B | J |
| | | C | K |
`Z` / `X` = octave down/up, `C` / `V` = velocity down/up.
Pitch-class → key lookup (index 011):
```
0 C -> A
1 C# -> W
2 D -> S
3 D# -> E
4 E -> D
5 F -> F
6 F# -> T
7 G -> G
8 G# -> Y
9 A -> H
10 A# -> U
11 B -> J
(12 C -> K, the octave key)
```
## Reading the scale from Live 12
Live 12 exposes the global/selected scale on the `Song` object in the Live
Object Model (LOM), accessible from Max for Live:
- `root_note` — root, integer `011` (0 = C … 11 = B)
- `scale_intervals` — list of ints, e.g. major = `[0, 2, 4, 5, 7, 9, 11]`
- `scale_name` — display name string
- `scale_mode` — whether scale mode is on
Compute the lit pitch classes:
```
lit = { (root_note + interval) % 12 for interval in scale_intervals }
```
Then map each pitch class to its QWERTY key via the table above.
**Caveat:** in Live 12 the scale is set **per clip**. Decide whether to observe
the selected clip's scale or fix a global scale (existing "Scale Awareness" M4L
packs can help enforce a global scale). Observe the relevant LOM property so the
lighting updates when the scale changes.
## Controlling the Apex 7 (SteelSeries GameSense SDK)
GameSense runs a **local REST server**. Flow:
1. Read the server port from `coreProps.json`:
- Windows: `%PROGRAMDATA%\SteelSeries\SteelSeries Engine 3\coreProps.json`
- macOS: `/Library/Application Support/SteelSeries Engine 3/coreProps.json`
- The file gives an address like `127.0.0.1:<port>`.
2. Register a game + event + handler, then POST event data to
`http://127.0.0.1:<port>/game_event` (register via `/game_metadata` and
`/bind_game_event`).
3. Per-key control options:
- **`custom-zone-keys`** — address specific keys by **USB HID usage code**,
with `context-color` to set their color at runtime.
- **`bitmap` mode** — paint the whole keyboard at once (a 132-length array of
`[R,G,B]`, interpreted as a 22×6 grid mapped to nearest keys). Best for
"dim background + bright scale keys" because it sets every key each update.
**HID usage codes** for the relevant letter keys (USB HID keyboard page):
```
A 0x04 S 0x16 D 0x07 F 0x09 G 0x0A H 0x0B J 0x0D K 0x0E
W 0x1A E 0x08 T 0x17 Y 0x1C U 0x18
```
**Caveat:** as soon as a GameSense event arrives, the keyboard enters
"GameSense mode" and other keys go dark unless you set them. Set a base color
for all keys (bitmap mode handles this cleanly), then override the scale keys.
## The bridge: Max for Live → GameSense
Cleanest option is **Node for Max** running inside the M4L device: it can read
`coreProps.json` and issue the HTTP POSTs directly — no separate app needed.
Alternative: Max sends OSC/UDP to a small external Python/Node script that talks
to GameSense.
## Suggested build order
1. Standalone script (Node or Python) that talks to GameSense: register a game,
light a hardcoded set of keys by HID code, set a dim background. Verify on
hardware.
2. M4L device that reads `root_note` / `scale_intervals` from the LOM and prints
the computed key set. Verify against the mapping table.
3. Wire the two together (Node for Max inside the device), observe the scale
property so lighting updates live.
4. Polish: base color, highlight color, octave key, handle scale-mode off.
## Open decisions for the user
- Platform: **Windows or macOS** (changes the `coreProps.json` path).
- Lighting style: **bitmap** (background + highlight) vs. just lighting the
scale keys.
- Scale source: selected clip vs. enforced global scale.
## References
- SteelSeries GameSense SDK (JSON handlers, per-key / bitmap lighting):
https://github.com/SteelSeries/gamesense-sdk
- Live Object Model — `Song` (scale properties):
https://docs.cycling74.com/apiref/lom/song/
- Controlling Live using Max for Live:
https://help.ableton.com/hc/en-us/articles/5402681764242-Controlling-Live-using-Max-for-Live
- Keys and Scales in Live 12 FAQ:
https://help.ableton.com/hc/en-us/articles/11425083250972-Keys-and-Scales-in-Live-12-FAQ