Compare commits

..

7 commits

Author SHA1 Message Date
Me Here
3213c6afe4 Remove QR sharing; README accuracy pass
Drop the QR-code share button, its external-service warning banner/CSS,
and the api.qrserver.com handler — sharing is now copy/open link only, so
the app makes no external requests at all. README: remove QR references
and the Deploy section, refresh Features/sounds/dynamics/swing, list the
808/909 voices, and note which acoustic voices use CC0 samples.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:54:19 -05:00
Me Here
06986e83aa Embed CC0 acoustic samples (VCSL) — kick/snare/hat/crash slice
Sample-playback engine: short one-shots are embedded as base64 WAV,
decoded to AudioBuffers at audio start; playInstrument plays the sample
when present, else falls back to synthesis. First slice covers kick,
snare, closed hat and crash from the Versilian Community Sample Library
(CC0), trimmed to mono 22.05kHz/16-bit (~117KB). More acoustic voices to
follow once the quality/size is confirmed by ear. Credit added to README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:29:29 -05:00
Me Here
e07b4bc42d Add synthesized 808/909 drum-machine voices
These machines are synthesizers in reality, so synthesis is the faithful
approach. Adds 808 (kick/snare/clap/hat/open hat/cowbell/tom) and 909
(kick/snare/clap/hat/ride/crash) voices via a shared metallic-hat helper
(6 detuned squares → bandpass+highpass) plus tuned tone/noise envelopes.
Acoustic kit will move to CC0 samples (VCSL) separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:23:48 -05:00
Me Here
d3403df6c2 Add ghost-note dynamics level (4 levels) + help on local/sharing
Pads now cycle accent → normal → ghost → mute. Ghost is a soft hit
(gain 0.25) — added as new level value 3 so set lists already saved at
the 3-level stage (0/1/2 = mute/normal/accent) keep their meaning with
no migration. Share pattern gains a "g" char; the Purdie shuffle's snare
ghosts now use it. Ghost pads render faint with a · marker.

Help: explain that set lists/items/log live only in localStorage and how
to move or share them (Share set-list link / Share settings link /
Export-Import).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 11:58:33 -05:00
Me Here
8171b37e72 Split demo seed into Styles + Practice; add shuffle/samba/nañigo
Two seed set lists instead of one mixed "Demos":
- 🥁 Styles — grooves/feels: four-on-the-floor, swing ride, Purdie
  half-time shuffle (triplet grid, backbeat on 3, snare ghosts), Samba
  (2/4, surdo on 2), Nañigo (6/8 bembé bell), 6/8, 7/8, 5/4.
- 🎯 Practice — polyrhythms, triplet hats, accent demo, tempo builder,
  gap trainer.

Seed is now versioned + additive: a bumped SEED_VERSION adds any seed
list whose title isn't already present, without clobbering or re-adding
the user's lists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 11:51:45 -05:00
Me Here
766d5d40ae Per-step accent/normal/mute dynamics + swing
Pads are now 3-state instead of on/off: click cycles accent → normal →
mute. Default keeps the first step of each beat accented (the rest
normal), so existing grooves are unchanged in feel; legacy on/off masks
migrate (on-downbeats → accent, on-subs → normal).
- Audio gain is driven per step by level (accent 1.0 / normal 0.6);
  the old auto group-start accent is replaced by explicit per-step level.
- Swing: "swing 8th / swing 16th" subdivision options apply a triplet
  (2:1) long–short feel to even subdivisions (per-lane).
- Share language: pattern uses X (accent) / x (normal) / . (mute), and
  the sub token takes a trailing s for swing (e.g. ride:4/2s). The
  default-accent pattern is omitted; legacy x/. still parse.
- Demos: "Swing ride" and an accents example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 11:13:53 -05:00
Me Here
7a6aa1d5ba Live-performance set switching, bar-length segments, favicon
Cue/commit model for moving through a set without audible gaps:
- Arrows/Home/End/PgUp/PgDn move an amber cue cursor across set lists.
  Enter commits with a SMOOTH cutover at the next bar; Shift+Enter a
  RUDE cutover at the next beat. N/P are rude quick-steps; Esc cancels.
- One gap-free cutover (armSwitch → scheduler horizon-cap →
  performCutover) replaces the old stop()/start() switch — the audio
  clock stays continuous across every transition (manual or auto).
- Per-item bar length (b<n> patch token + Bars input) with a bar
  countdown; Continue auto-advances at the boundary (cross-list aware).
  Each segment owns its tempo/ramp and resets at the cut.
- Decouple loaded vs viewed list (loadedSL) so the playing item can
  live in a list you're not currently viewing.

Set-list panel: editable name combobox (rename in place; the ▾ menu
lists the set lists with "+ New" last), auto-growing description,
add-item row moved below the list, trimmed hint.

Add an inline SVG favicon (brand-cyan metronome on navy).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:31:36 -05:00
2 changed files with 455 additions and 192 deletions

101
README.md
View file

@ -2,8 +2,8 @@
A browser **polymetric groove trainer / metronome** — and the design mockup for a 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 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 its own little metronome with a grouping, subdivision, drum voice and a per-step
pattern. Layering lanes produces polymeter and true ratio polyrhythm. pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
**Live:** https://metronome.varasys.io **Live:** https://metronome.varasys.io
@ -18,17 +18,18 @@ the browser may not persist `localStorage` between sessions, so use **Export all
## Features ## Features
- **Meter lanes** — grouping (odd meters), subdivision, a synthesized drum/percussion voice, perbeat - **Meter lanes** — grouping (odd meters), subdivision (incl. swing), a drum/percussion
on/off pattern (rests), mute, live measure counter. 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 - **Polyrhythm** — a perlane *poly* toggle fits a lane's beats evenly into lane 1's
bar (e.g. 5over4, 3over2). bar (e.g. 5over4, 3over2).
- **Practice** — gap/mute trainer (play N / mute M bars) and a tempo ramp with a - **Practice** — gap/mute trainer (play N / mute M bars) and a tempo ramp with a
start BPM and signed step. start BPM and signed step.
- **Set lists** — named, ordered lists of saved setups; click an item to load it - **Set lists** — named, ordered lists of saved setups; **cue** across lists and commit
(it switches live if you're already playing), **N** loads the next; each play is on a bar/beat boundary with no audible gap (see **Live performance**); each play is
logged for crossday comparison. logged for crossday comparison.
- **Sharing** — copy a link to your current settings or a whole set list (with an - **Sharing** — copy a link to your current settings or a whole set list.
optional QR generated by an external service).
- **Theming** — System / Light / Dark. - **Theming** — System / Light / Dark.
## The share language ## The share language
@ -39,7 +40,7 @@ goes in a share link, and you can handwrite or edit it.
### Patch grammar ### Patch grammar
``` ```
v1 ; t<bpm> [; vol<pct>] ; <lane> ; <lane> … [; tr<play>/<mute>] [; rmp<start>/<step>/<every>] v1 ; t<bpm> [; vol<pct>] [; cd<sec>] [; b<bars>] ; <lane> … [; tr<play>/<mute>] [; rmp<start>/<step>/<every>]
``` ```
| Token | Meaning | Example | | Token | Meaning | Example |
@ -47,6 +48,8 @@ v1 ; t<bpm> [; vol<pct>] ; <lane> ; <lane> … [; tr<play>/<mute>] [; rmp<start>
| `v1` | format version (always first) | `v1` | | `v1` | format version (always first) | `v1` |
| `t<bpm>` | tempo | `t120` | | `t<bpm>` | tempo | `t120` |
| `vol<pct>` | master volume 0100 | `vol70` | | `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` | | `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` | | `rmp<start>/<step>/<every>` | tempo ramp: start BPM, ±step, every N bars | `rmp80/5/4` |
| `<lane>` | a meter lane (see below) | `kick:4` | | `<lane>` | a meter lane (see below) | `kick:4` |
@ -59,20 +62,24 @@ Tokens are joined with `;`. `tr` and `rmp` are omitted when off.
<sound> : <grouping> [ / <sub> ] [ = <pattern> ] [ ~ ] [ ! ] <sound> : <grouping> [ / <sub> ] [ = <pattern> ] [ ~ ] [ ! ]
``` ```
- **sound** — one of: - **sound** — the acoustic kit: `beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`,
`beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`, `hatOpen`, `ride`, `crash`, `tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`,
`tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`, `woodblock`, `claves`, `jamblock` (kick / snare / closedhat / crash play embedded
`jamblock` (unknown → `beep`). 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`, - **grouping** — beats per bar, optionally grouped for odd meters: `4`, `3`,
`2+2+3`. The first beat of each group is accented. `2+2+3`. Groups get a visual divider; accents are perstep (see `=pattern`).
- **`/sub`** — subdivision: `1` quarter (default), `2` eighth, `3` triplet, - **`/sub`** — subdivision: `1` quarter (default), `2` eighth, `3` triplet,
`4` sixteenth, `6` sextuplet. This also sets how many **pads** each beat splits `4` sixteenth, `6` sextuplet. This also sets how many **pads** each beat splits
into (a beat becomes `sub` individuallytoggleable steps). Omit for quarter. into. Append **`s`** for **swing** on even subdivisions — `2s` (swung eighths) or
- **`=pattern`** — per**step** on/off as `x`/`.`, length = beats per bar × `sub` `4s` (swung sixteenths) delay the offbeats to a triplet (2:1) feel. Omit for quarter.
(one char per pad). Omit = all on. e.g. `4=.x.x` is a backbeat on 2 & 4; - **`=pattern`** — per**step dynamics**, one char per pad: **`X`** accent, **`x`**
`4/4=x..x..x.x...x...` is a sixteenthgrid pattern. A short pattern whose length normal, **`g`** ghost (soft), **`.`** mute (rest). Length = beats per bar × `sub`. Omit to get the
equals just the beat count is still accepted and expanded across each beat's default — the first step of **each beat** accented, the rest normal (click a pad in
subdivisions (backcompat). 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. - **`~`** — polyrhythm: fit this lane's beats evenly into **lane 1's** bar.
- **`!`** — mute the lane. - **`!`** — mute the lane.
@ -81,8 +88,9 @@ Tokens are joined with `;`. `tr` and `rmp` are omitted when off.
| Patch / lane | What it is | | Patch / lane | What it is |
|---|---| |---|---|
| `kick:4` | kick on 4 quarter beats | | `kick:4` | kick on 4 quarter beats |
| `snare:4=.x.x` | snare backbeat (2 & 4) | | `snare:4=.X.X` | accented snare backbeat (2 & 4) |
| `hatClosed:4/2` | eighthnote hihats | | `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`) | | `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 | | `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 | | `cowbell:3+2/2` | 5/4 grouped 3+2, eighth subdivision |
@ -102,10 +110,7 @@ clears the hash so a refresh won't reimport.
In the setlist panel's **⋯** menu: In the setlist panel's **⋯** menu:
- **Share settings link** / **Share setlist link** open a dialog with the link to - **Share settings link** / **Share setlist link** open a dialog with the link to
**Copy** or **Open**. **Copy** or **Open**. The link encodes everything in the URL — nothing is uploaded.
- **QR ↗** opens a thirdparty 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 your set lists and practice log as a JSON - **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). file (a legacy `presets` field is included for backward compatibility).
@ -115,15 +120,36 @@ In the setlist panel's **⋯** menu:
|-----|--------| |-----|--------|
| `Space` | play / stop (works everywhere except while typing in a text field) | | `Space` | play / stop (works everywhere except while typing in a text field) |
| `T` | tap tempo | | `T` | tap tempo |
| `↑` / `↓` | tempo ±1 (`Shift` = ±10) | | `←` / `→` | tempo ±1 (`Shift` = ±10) |
| `A` | add meter lane | | `A` | add meter lane |
| `N` | load next setlist item | | `↑` / `↓` / `Home` / `End` | move the **cue** cursor (crosses set lists) |
| `Alt`+`↑` / `Alt`+`↓` | reorder the selected setlist item | | `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 | | `1``9` | enable / silence lane 19 |
| `?` | shortcuts help | | `?` | shortcuts help |
| `Esc` | close the help / share dialog | | `Esc` | close the help / share dialog · cancel an armed switch |
(Arrow keys are left alone while a slider or dropdown is focused, so they still adjust it.) (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).
## Versioning ## Versioning
@ -136,12 +162,6 @@ 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). `VERSION`; it then tags the current commit `v<VERSION>` (requires a clean tree).
Push the tag, then deploy. Push the tag, then deploy.
## Deploy
`./deploy.sh` copies `index.html` (versionstamped) into the Caddy
web root and smoketests the live URL. No restart needed (`file_server` picks up
changes immediately).
## Files ## Files
| File | Purpose | | File | Purpose |
@ -164,3 +184,10 @@ later version. See [`LICENSE`](LICENSE) for the full text.
Because the app is served over a network, the AGPL's §13 applies: anyone 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 interacting with a hosted instance must be able to get its source. The repo
link in the inapp **?** help satisfies this. link in the inapp **?** help satisfies this.
### 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).

File diff suppressed because one or more lines are too long