/* ============================================================ * audio.js — tiny WebAudio synth SFX (no asset files needed) * ============================================================ */ 'use strict'; const AudioSys = { ctx: null, muted: false, /** Lazily create/resume the context (must follow a user gesture). */ ensure() { if (this.muted) return null; try { if (!this.ctx) { const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return null; this.ctx = new AC(); } if (this.ctx.state === 'suspended') this.ctx.resume(); return this.ctx; } catch (e) { return null; } }, tone(freq, dur, type = 'sine', vol = 0.12, when = 0, glideTo = 0) { const c = this.ensure(); if (!c) return; const t = c.currentTime + when; const o = c.createOscillator(); const g = c.createGain(); o.type = type; o.frequency.setValueAtTime(freq, t); if (glideTo) o.frequency.exponentialRampToValueAtTime(Math.max(30, glideTo), t + dur); g.gain.setValueAtTime(vol, t); g.gain.exponentialRampToValueAtTime(0.0001, t + dur); o.connect(g); g.connect(c.destination); o.start(t); o.stop(t + dur + 0.03); }, sfx(name) { switch (name) { case 'click': this.tone(650, .05, 'square', .04); break; case 'toast': this.tone(520, .07, 'sine', .06); this.tone(780, .06, 'sine', .04, .05); break; case 'error': this.tone(170, .16, 'sawtooth', .09); this.tone(120, .18, 'sawtooth', .08, .08); break; case 'place': this.tone(340, .07, 'triangle', .12); this.tone(240, .09, 'triangle', .10, .06); break; case 'sell': [880, 1175, 1568].forEach((f, i) => this.tone(f, .09, 'square', .06, i * .06)); break; case 'chime': this.tone(660, .12, 'sine', .09); this.tone(990, .14, 'sine', .07, .09); break; case 'level': this.tone(700, .08, 'square', .06); this.tone(1050, .12, 'square', .06, .07); break; case 'fanfare': [[523, 0], [659, .11], [784, .22], [1047, .33]].forEach(([f, d]) => this.tone(f, .16, 'triangle', .1, d)); this.tone(1047, .4, 'sine', .07, .5); break; case 'bill': this.tone(330, .1, 'square', .07); this.tone(330, .12, 'square', .07, .14); break; case 'horn': this.tone(290, .28, 'sawtooth', .08); this.tone(290, .34, 'sawtooth', .08, .32); break; case 'thud': this.tone(120, .22, 'sine', .16, 0, 55); break; case 'splash': this.tone(500, .1, 'sine', .08, 0, 180); this.tone(260, .16, 'sine', .08, .05, 90); break; case 'toll': this.tone(98, 1.4, 'sine', .2); this.tone(196, 1.2, 'sine', .08, .02); break; case 'kiss': this.tone(900, .06, 'sine', .07); this.tone(1300, .08, 'sine', .05, .06); break; default: break; } }, }; /* unlock audio on first gesture */ window.addEventListener('pointerdown', () => AudioSys.ensure(), { once: true });