diff --git a/README.md b/README.md index d985161..ea4c796 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ A browser **polymetric groove trainer / metronome** — and the design mockup for a Raspberry Pi Pico hardware build. Stack as many "meter lanes" as you like; each is -its own little metronome with a grouping, subdivision, drum voice and per-beat -pattern. Layering lanes produces polymeter and true ratio polyrhythm. +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. **Live:** https://metronome.varasys.io @@ -18,17 +18,18 @@ the browser may not persist `localStorage` between sessions, so use **Export all ## Features -- **Meter lanes** — grouping (odd meters), subdivision, a synthesized drum/percussion voice, per‑beat - on/off pattern (rests), mute, live measure counter. +- **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; click an item to load it - (it switches live if you're already playing), **N** loads the next; each play is +- **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 (with an - optional QR generated by an external service). +- **Sharing** — copy a link to your current settings or a whole set list. - **Theming** — System / Light / Dark. ## The share language @@ -61,10 +62,12 @@ Tokens are joined with `;`. `tr` and `rmp` are omitted when off. : [ / ] [ = ] [ ~ ] [ ! ] ``` -- **sound** — one of: - `beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`, - `tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`, - `jamblock` (unknown → `beep`). +- **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 play embedded + CC0 samples, the rest are synthesized); 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, @@ -74,7 +77,7 @@ Tokens are joined with `;`. `tr` and `rmp` are omitted when off. - **`=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 → mute). e.g. `4=.X.X` accents the backbeat (2 & 4); + 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. @@ -107,10 +110,7 @@ clears the hash so a refresh won't re‑import. In the set‑list panel's **⋯** menu: - **Share settings link** / **Share set‑list link** open a dialog with the link to - **Copy** or **Open**. -- **QR ↗** opens a third‑party QR service (api.qrserver.com) with the link in its - URL so you can scan it on a phone. A banner warns you it's external — confirm the - QR decodes to the shown link before trusting it. (No QR is generated locally.) + **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). @@ -162,12 +162,6 @@ Cut a release with `./release.sh [X.Y.Z]` — the optional arg bumps & commits `VERSION`; it then tags the current commit `v` (requires a clean tree). Push the tag, then deploy. -## Deploy - -`./deploy.sh` copies `index.html` (version‑stamped) into the Caddy -web root and smoke‑tests the live URL. No restart needed (`file_server` picks up -changes immediately). - ## Files | File | Purpose | diff --git a/index.html b/index.html index b45b2bd..7a30da1 100644 --- a/index.html +++ b/index.html @@ -189,8 +189,6 @@ .overlay { position:fixed; inset:0; background:rgba(0,0,0,.55); display:flex; align-items:center; justify-content:center; z-index:80; } .overlay[hidden] { display:none; } .overlay-box { background:var(--panel-2); border:1px solid var(--edge); border-radius:14px; padding:16px 20px; width:380px; max-width:92vw; box-shadow:0 20px 60px rgba(0,0,0,.6); } - .ext-banner { font-size:11px; color:#3a2f10; background:#ffe2a8; border:1px solid #d9a441; border-radius:8px; padding:8px 10px; margin-top:10px; line-height:1.35; } - .ext-banner[hidden] { display:none; } #shareUrl { width:100%; resize:vertical; background:var(--panel); color:var(--txt); border:1px solid var(--edge); border-radius:8px; padding:8px; font-family:"Courier New",monospace; font-size:12px; } .help-about { margin-top:14px; padding-top:12px; border-top:1px solid var(--edge); font-size:12px; color:var(--muted); line-height:1.45; } .help-about p { margin:0 0 8px; } @@ -371,14 +369,13 @@ - + @@ -1204,7 +1201,6 @@ function openShare(title, url, note) { $("shareTitle").textContent = title; $("shareUrl").value = url; $("shareNote").textContent = note || ""; - $("shareExtBanner").hidden = true; // reset the external-QR warning $("shareOverlay").hidden = false; } function shareSettings() { openShare("Share settings", shareLink("p=" + currentPatch()), "Encodes tempo, lanes & practice settings."); } @@ -1434,10 +1430,6 @@ $("shareClose").addEventListener("click", () => $("shareOverlay").hidden = true) $("shareOverlay").addEventListener("click", (e) => { if (e.target.id === "shareOverlay") $("shareOverlay").hidden = true; }); $("shareCopy").addEventListener("click", async () => { try { await navigator.clipboard.writeText($("shareUrl").value); const b = $("shareCopy"); b.textContent = "Copied!"; setTimeout(() => b.textContent = "Copy link", 1200); } catch (e) { $("shareUrl").select(); } }); $("shareOpen").addEventListener("click", () => window.open($("shareUrl").value, "_blank")); -$("shareQrExt").addEventListener("click", () => { - $("shareExtBanner").hidden = false; // warn that we're handing the link to a third party - window.open("https://api.qrserver.com/v1/create-qr-code/?size=320x320&data=" + encodeURIComponent($("shareUrl").value), "_blank", "noopener"); -}); window.addEventListener("keydown", (e) => { const t = e.target, tag = t ? t.tagName : "", type = (t && t.type ? String(t.type) : "").toLowerCase(); // Text entry is sacred — never hijack typing in a text field.