A kick-drum card mid-session: notation rendered live,
playhead and per-hit timing feedback overlaid as the player plays.
What it is
A metronome that grew into a practice engine. Patterns are cards;
a spaced-repetition scheduler (SM-2) decides which cards are due;
the player performs on an electronic kit over Web MIDI and the app
scores each bar against the note grid, renders the notation with
VexFlow, adapts the tempo per card, and logs every session to
charts, history and achievements. Eight workout types, 160 pattern
cards, plus song practice pages with a recording feature and MIDI
playback.
How it's built
Web Audio scheduler. A 25 ms lookahead over a 120 ms
horizon, with the audio side strictly separated from the draw
loop. The usual iOS and Chrome audio-unlock traps (resume
ordering, audio-session category, visibility changes, a music
app holding the output) are handled, plus a wake lock.
MIDI scoring. A 120 ms match window, a five-tier adaptive
BPM curve, and the detail that MIDI hits aren't user gestures,
so a one-shot pointer or key event primes the audio context.
Multi-user without a rewrite. The single-user app kept
its integrity-critical writer modules untouched. Sync is a
local-first snapshot-diff engine: an IndexedDB outbox and a
last-writer-wins overlay merge, over a Clojure back end (Ring,
Reitit, next.jdbc, SQLite in WAL mode, 32 migrations, argon2id
logins with invite codes and throttling). EDN and JSON are
negotiated over one store so the SPA and the original pages
coexist.
ClojureScript SPA port. Nine pages brought to visual
parity with replicant, nexus and reitit-frontend, reusing the
original CSS, with shadow-cljs kept off the JVM classpath and a
zero-npm surface.
The dashboard: weekly minutes by workout, the five
hardest cards, recent sessions.The metronome it started as: BPM ladder, bar counts,
subdivisions.
How it's verified
One command runs all three suites: 530 JS tests under Node, 49
back-end tests and 83 ClojureScript tests, all green as of
2026-09-01. Seam tests pin the data shapes passed between
DOM-coupled modules and pure ones.
A headless-Chrome recipe with a storage seed seam lets an agent
screenshot any page and read the result, which is how the
screenshots here were taken. A demo mode swaps storage for a
session shim before any module loads, so a tour never touches
real data.
A review audit closed ten findings: the main practice module
shrank from 3.5 k to 2.7 k lines by extracting song lifecycle,
workout-specific branches fell from 26 to 15, and every
storage access now goes through one module with a parity test
against the storage doc.