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