diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2c342a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Georgi Durankiev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..80ffe15 --- /dev/null +++ b/README.md @@ -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/). diff --git a/design/screenshots/explore-and-shift.png b/design/screenshots/explore-and-shift.png new file mode 100644 index 0000000..da5c664 Binary files /dev/null and b/design/screenshots/explore-and-shift.png differ diff --git a/design/screenshots/explore-shift.png b/design/screenshots/explore-shift.png new file mode 100644 index 0000000..6600c41 Binary files /dev/null and b/design/screenshots/explore-shift.png differ diff --git a/design/screenshots/explore.png b/design/screenshots/explore.png new file mode 100644 index 0000000..c785bab Binary files /dev/null and b/design/screenshots/explore.png differ