The 128×64 mono OLED was too pixelated, so step up to the realistic next
tier: a 2.0″ 320×240 colour IPS TFT (ST7789 — e.g. Pimoroni Pico Display 2.0),
~5× the resolution and full colour.
- Drop the 1-bit threshold + image-rendering:pixelated; render on a hi-DPI
canvas (backing = 320×240 × devicePixelRatio) with smooth anti-aliased type.
- Richer colour layout: dim header (position + green ▶ PLAY / grey ■ STOP),
a big cyan tempo with "BPM ♩ <grouping>", the centred item name (ellipsised),
and a bottom strip with bar·beat + an amber bars countdown.
The screen stays a fixed dark UI (a TFT shows whatever firmware draws); the page
chrome still follows light/dark/system. Beat matrix, encoder, buttons unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the single 16-px strip with a 4×16 WS2812 matrix (four 16-px strips,
still PIO-driven on the RP2040):
- bottom row = the beat (cyan downbeats / amber group-starts, current beat
bright, the rest a dim grid) — separated from the rows above by a divider;
- the three rows above stack the CURRENT beat's subdivisions as they pass:
a column climbs row-by-row with each subdivision and resets on the next beat,
with faint "slots" showing the ladder it will climb.
Subdivisions are driven by the finest lane that shares lane 1's beat grid
(non-poly, same beatsPerBar, max stepsPerBeat) — so an 8th-note hat shows one
row, 16ths show three, straight quarters show none. Also fixed a stray
"#05measure" typo left in the old .npx border rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /player-asbuilt.html showing the PM-1 with parts you'd actually solder
for an RP2040 build, alongside the idealized /player.html:
- 128×64 MONOCHROME OLED (SSD1306 class): rendered as a true 1-bit
framebuffer — drawn, then thresholded to crisp on/off pixels and scaled
with image-rendering:pixelated — so the cramped real layout is honest
(position / big BPM / grouping / scrolling name / bar·beat).
- Fixed 16-px WS2812 ("NeoPixel") RGB beat bar on a strip PCB: lights the
first beatsPerBar slots (cyan downbeats, amber group-starts, dim others),
the rest dark — showing the fixed-count hardware honestly.
- EC11 rotary encoder you actually turn (wheel / vertical drag) for tempo,
tactile buttons, MAX98357A-style speaker grille, USB-C, PWR LED, matte case.
Shares the same firmware via src/engine.js + src/setlists.js (same seed set
lists, same scheduler); only the panel rendering differs. The device is fixed
dark hardware; the page chrome follows light/dark/system. build.sh + deploy.sh
now assemble/serve all three pages; player.html links to it ("As-built ↗").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes from on-device testing:
1. Fill the screen. The earlier stage capped the device at min(96vw,168vh)
and centred it, leaving big margins on wide phones. Now the device frame
goes transparent/borderless at position:absolute inset:0 and the OLED
grows (flex:1) so the unit fills the whole viewport edge to edge.
2. Follow light/dark/system in full-screen. The full-screen skin is the
themed page gradient (light in light mode, dark in dark, OS-driven on
system), and a theme toggle (◐/☀/☾ — same cycle + "metronome.theme" key
as the main page) now sits beside the exit ✕, since the top bar that
normally holds it is hidden in stage.
The themed gradient is painted on the device element rather than the body
because a position:fixed body doesn't propagate its background to the
canvas (left a white area). The decorative PWR dot is hidden in stage so
it doesn't sit under the floating controls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a ⛶ button (top bar) that takes the PM-1 device full-screen and locks
it to landscape — turning it into a glanceable stage display.
A single body.stage class drives the layout: top bar + load panel hidden,
OLED / beat-LEDs / transport enlarged with viewport units, plus a floating
✕ exit (the top bar is hidden in stage mode). Per platform:
- Android: requestFullscreen() + screen.orientation.lock("landscape").
- Desktop: real fullscreen; the lock harmlessly no-ops (already landscape).
- iPhone (no Fullscreen/orientation-lock API): CSS pseudo-fullscreen + a
"⟳ rotate to landscape" overlay shown when held in portrait.
Screen Wake Lock keeps the display awake during a performance (re-acquired
on visibility change). 'F' toggles; Esc / fullscreenchange tear the stage
down and unlock cleanly. All API calls are guarded so rejections on
unsupported platforms never throw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The device case was nearly the same tone as the room — the body's
radial-gradient even peaked *lighter* than the case — so the unit blended
into the background. Added a theme toggle mirroring the editor (◐/☀/☾,
cycles system → light → dark, shares the "metronome.theme" key, with a
pre-paint head script to avoid a flash) and reworked the palette around it:
- dark: a charcoal device sits a clear step lighter than a near-black room,
with a rim highlight + drop shadow + faint cyan glow so it reads as an object;
- light: the dark device sits on a bright "desk" card (panel + fields go light).
Device internals (OLED, beat LEDs, buttons, knob, screws) keep fixed
dark-hardware colours in both themes via --dtxt/--dmuted, so only the
environment switches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both index.html (editor) and player.html (hardware-player mockup) now pull
their common code from src/ via a new build-time include marker
(/*@BUILD:include:src/…@*/), resolved by build.sh:
src/engine.js — audio voices (DRUMS×30), Web Audio scheduler primitives,
and the share-language codec (patch/set-list encode+decode)
src/setlists.js — SEED_SETLISTS, so the player ships the SAME default set
lists as the editor (player BUILTIN = SEED_SETLISTS)
src/base.css — reset + VARASYS brand palette + type stack
The editor inlines the CC0 acoustic samples; the player passes an empty
SAMPLES object and the shared playInstrument falls back to its synth voices,
so the device stays faithfully synth-only. Each app keeps its own state
globals, setBpm, advanceMaster/scheduler, and UI. ~400 lines of duplicated
engine code removed; the player's favicon is now the shared @BUILD:favicon@.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The source index.html now keeps small @BUILD:* markers instead of the
~250KB of base64 blobs (audio samples, logos, favicon), which move to
assets/. build.sh inlines them into a self-contained dist/index.html
(+ dist/player.html); deploy.sh runs the build first and serves dist/.
dist/ is git-ignored. Keeps the single-file deploy while stopping the
samples from eating the editing budget.
Also reframe the main page as the full web app (it is not a mockup —
only the play-only player.html device is): drop "Mockup" from the title,
the source comment, and the README intro; add Build/Files docs and
correct the "no build step" claim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A self-contained simulator of the RP2040 "PM-1" unit: it plays the share
language (synth voices, same scheduler) and drives an OLED + beat-LED
display like the firmware would. Loads from a #p=/#sl= link, the editor's
saved set lists (localStorage), or a pasted patch / set-list code — with
validation. Transport: play/stop, prev/next item, tempo ±, tap; bar-count
segments auto-advance. deploy.sh now version-stamps and publishes it too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Below 580px the header becomes a column-reverse so the controls (theme,
help, logo) sit above the title, then the legend — all left-aligned.
Uses !important to beat the inline align-items/flex-wrap on the row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move the shortcut legend to the left, directly under the title (was
right-aligned), and break it into non-breaking segments so it wraps
only between groups (at the · separators), never mid-token.
- Header row now wraps: on narrow screens the buttons + logo sit on their
own line above the legend (controls use margin-left:auto to stay right).
- Drop the rule that hid the legend below 620px — it now shows and wraps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move the VARASYS logo to the right-hand control group (was left of the
title).
- Alt+↑/↓ now seeds the cue when none is set, so reordering works without
having to cue an item first (it was silently no-opping).
- Theme button uses plain glyphs (◐ system / ☀ light / ☾ dark) instead of
the 🖥 emoji, which didn't render in some browsers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New "🎵 Song" seed list (SEED_VERSION 3): 8 bar-length segments that
auto-advance through tempo ramps (92→120 build, 132→83 outro), a 2/4
samba, a half-time shuffle, a 909 four-on-the-floor and a 16th peak —
totalling ~4:00. Also gate bar-count auto-advance on the Continue toggle
(matches its "countdown / bars" label; only bar-length items are
affected, which previously didn't exist outside this song).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add embedded CC0 one-shots for toms (stick), tambourine, cowbell,
woodblock and claves; tomMid is the low tom pitched up (VCSL has only
two toms). Now 10 sampled voices. rim, open-hat and ride stay
synthesized (no clean VCSL source), as do beep/clap/jamblock and 808/909.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In-app help "Source" link and README now reference
codeberg.org/VARASYS/metronome (the public mirror) instead of the
internal git.varasys.io.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Sample-playback engine: short one-shots are embedded as base64 WAV,
decoded to AudioBuffers at audio start; playInstrument plays the sample
when present, else falls back to synthesis. First slice covers kick,
snare, closed hat and crash from the Versilian Community Sample Library
(CC0), trimmed to mono 22.05kHz/16-bit (~117KB). More acoustic voices to
follow once the quality/size is confirmed by ear. Credit added to README.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These machines are synthesizers in reality, so synthesis is the faithful
approach. Adds 808 (kick/snare/clap/hat/open hat/cowbell/tom) and 909
(kick/snare/clap/hat/ride/crash) voices via a shared metallic-hat helper
(6 detuned squares → bandpass+highpass) plus tuned tone/noise envelopes.
Acoustic kit will move to CC0 samples (VCSL) separately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pads now cycle accent → normal → ghost → mute. Ghost is a soft hit
(gain 0.25) — added as new level value 3 so set lists already saved at
the 3-level stage (0/1/2 = mute/normal/accent) keep their meaning with
no migration. Share pattern gains a "g" char; the Purdie shuffle's snare
ghosts now use it. Ghost pads render faint with a · marker.
Help: explain that set lists/items/log live only in localStorage and how
to move or share them (Share set-list link / Share settings link /
Export-Import).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two seed set lists instead of one mixed "Demos":
- 🥁 Styles — grooves/feels: four-on-the-floor, swing ride, Purdie
half-time shuffle (triplet grid, backbeat on 3, snare ghosts), Samba
(2/4, surdo on 2), Nañigo (6/8 bembé bell), 6/8, 7/8, 5/4.
- 🎯 Practice — polyrhythms, triplet hats, accent demo, tempo builder,
gap trainer.
Seed is now versioned + additive: a bumped SEED_VERSION adds any seed
list whose title isn't already present, without clobbering or re-adding
the user's lists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pads are now 3-state instead of on/off: click cycles accent → normal →
mute. Default keeps the first step of each beat accented (the rest
normal), so existing grooves are unchanged in feel; legacy on/off masks
migrate (on-downbeats → accent, on-subs → normal).
- Audio gain is driven per step by level (accent 1.0 / normal 0.6);
the old auto group-start accent is replaced by explicit per-step level.
- Swing: "swing 8th / swing 16th" subdivision options apply a triplet
(2:1) long–short feel to even subdivisions (per-lane).
- Share language: pattern uses X (accent) / x (normal) / . (mute), and
the sub token takes a trailing s for swing (e.g. ride:4/2s). The
default-accent pattern is omitted; legacy x/. still parse.
- Demos: "Swing ride" and an accents example.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cue/commit model for moving through a set without audible gaps:
- Arrows/Home/End/PgUp/PgDn move an amber cue cursor across set lists.
Enter commits with a SMOOTH cutover at the next bar; Shift+Enter a
RUDE cutover at the next beat. N/P are rude quick-steps; Esc cancels.
- One gap-free cutover (armSwitch → scheduler horizon-cap →
performCutover) replaces the old stop()/start() switch — the audio
clock stays continuous across every transition (manual or auto).
- Per-item bar length (b<n> patch token + Bars input) with a bar
countdown; Continue auto-advances at the boundary (cross-list aware).
Each segment owns its tempo/ramp and resets at the cut.
- Decouple loaded vs viewed list (loadedSL) so the playing item can
live in a list you're not currently viewing.
Set-list panel: editable name combobox (rename in place; the ▾ menu
lists the set lists with "+ New" last), auto-growing description,
add-item row moved below the list, trimmed hint.
Add an inline SVG favicon (brand-cyan metronome on navy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move the VARASYS logo to the left of the title and switch to the
tagline-bottom lockup, with theme-aware dark/light variants swapped
via CSS (both inlined as data URIs).
- Right-justify the theme/help buttons; drop the shortcut legend to its
own line so the buttons no longer wrap on medium widths.
- Save button: wrap it so the tooltip shows while disabled, and make the
tooltip dynamic (why it's disabled vs. which item it overwrites).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the false "vendored QR library" line; state it's a single
self-contained, zero-dependency file and document offline use.
- Fix keyboard shortcuts to match the in-app help: remove the
non-existent R toggle, add Alt+↑/↓ (reorder), 1–9 toggles a lane.
- "GM drum voice" → synthesized; set-list items load on click (no ▶
auto-start); QR is an external service; clarify release.sh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline the compact cyan VARASYS banner (base64 data URI) in the header,
between the shortcut legend and the theme/help buttons, linked to
varasys.io. Inlined rather than shipped as a file so deploy.sh's
single-file publish still covers it; the self-contained banner reads the
same on light and dark themes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LICENSE holds the verbatim AGPL-3.0 text. index.html carries the SPDX
header + notice; README gains a License section noting §13 is met by the
in-app source link (git.varasys.io/VARASYS/metronome).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The real conflict wasn't the key: shortcuts bailed whenever ANY form
control (slider/checkbox/menu/button) had focus, and those keep focus
after use — so P/T/A/N seemed dead most of the time.
- Guard now stands down only for text-entry fields (text/number/textarea/
contenteditable), so shortcuts work right after you touch a slider or
checkbox.
- Space always = play/stop (preventDefault so it won't scroll, toggle a
focused checkbox, or re-fire a focused button) — the DAW standard,
which also fixes the original Space/checkbox conflict.
- Arrow keys still defer to a focused range slider / select.
- Legend, help overlay, README updated back to Space.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Pad row now shows beatsPerBar × subdivision pads, each individually
toggleable (the subdivision control sets pad resolution). Subdivision
pads render smaller; downbeats labeled; group/beat gaps preserved.
- Mask (beatsOn) is now per-step; playhead tracks the current step.
recomputeLane remaps on grouping/subdivision change and migrates legacy
per-beat masks (saved data, short share patterns) by expanding across subs.
- Share language: =pattern is now per-step (len = beats × sub); short
per-beat patterns still accepted and expanded. README updated.
- Removed the Sig time-signature preset dropdown (confusing vs subdivision).
- Help dialog: link to git.varasys.io/VARASYS/metronome + note that it's a
single-page app you can save & run offline, but file:// won't auto-save
the set list (export a backup).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move per-item 💾 out of set-list rows into one 💾 Save next to Tap; it
overwrites the loaded item and is disabled when no item is loaded.
- History list: red ✕ on hover deletes one session; Clear all wipes
history for the current item only.
- Bump dark-display text again (BPM 80px, timers 26px, status 19px);
widen the display column to fit.
- README: play key Space -> P.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Each meter lane has an 'enable' checkbox right after its number (default on,
green-dim row when off); replaces the right-side 'mute'. Renamed mute→enabled
throughout (scheduler, snapshot, share '!' flag, 1–9 keys, now-playing). Old
saved data still loads (back-compat).
- Features area redesigned into highlighting boxes (Gap trainer / Tempo ramp /
Timers); trainer & ramp boxes light up + un-dim when enabled.
- Set list 'Continue' mode: per-item countdown (saved in each item, 'cd' token),
and when a playing item's countdown hits 0 it auto-loads the next — so a list
with countdowns plays straight through.
- Removed the in-app QR (vendored qrcode.js); 'QR ↗' now opens api.qrserver.com
with the link, behind a banner warning it's a third party (verify it decodes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>