- Extract MIDI-out into a shared partial src/midiout.js (one copy, no drift); both
editors @BUILD:include it. The page wires three transport hooks: midiOutStart(t0)
in start(), midiOutStop() in stop(), midiOutClock(ahead) at end of scheduler();
engine.js calls onMeterHit() per hit.
- Clock-out: a "clock" checkbox (default on) appears with the port picker. When on:
MIDI Start (0xFA) at the downbeat, 24-PPQN clock (0xF8) scheduled across the audio
look-ahead window (timestamped, tracks tempo/ramp, stays tight), Stop (0xFC) on
stop. Guarded against starve-looping at extreme tempos.
- Mirror the feature into editor.html (PM_E-1): header .devctrl pills, the include,
_wireMidi port refresh, transport hooks, listener, and the _isDevicePort fix
(recognizes PM_G-1 Grid etc.).
Conformance suite still green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
engine.js: add an opt-in per-hit hook in scheduleMeterTick — onMeterHit(sound,
time, lvl) — called only if a page defines it (no-op everywhere else). Lets a
page emit MIDI per scheduled hit, in lockstep with the audio scheduler.
pm_e-2.html: a "🎛 MIDI out" header toggle + output-port picker. When on, each
groove hit is sent as a GM drum Note-On (channel 10; note from SOUND_GM e.g.
kick*->36, snare*->38, hat*->42; velocity by accent/normal/ghost) to the chosen
port via output.send([..], ts) with a timestamp derived from the hits audio time
(performance.now() + (time - audioCtx.currentTime)*1000) for tight sync; a note-off
follows 60ms later. Port list prefers a non-PM output (the external gear) and
refreshes on MIDI connect/disconnect. Independent of the local synth + Device
audio; goes green (blue) when on. Web MIDI (Chrome/Edge/Firefox).
Conformance suite still green (engine.js change is in the scheduler, not the codec).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/playback-flow-test.md: on-device verification checklist for the runtime
(stop / rep / next / relative-goto / boundary / manual-override cases).
- editor.html + editor-beta.html: graphical "At end" control (loop / next / stop /
goto ±N) plus a rep-count input in the arrangement panel, wired through
state.rep/state.end -> currentSetup/currentPatch. Authoring is no longer
text-field-only.
- src/engine.js: patchToSetup now clamps tempo to [5,300] and defaults to a beep:4
lane when no lanes are given, matching the firmware. The editors keep their
"no lanes" hint by checking the raw input for a ':' token instead of parsed lanes.
- fixtures: tempo-clamp-high + empty-defaults-to-beep now pass on both engines.
Suite: 41 pass / 1 known (only the intentional vol/cd host boundary remains).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the per-track end-action model designed in docs/track-format.md §3, end to
end across both engines, both firmwares, and the editors.
Grammar (parsed + serialized by engine.js and both app.py):
rep=<n> cycles before the end-action fires (default 1)
end=stop stop after rep cycles
end=next advance one track (sugar for end=+1)
end=<±N> relative goto after rep cycles (e.g. end=-2 = D.S.)
(absent) loop forever — the metronome default
Firmware runtime (pico-cp + pico-explorer): _on_new_bar now consults a per-track
_end_plan() and fires stop / gapless-advance / relative-goto at the right bar.
A cycle = b<bars>, else one master bar; fire bar = rep * cycle. Explicit end=
governs; with no end, the global Continue toggle stays a default (=end=next, still
needs b<bars>) so existing set-lists and the CONT UI are unchanged. _prepare_next
takes a target index; the seam machinery, _do_advance and live-sync all carry rep/end.
Editors (editor.html + editor-beta.html): state.rep/state.end thread through
applySetup / currentSetup / currentPatch so load -> edit -> save preserves the
flow; authoring is via the program-string field (no graphical control yet).
Tests: the 3 playback-flow vectors now pass on both engines (39 pass / 3 known).
Runtime decision logic (_end_plan / _goto_target) unit-tested for stop, rep,
relative goto clamp/wrap, and legacy-Continue precedence. Codec round-trip
verified idempotent. Both firmwares compile + mpy-cross clean.
Also: untrack stale __pycache__/*.pyc build artifacts and gitignore them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A lane with no =pattern produced different defaults on web vs device — a real,
shipped divergence the new conformance suite caught (e.g. hatClosed:4/2 in
"Four-on-the-floor" played steady 8ths in the browser but quarter-notes on the
device). Adopt one rule everywhere: every subdivision sounds at normal level,
accents fall ONLY on group starts (the grouping is the accent map).
- pico-cp/app.py, pico-explorer/app.py: off-beat subdivisions sound at normal (1)
instead of resting (0); group-start accenting was already correct.
- src/engine.js: default beatsOn accents group starts only (was: every beat);
laneCfgToStr isDefault check updated to match so round-trips stay idempotent.
- docs + fixtures: document the rule; default-pattern vectors now pass on both.
Audible effect (intended): device subdivided hat/ride lanes gain their off-beat
strokes (now match the web); web stops over-accenting every beat. Lanes with an
explicit =pattern are unchanged. Verified green via node tests/run.mjs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New editor-beta.html: a bidirectional live mirror over the existing USB-MIDI
SysEx channel (0x7D). Either the website or the device can edit grooves, change
tempo/volume, start/stop, or select set-list items, and the other reflects it.
- src/livesync.js: LiveSync layer (opcodes 0x40 HELLO / 0x41 FULL / 0x42 DELTA /
0x43 BYE) riding the existing _ensureMidi/_send/onDeviceMidi plumbing. Fine
deltas for transport/bpm/vol/sel/beat, coalesced full-state for structural
edits; echo suppression via origin + _applyingRemote guard; device-authoritative
heartbeat reconciles drift. ?loopback=1 self-test mode (no hardware needed).
- editor-beta.html: copy of editor.html + "Live sync" toggle, SysEx routing,
and broadcast hooks at each mutation choke point (guarded by _applyingRemote).
- docs/livesync-protocol.md: wire spec + firmware checklist for pico-cp/app.py
(firmware half owned by the other instance — editor side + spec only here).
- build.sh / deploy.sh: add editor-beta.html to the build + version-stamp loops.
Editor side only; pico-cp/app.py untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously every device .html bundled its full narrative (purpose, BOM,
dimensioned drawings, embedding docs) inside a #techinfo block that embed
mode (?embed=1) only CSS-hid — so every embedder and every landing-page
iframe downloaded all of it (showcase 77%, teacher 49% of the file) for
content no embedder ever sees.
Now:
- <device>.html is the lean widget only: header + front view/controls +
title + summary + program box. ?embed=1 still collapses to the bare
widget; the heavy narrative is gone from the payload.
- info-<device>.html (new, one per form factor) carries all the words —
purpose, dimensions, priced BOM, embedding docs — and embeds the live
widget at the top via the existing iframe + auto-resize protocol
(new shared src/infoembed.html + src/infoembed.js).
- Each device links out to its info page ("…dimensions & BOM →"); the
landing panes and viewport bar now offer both Open ↗ and Specs & info ⓘ.
- Dropped the now-dead "Show info" toggle (CSS + progbox.js).
Branding: adopt the official VARASYS "tagline on the bottom" logos from the
brand kit (light-background variant now matches; dark already did). The
tagline is baked into the PNGs, so remove the CSS .brand-tag / .dev-tag
spans and the showcase canvas-drawn tagline. Brand cyan #0AB3F7 / navy
#1C283F already match the official palette.
build.sh / deploy.sh: build + deploy the six new info-*.html pages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logos: the brand is now a consistent lockup — wordmark image + a crisp CSS
"Simplifying Complexity" tagline — in the shared header, device silkscreens
(teacher/stage/micro), the player (was a CSS text box) and the showcase canvas
(was drawn text; now the real logo image + tagline). Cropped the baked tagline
out of logo-light.b64 so both themes render the tagline once. Renamed device
silk logos to .dev-logo so they no longer shrink the shared header logo.
Embeds: every form factor now loads its default set lists when embedded with no
config — and the Concepts landing embeds them that way (viewport loads
<device>?embed=1 with no forced #p=; the program box reflects what the device
reports and only overrides on explicit Load).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add front + top/side dimensioned schematic views (inch + mm) inside the
"Show info" section of Stage (front + top-edge I/O), Practice (front bar +
both end faces), and Display (pyramid front + side profile). Shared .dview /
.dschem CSS in base.css.
- Fix: progbox.js now defers to DOM-ready, so the "Show info" toggle + #techinfo
are wired even when they sit after the page script (Stage/Practice/Player were
silently not toggling / ignoring ?info=1).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each form-factor page is now plain by default: title + summary + the front view +
a program I/O box (shared src/progbox.* — paste a patch OR a base64 set-list code,
decoded + linted + loaded; copy; reflects the device state and posts it to an
embedding parent). All technical content — description, BOM, dimensions, top/side
views, embedding — hidden behind a "Show technical info" checkbox; ?info=1 opens it
checked. Teacher's top-edge view + dimensions are marked .tech so they hide too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the VCSL sample kit entirely (editor 351K → 141K). All voices are
synthesized; the friendly GM names now alias to the punchier 808/909 renders
(KIT_ALIAS). build.sh drops the @BUILD:samples inlining; assets/samples.json gone.
- Conventions (backward-compatible): GM note-number aliases (36=kick…), '-'/'_'
rest aliases in step patterns, Euclidean (k,n[,rot]) shorthand.
- Per-lane gain in dB (@<db> in the grammar) applied as a velocity multiplier at
schedule time — no stutter; threaded through every host's buildMeters + the
editor's lanes (knob UI comes in Phase B).
- 15/15 engine round-trip tests pass; pages console-clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
- Concepts is now the landing (/): index.html is the form-factor gallery with the
LIVE widget embedded in every box (editor/teacher/stage/micro/showcase/initial),
on the shared header/footer. concepts.html retired; every "Concepts" link → /.
- New shared chrome partials src/header.html, src/footer.html, src/chrome.js
(assembled by build.sh) + .site-foot / details.spec styles in base.css. Applied
to the landing + showcase this pass.
- Showcase redesign per spec: the pendulum bar IS the display — each lane's
subdivisions/accents ride along the rod as moving RGB light (all meters combined);
transparent outside the body (no black window); a printed tempo scale on the
vertical axis with a draggable weight to set tempo; start is an external button
(the real unit starts when lifted from its holder).
Next pass: roll the shared header/footer onto the remaining pages (incl. the editor
header-above-toolbar), merge Open=Info into one page per form factor with the
expandable Info & BOM, and add teacher-style dimensioned views to Stage/Micro/Showcase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each form factor now has an information page (purpose + live embedded widget),
with priced BOMs only on the buildable hardware (Stage ≈$59, Micro ≈$28):
- info-editor.html / info-initial.html — purpose only (web app / concept)
- info-stage.html — purpose + the priced BOM moved out of stage.html
- info-micro.html — purpose + a new ~$28 practice-unit BOM
stage.html drops the BOM panel (+ its .bom CSS) and gains a "Spec & BOM" link;
the shared .bom/.sub table CSS lives in src/base.css. "Info" added to every
page nav and to the concept cards. Wired into build.sh + deploy.sh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Every form factor supports ?embed=1: a head flag (set on <html>, no flash)
strips the site chrome (base.css [data-embed]) + page-specific panels, leaving
just the widget, and posts its height so the host can auto-size it.
- Config/settings string preloads via the existing #p=/#sl= hash. Added that
hash handling to micro.html (it previously only loaded built-in tracks).
- New embed.js loader: <div data-varasys-metronome="micro" data-patch="…"> + one
<script> → an auto-sizing iframe to <page>?embed=1#p=…. New embed.html documents
it and dogfoods a live embedded widget.
- "Embed" nav link added across pages; build.sh/deploy.sh build embed.html and
serve embed.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Shared site header in src/base.css (.site-head/.site-nav/.brand-logo + theme-
aware logo + .tbtn). Applied to player/stage/micro (replacing their text
topbars) so the VARASYS logo + tagline + Editor/Concepts nav is on every page.
- Rebrand the editor: "Stackable Metronome" → "PE-1 — PolyMeter Editor" (title +
h1), with a Concepts link in its header.
- New concepts.html — the PolyMeter Concepts library: cards for the editor and
each form factor (PM-1 Initial/Stage, PM-µ Micro) + a "more coming" card.
- build.sh + deploy.sh build/deploy concepts.html; deploy.sh now loops over pages.
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>