# 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 a per-step pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm. **Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome It's a single, self‑contained `index.html` — **zero dependencies**: no framework, no build step, no bundled or CDN libraries, and nothing fetched at runtime. State (set lists, the practice log, theme and UI preferences) lives in `localStorage`. Because nothing loads from the network, you can save the page (`Ctrl`/`⌘`+`S`) and open it straight from disk to run fully offline. One catch from a local `file://`: the browser may not persist `localStorage` between sessions, so use **Export all** (set‑list **⋯** menu) to back up your work. ## Features - **Meter lanes** — grouping (odd meters), subdivision (incl. swing), a drum/percussion voice, per‑**step dynamics** (accent / normal / ghost / mute), mute, live measure counter. - **Sounds** — a sampled acoustic kit plus synthesized **808 / 909** and electronic voices; click each pad to set its dynamics; pick a *swing* subdivision for a triplet feel. - **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; **cue** across lists and commit on a bar/beat boundary with no audible gap (see **Live performance**); each play is logged for cross‑day comparison. - **Sharing** — copy a link 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] [; cd] [; b] ; … [; tr/] [; rmp//] ``` | Token | Meaning | Example | |-------|---------|---------| | `v1` | format version (always first) | `v1` | | `t` | tempo | `t120` | | `vol` | master volume 0–100 | `vol70` | | `cd` | time countdown, seconds (auto-advance with Continue) | `cd60` | | `b` | segment length in bars (auto-advance with Continue) | `b16` | | `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** — the acoustic kit: `beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`, `tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`, `jamblock` (kick / snare / closed‑hat / crash play embedded CC0 samples, the rest are synthesized); plus synthesized drum machines — `kick808 snare808 clap808 hat808 openHat808 cowbell808 tom808` and `kick909 snare909 clap909 hat909 ride909 crash909`. Unknown → `beep`. - **grouping** — beats per bar, optionally grouped for odd meters: `4`, `3`, `2+2+3`. Groups get a visual divider; accents are per‑step (see `=pattern`). - **`/sub`** — subdivision: `1` quarter (default), `2` eighth, `3` triplet, `4` sixteenth, `6` sextuplet. This also sets how many **pads** each beat splits into. Append **`s`** for **swing** on even subdivisions — `2s` (swung eighths) or `4s` (swung sixteenths) delay the off‑beats to a triplet (2:1) feel. Omit for quarter. - **`=pattern`** — per‑**step dynamics**, one char per pad: **`X`** accent, **`x`** normal, **`g`** ghost (soft), **`.`** mute (rest). Length = beats per bar × `sub`. Omit to get the default — the first step of **each beat** accented, the rest normal (click a pad in the UI to cycle accent → normal → ghost → mute). e.g. `4=.X.X` accents the backbeat (2 & 4); `4/2s` is swung eighths with the default accents. (Legacy `x`/`.` on/off patterns and short beat‑count patterns still parse.) - **`~`** — 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` | accented snare backbeat (2 & 4) | | `hatClosed:4/2` | eighth‑note hi‑hats (downbeat of each beat accented) | | `ride:4/2s` | **swung** eighth‑note ride | | `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**. The link encodes everything in the URL — nothing is uploaded. - **Export all / Import file** back up your set lists and practice log as a JSON file (a legacy `presets` field is included for backward compatibility). ## Keyboard shortcuts | Key | Action | |-----|--------| | `Space` | play / stop (works everywhere except while typing in a text field) | | `T` | tap tempo | | `←` / `→` | tempo ±1 (`Shift` = ±10) | | `A` | add meter lane | | `↑` / `↓` / `Home` / `End` | move the **cue** cursor (crosses set lists) | | `PgUp` / `PgDn` | cue the previous / next set list | | `Enter` | commit the cued item — switches on the next **bar** (smooth) | | `Shift`+`Enter` | commit now — switches on the next **beat** (rude) | | `N` / `P` | load next / previous immediately (rude quick‑step) | | `Alt`+`↑` / `Alt`+`↓` | reorder the cued item | | `1`–`9` | enable / silence lane 1–9 | | `?` | shortcuts help | | `Esc` | close the help / share dialog · cancel an armed switch | (Arrow / navigation keys are left alone while a slider or dropdown is focused, so they still adjust it.) ## Live performance The set list is performance-ready: you can line up where you're going next without disturbing what's playing, then commit on a musical boundary — no audible gap. - **Cue, then commit.** The arrows / `Home` / `End` / `PgUp` / `PgDn` move a *cue cursor* (amber outline) through items — across set lists, without loading anything. **`Enter`** commits the cued item with a **smooth** cutover at the next **bar**; **`Shift`+`Enter`** is a **rude** cutover at the next **beat** ("wrong thing playing, fix it now"). `N` / `P` are immediate rude quick‑steps. `Esc` cancels an armed switch. - **Bar‑length segments.** Give an item a **bar** count (Timers box, or the `b` patch token) and a bar countdown (▦) shows bars remaining. With **Continue** on, it auto‑advances to the next item at the bar boundary — so a *song* is just a set list of segments (each with its own tempo, ramp and bar length) that hand off seamlessly. - All transitions — manual or auto, beat or bar — keep the clock continuous; the loaded item can even live in a set list you're not currently viewing (the player names it). ## 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]` — the optional arg bumps & commits `VERSION`; it then tags the current commit `v` (requires a clean tree). Push the tag, then deploy. ## 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 | | `LICENSE` | GNU AGPL v3 license text | ## License Copyright (C) 2026 Varasys. This program is free software: you can redistribute it and/or modify it under the terms of the **GNU Affero General Public License** as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See [`LICENSE`](LICENSE) for the full text. Because the app is served over a network, the AGPL's §13 applies: anyone interacting with a hosted instance must be able to get its source — the public repository is **** (also linked from the in‑app **?** help). ### Credits Acoustic drum one‑shots are from the **[Versilian Community Sample Library (VCSL)](https://github.com/sgossner/VCSL)**, released under **CC0** (public domain) — trimmed and downsampled, embedded inline. The 808/909 voices and the electronic/percussion sounds are synthesized in Web Audio (no samples).