/* Neon Rampage — synthesized audio (WebAudio). All sounds generated at runtime. */ (function () { 'use strict'; var US = (window.US = window.US || {}); var ctx = null; var master = null; var sfxGain = null; var musicGain = null; var musicTimer = null; var step = 0; var windSrc = null; // { src, lfo } function ensure() { if (ctx) return true; try { var AC = window.AudioContext || window.webkitAudioContext; if (!AC) return false; ctx = new AC(); master = ctx.createGain(); master.gain.value = 0.9; master.connect(ctx.destination); sfxGain = ctx.createGain(); sfxGain.gain.value = US.save.get().settings.sfx; sfxGain.connect(master); musicGain = ctx.createGain(); musicGain.gain.value = US.save.get().settings.music * 0.5; musicGain.connect(master); return true; } catch (e) { return false; } } function resume() { if (!ensure()) return; if (ctx.state === 'suspended') ctx.resume(); } // Generic one-shot: oscillator with pitch slide + optional noise burst. function tone(opts) { if (!ensure() || ctx.state === 'suspended') { if (!resume()) return; } var t = ctx.currentTime; var o = ctx.createOscillator(); var g = ctx.createGain(); o.type = opts.type || 'square'; o.frequency.setValueAtTime(opts.f0 || 440, t); if (opts.f1) o.frequency.exponentialRampToValueAtTime(Math.max(20, opts.f1), t + (opts.dur || 0.15)); g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(opts.vol || 0.2, t + 0.008); g.gain.exponentialRampToValueAtTime(0.0001, t + (opts.dur || 0.15)); o.connect(g); g.connect(sfxGain); o.start(t); o.stop(t + (opts.dur || 0.15) + 0.05); if (opts.noise) { var len = Math.floor(ctx.sampleRate * (opts.noiseDur || 0.12)); var buf = ctx.createBuffer(1, len, ctx.sampleRate); var d = buf.getChannelData(0); for (var i = 0; i < len; i++) d[i] = (Math.random() * 2 - 1) * (1 - i / len); var src = ctx.createBufferSource(); src.buffer = buf; var ng = ctx.createGain(); ng.gain.value = opts.noiseVol || 0.25; src.connect(ng); ng.connect(sfxGain); src.start(t); } } var SFX = { shoot: function () { tone({ type: 'square', f0: 720, f1: 160, dur: 0.07, vol: 0.10 }); }, shotgun: function () { tone({ type: 'sawtooth', f0: 300, f1: 90, dur: 0.16, vol: 0.18, noise: true, noiseVol: 0.2 }); }, rail: function () { tone({ type: 'sawtooth', f0: 1400, f1: 200, dur: 0.22, vol: 0.14 }); }, rocket: function () { tone({ type: 'triangle', f0: 220, f1: 60, dur: 0.3, vol: 0.2, noise: true }); }, smg: function () { tone({ type: 'square', f0: 520, f1: 140, dur: 0.05, vol: 0.07 }); }, hit: function () { tone({ type: 'triangle', f0: 300, f1: 120, dur: 0.05, vol: 0.08 }); }, kill: function () { tone({ type: 'square', f0: 260, f1: 520, dur: 0.09, vol: 0.10 }); }, explode: function () { tone({ type: 'sawtooth', f0: 130, f1: 40, dur: 0.4, vol: 0.28, noise: true, noiseVol: 0.35, noiseDur: 0.3 }); }, hurt: function () { tone({ type: 'sawtooth', f0: 180, f1: 70, dur: 0.18, vol: 0.22 }); }, pickup: function () { tone({ type: 'sine', f0: 620, f1: 1240, dur: 0.09, vol: 0.12 }); }, heal: function () { tone({ type: 'sine', f0: 440, f1: 880, dur: 0.18, vol: 0.14 }); }, card: function () { tone({ type: 'sine', f0: 500, f1: 1000, dur: 0.14, vol: 0.13 }); }, onfire: function () { [523, 659, 784, 1047].forEach(function (f, i) { setTimeout(function () { tone({ type: 'square', f0: f, dur: 0.12, vol: 0.16 }); }, i * 70); }); }, ult: function () { tone({ type: 'sawtooth', f0: 80, f1: 900, dur: 0.5, vol: 0.3, noise: true, noiseVol: 0.3 }); }, bomb: function () { tone({ type: 'sawtooth', f0: 100, f1: 30, dur: 0.6, vol: 0.32, noise: true, noiseVol: 0.45, noiseDur: 0.5 }); }, bossRoar:function () { tone({ type: 'sawtooth', f0: 90, f1: 45, dur: 0.7, vol: 0.3, noise: true, noiseVol: 0.2 }); }, stomp: function (vol) { var v = vol || 0.18; tone({ type: 'sine', f0: 85, f1: 26, dur: 0.17, vol: v }); tone({ type: 'triangle', f0: 55, f1: 22, dur: 0.24, vol: v * 0.7 }); }, ui: function () { tone({ type: 'sine', f0: 700, f1: 900, dur: 0.05, vol: 0.08 }); }, buy: function () { [660, 880].forEach(function (f, i) { setTimeout(function () { tone({ type: 'sine', f0: f, dur: 0.1, vol: 0.12 }); }, i * 90); }); }, lose: function () { [392, 330, 262, 196].forEach(function (f, i) { setTimeout(function () { tone({ type: 'triangle', f0: f, dur: 0.22, vol: 0.16 }); }, i * 160); }); }, win: function () { [523, 659, 784, 1047, 1319].forEach(function (f, i) { setTimeout(function () { tone({ type: 'square', f0: f, dur: 0.15, vol: 0.14 }); }, i * 110); }); } }; /* --- tiny procedural music loop: dark synthwave bass + hats --- */ var BASS = [0, 0, 7, 0, 5, 5, 3, 7]; // semitone offsets from A1 function note(semi) { return 55 * Math.pow(2, semi / 12); } function musicStep() { if (!ctx || !musicOn) return; var t = ctx.currentTime; // bass var o = ctx.createOscillator(); var g = ctx.createGain(); o.type = 'sawtooth'; var root = BASS[Math.floor(step / 2) % BASS.length]; var playingBoss = US.game && US.game.isBossWave && US.game.isBossWave(); var mul = playingBoss ? Math.pow(2, 3 / 12) : 1; o.frequency.value = note(root) * mul; g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.22, t + 0.02); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.24); o.connect(g); g.connect(musicGain); o.start(t); o.stop(t + 0.3); // hat every other step if (step % 2 === 1) { var len = Math.floor(ctx.sampleRate * 0.03); var buf = ctx.createBuffer(1, len, ctx.sampleRate); var d = buf.getChannelData(0); for (var i = 0; i < len; i++) d[i] = (Math.random() * 2 - 1) * (1 - i / len); var src = ctx.createBufferSource(); src.buffer = buf; var hg = ctx.createGain(); hg.gain.value = 0.05; src.connect(hg); hg.connect(musicGain); src.start(t); } step++; } var musicOn = false; /* --- wind ambience: filtered brown noise with slow gusts --- */ var ambGain = null; function startWind() { if (!ctx || windSrc || !musicOn) return; var len = ctx.sampleRate * 2; var buf = ctx.createBuffer(1, len, ctx.sampleRate); var d = buf.getChannelData(0); var lastV = 0; for (var i = 0; i < len; i++) { var wn = Math.random() * 2 - 1; lastV = (lastV + 0.02 * wn) / 1.02; // brown-ish noise d[i] = lastV * 3.5; } var src = ctx.createBufferSource(); src.buffer = buf; src.loop = true; var lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 300; lp.Q.value = 0.7; // gust LFO breathing on the filter var lfo = ctx.createOscillator(); lfo.frequency.value = 0.11; var lfoG = ctx.createGain(); lfoG.gain.value = 150; lfo.connect(lfoG); lfoG.connect(lp.frequency); ambGain = ctx.createGain(); ambGain.gain.value = 0; src.connect(lp); lp.connect(ambGain); ambGain.connect(musicGain); try { ambGain.gain.linearRampToValueAtTime(0.5, ctx.currentTime + 2.5); src.start(); lfo.start(); windSrc = { src: src, lfo: lfo }; } catch (e) { /* autoplay quirks */ } } function stopWind() { if (!windSrc) return; try { ambGain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.4); var w = windSrc; setTimeout(function () { try { w.src.stop(); w.lfo.stop(); } catch (e) {} }, 450); } catch (e) {} windSrc = null; } US.audio = { resume: resume, sfx: function (name) { if (!ensure()) return; sfxGain.gain.value = US.save.get().settings.sfx; if (SFX[name]) SFX[name].apply(null, Array.prototype.slice.call(arguments, 1)); }, setVolumes: function () { if (!ctx) return; sfxGain.gain.value = US.save.get().settings.sfx; musicGain.gain.value = US.save.get().settings.music * 0.5; }, startMusic: function () { if (!ensure()) return; resume(); if (!musicTimer) { musicOn = true; musicStep(); musicTimer = setInterval(musicStep, 250); } startWind(); }, stopMusic: function () { musicOn = false; if (musicTimer) { clearInterval(musicTimer); musicTimer = null; } stopWind(); }, hoverTick: function () { if (!ensure()) return; tone({ type: 'sine', f0: 900, f1: 1100, dur: 0.035, vol: 0.05 }); }, thunder: function () { if (!ensure()) return; tone({ type: 'sawtooth', f0: 52, f1: 22, dur: 1.2, vol: 0.3, noise: true, noiseVol: 0.55, noiseDur: 1.3 }); }, distantRoar: function () { if (!ensure()) return; tone({ type: 'sawtooth', f0: 120, f1: 36, dur: 1.5, vol: 0.09 }); setTimeout(function () { tone({ type: 'triangle', f0: 70, f1: 28, dur: 1.9, vol: 0.07 }); }, 180); } }; })();