metronome/index.html
Me Here 17053719f1 New Stage (foot-pedal) + Showcase (RGB pendulum); fix audio/visual sync
Sync: the visual playhead now advances on a latency-compensated clock
(currentTime − outputLatency||baseLatency) so the on-screen pulse lands when the
click is HEARD, not when it's queued — previously the visual could lead the audio
by the output buffer / Bluetooth latency (up to ~a subdivision). Applied to
editor, player, teacher, and the new pages; also bound the visual queue (vq trim).
No data races: single-threaded; only the rAF draw touches vqPtr/currentStep, and
each vq entry carries the exact scheduled time of its sound.

stage.html — foot-pedal stompbox: two heavy footswitches (Tap=tempo / hold=start-
stop, Next=item / hold=prev), 1/4" expression-pedal input → tempo sweep, big
floor-readable RGB beat light + angled TFT, analog instrument pass-through.
showcase.html — pyramid display piece: an RGB-light pendulum easing to each beat
plus per-lane segment rows showing subdivisions/accents/mutes (canvas).
Both: dual USB-C (data+power and power-thru) to daisy-chain off one source.

Wired into embed.js (stage, showcase variants), build.sh, deploy.sh, the
concepts gallery + landing cards, info-stage.html (~$52) + info-showcase.html
(~$39) with BOMs, and the README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 08:40:20 -05:00

196 lines
11 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PolyMeter — polymetric groove trainer &amp; metronome</title>
<meta name="description" content="PolyMeter — a polymetric groove trainer and metronome. One engine, many form factors: a free web editor, hardware concepts, and an embeddable widget." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<!-- Landing page: the front door to the PolyMeter family. The app itself is /editor.html. Static page. -->
<script>
(function(){ try{
var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
/* hero */
.hero{ text-align:center; padding:54px 12px 38px; }
.hero h1{ font-size:clamp(40px, 9vw, 76px); margin:0; letter-spacing:-.02em; line-height:1;
background:linear-gradient(90deg, var(--cyan), #6cb6ff); -webkit-background-clip:text; background-clip:text; color:transparent; }
.hero .tagline{ margin:16px auto 0; font-size:clamp(16px, 2.6vw, 21px); color:var(--txt); font-weight:600; }
.hero .pitch{ margin:14px auto 0; max-width:60ch; color:var(--muted); font-size:15px; line-height:1.6; }
.cta{ display:flex; gap:12px; justify-content:center; flex-wrap:wrap; margin-top:26px; }
.btn{ display:inline-flex; align-items:center; gap:6px; text-decoration:none; font-weight:600; font-size:15px;
padding:11px 20px; border-radius:10px; border:1px solid var(--panel-bd); color:var(--txt); background:var(--panel-bg);
transition:.14s; }
.btn:hover{ border-color:var(--cyan); }
.btn.primary{ color:#04121b; border-color:transparent; background:linear-gradient(180deg, #34c6ff, var(--cyan)); }
.btn.primary:hover{ filter:brightness(1.06); }
/* form-factor cards (shared look with the Concepts gallery) */
.section-label{ text-align:center; font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); margin:30px 0 14px; }
.grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr)); gap:16px; }
.card{ background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:16px;
display:flex; flex-direction:column; gap:9px; }
.card h3{ margin:0; font-size:16px; }
.chip{ align-self:flex-start; font-size:10px; text-transform:uppercase; letter-spacing:.08em;
padding:2px 9px; border-radius:999px; border:1px solid var(--panel-bd); color:var(--muted); }
.chip.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.chip.app{ color:#2fe07a; border-color:rgba(47,224,160,.45); }
.card p{ margin:0; font-size:13px; color:var(--muted); line-height:1.5; flex:1; }
.card .links{ display:flex; gap:16px; margin-top:4px; }
.card .links a{ color:var(--link); text-decoration:none; font-size:13px; font-weight:600; }
.more{ text-align:center; margin-top:18px; font-size:14px; }
/* philosophy section */
.philosophy{ margin-top:34px; }
.phil-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:16px; }
.phil{ background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:18px 18px 16px; }
.phil h3{ margin:0 0 8px; font-size:15px; display:flex; align-items:center; gap:8px; }
.phil .ic{ font-size:17px; line-height:1; filter:grayscale(.1); }
.phil p{ margin:0; font-size:13.5px; color:var(--muted); line-height:1.62; }
.phil p b{ color:var(--txt); }
.site-foot{ max-width:980px; margin:42px auto 0; font-size:12px; color:var(--muted); text-align:center; }
.site-foot a{ color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PolyMeter</b></span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/concepts.html">Concepts</a>
<a href="/embed.html">Embed</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<section class="hero">
<h1>PolyMeter</h1>
<p class="tagline">Polymetric grooves — one engine, many form factors.</p>
<p class="pitch">Stack independent meter lanes, each with its own subdivision, drum voice and perstep
accents, to build true polymeter and ratio polyrhythm. Design it in the browser, save it as a compact
program string, and play it back on the editor, the hardware concepts, or an embedded widget — all the
same engine.</p>
<div class="cta">
<a class="btn primary" href="/editor.html">Open the Editor →</a>
<a class="btn" href="/concepts.html">Browse concepts</a>
</div>
</section>
<div class="section-label">The PolyMeter family</div>
<div class="grid">
<div class="card">
<span class="chip app">Web app</span>
<h3>PE1 — PolyMeter Editor</h3>
<p>The full editor: stack meter lanes, perstep accents / ghosts / mutes, swing &amp; ratio polyrhythm,
set lists, and shareable links. This is where you design grooves.</p>
<div class="links"><a href="/editor.html">Open ↗</a><a href="/info-editor.html">Info ⓘ</a></div>
</div>
<div class="card">
<span class="chip hw">Hardware</span>
<h3>PM1 — Teacher</h3>
<p>Fullfeature desktop console for studio &amp; lessons: 2.0″ colour TFT showing every lane, arcade buttons,
thumbroller, 1/4″ instrument passthrough with analog click injection + balancedTRS out. USBC powered.</p>
<div class="links"><a href="/teacher.html">Open ↗</a><a href="/info-teacher.html">Info &amp; BOM ⓘ</a></div>
</div>
<div class="card">
<span class="chip hw">Hardware</span>
<h3>PM1 — Stage</h3>
<p>Footpedal stompbox: two footswitches (tap / next), expressionpedal input, a big floorreadable RGB
beat light, instrument passthrough with analog click. DualUSBC daisychain.</p>
<div class="links"><a href="/stage.html">Open ↗</a><a href="/info-stage.html">Info &amp; BOM ⓘ</a></div>
</div>
<div class="card">
<span class="chip hw">Hardware</span>
<h3>PMµ — Micro</h3>
<p>Long, narrow inline practice bar: instrument in one end, amp/headphones out the other, click mixed in.
Clickable thumbroller, amber 14segment display, USBC powered.</p>
<div class="links"><a href="/micro.html">Open ↗</a><a href="/info-micro.html">Info &amp; BOM ⓘ</a></div>
</div>
<div class="card">
<span class="chip hw">Hardware</span>
<h3>PMS — Showcase</h3>
<p>A display piece shaped like a classic pyramid windup metronome — an RGBlight pendulum easing to the
beat, with light rows for every lane's subdivisions, accents &amp; mutes. USBC powered.</p>
<div class="links"><a href="/showcase.html">Open ↗</a><a href="/info-showcase.html">Info &amp; BOM ⓘ</a></div>
</div>
<div class="card">
<span class="chip">Widget</span>
<h3>Embed anywhere</h3>
<p>Drop any form factor into your own page with one <code>&lt;div&gt;</code> + a script — preloaded with a
program string and autosizing. Our own pages use the same loader.</p>
<div class="links"><a href="/embed.html">Docs ↗</a></div>
</div>
</div>
<p class="more"><a href="/concepts.html">See all concepts, including the PM1 Initial render →</a></p>
<section class="philosophy">
<div class="section-label">Philosophy</div>
<div class="phil-grid">
<div class="phil">
<h3><span class="ic">🛠️</span> Program on the web, play on any device</h3>
<p>The website is the workbench. Design your grooves in the <a href="/editor.html">PE1 editor</a>
stack meters, set perstep accents, build set lists — and every pattern saves to a compact
<b>program string</b> (a whole set list to a single code). That same string loads into whichever
form factor fits the moment: the <a href="/teacher.html">Teacher</a> on a studio desk, the
<a href="/micro.html">Micro</a> inline at the practice desk, or an <a href="/embed.html">embedded
widget</a> in someone else's app. One engine, one language — you author once and run it anywhere,
choosing the device by the use scenario rather than relearning a new box each time.</p>
</div>
<div class="phil">
<h3><span class="ic">🔌</span> USBC power everywhere — no batteries</h3>
<p>Every device in the family is powered over a single <b>USBC</b> port — no internal battery to
swell, leak or wear out, and nothing proprietary to replace. Plug into a wall adapter for a
permanent install, or carry a power bank exactly the way you already do for your phone. Standardising
on one connector across the whole range keeps the builds simple and <b>futureproofs</b> the
project as USBC becomes universal.</p>
</div>
</div>
</section>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity ·
<a href="https://codeberg.org/VARASYS/metronome" target="_blank" rel="noopener">source</a>
<span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p+" (system → light → dark)"; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
</body>
</html>