Files
404-Layers/README.md
ncode 09feb899d6 Drop the Design section from the README
It described the visual language rather than telling a reader anything
they need in order to build, run or understand the app. The fonts it
mentioned are still covered by public/fonts/README.md and the licence
section, so nothing is orphaned by removing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:35:36 +03:00

143 lines
6.1 KiB
Markdown

# 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.
## 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/).