# Stackable Metronome A browser **polymetric groove trainer / metronome** — and the design mockup for a Raspberry Pi Pico hardware build. Stack as many "meter lanes" as you like; each is its own little metronome with a grouping, subdivision, drum voice and per-beat pattern. Layering lanes produces polymeter and true ratio polyrhythm. **Live:** https://metronome.varasys.io It's a single page (`index.html`) plus a vendored QR library — no build step, no framework. State (presets, set lists, practice log, theme) lives in `localStorage`. ## Features - **Meter lanes** — grouping (odd meters), subdivision, GM drum voice, per‑beat on/off pattern (rests), mute, live measure counter. - **Polyrhythm** — a per‑lane *poly* toggle fits a lane's beats evenly into lane 1's bar (e.g. 5‑over‑4, 3‑over‑2). - **Practice** — gap/mute trainer (play N / mute M bars) and a tempo ramp with a start BPM and signed step. - **Set lists** — named, ordered lists of saved setups; ▶ loads + starts an item, **N** advances; each play is logged for cross‑day comparison. - **Sharing** — copy a link (with QR) to your current settings or a whole set list. - **Theming** — System / Light / Dark. ## The share language A compact, human‑readable text encodes a full configuration (a *patch*). It's what goes in a share link, and you can hand‑write or edit it. ### Patch grammar ``` v1 ; t [; vol] ; ; … [; tr/] [; rmp//] ``` | Token | Meaning | Example | |-------|---------|---------| | `v1` | format version (always first) | `v1` | | `t` | tempo | `t120` | | `vol` | master volume 0–100 | `vol70` | | `tr/` | gap trainer: play N bars, mute M | `tr2/2` | | `rmp//` | tempo ramp: start BPM, ±step, every N bars | `rmp80/5/4` | | `` | a meter lane (see below) | `kick:4` | Tokens are joined with `;`. `tr` and `rmp` are omitted when off. ### Lane grammar ``` : [ / ] [ = ] [ ~ ] [ ! ] ``` - **sound** — one of: `beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`, `tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`, `jamblock` (unknown → `beep`). - **grouping** — beats per bar, optionally grouped for odd meters: `4`, `3`, `2+2+3`. The first beat of each group is accented. - **`/sub`** — subdivision: `1` quarter (default), `2` eighth, `3` triplet, `4` sixteenth, `6` sextuplet. This also sets how many **pads** each beat splits into (a beat becomes `sub` individually‑toggleable steps). Omit for quarter. - **`=pattern`** — per‑**step** on/off as `x`/`.`, length = beats per bar × `sub` (one char per pad). Omit = all on. e.g. `4=.x.x` is a backbeat on 2 & 4; `4/4=x..x..x.x...x...` is a sixteenth‑grid pattern. A short pattern whose length equals just the beat count is still accepted and expanded across each beat's subdivisions (back‑compat). - **`~`** — polyrhythm: fit this lane's beats evenly into **lane 1's** bar. - **`!`** — mute the lane. ### Examples | Patch / lane | What it is | |---|---| | `kick:4` | kick on 4 quarter beats | | `snare:4=.x.x` | snare backbeat (2 & 4) | | `hatClosed:4/2` | eighth‑note hi‑hats | | `claves:5~` | 5 evenly across lane 1's bar (5‑over‑4 if lane 1 is `4`) | | `kick:2+2+3=x..x..x` | 7/8, kick on each group start | | `cowbell:3+2/2` | 5/4 grouped 3+2, eighth subdivision | | **Full:** `v1;t120;kick:4;snare:4=.x.x;hatClosed:4/2;tr2/2` | backbeat groove with gap trainer | ### In URLs - **Settings:** `…/#p=` — readable, e.g. `…/#p=v1;t120;kick:4;claves:5~` - **Set list:** `…/#sl=` — a JSON `{title, description, items[]}` where each item's config is a patch string. Used because titles/notes are free text. Opening such a link applies the settings (or imports the set list) on load, then clears the hash so a refresh won't re‑import. ## Sharing In the set‑list panel's **⋯** menu: - **Share settings link** / **Share set‑list link** open a dialog with the link to **Copy** or **Open**. - **QR ↗** opens a third‑party QR service (api.qrserver.com) with the link in its URL so you can scan it on a phone. A banner warns you it's external — confirm the QR decodes to the shown link before trusting it. (No QR is generated locally.) - **Export all / Import file** back up presets + set lists + logs as a JSON file. ## Keyboard shortcuts | Key | Action | |-----|--------| | `P` | play / stop | | `T` | tap tempo | | `↑` / `↓` | tempo ±1 (`Shift` = ±10) | | `A` | add meter lane | | `1`–`9` | mute lane N | | `R` | toggle the set‑list panel | | `N` | next set‑list item | | `?` | shortcuts help | | `Esc` | close dialog / panel | ## Versioning `VERSION` holds the formal version. `deploy.sh` stamps the served page: - **Formal** — a clean commit tagged `v` → `X.Y.Z`. - **Dev** — anything else → `X.Y.Z-dev..[.dirty]`. Cut a release with `./release.sh [X.Y.Z]` (bumps `VERSION` + tags `v`), then push the tag and deploy. ## Deploy `./deploy.sh` copies `index.html` (version‑stamped) into the Caddy web root and smoke‑tests the live URL. No restart needed (`file_server` picks up changes immediately). ## Files | File | Purpose | |------|---------| | `index.html` | the whole app | | `deploy.sh` | publish to the Caddy web root | | `release.sh` | tag a formal version | | `VERSION` | formal version string |