From cffb5868b6d66c80b5de961183b7a8b1d528b9e1 Mon Sep 17 00:00:00 2001 From: Sterling Archer Date: Sun, 31 May 2026 14:27:41 -0700 Subject: [PATCH] fix: create AudioContext on touchstart gesture so it starts running, skip sounds if not ready --- game.js | 71 ++++++++++++++++++--------------------------------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/game.js b/game.js index 04ee41b..cd9adf0 100644 --- a/game.js +++ b/game.js @@ -186,16 +186,30 @@ const Music = (() => { const SFX = (() => { let ctx = null; - let unlocked = false; - const pending = []; function getCtx() { if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)(); + if (ctx.state === 'suspended') ctx.resume().catch(() => {}); return ctx; } - function playTone(freq, type, vol, attack, sustain, release, when) { - const c = ctx; + document.addEventListener('touchstart', () => { + if (!ctx) { + ctx = new (window.AudioContext || window.webkitAudioContext)(); + } else if (ctx.state === 'suspended') { + ctx.resume().catch(() => {}); + } + }, { passive: true }); + + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible' && ctx && ctx.state === 'suspended') { + ctx.resume().catch(() => {}); + } + }); + + function tone(freq, type, vol, attack, sustain, release, when) { + const c = getCtx(); + if (c.state !== 'running') return; const t = when ?? c.currentTime; const osc = c.createOscillator(); const gain = c.createGain(); @@ -211,52 +225,11 @@ const SFX = (() => { osc.stop(t + attack + sustain + release + 0.01); } - function flushPending() { - if (!ctx || ctx.state !== 'running') return; - while (pending.length) { - const fn = pending.shift(); - try { fn(); } catch(e) {} - } - } - - function unlock() { - if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)(); - if (ctx.state === 'suspended') { - ctx.resume().then(() => { unlocked = true; flushPending(); }).catch(() => {}); - } else if (ctx.state === 'running') { - unlocked = true; - flushPending(); - } - } - - document.addEventListener('touchstart', unlock, { passive: true }); - document.addEventListener('touchend', unlock, { passive: true }); - document.addEventListener('pointerdown', unlock, { passive: true }); - document.addEventListener('click', unlock, { passive: true }); - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible' && ctx && ctx.state === 'suspended') { - ctx.resume().then(() => { unlocked = true; flushPending(); }).catch(() => {}); - } - }); - - function tone(freq, type, vol, attack, sustain, release, when) { - if (!unlocked || !ctx || ctx.state !== 'running') { - pending.push(() => { playTone(freq, type, vol, attack, sustain, release, ctx.currentTime); }); - return; - } - playTone(freq, type, vol, attack, sustain, release, when); - } - function seq(notes, type='square', vol=0.18) { - if (!unlocked || !ctx || ctx.state !== 'running') { - pending.push(() => { - let t = ctx.currentTime + 0.02; - notes.forEach(n => { if (n.f) playTone(n.f, type, vol, 0.01, n.d * 0.6, n.d * 0.4, t); t += n.d; }); - }); - return; - } - let t = ctx.currentTime + 0.02; - notes.forEach(n => { if (n.f) playTone(n.f, type, vol, 0.01, n.d * 0.6, n.d * 0.4, t); t += n.d; }); + const c = getCtx(); + if (c.state !== 'running') return; + let t = c.currentTime + 0.02; + notes.forEach(n => { if (n.f) tone(n.f, type, vol, 0.01, n.d * 0.6, n.d * 0.4, t); t += n.d; }); } return {