/* ========================================================= * REPRTERRA WEB — audio.js * Tiny WebAudio synth for SFX. No assets needed. * ========================================================= */ 'use strict'; window.RTS = window.RTS || {}; RTS.audio = (function () { let ctx = null; let master = null; let muted = false; let lastPlay = {}; // throttle function ensure() { if (ctx) return true; try { const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return false; ctx = new AC(); master = ctx.createGain(); master.gain.value = 0.32; master.connect(ctx.destination); } catch (e) { return false; } return true; } function resume() { if (!ensure()) return; if (ctx.state === 'suspended') ctx.resume(); } function throttled(name, minGap) { const t = performance.now(); if (lastPlay[name] && t - lastPlay[name] < minGap) return true; lastPlay[name] = t; return false; } // basic tone function tone(freq, dur, type, vol, slideTo, delay) { if (muted || !ensure()) return; const t0 = ctx.currentTime + (delay || 0); const o = ctx.createOscillator(); const g = ctx.createGain(); o.type = type || 'square'; o.frequency.setValueAtTime(freq, t0); if (slideTo) o.frequency.exponentialRampToValueAtTime(Math.max(20, slideTo), t0 + dur); g.gain.setValueAtTime(vol || 0.2, t0); g.gain.exponentialRampToValueAtTime(0.001, t0 + dur); o.connect(g); g.connect(master); o.start(t0); o.stop(t0 + dur + 0.02); } // noise burst function noise(dur, vol, filterFreq, delay) { if (muted || !ensure()) return; const t0 = ctx.currentTime + (delay || 0); const len = Math.max(1, Math.floor(ctx.sampleRate * dur)); const buf = ctx.createBuffer(1, len, ctx.sampleRate); const d = buf.getChannelData(0); for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1; const src = ctx.createBufferSource(); src.buffer = buf; const f = ctx.createBiquadFilter(); f.type = 'lowpass'; f.frequency.value = filterFreq || 1200; const g = ctx.createGain(); g.gain.setValueAtTime(vol || 0.2, t0); g.gain.exponentialRampToValueAtTime(0.001, t0 + dur); src.connect(f); f.connect(g); g.connect(master); src.start(t0); src.stop(t0 + dur + 0.02); } const S = {}; S.click = () => { tone(700, 0.05, 'square', 0.10); }; S.build = () => { noise(0.15, 0.3, 500); tone(140, 0.12, 'triangle', 0.25, 90); }; S.deny = () => { tone(180, 0.12, 'sawtooth', 0.14, 120); }; S.shoot = () => { if (!throttled('shoot', 60)) noise(0.06, 0.14, 3200); }; S.arrow = () => { if (!throttled('arrow', 70)) tone(900, 0.07, 'triangle', 0.08, 300); }; S.cannon = () => { noise(0.35, 0.5, 350); tone(70, 0.3, 'sine', 0.4, 40); }; S.spit = () => { if (!throttled('spit', 120)) tone(300, 0.1, 'sawtooth', 0.09, 160); }; S.die = () => { if (!throttled('die', 80)) tone(220, 0.18, 'sawtooth', 0.16, 60); }; S.thud = () => { if (!throttled('thud', 120)) { noise(0.08, 0.22, 240); tone(90, 0.09, 'sine', 0.2, 55); } }; S.roar = () => { tone(110, 0.7, 'sawtooth', 0.34, 55); noise(0.5, 0.22, 300); }; S.alarm = () => { tone(520, 0.28, 'square', 0.16); tone(380, 0.28, 'square', 0.16, null, 0.3); tone(520, 0.28, 'square', 0.16, null, 0.6); }; S.coin = () => { tone(880, 0.05, 'sine', 0.08); tone(1320, 0.08, 'sine', 0.08, null, 0.05); }; S.train = () => { tone(440, 0.08, 'square', 0.1); tone(660, 0.1, 'square', 0.1, null, 0.08); }; S.win = () => { [523, 659, 784, 1046].forEach((f, i) => tone(f, 0.22, 'triangle', 0.22, null, i * 0.16)); }; S.lose = () => { [392, 330, 262, 196].forEach((f, i) => tone(f, 0.3, 'sawtooth', 0.2, null, i * 0.22)); }; S.explode = () => { noise(0.6, 0.55, 260); tone(55, 0.5, 'sine', 0.45, 30); }; S.toggleMute = function () { muted = !muted; return muted; }; S.isMuted = () => muted; S.resume = resume; return S; })();