In-app help "Source" link and README now reference codeberg.org/VARASYS/metronome (the public mirror) instead of the internal git.varasys.io. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
193 lines
9.2 KiB
Markdown
193 lines
9.2 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 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<bpm> [; vol<pct>] [; cd<sec>] [; b<bars>] ; <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` |
|
||
| `cd<sec>` | time countdown, seconds (auto-advance with Continue) | `cd60` |
|
||
| `b<bars>` | segment length in bars (auto-advance with Continue) | `b16` |
|
||
| `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** — 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=<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**. 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<n>`
|
||
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<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]` — the optional arg bumps & commits
|
||
`VERSION`; it then tags the current commit `v<VERSION>` (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 repo
|
||
link in the in‑app **?** help satisfies this.
|
||
|
||
### 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).
|