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>
143 lines
6.1 KiB
Markdown
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.
|
|
|
|

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