- Shared header/footer/chrome (src/header.html, src/footer.html, src/chrome.js) now on every page: editor (header above its app toolbar), player, teacher, stage, micro, showcase, embed. chrome.js defers to DOMContentLoaded so the footer version stamps regardless of placement. Player's fullscreen toggle relocated out of the header to a floating control. - Open = Info: each form-factor page is self-contained — a more-detailed description (.about) + an expandable "Spec & BOM" (<details class="spec">, hidden in embed). info-*.html retired; build/deploy/README updated. Next: teacher-style dimensioned front + top/side views + loading panels for Stage, Micro and Showcase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
270 lines
14 KiB
Markdown
270 lines
14 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 **PE‑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) and ships as an
|
||
**embeddable widget** anyone can drop into their own page.
|
||
|
||
**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 audio samples + brand assets (kept in `assets/`) into each page, so the sources stay lean.
|
||
State (set lists, the practice log, theme and UI preferences) lives in `localStorage`.
|
||
|
||
## Pages
|
||
|
||
| URL | What |
|
||
|-----|------|
|
||
| [`/`](https://metronome.varasys.io/) `index.html` | **Concepts** — the landing / form‑factor gallery; each box embeds the live widget |
|
||
| `/editor.html` | **PE‑1 — PolyMeter Editor** (the main app) |
|
||
| `/player.html` | **PM‑1 Initial** — idealized concept device (full display + set‑list nav, theme, fullscreen "stage" view) |
|
||
| `/teacher.html` | **PM‑1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument pass‑through with analog click injection) |
|
||
| `/stage.html` | **PM‑1 Stage** — foot‑pedal stompbox (two footswitches, expression‑pedal in, RGB beat light, instrument pass‑through) |
|
||
| `/micro.html` | **PM‑µ Micro** — inline practice bar (instrument in / out pass‑through, clickable thumb‑roller, 14‑segment display) |
|
||
| `/showcase.html` | **PM‑S Showcase** — 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 |
|
||
|
||
Each form‑factor page is self‑contained ("Open" = "Info"): a more‑detailed description,
|
||
the live device with front + top/side dimensioned views and loading instructions, and an
|
||
expandable **Spec & BOM** (priced, for the buildable hardware). The buildable units are the
|
||
Teacher, Stage, Micro and Showcase; the Editor (web app) and Initial (concept) have no BOM.
|
||
|
||
Each page carries the same VARASYS header (logo + 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 the 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 your work.
|
||
|
||
## 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** — 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 per‑lane *poly* toggle fits a lane's beats evenly into lane 1's
|
||
bar (e.g. 5‑over‑4, 3‑over‑2).
|
||
- **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 for cross‑day comparison.
|
||
- **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, and 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> ] [ = <pattern> ] [ ~ ] [ ! ]
|
||
```
|
||
|
||
- **sound** — the acoustic kit: `beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`,
|
||
`hatOpen`, `ride`, `crash`, `tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`,
|
||
`woodblock`, `claves`, `jamblock` (kick, snare, closed‑hat, crash, toms, tambourine,
|
||
cowbell, woodblock and claves play embedded CC0 samples; `beep`, `clap`, `rim`,
|
||
`hatOpen` and `ride` stay synthesized — VCSL has no clean source for those); 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`,
|
||
`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. This also sets how many **pads** each beat splits
|
||
into. Append **`s`** for **swing** on even subdivisions — `2s` (swung eighths) or
|
||
`4s` (swung sixteenths) delay the off‑beats to a triplet (2:1) feel. Omit for quarter.
|
||
- **`=pattern`** — per‑**step dynamics**, one char per pad: **`X`** accent, **`x`**
|
||
normal, **`g`** ghost (soft), **`.`** mute (rest). Length = beats per bar × `sub`. Omit to get the
|
||
default — the first step of **each beat** accented, the rest normal (click a pad in
|
||
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 beat‑count patterns still parse.)
|
||
- **`~`** — 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 |
|
||
| `claves:5~` | 5 evenly across lane 1's bar (5‑over‑4 if lane 1 is `4`) |
|
||
| `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 |
|
||
| **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. Used because titles/notes are free text.
|
||
|
||
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 (a legacy `presets` field is included for backward compatibility).
|
||
|
||
## 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` · `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) dogfoods this exact mechanism (every box is a live widget).
|
||
See `/embed.html`.
|
||
|
||
## 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 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 quick‑steps. `Esc` cancels an armed switch.
|
||
- **Bar‑length 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
|
||
auto‑advances 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).
|
||
|
||
## 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** as the editor), and base styling (`src/base.css`:
|
||
reset + brand palette + type + the shared site header / nav / BOM table styles).
|
||
- `@BUILD:favicon@`, `@BUILD:logo-*@`, `/*@BUILD:samples@*/{}` inline the base64
|
||
assets from `assets/`.
|
||
|
||
`./build.sh` resolves every marker into a self‑contained page in `dist/` — the editor +
|
||
the Concepts landing + editor + device/form-factor pages — and copies `embed.js` through as‑is
|
||
(the editor inlines the CC0 samples; the device pages pass an empty `SAMPLES` for pure synth).
|
||
`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 **PE‑1 editor** app (source, with `@BUILD:*` markers) |
|
||
| `src/header.html` · `src/footer.html` · `src/chrome.js` | shared header / footer / theme chrome, inlined into every page |
|
||
| `player.html` · `teacher.html` · `stage.html` · `micro.html` · `showcase.html` | the device mockups (PM‑1 Initial / Teacher / Stage, PM‑µ Micro, PM‑S Showcase) |
|
||
| `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` |
|
||
| `assets/` | base64 blobs inlined at build (samples, logos, favicon) |
|
||
| `build.sh` | resolve markers → self‑contained `dist/` pages |
|
||
| `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) for the full text.
|
||
|
||
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
|
||
|
||
Acoustic drum one‑shots 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).
|