README was stale (pre-dating several shipped changes). Rewrite it to match reality:
- voices are all synthesized now (KIT_ALIAS -> 808/909); drop the VCSL samples
narrative, the @BUILD:samples wording, and the sample credits.
- document the lean-widget + separate info-<device>.html page model (was 'Open=Info').
- add kit.html + all info-*.html to the Pages table; add the new src/ partials
(header/footer/chrome/progbox/infoembed), pico/ firmware, and pico-main.py to Files/Build.
- document the share-grammar additions: GM note numbers, Euclidean (k,n[,rot]), per-lane @<db> gain.
- add a 'Build it (hardware)' section for the PM_K-1 Kit + MicroPython firmware.
embed.js: add the 'kit' form factor to the variant map (it previously fell back to micro),
so data-varasys-metronome="kit" embeds kit.html as the README now documents.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
286 lines
16 KiB
Markdown
286 lines
16 KiB
Markdown
# 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_E‑1 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 per‑step pattern with accents. Layering lanes
|
||
produces polymeter and true ratio polyrhythm. The same engine drives an ever‑expanding library
|
||
of **form‑factor 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_K‑1 Kit**).
|
||
|
||
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
|
||
|
||
Every **deployed page is a single, self‑contained `.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 / form‑factor gallery; each box embeds the live widget (Open ↗ / Specs & info ⓘ) |
|
||
| `/editor.html` · `/info-editor.html` | **PM_E‑1 — PolyMeter Editor** (the main app) + its overview |
|
||
| `/kit.html` · `/info-kit.html` | **PM_K‑1 Kit** — buildable Raspberry Pi Pico touchscreen unit (52Pi EP‑0172); info page has the wiring, parts and firmware |
|
||
| `/player.html` · `/info-player.html` | **PM_C‑1 Concept** — idealized concept device (full display + set‑list nav, theme, fullscreen "stage" view) |
|
||
| `/teacher.html` · `/info-teacher.html` | **PM_T‑1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument pass‑through with analog click injection) |
|
||
| `/stage.html` · `/info-stage.html` | **PM_S‑1 Stage** — foot‑pedal stompbox (two footswitches, expression‑pedal in, RGB beat light, instrument pass‑through) |
|
||
| `/micro.html` · `/info-micro.html` | **PM_P‑1 Practice** — inline practice bar (instrument in / out pass‑through, clickable thumb‑roller, 14‑segment display) |
|
||
| `/showcase.html` · `/info-showcase.html` | **PM_D‑1 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 drop‑in loader |
|
||
| `/pico-main.py` | the PM_K‑1 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 baked‑in 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** (set‑list **⋯** 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.
|
||
- **Per‑lane gain** — a dB trim knob per lane (`@<db>` in the share language), applied at
|
||
schedule time so changing it never stutters playback.
|
||
- **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).
|
||
- **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, human‑readable 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
|
||
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> ] [ (<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 drum‑machine voices
|
||
by name — `kick808 snare808 clap808 hat808 openHat808 cowbell808 tom808` and
|
||
`kick909 snare909 clap909 hat909 ride909 crash909`; or a **General‑MIDI 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 per‑step (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 off‑beats 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>`** — per‑lane 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` | eighth‑note hi‑hats (downbeat of each beat accented) |
|
||
| `ride:4/2s` | **swung** eighth‑note ride |
|
||
| `kick:4(3,8)` | a 3‑over‑8 Euclidean kick |
|
||
| `claves:5~` | 5 evenly across lane 1's bar (5‑over‑4 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 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.
|
||
|
||
## Embedding
|
||
|
||
Any form factor can be embedded in another page as a self‑sizing widget. Drop in a
|
||
container and the loader script — it builds an `<iframe>` to the chrome‑stripped
|
||
(`?embed=1`) page, preloads your config string, and auto‑resizes 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 set‑list 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_K‑1 "Kit"
|
||
|
||
The **PM_K‑1 Kit** runs the same engine and program strings on a real device you can build today:
|
||
a **Raspberry Pi Pico** on the **52Pi EP‑0172 "Pico Breadboard Kit Plus"** — a 3.5″ ST7796
|
||
320×480 capacitive‑touch screen (GT911), a PSP joystick, a WS2812 RGB LED, a buzzer and two
|
||
buttons, all pre‑wired. See **`/info-kit.html`** for the pinout, parts (~$45 incl. Pico) and
|
||
flashing steps. Firmware lives in **`pico/`**:
|
||
|
||
- **`pico/main.py`** — single‑file **MicroPython** firmware: an ST7796 driver, GT911 touch,
|
||
WS2812 RGB, PWM buzzer, ADC joystick, baked anti‑aliased 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 anti‑aliased fonts embedded in the firmware.
|
||
|
||
## 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 with a **smooth** cutover at the next **bar**; **`Shift`+`Enter`** is a **rude**
|
||
cutover at the next **beat**. `N` / `P` are immediate rude quick‑steps. `Esc` cancels.
|
||
- **Bar‑length segments.** Give an item a **bar** count (Timers box, or `b<n>`) and a bar
|
||
countdown (▦) shows bars remaining. With **Continue** on, it auto‑advances 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 per‑device **program box**
|
||
(`src/progbox.{html,js}`) and the info‑page **live‑widget 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 self‑contained page in `dist/` (the Concepts landing,
|
||
the editor, the device/form‑factor pages and their `info-*.html`), copies `embed.js` through
|
||
as‑is, and copies the Pico firmware to `dist/pico-main.py`. `dist/` is generated, git‑ignored —
|
||
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_E‑1 editor** app (source, with `@BUILD:*` markers) |
|
||
| `kit.html` · `player.html` · `teacher.html` · `stage.html` · `micro.html` · `showcase.html` | the device widget pages (PM_K‑1 Kit, PM_C‑1 Concept, Teacher, Stage, PM_P‑1 Practice, PM_D‑1 Display) |
|
||
| `info-*.html` | per‑form‑factor spec pages (embed the live widget + description + dimensions + BOM) |
|
||
| `embed.html` · `embed.js` | embed docs and the drop‑in 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_K‑1 firmware: `main.py` (MicroPython), `gen_font.py` (font generator), `README.md` |
|
||
| `build.sh` | resolve markers → self‑contained `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 in‑app **?** help).
|
||
|
||
### Credits
|
||
|
||
All drum and percussion voices are **synthesized in Web Audio** (808/909‑style and electronic) —
|
||
there are no audio samples. The on‑device fonts (PM_K‑1) are rendered from **DejaVu Sans**.
|