fix: replace SFX with pending queue pattern, defer playback until AudioContext is running
This commit is contained in:
@@ -186,32 +186,19 @@ const Music = (() => {
|
|||||||
|
|
||||||
const SFX = (() => {
|
const SFX = (() => {
|
||||||
let ctx = null;
|
let ctx = null;
|
||||||
|
let unlocked = false;
|
||||||
|
const pending = [];
|
||||||
|
|
||||||
function getCtx() {
|
function getCtx() {
|
||||||
if (!ctx || ctx.state === 'closed') ctx = new (window.AudioContext || window.webkitAudioContext)();
|
if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
if (ctx.state === 'suspended') ctx.resume().catch(() => {});
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlock() {
|
function playTone(freq, type, vol, attack, sustain, release, when) {
|
||||||
if (!ctx || ctx.state === 'closed') ctx = new (window.AudioContext || window.webkitAudioContext)();
|
const c = ctx;
|
||||||
if (ctx.state === 'suspended') ctx.resume().catch(() => {});
|
|
||||||
}
|
|
||||||
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().catch(() => {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function tone(freq, type, vol, attack, sustain, release, when) {
|
|
||||||
const c = getCtx();
|
|
||||||
const t = when ?? c.currentTime;
|
const t = when ?? c.currentTime;
|
||||||
const osc = c.createOscillator();
|
const osc = c.createOscillator();
|
||||||
const gain= c.createGain();
|
const gain = c.createGain();
|
||||||
osc.connect(gain);
|
osc.connect(gain);
|
||||||
gain.connect(c.destination);
|
gain.connect(c.destination);
|
||||||
osc.type = type;
|
osc.type = type;
|
||||||
@@ -224,22 +211,61 @@ const SFX = (() => {
|
|||||||
osc.stop(t + attack + sustain + release + 0.01);
|
osc.stop(t + attack + sustain + release + 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
function seq(notes, type='square', vol=0.18) {
|
function flushPending() {
|
||||||
const c = getCtx();
|
if (!ctx || ctx.state !== 'running') return;
|
||||||
let t = c.currentTime + 0.02;
|
while (pending.length) {
|
||||||
notes.forEach(n => {
|
const fn = pending.shift();
|
||||||
if (n.f) tone(n.f, type, vol, 0.01, n.d * 0.6, n.d * 0.4, t);
|
try { fn(); } catch(e) {}
|
||||||
t += n.d;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
boot() { ctx = new (window.AudioContext || window.webkitAudioContext)(); seq([{f:262,d:.1},{f:330,d:.1},{f:392,d:.1},{f:523,d:.25}], 'square', 0.2); },
|
boot() { seq([{f:262,d:.1},{f:330,d:.1},{f:392,d:.1},{f:523,d:.25}], 'square', 0.2); },
|
||||||
overworldStart() { seq([{f:392,d:.12},{f:440,d:.12},{f:494,d:.12},{f:523,d:.18},{f:494,d:.1},{f:440,d:.1},{f:392,d:.18},{f:330,d:.12},{f:392,d:.12},{f:440,d:.12},{f:392,d:.24}], 'square', 0.12); },
|
overworldStart() { seq([{f:392,d:.12},{f:440,d:.12},{f:494,d:.12},{f:523,d:.18},{f:494,d:.1},{f:440,d:.1},{f:392,d:.18},{f:330,d:.12},{f:392,d:.12},{f:440,d:.12},{f:392,d:.24}], 'square', 0.12); },
|
||||||
step() { tone(180, 'square', 0.04, 0.005, 0.02, 0.03); },
|
step() { tone(180, 'square', 0.04, 0.005, 0.02, 0.03); },
|
||||||
encounter() { seq([{f:523,d:.1},{f:659,d:.18}], 'square', 0.22); },
|
encounter() { seq([{f:523,d:.1},{f:659,d:.18}], 'square', 0.22); },
|
||||||
type() { tone(880 + Math.random()*200, 'square', 0.03, 0.005, 0.01, 0.02); },
|
type() { tone(880 + Math.random()*200, 'square', 0.03, 0.005, 0.01, 0.02); },
|
||||||
select() { const c = getCtx(); tone(440, 'square', 0.12, 0.005, 0.03, 0.04, c.currentTime + 0.05); },
|
select() { tone(440, 'square', 0.12, 0.005, 0.03, 0.04); },
|
||||||
backspace() { seq([{f:350,d:.05},{f:250,d:.08}], 'triangle', 0.12); },
|
backspace() { seq([{f:350,d:.05},{f:250,d:.08}], 'triangle', 0.12); },
|
||||||
correct() { seq([{f:523,d:.08},{f:659,d:.08},{f:784,d:.08},{f:1047,d:.2}], 'square', 0.18); },
|
correct() { seq([{f:523,d:.08},{f:659,d:.08},{f:784,d:.08},{f:1047,d:.2}], 'square', 0.18); },
|
||||||
streak() { seq([{f:523,d:.06},{f:659,d:.06},{f:784,d:.06},{f:1047,d:.06},{f:1319,d:.2}], 'square', 0.18); },
|
streak() { seq([{f:523,d:.06},{f:659,d:.06},{f:784,d:.06},{f:1047,d:.06},{f:1319,d:.2}], 'square', 0.18); },
|
||||||
|
|||||||
Reference in New Issue
Block a user