| assets | ||
| docs | ||
| hardware | ||
| pico | ||
| pico-cp | ||
| pico-explorer | ||
| pico-scroll | ||
| pico-wm8960 | ||
| rust | ||
| src | ||
| tests | ||
| wokwi | ||
| .gitignore | ||
| build.sh | ||
| CLAUDE.md | ||
| COORDINATION.md | ||
| deploy.sh | ||
| editor-beta.html | ||
| editor.html | ||
| embed.html | ||
| embed.js | ||
| explorer.html | ||
| grid.html | ||
| index.html | ||
| info-editor.html | ||
| info-explorer.html | ||
| info-grid.html | ||
| info-kit.html | ||
| info-micro.html | ||
| info-player.html | ||
| info-showcase.html | ||
| info-stage.html | ||
| info-teacher.html | ||
| kit.html | ||
| LICENSE | ||
| micro.html | ||
| player.html | ||
| README.md | ||
| release.sh | ||
| showcase.html | ||
| stage.html | ||
| teacher.html | ||
| VERSION | ||
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=1serves 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 |
|---|---|
/ 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).
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 tom808andkick909 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:1quarter (default),2eighth,3triplet,4sixteenth,6sextuplet — also sets how many pads each beat splits into. Appendsfor swing on even subdivisions (2s,4s) to delay the off‑beats to a 2:1 triplet feel.(k,n[,rot])— Euclidean fill: placekhits as evenly as possible acrossnsteps, optionally rotated byrot. e.g.kick:4(3,8).=pattern— per‑step dynamics, one char per pad:Xaccent,xnormal,gghost (soft),.-_mute (rest). Length = beats ×sub. Omit to get the default — first step of each beat accented, the rest normal. e.g.4=.X.Xaccents 2 & 4.@<db>— per‑lane gain trim in decibels, e.g.@-3or@+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:
<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 (maps to#p=); ordata-setlistfor a set‑list code (maps to#sl=).data-width/data-height— optional initial size (default100%×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 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, copymain.py, edit thePROGRAMSlist 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 + yourprograms.json+ an offline editor, drives a full lanes/pads touchscreen, logs practice tohistory.jsonon 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/PgDnmove a cue cursor (amber outline) through items — across set lists, without loading anything.Entercommits with a smooth cutover at the next bar;Shift+Enteris a rude cutover at the next beat.N/Pare immediate rude quick‑steps.Esccancels. - 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 fromassets/(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 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.
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.