# 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 |
| `/pm_e-2.html` · `/info-pm_e-2.html` | **PM_E‑2 — PolyMeter Editor (Notation)** — second-gen, engraved drum notation (Bravura/SMuFL): Staff / TUBS / Konnakol views, edit-on-staff |
| `/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 (used by both firmwares).
- **`pico-cp/`** — a **CircuitPython** edition (download `/pm_k1_circuitpy.zip`): a self‑contained
  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 USB‑MIDI** (with a universal download‑and‑drag fallback), and
  plays **out your computer's speakers over USB‑MIDI** (the editor's **🎹 Device audio**). By default the
  firmware owns the drive (read‑only to the computer, so it's protected); hold **button A** at power‑on for
  editor mode (drive writable). **Firmware updates are one click** from the editor (⋯ → Update firmware) —
  pushed over USB‑MIDI as an A/B update with automatic rollback. The MicroPython build stays the simple,
  no‑computer 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 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) |
| `pm_e-2.html` · `src/notation.js` | the **PM_E‑2 notation editor** + its Bravura/SMuFL engraving engine (`tools/bravura/` subsets the font → `assets/bravura.woff2.b64`, inlined via `@BUILD:bravura@`) |
| `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 MicroPython firmware: `main.py`, `gen_font.py` (font generator), `README.md` |
| `pico-cp/` | PM_K‑1 CircuitPython edition: `code.py`, `programs.json`, `font_*.bin`, `README.md` (bundled + served as `/pm_k1_circuitpy.zip`) |
| `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**.
