diff --git a/index.html b/index.html
index f04836e..c72d5a5 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
-
+
gandora
@@ -595,8 +595,35 @@ function drawRings(canvas, spec, { lit = 1, pulse = null } = {}) {
}
/* ---------- the visualizer: an analyser on the player, driving the cover while music plays ---------- */
+/* the first tap unlocks the player: a silent clip played inside the gesture makes the element "user-started",
+ so the first real track can start itself when the render lands, however long that takes */
+const SILENCE = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=";
+function prime() {
+ if (state.primed || (state.audio.src && !state.audio.paused)) return;
+ try { state.audio.src = SILENCE; const p = state.audio.play(); if (p && p.then) p.then(() => { state.primed = true; }).catch(() => {}); } catch {}
+}
+for (const ev of ["pointerdown", "touchend", "keydown"]) document.addEventListener(ev, prime, { capture: true, passive: true });
+/* iOS routes sound through Web Audio the moment the element is wired to an analyser, and suspends Web Audio when
+ the screen locks (and obeys the mute switch). So on iOS the cover pulses to the beat from the sheet instead,
+ and the music stays a plain element that keeps playing in the background. */
+const IOS = /iP(hone|ad|od)/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
const viz = { ctx: null, an: null, data: null, raf: 0, smooth: { bass: 0, mid: 0, high: 0 } };
+function vizBeat() {
+ cancelAnimationFrame(viz.raf);
+ const canvas = $("#cover canvas"), t0 = performance.now(), rnd = prng(hash32(state.current.spec.title || ""));
+ const frame = () => {
+ if (!state.playing || !state.current || !state.current.blob) { viz.raf = 0; return; }
+ const spec = state.current.spec, t = (performance.now() - t0) / 1000, bps = (+spec.bpm || 100) / 60, en = +spec.energy || 0.5;
+ const beat = Math.pow(Math.max(0, Math.cos(2 * Math.PI * t * bps)), 6); // a kick on every beat
+ const bar = Math.pow(Math.max(0, Math.cos(2 * Math.PI * t * bps / 4)), 2); // and a swell on the bar
+ const bass = Math.min(1, (0.25 + 0.75 * beat) * (0.5 + 0.5 * en)), mid = 0.3 + 0.3 * bar * en, high = 0.2 + 0.25 * Math.pow(Math.max(0, Math.sin(2 * Math.PI * t * bps * 2)), 4) * en;
+ drawCover(canvas, spec, { pulse: { bass, mid, high, t } });
+ viz.raf = requestAnimationFrame(frame);
+ };
+ viz.raf = requestAnimationFrame(frame);
+}
function vizStart() {
+ if (IOS || state.beatViz) return vizBeat();
// the player is only wired through the analyser once the context is actually running: a suspended context
// (iOS before a tap) would mute the music, and the cover can wait for the next gesture
try {