Add an MIT licence and a README for people who are not me

LICENSE is MIT, copyright Georgi Durankiev.

The README is written for a stranger: what the device hides behind
modifier combos and why that needs an app at all, what each of the four
tabs does, how the Leitner drill works, how to build and install it, and
a map of the source with the domain and UI split explained.

It also records the four things someone reading the code would otherwise
have to discover: that the combo table is generated from reference/ and
must never be typed by hand, that Entry.id and STORAGE_KEY are
load-bearing for migrating progress off the old version, that
touch-action:none and window-level pointerup are what make chorded
answers possible, and that the domain is pure specifically so it can be
tested - which is how a real bug in the weighted card pick surfaced.

Screenshots show Explore with nothing held beside the same screen
holding SHIFT: three functions becoming thirty-five, pads turning from
numerals into FIX VEL, 16 VEL, CUE and CHROMA. That pair explains the
app faster than the prose does. Captured headlessly at the phone's real
360x735, which needed the held layer temporarily patched in source and
restored afterwards.

Notes the data comes from the SP-404MK2 reference manual v5.50 and that
this is unofficial and unaffiliated with Roland, and points at the
bundled OFL font licences.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 21:32:48 +03:00
parent 2b28f27e79
commit c919e160ee
5 changed files with 174 additions and 0 deletions

153
README.md Normal file
View File

@@ -0,0 +1,153 @@
# 404 Layers
A trainer for the Roland SP-404MK2's button combinations, built as a sideloaded
Android app.
The SP-404MK2 has 54 controls and roughly 160 functions. The difference is hidden
behind modifiers: hold `SHIFT` and the pads stop being pads and become
`FIXED VELOCITY`, `16 VELOCITY`, `CUE`, `CHROMATIC`, and so on. Hold `REMAIN` and
they become bus routing. The same physical key does something different in sample
mode, pattern mode, TR-REC, the looper, DJ mode and chromatic mode.
This app draws the panel, lets you hold a modifier, and relabels every control
with what it does on that layer. Then it drills you on them.
![Explore with no modifier held, and the same screen holding SHIFT](design/screenshots/explore-and-shift.png)
Left: sample mode, nothing held — three functions available. Right: the same
screen with `SHIFT` held — thirty-five, and every pad now shows what it becomes.
## What it does
| Tab | |
|---|---|
| **Explore** | Hold or tap a modifier; the panel relabels itself. Tap a lit control to read what it does, where it applies, and its page in the manual. |
| **Drill** | Spaced repetition over the combos. Either "here's a function, press it" — answered by holding the actual chord on the panel — or "here's a combo, name it". |
| **Find** | Search by function, by button, or by mode. Pick a result and it opens on the panel, lit. |
| **Rules** | The nine patterns behind the combos, so you can stop memorising 160 things and learn the handful of rules they follow. |
Drill uses a five-box Leitner system. A right answer promotes a card, a wrong one
sends it back to box one, and the chance of being asked halves with each box — so
the combos you keep missing keep coming back. Progress is kept on the device.
## Status
It works and it's in daily use on one phone. It is **not** on any app store: it's
a debug-signed APK, sideloaded over `adb`.
It was built in two passes, and both are still in the repo:
- **`reference/`** — the original single-file web app: one HTML file, vanilla JS,
no build step. It is **frozen**. It remains the authority on behaviour and on
the combo data, and the React port is checked against it.
- **`src/`** — the current app: React, TypeScript, Vite, wrapped with Capacitor.
## Running it
Needs Node, a JDK, and the Android SDK. The toolchain script sets up the SDK side
on a Debian/Ubuntu machine:
```bash
./scripts/install-android-toolchain.sh # JDK 21, cmdline-tools, platform-tools
npm install
```
Then, with a phone connected and USB debugging on:
```bash
npm run apk # vite build, cap sync, gradle assembleDebug
npm run install:apk # adb install -r
npm run device:launch
```
Or just work in a browser, which is where most of the UI was built:
```bash
npm run dev # http://localhost:5173
```
### Everything else
| Command | |
|---|---|
| `npm test` | The domain suite |
| `npm run typecheck` | `tsc --noEmit` |
| `npm run extract:data` | Regenerate the combo table from `reference/` |
| `npm run icon` | Redraw the launcher icon |
| `npm run apk:phase1` | Build the old wrapper instead of the React app |
## How it's put together
```
reference/ the original app. Frozen: the behaviour and data spec
src/domain/ pure logic, no React, fully tested
raw.ts GENERATED — the 159-row combo table
labels.ts GENERATED — control labels, groups, contexts
rules.ts GENERATED — the nine rules and the pad map
entries.ts derives Entry[] from the raw table
layers.ts which combos exist for a context and held modifiers
drill.ts decks, Leitner boxes, answer checking, card selection
search.ts the Find matcher
storage.ts the persisted blob and backup parsing
format.ts ids to printed words
src/ui/ React. Panel, the four screens, sheets, drill reducer
android/ the Capacitor project
scripts/ toolchain, data extraction, icon, device helpers
```
The split is deliberate: everything in `src/domain/` is pure and has no React in
it, so the interesting logic is testable without rendering anything. The UI layer
decides what a gesture *means*; the domain decides what is *true*.
### Things worth knowing if you're reading the code
**The combo table is never typed by hand.** `scripts/extract-reference-data.mjs`
lifts it out of `reference/index.html`, evaluating only pure-data declarations in
a `vm` with no DOM, and writes `raw.ts`, `labels.ts` and `rules.ts`. Re-run it
after editing the reference; don't edit those three files. Retyping 159 rows is
exactly where silent errors would creep in.
**Two strings are load-bearing.** `Entry.id` (`shift>p1@S#FIX VEL`) and
`STORAGE_KEY` (`sp404mk2-layers-v1`) are unchanged from the original app, so
progress earned in the old version is picked up by this one rather than orphaned.
There's a test that parses a payload copied verbatim off a phone.
**Multitouch is the whole point of the Drill input.** Answering means holding two
or three controls at once, so `Panel` tracks pointers per `pointerId` and grades
when the last finger lifts. Two things make it work and are easy to delete by
accident: `touch-action: none` on the panel, without which the browser claims the
second finger as a pinch; and listening for `pointerup` on the **window**,
because fingers routinely lift outside the key they went down on.
**The domain is pure so it can be tested.** The original mutated module-level
state and called `Math.random()` inline. Here, boxes go in and new boxes come
out, and the random source is injected — which is what made the card selection
testable, and immediately surfaced a real bug in the weighted pick.
`CLAUDE.md` holds the fuller engineering notes, including a number of
Android-specific traps that cost real time.
## Design
The look follows Teenage Engineering's EP-133 manual: flat fills, heavy outlines,
hard zero-blur shadows, one orange accent that means exactly one thing (this
control is live), and monospaced technical annotation. The panel is laid out from
the hardware — the control groups carry the same silkscreen captions as the
device, and the screen is a circular housing behind a rectangular LCD.
Type is Space Grotesk and Space Mono, self-hosted so the app works with no
network.
## Data
The combo data is derived from the SP-404MK2 reference manual (v5.50): control
names, short labels, one-line descriptions, and page numbers for looking things
up. Roland owns the device, its manual and its trademarks; this is an unofficial
tool and has nothing to do with them.
## Licence
MIT — see [LICENSE](LICENSE).
The bundled fonts are under the SIL Open Font License, with their licence texts
in [`public/fonts/`](public/fonts/).