|
| 1 | +const AudioManager = (() => { |
| 2 | + let _ctx = null; |
| 3 | + function ctx() { |
| 4 | + if (!_ctx) _ctx = new (window.AudioContext || window.webkitAudioContext)(); |
| 5 | + if (_ctx.state === 'suspended') _ctx.resume(); |
| 6 | + return _ctx; |
| 7 | + } |
| 8 | + function enabled() { |
| 9 | + const v = localStorage.getItem('soundEnabled'); |
| 10 | + return v === null ? true : v === 'true'; |
| 11 | + } |
| 12 | + function beep(freq, dur, type='sine', gain=0.3, t=null) { |
| 13 | + const c = ctx(); const now = t ?? c.currentTime; |
| 14 | + const osc = c.createOscillator(), g = c.createGain(); |
| 15 | + osc.connect(g); g.connect(c.destination); |
| 16 | + osc.type = type; osc.frequency.setValueAtTime(freq, now); |
| 17 | + g.gain.setValueAtTime(0, now); |
| 18 | + g.gain.linearRampToValueAtTime(gain, now+0.01); |
| 19 | + g.gain.exponentialRampToValueAtTime(0.001, now+dur); |
| 20 | + osc.start(now); osc.stop(now+dur+0.01); |
| 21 | + } |
| 22 | + function noise(dur=0.1, gain=0.2) { |
| 23 | + const c = ctx(), buf = c.createBuffer(1, c.sampleRate*dur, c.sampleRate); |
| 24 | + const d = buf.getChannelData(0); |
| 25 | + for (let i=0;i<d.length;i++) d[i]=Math.random()*2-1; |
| 26 | + const src = c.createBufferSource(); src.buffer = buf; |
| 27 | + const g = c.createGain(), f = c.createBiquadFilter(); |
| 28 | + f.type='highpass'; f.frequency.value=1000; |
| 29 | + src.connect(f); f.connect(g); g.connect(c.destination); |
| 30 | + const t = c.currentTime; |
| 31 | + g.gain.setValueAtTime(gain, t); |
| 32 | + g.gain.exponentialRampToValueAtTime(0.001, t+dur); |
| 33 | + src.start(t); src.stop(t+dur+0.01); |
| 34 | + } |
| 35 | + const sounds = { |
| 36 | + snake_eat() { const c=ctx(),n=c.currentTime; beep(300,.06,'square',.25,n); beep(600,.08,'square',.20,n+.05); }, |
| 37 | + snake_die() { const c=ctx(),n=c.currentTime; beep(400,.12,'sawtooth',.3,n); beep(200,.20,'sawtooth',.25,n+.10); beep(100,.30,'sine',.20,n+.25); }, |
| 38 | + mole_hit() { noise(.08,.35); const c=ctx(); beep(180,.10,'sine',.2,c.currentTime+.02); }, |
| 39 | + card_flip() { noise(.05,.12); const c=ctx(); beep(800,.04,'sine',.08,c.currentTime+.01); }, |
| 40 | + card_deal() { noise(.07,.18); const c=ctx(); beep(300,.06,'sine',.12,c.currentTime+.02); }, |
| 41 | + game_win() { const c=ctx(),n=c.currentTime; [523,659,784,1047].forEach((f,i)=>beep(f,.15,'sine',.25,n+i*.12)); }, |
| 42 | + game_over() { const c=ctx(),n=c.currentTime; beep(440,.15,'sawtooth',.3,n); beep(349,.20,'sawtooth',.28,n+.18); beep(262,.35,'sine',.25,n+.40); }, |
| 43 | + score_point() { beep(880,.08,'sine',.2); }, |
| 44 | + wrong() { beep(150,.15,'sawtooth',.2); }, |
| 45 | + click() { beep(600,.04,'sine',.1); }, |
| 46 | + }; |
| 47 | + return { |
| 48 | + play(name) { |
| 49 | + if (!enabled()) return false; |
| 50 | + if (sounds[name]) { try { sounds[name](); } catch(e){} return true; } |
| 51 | + return false; |
| 52 | + }, |
| 53 | + setEnabled(v) { localStorage.setItem('soundEnabled', String(!!v)); }, |
| 54 | + isEnabled() { return enabled(); }, |
| 55 | + list() { return Object.keys(sounds); }, |
| 56 | + }; |
| 57 | +})(); |
| 58 | +window.AudioManager = AudioManager; |
0 commit comments