/** * Module that takes care of audio playback */ var AudioEngine = { FADE_TIME: 1, AUDIO_BUFFER_CACHE: {}, _audioContext: null, _master: null, _currentBackgroundMusic: null, _currentEventAudio: null, _currentSoundEffectAudio: null, init: function () { AudioEngine._initAudioContext(); }, _initAudioContext: function () { // for legacy browsers AudioEngine._audioContext = new (window.AudioContext || window.webkitAudioContext); if (AudioEngine._audioContext.state === 'suspended') { AudioEngine._audioContext.resume().then(function () { AudioEngine._createMasterChannel(); }); } else { AudioEngine._createMasterChannel(); } }, _createMasterChannel: function () { // create master AudioEngine._master = AudioEngine._audioContext.createGain(); AudioEngine._master.gain.setValueAtTime(1.0, AudioEngine._audioContext.currentTime); AudioEngine._master.connect(AudioEngine._audioContext.destination); }, _canPlayAudio: function () { if (AudioEngine._audioContext.state === 'suspended') { return false; } return true; }, _getMissingAudioBuffer: function () { // plays beeping sound to indicate missing audio var buffer = AudioEngine._audioContext.createBuffer( 1, AudioEngine._audioContext.sampleRate, AudioEngine._audioContext.sampleRate ); // Fill the buffer var bufferData = buffer.getChannelData(0); for (var i = 0; i < buffer.length / 2; i++) { bufferData[i] = Math.sin(i * .05) / 4; // max .25 gain value } return buffer; }, _playSound: function (buffer) { if (!AudioEngine._canPlayAudio()) return; if (AudioEngine._currentSoundEffectAudio && AudioEngine._currentSoundEffectAudio.source.buffer == buffer) { return; } var source = AudioEngine._audioContext.createBufferSource(); source.buffer = buffer; source.onended = function(event) { // dereference current sound effect when finished if (AudioEngine._currentSoundEffectAudio && AudioEngine._currentSoundEffectAudio.source.buffer == buffer) { AudioEngine._currentSoundEffectAudio = null; } }; source.connect(AudioEngine._master); source.start(); AudioEngine._currentSoundEffectAudio = { source: source }; }, _playBackgroundMusic: function (buffer) { if (!AudioEngine._canPlayAudio()) return; var source = AudioEngine._audioContext.createBufferSource(); source.buffer = buffer; source.loop = true; var envelope = AudioEngine._audioContext.createGain(); envelope.gain.setValueAtTime(0.0, AudioEngine._audioContext.currentTime); var fadeTime = AudioEngine._audioContext.currentTime + AudioEngine.FADE_TIME; // fade out current background music if (AudioEngine._currentBackgroundMusic) { var currentBackgroundGainValue = AudioEngine._currentBackgroundMusic.envelope.gain.value; AudioEngine._currentBackgroundMusic.envelope.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.setValueAtTime(currentBackgroundGainValue, AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.linearRampToValueAtTime(0.0, fadeTime); AudioEngine._currentBackgroundMusic.source.stop(fadeTime + 0.3); // make sure fade has completed } // fade in new backgorund music source.connect(envelope); envelope.connect(AudioEngine._master); source.start(); envelope.gain.linearRampToValueAtTime(1.0, fadeTime); // update current background music AudioEngine._currentBackgroundMusic = { source: source, envelope: envelope }; }, _playEventMusic: function (buffer) { if (!AudioEngine._canPlayAudio()) return; var source = AudioEngine._audioContext.createBufferSource(); source.buffer = buffer; source.loop = true; var envelope = AudioEngine._audioContext.createGain(); envelope.gain.setValueAtTime(0.0, AudioEngine._audioContext.currentTime); var fadeTime = AudioEngine._audioContext.currentTime + AudioEngine.FADE_TIME * 2; // turn down current background music if (AudioEngine._currentBackgroundMusic != null) { var currentBackgroundGainValue = AudioEngine._currentBackgroundMusic.envelope.gain.value; AudioEngine._currentBackgroundMusic.envelope.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.setValueAtTime(currentBackgroundGainValue, AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.linearRampToValueAtTime(0.2, fadeTime); } // fade in event music source.connect(envelope); envelope.connect(AudioEngine._master); source.start(); envelope.gain.linearRampToValueAtTime(1.0, fadeTime); // update reference AudioEngine._currentEventAudio = { source: source, envelope: envelope }; }, _stopEventMusic: function () { var fadeTime = AudioEngine._audioContext.currentTime + AudioEngine.FADE_TIME * 2; // fade out event music and stop if (AudioEngine._currentEventAudio) { var currentEventGainValue = AudioEngine._currentEventAudio.envelope.gain.value; AudioEngine._currentEventAudio.envelope.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime); AudioEngine._currentEventAudio.envelope.gain.setValueAtTime(currentEventGainValue, AudioEngine._audioContext.currentTime); AudioEngine._currentEventAudio.envelope.gain.linearRampToValueAtTime(0.0, fadeTime); AudioEngine._currentEventAudio.source.stop(fadeTime + 1); // make sure fade has completed AudioEngine._currentEventAudio = null; } // turn up background music var currentBackgroundGainValue = AudioEngine._currentBackgroundMusic.envelope.gain.value; AudioEngine._currentBackgroundMusic.envelope.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.setValueAtTime(currentBackgroundGainValue, AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.linearRampToValueAtTime(1.0, fadeTime); }, playBackgroundMusic: function (src) { AudioEngine.loadAudioFile(src) .then(function (buffer) { AudioEngine._playBackgroundMusic(buffer); }); }, playEventMusic: function (src) { AudioEngine.loadAudioFile(src) .then(function (buffer) { AudioEngine._playEventMusic(buffer); }); }, stopEventMusic: function () { AudioEngine._stopEventMusic(); }, playSound: function (src) { AudioEngine.loadAudioFile(src) .then(function (buffer) { AudioEngine._playSound(buffer); }); }, loadAudioFile: function (src) { if (src.indexOf('http') === -1) { src = window.location + src; } if (AudioEngine.AUDIO_BUFFER_CACHE[src]) { return new Promise(function (resolve, reject) { resolve(AudioEngine.AUDIO_BUFFER_CACHE[src]); }); } else { var request = new Request(src); return fetch(request).then(function (response) { return response.arrayBuffer(); }).then(function (buffer) { if (buffer.byteLength === 0) { console.error('cannot load audio from ' + src); return AudioEngine._getMissingAudioBuffer(); } return AudioEngine._audioContext.decodeAudioData(buffer, function (decodedData) { AudioEngine.AUDIO_BUFFER_CACHE[src] = decodedData; return AudioEngine.AUDIO_BUFFER_CACHE[src]; }); }); } }, setBackgroundMusicVolume: function (volume, s) { if (AudioEngine._master == null) return; // master may not be ready yet if (volume === undefined) { volume = 1.0; } if (s === undefined) { s = 1.0; } // cancel any current schedules and then ramp var currentBackgroundGainValue = AudioEngine._currentBackgroundMusic.envelope.gain.value; AudioEngine._currentBackgroundMusic.envelope.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.setValueAtTime(currentBackgroundGainValue, AudioEngine._audioContext.currentTime); AudioEngine._currentBackgroundMusic.envelope.gain.linearRampToValueAtTime( volume, AudioEngine._audioContext.currentTime + s ); }, setMasterVolume: function (volume, s) { if (AudioEngine._master == null) return; // master may not be ready yet if (volume === undefined) { volume = 1.0; } if (s === undefined) { s = 1.0; } // cancel any current schedules and then ramp var currentGainValue = AudioEngine._master.gain.value; AudioEngine._master.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime); AudioEngine._master.gain.setValueAtTime(currentGainValue, AudioEngine._audioContext.currentTime); AudioEngine._master.gain.linearRampToValueAtTime( volume, AudioEngine._audioContext.currentTime + s ); } };