metronome/README.md
Me Here e8945ee1d1 PM_K-1: one-click A/B firmware updates over USB-MIDI (+ version check)
Split the CircuitPython firmware into a tiny stable loader (code.py) + the application (app.py,
carries APP_VERSION). The editor's ⋯ → "⬆ Update firmware" queries the device version (SysEx 0x02
-> 0x03 reply), fetches the latest app from the site (/pico-cp-app.py), shows device-vs-latest, and
pushes the new app.py over USB-MIDI (SysEx 0x20). The device installs it to a trial slot (old build
kept as app.bak), reboots, and the loader AUTO-ROLLS-BACK to app.bak if the new build fails to start;
a build that runs cleanly ~5s is confirmed (clears /trial). No BOOTSEL, no dragging; Chromium/Firefox.
app.py forced to pure ASCII so it pushes raw (no base64); SysEx buffer raised to 60KB.

build.sh/deploy.sh: bundle code.py+app.py and serve /pico-cp-app.py. Docs updated.

Verified in CPython: version reply, update install+reboot+ACK, rollback file dance; editor loads clean
with the updater wired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 06:55:58 -05:00

296 lines
17 KiB
Markdown
Raw 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.

# VARASYS PolyMeter
A small **website** built around one **polymetric groove trainer / metronome** engine.
A landing page is the front door; the main app is the **PM_E1 PolyMeter Editor** — a full
web app where you stack as many "meter lanes" as you like, each its own little metronome
with a grouping, subdivision, drum voice and a perstep pattern with accents. Layering lanes
produces polymeter and true ratio polyrhythm. The same engine drives an everexpanding library
of **formfactor concepts** (idealized and buildable hardware mockups), ships as an
**embeddable widget** anyone can drop into their own page, and even runs as **firmware** on a
real Raspberry Pi Pico build (the **PM_K1 Kit**).
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
Every **deployed page is a single, selfcontained `.html` file****zero dependencies**:
no framework, no CDN libraries, nothing fetched at runtime. They're assembled by a small
build step (`build.sh`) that inlines a shared engine, the seed set lists, base styling and
the brand assets (kept in `assets/`) into each page, so the sources stay lean. Every voice is
**synthesized** in Web Audio — there are no audio samples to load. State (set lists, the
practice log, theme and UI preferences) lives in `localStorage`.
## Pages
The site is **one editor + a gallery of form factors**, and each form factor is split into a
**lean widget page** and a **separate info page**:
- **`<device>.html`** — just the live widget (front view, controls, program box). This is what
`?embed=1` serves and what the landing embeds; it never ships the BOM/narrative.
- **`info-<device>.html`** — the spec page: it embeds the live widget at the top, then the
description, dimensioned drawings and a priced **Bill of Materials** (for the buildable hardware).
| URL | What |
|-----|------|
| [`/`](https://metronome.varasys.io/) `index.html` | **Concepts** — the landing / formfactor gallery; each box embeds the live widget (Open ↗ / Specs & info ⓘ) |
| `/editor.html` · `/info-editor.html` | **PM_E1 — PolyMeter Editor** (the main app) + its overview |
| `/kit.html` · `/info-kit.html` | **PM_K1 Kit** — buildable Raspberry Pi Pico touchscreen unit (52Pi EP0172); info page has the wiring, parts and firmware |
| `/player.html` · `/info-player.html` | **PM_C1 Concept** — idealized concept device (full display + setlist nav, theme, fullscreen "stage" view) |
| `/teacher.html` · `/info-teacher.html` | **PM_T1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument passthrough with analog click injection) |
| `/stage.html` · `/info-stage.html` | **PM_S1 Stage** — footpedal stompbox (two footswitches, expressionpedal in, RGB beat light, instrument passthrough) |
| `/micro.html` · `/info-micro.html` | **PM_P1 Practice** — inline practice bar (instrument in / out passthrough, clickable thumbroller, 14segment display) |
| `/showcase.html` · `/info-showcase.html` | **PM_D1 Display** — pyramid display piece; the pendulum is an RGB light bar combining every lane's subdivisions/accents |
| `/embed.html` · `/embed.js` | embed docs and the dropin loader |
| `/pico-main.py` | the PM_K1 MicroPython firmware (download) |
The buildable units (Teacher, Stage, Practice, Display, Kit) carry a priced BOM on their info
page; the Editor (web app) and Concept have none. Every page shares the same VARASYS header
(official logo with bakedin tagline, nav, theme toggle). The editor also shows a subtle live
**program string** of what's loaded — editable, with copy/paste — under the app (press `Enter`
or paste to apply; see [the share language](#the-share-language)).
Because nothing loads from the network, you can save a 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.
## 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** — every voice is **synthesized** in Web Audio: a friendly drum kit
(`kick`, `snare`, `hatClosed`, …) rendered with the **808 / 909** voices, the 808/909 voices
by name, and electronic/percussion tones. No samples are loaded.
- **Perlane gain** — a dB trim knob per lane (`@<db>` in the share language), applied at
schedule time so changing it never stutters playback.
- **Polyrhythm** — a perlane *poly* toggle fits a lane's beats evenly into lane 1's
bar (e.g. 5over4, 3over2).
- **Euclidean rhythms** — spread *k* hits evenly across *n* steps with `(k,n[,rot])`.
- **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.
- **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, the editor's program box, and a device's program list. 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> ] [ (<k>,<n>[,<rot>]) ] [ = <pattern> ] [ @ <db> ] [ ~ ] [ ! ]
```
- **sound** — a synthesized voice. Friendly kit names (rendered with 808/909):
`beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`, `tomLow`,
`tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`; the drummachine voices
by name — `kick808 snare808 clap808 hat808 openHat808 cowbell808 tom808` and
`kick909 snare909 clap909 hat909 ride909 crash909`; or a **GeneralMIDI note number**
(`36`→kick, `38`→snare, `42`→closed hat, …). 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 — also sets how many **pads** each beat splits into. Append **`s`** for **swing**
on even subdivisions (`2s`, `4s`) to delay the offbeats to a 2:1 triplet feel.
- **`(k,n[,rot])`** — **Euclidean** fill: place `k` hits as evenly as possible across `n`
steps, optionally rotated by `rot`. e.g. `kick:4(3,8)`.
- **`=pattern`** — per**step dynamics**, one char per pad: **`X`** accent, **`x`** normal,
**`g`** ghost (soft), **`.`** `-` `_` mute (rest). Length = beats × `sub`. Omit to get the
default — first step of each beat accented, the rest normal. e.g. `4=.X.X` accents 2 & 4.
- **`@<db>`** — perlane gain trim in decibels, e.g. `@-3` or `@+2`.
- **`~`** — 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 |
| `kick:4(3,8)` | a 3over8 Euclidean kick |
| `claves:5~` | 5 evenly across lane 1's bar (5over4 if lane 1 is `4`) |
| `hat909:4/2@-4` | eighth 909 hats, trimmed 4 dB |
| `kick:2+2+3=x..x..x` | 7/8, kick on each group start |
| **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.
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.
## Embedding
Any form factor can be embedded in another page as a selfsizing widget. Drop in a
container and the loader script — it builds an `<iframe>` to the chromestripped
(`?embed=1`) page, preloads your config string, and autoresizes to the content:
```html
<div data-varasys-metronome="micro"
data-patch="v1;t120;kick:4;snare:4=.X.X;hatClosed:4/2"></div>
<script src="https://metronome.varasys.io/embed.js"></script>
```
- `data-varasys-metronome` — variant: `editor` · `kit` · `initial` · `teacher` · `stage` · `micro` · `showcase`.
- `data-patch` — a [patch string](#patch-grammar) (maps to `#p=`); or `data-setlist`
for a setlist code (maps to `#sl=`).
- `data-width` / `data-height` — optional initial size (default `100%` × `300px`;
height then tracks the widget, which posts `{type:'varasys-h', h}` to the parent).
Prefer your own iframe? `…/<variant>.html?embed=1#p=<patch>` works directly. The
[Concepts landing](index.html) and every `info-*.html` page dogfood this exact mechanism.
See `/embed.html`.
## Build it (hardware) — PM_K1 "Kit"
The **PM_K1 Kit** runs the same engine and program strings on a real device you can build today:
a **Raspberry Pi Pico** on the **52Pi EP0172 "Pico Breadboard Kit Plus"** — a 3.5″ ST7796
320×480 capacitivetouch screen (GT911), a PSP joystick, a WS2812 RGB LED, a buzzer and two
buttons, all prewired. See **`/info-kit.html`** for the pinout, parts (~$45 incl. Pico) and
flashing steps. Firmware lives in **`pico/`**:
- **`pico/main.py`** — singlefile **MicroPython** firmware: an ST7796 driver, GT911 touch,
WS2812 RGB, PWM buzzer, ADC joystick, baked antialiased fonts, and the polymeter engine.
It parses the same program strings as the web editor. Flash MicroPython, copy `main.py`,
edit the `PROGRAMS` list to change grooves. Download: `/pico-main.py`.
- **`pico/gen_font.py`** — generates the baked antialiased fonts (used by both firmwares).
- **`pico-cp/`** — a **CircuitPython** edition (download `/pm_k1_circuitpy.zip`): a selfcontained
appliance. The Pico mounts as a USB drive carrying the firmware + your `programs.json` + an offline
editor, drives a full lanes/pads touchscreen, **logs practice to `history.json`** on the device, takes
set lists **pushed from the editor over USBMIDI** (with a universal downloadanddrag fallback), and
plays **out your computer's speakers over USBMIDI** (the editor's **🎹 Device audio**). By default the
firmware owns the drive (readonly to the computer, so it's protected); hold **button A** at poweron for
editor mode (drive writable). **Firmware updates are one click** from the editor (⋯ → Update firmware) —
pushed over USBMIDI as an A/B update with automatic rollback. The MicroPython build stays the simple,
nocomputer option.
## 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 with a **smooth** cutover at the next **bar**; **`Shift`+`Enter`** is a **rude**
cutover at the next **beat**. `N` / `P` are immediate rude quicksteps. `Esc` cancels.
- **Barlength segments.** Give an item a **bar** count (Timers box, or `b<n>`) and a bar
countdown (▦) shows bars remaining. With **Continue** on, it autoadvances at the bar
boundary — so a *song* is just a set list of segments that hand off seamlessly.
- All transitions keep the clock continuous; the loaded item can live in a set list you're
not currently viewing (the player names it).
## Build
Every page is a source that shares code through `@BUILD:*` markers, so they all 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 every page ships the **same
default set lists**), base styling (`src/base.css`), the site **header/footer/chrome**
(`src/header.html`, `src/footer.html`, `src/chrome.js`), the perdevice **program box**
(`src/progbox.{html,js}`) and the infopage **livewidget embed** (`src/infoembed.{html,js}`).
- `@BUILD:favicon@`, `@BUILD:logo-dark@`, `@BUILD:logo-light@` inline the base64 assets from
`assets/` (the official logos already include the tagline).
`./build.sh` resolves every marker into a selfcontained page in `dist/` (the Concepts landing,
the editor, the device/formfactor pages and their `info-*.html`), copies `embed.js` through
asis, and copies the Pico firmware to `dist/pico-main.py`. `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 **Concepts** landing / gallery (embeds each widget live) |
| `editor.html` | the **PM_E1 editor** app (source, with `@BUILD:*` markers) |
| `kit.html` · `player.html` · `teacher.html` · `stage.html` · `micro.html` · `showcase.html` | the device widget pages (PM_K1 Kit, PM_C1 Concept, Teacher, Stage, PM_P1 Practice, PM_D1 Display) |
| `info-*.html` | performfactor spec pages (embed the live widget + description + dimensions + BOM) |
| `embed.html` · `embed.js` | embed docs and the dropin widget loader |
| `src/` | shared partials inlined into every page: `engine.js`, `setlists.js`, `base.css`, `header.html`, `footer.html`, `chrome.js`, `progbox.{html,js}`, `infoembed.{html,js}` |
| `assets/` | base64 blobs inlined at build (`favicon`, `logo-dark`, `logo-light`) |
| `pico/` | PM_K1 MicroPython firmware: `main.py`, `gen_font.py` (font generator), `README.md` |
| `pico-cp/` | PM_K1 CircuitPython edition: `code.py`, `programs.json`, `font_*.bin`, `README.md` (bundled + served as `/pm_k1_circuitpy.zip`) |
| `build.sh` | resolve markers → selfcontained `dist/` pages (+ `pico-main.py`) |
| `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).
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
All drum and percussion voices are **synthesized in Web Audio** (808/909style and electronic) —
there are no audio samples. The ondevice fonts (PM_K1) are rendered from **DejaVu Sans**.