Port phase 1 to TypeScript
Move the standalone GameSense scale lighting to TypeScript ahead of the
Max for Live work, so step 3 gets type declarations at the seam.
Build emits CommonJS at ES2020 into dist/, because Node for Max loads CJS
on a Node version we do not control. bin/apex7-scale.js becomes a plain-JS
launcher so `node bin/apex7-scale.js` keeps working; the CLI itself moves
to src/cli.ts.
Two modules make previously implicit structure explicit:
- src/protocol.ts, the GameSense wire types, so the transport and the
payload construction agree on shapes neither of them owns
- src/errors.ts, since `catch (err)` binds `unknown` under strict
Typing surfaced a few real fixes:
- ScaleLighting.started was a public field the CLI set by hand to make
--off work without start(); that is now release(), with started
private behind isStarted
- readAddress() trusted JSON.parse output; the address is now checked
for being a non-empty string
- res.statusCode is number|undefined, so the old `>= 200` comparison
coerced silently; a missing status now rejects
- parseIntervals claimed to dedupe and sort, and never did (comment
corrected; pitchClassesFor is what dedupes)
Behavior is otherwise unchanged: all 18 tests pass and --once, --off,
--demo, --key-test, the resident stdin loop and the error paths were
verified against tools/fake-gamesense.ts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
44
README.md
44
README.md
@@ -7,14 +7,18 @@ This is **build step 1** of [`apex7-ableton-scale-lighting.md`](./apex7-ableton-
|
||||
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+.
|
||||
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:
|
||||
@@ -78,12 +82,12 @@ Two design notes:
|
||||
|
||||
## Testing without hardware
|
||||
|
||||
`tools/fake-gamesense.js` stands in for the GameSense server: it accepts the
|
||||
`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
|
||||
node tools/fake-gamesense.js --port 51000 # terminal 1
|
||||
npm run stub -- --port 51000 # terminal 1
|
||||
node bin/apex7-scale.js --address 127.0.0.1:51000 --root F# --scale dorian # terminal 2
|
||||
```
|
||||
|
||||
@@ -99,22 +103,31 @@ 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
|
||||
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.js` is the seam — no CLI concerns in it:
|
||||
`src/index.ts` is the seam — no CLI concerns in it, and it ships type
|
||||
declarations alongside the compiled JS:
|
||||
|
||||
```js
|
||||
const { ScaleLighting } = require('./src');
|
||||
```ts
|
||||
import { ScaleLighting } from 'steelseries-live-scale';
|
||||
// or, from a Node for Max script: require('<repo>/dist/src')
|
||||
|
||||
const lights = new ScaleLighting();
|
||||
await lights.start(); // register + bind + heartbeat
|
||||
@@ -135,5 +148,6 @@ as `root_note` and `scale_intervals`.
|
||||
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;
|
||||
- **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`.
|
||||
|
||||
Reference in New Issue
Block a user