Remove QR sharing; README accuracy pass
Drop the QR-code share button, its external-service warning banner/CSS, and the api.qrserver.com handler — sharing is now copy/open link only, so the app makes no external requests at all. README: remove QR references and the Deploy section, refresh Features/sounds/dynamics/swing, list the 808/909 voices, and note which acoustic voices use CC0 samples. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
06986e83aa
commit
3213c6afe4
2 changed files with 19 additions and 33 deletions
40
README.md
40
README.md
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
A browser **polymetric groove trainer / metronome** — and the design mockup for a
|
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
|
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
|
its own little metronome with a grouping, subdivision, drum voice and a per-step
|
||||||
pattern. Layering lanes produces polymeter and true ratio polyrhythm.
|
pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
|
||||||
|
|
||||||
**Live:** https://metronome.varasys.io
|
**Live:** https://metronome.varasys.io
|
||||||
|
|
||||||
|
|
@ -18,17 +18,18 @@ the browser may not persist `localStorage` between sessions, so use **Export all
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Meter lanes** — grouping (odd meters), subdivision, a synthesized drum/percussion voice, per‑beat
|
- **Meter lanes** — grouping (odd meters), subdivision (incl. swing), a drum/percussion
|
||||||
on/off pattern (rests), mute, live measure counter.
|
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
|
- **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).
|
bar (e.g. 5‑over‑4, 3‑over‑2).
|
||||||
- **Practice** — gap/mute trainer (play N / mute M bars) and a tempo ramp with a
|
- **Practice** — gap/mute trainer (play N / mute M bars) and a tempo ramp with a
|
||||||
start BPM and signed step.
|
start BPM and signed step.
|
||||||
- **Set lists** — named, ordered lists of saved setups; click an item to load it
|
- **Set lists** — named, ordered lists of saved setups; **cue** across lists and commit
|
||||||
(it switches live if you're already playing), **N** loads the next; each play is
|
on a bar/beat boundary with no audible gap (see **Live performance**); each play is
|
||||||
logged for cross‑day comparison.
|
logged for cross‑day comparison.
|
||||||
- **Sharing** — copy a link to your current settings or a whole set list (with an
|
- **Sharing** — copy a link to your current settings or a whole set list.
|
||||||
optional QR generated by an external service).
|
|
||||||
- **Theming** — System / Light / Dark.
|
- **Theming** — System / Light / Dark.
|
||||||
|
|
||||||
## The share language
|
## The share language
|
||||||
|
|
@ -61,10 +62,12 @@ Tokens are joined with `;`. `tr` and `rmp` are omitted when off.
|
||||||
<sound> : <grouping> [ / <sub> ] [ = <pattern> ] [ ~ ] [ ! ]
|
<sound> : <grouping> [ / <sub> ] [ = <pattern> ] [ ~ ] [ ! ]
|
||||||
```
|
```
|
||||||
|
|
||||||
- **sound** — one of:
|
- **sound** — the acoustic kit: `beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`,
|
||||||
`beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`,
|
`hatOpen`, `ride`, `crash`, `tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`,
|
||||||
`tomLow`, `tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`,
|
`woodblock`, `claves`, `jamblock` (kick / snare / closed‑hat / crash play embedded
|
||||||
`jamblock` (unknown → `beep`).
|
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`,
|
- **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`).
|
`2+2+3`. Groups get a visual divider; accents are per‑step (see `=pattern`).
|
||||||
- **`/sub`** — subdivision: `1` quarter (default), `2` eighth, `3` triplet,
|
- **`/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`**
|
- **`=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
|
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
|
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
|
`4/2s` is swung eighths with the default accents. (Legacy `x`/`.` on/off patterns and
|
||||||
short beat‑count patterns still parse.)
|
short beat‑count patterns still parse.)
|
||||||
- **`~`** — polyrhythm: fit this lane's beats evenly into **lane 1's** bar.
|
- **`~`** — 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:
|
In the set‑list panel's **⋯** menu:
|
||||||
- **Share settings link** / **Share set‑list link** open a dialog with the link to
|
- **Share settings link** / **Share set‑list link** open a dialog with the link to
|
||||||
**Copy** or **Open**.
|
**Copy** or **Open**. The link encodes everything in the URL — nothing is uploaded.
|
||||||
- **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.)
|
|
||||||
- **Export all / Import file** back up your set lists and practice log as a JSON
|
- **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).
|
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<VERSION>` (requires a clean tree).
|
`VERSION`; it then tags the current commit `v<VERSION>` (requires a clean tree).
|
||||||
Push the tag, then deploy.
|
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
|
## Files
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|
|
|
||||||
12
index.html
12
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 { 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[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); }
|
.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; }
|
#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 { 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; }
|
.help-about p { margin:0 0 8px; }
|
||||||
|
|
@ -371,14 +369,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Share dialog: copyable link + QR code -->
|
<!-- Share dialog: copyable link -->
|
||||||
<div id="shareOverlay" class="overlay" hidden>
|
<div id="shareOverlay" class="overlay" hidden>
|
||||||
<div class="overlay-box">
|
<div class="overlay-box">
|
||||||
<div class="tray-head"><h2 id="shareTitle" style="margin:0">Share</h2><button class="x" id="shareClose" title="close" style="margin-left:0">✕</button></div>
|
<div class="tray-head"><h2 id="shareTitle" style="margin:0">Share</h2><button class="x" id="shareClose" title="close" style="margin-left:0">✕</button></div>
|
||||||
<textarea id="shareUrl" readonly rows="3"></textarea>
|
<textarea id="shareUrl" readonly rows="3"></textarea>
|
||||||
<div class="btnrow" style="margin-top:8px"><button id="shareCopy">Copy link</button><button id="shareOpen">Open ↗</button><button id="shareQrExt">QR ↗</button></div>
|
<div class="btnrow" style="margin-top:8px"><button id="shareCopy">Copy link</button><button id="shareOpen">Open ↗</button></div>
|
||||||
<div class="hint" id="shareNote"></div>
|
<div class="hint" id="shareNote"></div>
|
||||||
<div class="ext-banner" id="shareExtBanner" hidden>⚠ “QR ↗” opens an external site (api.qrserver.com) with this link embedded in its URL. It is a third party — after scanning, confirm the QR decodes to the link above before trusting it.</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1204,7 +1201,6 @@ function openShare(title, url, note) {
|
||||||
$("shareTitle").textContent = title;
|
$("shareTitle").textContent = title;
|
||||||
$("shareUrl").value = url;
|
$("shareUrl").value = url;
|
||||||
$("shareNote").textContent = note || "";
|
$("shareNote").textContent = note || "";
|
||||||
$("shareExtBanner").hidden = true; // reset the external-QR warning
|
|
||||||
$("shareOverlay").hidden = false;
|
$("shareOverlay").hidden = false;
|
||||||
}
|
}
|
||||||
function shareSettings() { openShare("Share settings", shareLink("p=" + currentPatch()), "Encodes tempo, lanes & practice settings."); }
|
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; });
|
$("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(); } });
|
$("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"));
|
$("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) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
const t = e.target, tag = t ? t.tagName : "", type = (t && t.type ? String(t.type) : "").toLowerCase();
|
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.
|
// Text entry is sacred — never hijack typing in a text field.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue