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>
55 lines
2.6 KiB
JavaScript
55 lines
2.6 KiB
JavaScript
/* VARASYS PolyMeter — embed loader (zero-dependency, ~1 KB).
|
||
*
|
||
* Drop a placeholder + this script into any page:
|
||
* <div data-varasys-metronome="micro" data-patch="v1;t120;kick:4;snare:4=.X.X"></div>
|
||
* <script src="https://metronome.varasys.io/embed.js"></script>
|
||
*
|
||
* Attributes:
|
||
* data-varasys-metronome editor | initial | teacher | stage | micro | showcase (which form factor)
|
||
* data-patch a PolyMeter program/settings string (preloads it)
|
||
* data-setlist a base64url set-list code (alternative to data-patch)
|
||
* data-width / data-height iframe size (default 100% × 300; height auto-grows)
|
||
*
|
||
* Each placeholder becomes an <iframe src=".../<page>?embed=1#p=<patch>"> (the page's
|
||
* own ?embed=1 mode strips the site chrome) and auto-resizes to the widget's content.
|
||
*/
|
||
(function () {
|
||
var PAGES = { editor: "editor.html", initial: "player.html", teacher: "teacher.html",
|
||
stage: "stage.html", micro: "micro.html", showcase: "showcase.html" };
|
||
var me = document.currentScript;
|
||
var ORIGIN = me ? me.src.replace(/\/embed\.js(\?.*)?$/, "") : location.origin;
|
||
|
||
function build(el) {
|
||
var v = (el.getAttribute("data-varasys-metronome") || "micro").toLowerCase();
|
||
var page = PAGES[v] || "micro.html";
|
||
var patch = el.getAttribute("data-patch");
|
||
var sl = el.getAttribute("data-setlist");
|
||
var hash = patch ? "#p=" + encodeURIComponent(patch)
|
||
: sl ? "#sl=" + encodeURIComponent(sl) : "";
|
||
var f = document.createElement("iframe");
|
||
f.src = ORIGIN + "/" + page + "?embed=1" + hash;
|
||
f.title = "VARASYS PolyMeter — " + v;
|
||
f.loading = "lazy";
|
||
f.setAttribute("allow", "autoplay");
|
||
f.setAttribute("data-vmeter", v);
|
||
f.style.cssText = "border:0;display:block;max-width:100%;width:" +
|
||
(el.getAttribute("data-width") || "100%") + ";height:" +
|
||
(el.getAttribute("data-height") || "300") + "px";
|
||
(el.replaceWith ? el.replaceWith(f) : el.parentNode.replaceChild(f, el));
|
||
}
|
||
|
||
function init() {
|
||
var els = document.querySelectorAll("[data-varasys-metronome]");
|
||
for (var i = 0; i < els.length; i++) build(els[i]);
|
||
}
|
||
|
||
// auto-resize: the widget posts { type:'varasys-h', h } on load/resize
|
||
window.addEventListener("message", function (e) {
|
||
if (!e.data || e.data.type !== "varasys-h" || typeof e.data.h !== "number") return;
|
||
var f = document.querySelectorAll("iframe[data-vmeter]");
|
||
for (var i = 0; i < f.length; i++) if (f[i].contentWindow === e.source) f[i].style.height = e.data.h + "px";
|
||
});
|
||
|
||
if (document.readyState !== "loading") init();
|
||
else document.addEventListener("DOMContentLoaded", init);
|
||
})();
|