metronome/README.md
Me Here b548c64d2a Share engine, seed set lists & base CSS between editor and player
Both index.html (editor) and player.html (hardware-player mockup) now pull
their common code from src/ via a new build-time include marker
(/*@BUILD:include:src/…@*/), resolved by build.sh:

  src/engine.js   — audio voices (DRUMS×30), Web Audio scheduler primitives,
                    and the share-language codec (patch/set-list encode+decode)
  src/setlists.js — SEED_SETLISTS, so the player ships the SAME default set
                    lists as the editor (player BUILTIN = SEED_SETLISTS)
  src/base.css    — reset + VARASYS brand palette + type stack

The editor inlines the CC0 acoustic samples; the player passes an empty
SAMPLES object and the shared playInstrument falls back to its synth voices,
so the device stays faithfully synth-only. Each app keeps its own state
globals, setBpm, advanceMaster/scheduler, and UI. ~400 lines of duplicated
engine code removed; the player's favicon is now the shared @BUILD:favicon@.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:49:35 -05:00

220 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Stackable Metronome
A browser **polymetric groove trainer / metronome** — a full web app, and the
reference design for a Raspberry Pi Pico hardware build (whose playonly device is
mocked up at [`player.html`](player.html)). Stack as many "meter lanes" as you like;
each is its own little metronome with a grouping, subdivision, drum voice and a
perstep pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
The **deployed page is a single, selfcontained `index.html`****zero dependencies**:
no framework, no CDN libraries, nothing fetched at runtime. It's assembled by a small
build step (`build.sh`) that inlines the audio samples + brand assets (kept in `assets/`)
into the HTML, so the source stays lean. 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**
(setlist **⋯** 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 perlane *poly* toggle fits a lane's beats evenly into lane 1's
bar (e.g. 5over4, 3over2).
- **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 crossday comparison.
- **Sharing** — copy a link to your current settings or a whole set list.
- **Theming** — System / Light / Dark.
## The share language
A compact, humanreadable text encodes a full configuration (a *patch*). It's what
goes in a share link, and you can handwrite 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 0100 | `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, closedhat, crash, toms, tambourine,
cowbell, woodblock and claves play embedded CC0 samples; `beep`, `clap`, `rim`,
`hatOpen` and `ride` stay synthesized — VCSL has no clean source for those); 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 perstep (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 offbeats 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 beatcount 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` | eighthnote hihats (downbeat of each beat accented) |
| `ride:4/2s` | **swung** eighthnote ride |
| `claves:5~` | 5 evenly across lane 1's bar (5over4 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 reimport.
## Sharing
In the setlist panel's **⋯** menu:
- **Share settings link** / **Share setlist 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 quickstep) |
| `Alt`+`↑` / `Alt`+`↓` | reorder the cued item |
| `1``9` | enable / silence lane 19 |
| `?` | 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 quicksteps. `Esc` cancels an armed switch.
- **Barlength 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
autoadvances 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).
## Build
`index.html` (the editor) and `player.html` (the hardwareplayer mockup) are both
sources that share code through `@BUILD:*` markers, so the two stay in sync:
- `/*@BUILD:include:src/…@*/` inlines a **shared partial** — the audio/scheduler
engine (`src/engine.js`), the seed set lists (`src/setlists.js`, so the player
ships the **same default set lists** as the editor), and base styling
(`src/base.css`: reset + brand palette + type).
- `@BUILD:favicon@`, `@BUILD:logo-*@`, `/*@BUILD:samples@*/{}` inline the base64
assets from `assets/`.
`./build.sh` resolves every marker into a selfcontained `dist/index.html` +
`dist/player.html` (the editor inlines the CC0 samples; the player passes an empty
`SAMPLES` for pure synth). `dist/` is generated, gitignored — don't edit it by hand.
`deploy.sh` runs the build first, so a deploy always serves freshly assembled pages.
## Versioning
`VERSION` holds the formal version. `deploy.sh` builds, then 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 editor app (source, with `@BUILD:*` markers) |
| `player.html` | the playonly hardwaredevice mockup (`/player.html`) |
| `src/` | shared partials inlined into both: `engine.js`, `setlists.js`, `base.css` |
| `assets/` | base64 blobs inlined at build (samples, logos, favicon) |
| `build.sh` | resolve markers → selfcontained `dist/` pages |
| `deploy.sh` | build, then 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 **<https://codeberg.org/VARASYS/metronome>** (also linked from the
inapp **?** help).
### Credits
Acoustic drum oneshots 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).