;(function(){ function hasStereoPanner(ctx){ return !!(ctx && ctx.createStereoPanner) } function makePanner(ctx){ if(hasStereoPanner(ctx)) return ctx.createStereoPanner(); var gainL = ctx.createGain(); var gainR = ctx.createGain(); var merger = ctx.createChannelMerger(2); gainL.connect(merger, 0, 0); gainR.connect(merger, 0, 1); var node = { _ctx: ctx, _l: gainL, _r: gainR, _merger: merger, connect: function(dest){ merger.connect(dest) }, pan: { value: 0 }, setPan: function(p){ var pan = Math.max(-1, Math.min(1, p||0)); var l = Math.cos((pan+1)*Math.PI/4); var r = Math.sin((pan+1)*Math.PI/4); this._l.gain.setValueAtTime(l, ctx.currentTime); this._r.gain.setValueAtTime(r, ctx.currentTime); this.pan.value = pan } } return node } var AudioEngine = { ctx: null, master: null, noiseNodes: [], panInterval: null, ensureCtx: function(){ if(!this.ctx){ this.ctx = new (window.AudioContext||window.webkitAudioContext)(); this.master = this.ctx.createGain(); this.master.gain.value = 0.6; this.master.connect(this.ctx.destination) } if(this.ctx.state==='suspended'){ this.ctx.resume() } return this.ctx }, setVolume: function(v){ var ctx = this.ensureCtx(); var val = Math.max(0, Math.min(1, v||0.6)); this.master.gain.setValueAtTime(val, ctx.currentTime) }, // Enhanced original sounds playClick: function(pan){ var ctx = this.ensureCtx(); var osc = ctx.createOscillator(); var gain = ctx.createGain(); var filter = ctx.createBiquadFilter(); var p = makePanner(ctx); if(p.setPan) p.setPan(pan||0); else if(p.pan) p.pan.value = pan||0; osc.type='square'; osc.frequency.setValueAtTime(1200, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, ctx.currentTime+0.02); filter.type = 'lowpass'; filter.frequency.value = 3000; filter.Q.value = 2; gain.gain.setValueAtTime(0.0001, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.5, ctx.currentTime+0.003); gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime+0.025); osc.connect(filter); filter.connect(gain); if(p._l){ gain.connect(p._l); gain.connect(p._r) } else { gain.connect(p) } p.connect(this.master); osc.start(); osc.stop(ctx.currentTime+0.05) }, playPing: function(pan){ var ctx = this.ensureCtx(); var osc = ctx.createOscillator(); var osc2 = ctx.createOscillator(); var gain = ctx.createGain(); var filter = ctx.createBiquadFilter(); var p = makePanner(ctx); if(p.setPan) p.setPan(pan||0); osc.type='sine'; osc.frequency.setValueAtTime(1600, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, ctx.currentTime+0.15); osc2.type='sine'; osc2.frequency.setValueAtTime(2400, ctx.currentTime); osc2.frequency.exponentialRampToValueAtTime(1200, ctx.currentTime+0.15); filter.type = 'lowpass'; filter.frequency.value = 4000; filter.Q.value = 1; gain.gain.setValueAtTime(0.0001, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.4, ctx.currentTime+0.01); gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime+0.2); osc.connect(filter); osc2.connect(filter); filter.connect(gain); if(p._l){ gain.connect(p._l); gain.connect(p._r) } else { gain.connect(p) } p.connect(this.master); osc.start(); osc2.start(); osc.stop(ctx.currentTime+0.25); osc2.stop(ctx.currentTime+0.25); }, playWoodblock: function(pan){ var ctx = this.ensureCtx(); var buffer = ctx.createBuffer(1, 44100, 44100); var data = buffer.getChannelData(0); for(var i=0;i