- Pad row now shows beatsPerBar × subdivision pads, each individually toggleable (the subdivision control sets pad resolution). Subdivision pads render smaller; downbeats labeled; group/beat gaps preserved. - Mask (beatsOn) is now per-step; playhead tracks the current step. recomputeLane remaps on grouping/subdivision change and migrates legacy per-beat masks (saved data, short share patterns) by expanding across subs. - Share language: =pattern is now per-step (len = beats × sub); short per-beat patterns still accepted and expanded. README updated. - Removed the Sig time-signature preset dropdown (confusing vs subdivision). - Help dialog: link to git.varasys.io/VARASYS/metronome + note that it's a single-page app you can save & run offline, but file:// won't auto-save the set list (export a backup). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
5.4 KiB
Markdown
141 lines
5.4 KiB
Markdown
# 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<bpm> [; vol<pct>] ; <lane> ; <lane> … [; tr<play>/<mute>] [; rmp<start>/<step>/<every>]
|
||
```
|
||
|
||
| Token | Meaning | Example |
|
||
|-------|---------|---------|
|
||
| `v1` | format version (always first) | `v1` |
|
||
| `t<bpm>` | tempo | `t120` |
|
||
| `vol<pct>` | master volume 0–100 | `vol70` |
|
||
| `tr<play>/<mute>` | gap trainer: play N bars, mute M | `tr2/2` |
|
||
| `rmp<start>/<step>/<every>` | tempo ramp: start BPM, ±step, every N bars | `rmp80/5/4` |
|
||
| `<lane>` | a meter lane (see below) | `kick:4` |
|
||
|
||
Tokens are joined with `;`. `tr` and `rmp` are omitted when off.
|
||
|
||
### Lane grammar
|
||
|
||
```
|
||
<sound> : <grouping> [ / <sub> ] [ = <pattern> ] [ ~ ] [ ! ]
|
||
```
|
||
|
||
- **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=<patch>` — readable, e.g.
|
||
`…/#p=v1;t120;kick:4;claves:5~`
|
||
- **Set list:** `…/#sl=<base64url>` — 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<VERSION>` → `X.Y.Z`.
|
||
- **Dev** — anything else → `X.Y.Z-dev.<utc-timestamp>.<short-sha>[.dirty]`.
|
||
|
||
Cut a release with `./release.sh [X.Y.Z]` (bumps `VERSION` + tags `v<VERSION>`),
|
||
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 |
|