2020-05-28 19:10:09 -04:00
|
|
|
/**
|
|
|
|
|
* Module that takes care of audio playback
|
|
|
|
|
*/
|
|
|
|
|
var AudioEngine = {
|
2020-05-30 19:48:44 -04:00
|
|
|
FADE_TIME: 1,
|
2020-05-28 19:10:09 -04:00
|
|
|
AUDIO_BUFFER_CACHE: {},
|
|
|
|
|
audioContext: null,
|
2020-05-29 13:59:58 -04:00
|
|
|
master: null,
|
2020-05-30 19:48:44 -04:00
|
|
|
tracks: {
|
|
|
|
|
'bg1': null,
|
|
|
|
|
'bg2': null,
|
|
|
|
|
'events': null,
|
|
|
|
|
'sfx': null
|
|
|
|
|
},
|
|
|
|
|
currentBackgroundChannel: 'bg1',
|
|
|
|
|
currentBackgroundAudio: null,
|
|
|
|
|
currentEventAudio: null,
|
2020-05-30 22:20:29 -04:00
|
|
|
init: function (options) {
|
2020-05-28 19:10:09 -04:00
|
|
|
// for legacy browsers
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.audioContext = new (window.AudioContext || window.webkitAudioContext);
|
2020-05-29 13:59:58 -04:00
|
|
|
|
2020-05-30 17:20:01 -04:00
|
|
|
if (AudioEngine.audioContext.state === 'suspended') {
|
|
|
|
|
AudioEngine.audioContext.resume().then(function () {
|
|
|
|
|
AudioEngine.createChannels();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
AudioEngine.createChannels();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
createChannels() {
|
2020-05-29 13:59:58 -04:00
|
|
|
// create master
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.master = AudioEngine.audioContext.createGain();
|
2020-05-30 19:48:44 -04:00
|
|
|
AudioEngine.master.gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime);
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.master.connect(AudioEngine.audioContext.destination);
|
2020-05-29 13:59:58 -04:00
|
|
|
|
|
|
|
|
// create 4 tracks to output to master
|
2020-05-30 19:48:44 -04:00
|
|
|
AudioEngine.tracks['bg1'] = AudioEngine.audioContext.createGain();
|
|
|
|
|
AudioEngine.tracks['bg1'].connect(AudioEngine.master);
|
|
|
|
|
AudioEngine.tracks['bg1'].gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime);
|
|
|
|
|
AudioEngine.tracks['bg2'] = AudioEngine.audioContext.createGain();
|
|
|
|
|
AudioEngine.tracks['bg2'].connect(AudioEngine.master);
|
|
|
|
|
AudioEngine.tracks['bg2'].gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime);
|
|
|
|
|
AudioEngine.tracks['events'] = AudioEngine.audioContext.createGain();
|
|
|
|
|
AudioEngine.tracks['events'].connect(AudioEngine.master);
|
|
|
|
|
AudioEngine.tracks['events'].gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime);
|
|
|
|
|
AudioEngine.tracks['sfx'] = AudioEngine.audioContext.createGain();
|
|
|
|
|
AudioEngine.tracks['sfx'].connect(AudioEngine.master);
|
|
|
|
|
AudioEngine.tracks['sfx'].gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime);
|
|
|
|
|
},
|
|
|
|
|
options: {}, // Nothing for now,
|
2020-05-30 22:20:29 -04:00
|
|
|
_canPlayAudio: function () {
|
2020-05-30 19:48:44 -04:00
|
|
|
if (AudioEngine.audioContext.state === 'suspended') {
|
|
|
|
|
return false;
|
2020-05-29 13:59:58 -04:00
|
|
|
}
|
2020-05-30 19:48:44 -04:00
|
|
|
return true;
|
2020-05-30 17:20:01 -04:00
|
|
|
},
|
2020-06-01 11:12:00 -04:00
|
|
|
_getMissingAudioBuffer: function () {
|
|
|
|
|
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) / 2;
|
|
|
|
|
}
|
|
|
|
|
return buffer;
|
|
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
_playSound: function (buffer) {
|
2020-05-30 19:48:44 -04:00
|
|
|
if (!AudioEngine._canPlayAudio()) return;
|
2020-05-30 17:20:01 -04:00
|
|
|
|
|
|
|
|
var source = AudioEngine.audioContext.createBufferSource();
|
2020-05-28 19:10:09 -04:00
|
|
|
source.buffer = buffer;
|
2020-05-30 19:48:44 -04:00
|
|
|
source.connect(AudioEngine.tracks['sfx']);
|
2020-05-30 17:20:01 -04:00
|
|
|
source.start(AudioEngine.audioContext.currentTime);
|
2020-05-28 19:10:09 -04:00
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
_fadeTrack: function (buffer) {
|
2020-05-30 19:48:44 -04:00
|
|
|
if (!AudioEngine._canPlayAudio()) return;
|
2020-05-30 17:20:01 -04:00
|
|
|
|
2020-05-30 19:48:44 -04:00
|
|
|
var bufferSource = AudioEngine.audioContext.createBufferSource();
|
|
|
|
|
bufferSource.buffer = buffer;
|
|
|
|
|
bufferSource.loop = true;
|
2020-05-29 13:59:58 -04:00
|
|
|
|
|
|
|
|
// figure out which background track to start on
|
|
|
|
|
// in order to do crossfade
|
|
|
|
|
var nextBackgroundChannel;
|
2020-05-30 19:48:44 -04:00
|
|
|
if (AudioEngine.currentBackgroundChannel === 'bg1') {
|
|
|
|
|
nextBackgroundChannel = 'bg2';
|
2020-05-29 13:59:58 -04:00
|
|
|
} else {
|
2020-05-30 19:48:44 -04:00
|
|
|
nextBackgroundChannel = 'bg1';
|
2020-05-29 13:59:58 -04:00
|
|
|
}
|
2020-05-30 22:20:29 -04:00
|
|
|
|
2020-05-29 13:59:58 -04:00
|
|
|
// fade in new track
|
2020-05-30 19:48:44 -04:00
|
|
|
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME;
|
|
|
|
|
bufferSource.connect(AudioEngine.tracks[nextBackgroundChannel]);
|
|
|
|
|
bufferSource.start(AudioEngine.audioContext.currentTime);
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.tracks[nextBackgroundChannel].gain.setValueAtTime(0.0, AudioEngine.audioContext.currentTime);
|
2020-05-30 19:48:44 -04:00
|
|
|
AudioEngine.tracks[nextBackgroundChannel].gain.linearRampToValueAtTime(1.0, fadeTime);
|
2020-05-29 13:59:58 -04:00
|
|
|
|
|
|
|
|
// fade out old track
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.tracks[AudioEngine.currentBackgroundChannel].gain.linearRampToValueAtTime(0.0, fadeTime);
|
2020-05-30 19:48:44 -04:00
|
|
|
if (AudioEngine.currentBackgroundAudio) {
|
|
|
|
|
AudioEngine.currentBackgroundAudio.stop(fadeTime + 0.3); // make sure fade has completed
|
2020-05-29 13:59:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// switch background track
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.currentBackgroundChannel = nextBackgroundChannel;
|
2020-05-30 19:48:44 -04:00
|
|
|
AudioEngine.currentBackgroundAudio = bufferSource;
|
|
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
_playEvent: function (buffer) {
|
2020-05-30 19:48:44 -04:00
|
|
|
if (!AudioEngine._canPlayAudio()) return;
|
|
|
|
|
|
|
|
|
|
var bufferSource = AudioEngine.audioContext.createBufferSource();
|
|
|
|
|
bufferSource.buffer = buffer;
|
|
|
|
|
bufferSource.loop = true;
|
2020-05-30 22:20:29 -04:00
|
|
|
|
2020-05-30 19:48:44 -04:00
|
|
|
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME * 2;
|
|
|
|
|
|
|
|
|
|
// turn down background music
|
|
|
|
|
AudioEngine.tracks['bg1'].gain.linearRampToValueAtTime(0.2, fadeTime);
|
|
|
|
|
AudioEngine.tracks['bg2'].gain.linearRampToValueAtTime(0.2, fadeTime);
|
2020-05-30 22:20:29 -04:00
|
|
|
|
2020-05-30 19:48:44 -04:00
|
|
|
// fade in event music
|
|
|
|
|
bufferSource.connect(AudioEngine.tracks['events']);
|
|
|
|
|
bufferSource.start(0);
|
|
|
|
|
AudioEngine.currentEventAudio = bufferSource;
|
|
|
|
|
|
|
|
|
|
AudioEngine.tracks['events'].gain.setValueAtTime(0.0, AudioEngine.audioContext.currentTime);
|
2020-06-01 15:37:12 -04:00
|
|
|
AudioEngine.tracks['events'].gain.linearRampToValueAtTime(1.0, fadeTime);
|
2020-05-30 19:48:44 -04:00
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
_stopEventMusic: function () {
|
2020-05-30 19:48:44 -04:00
|
|
|
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME * 2;
|
|
|
|
|
|
|
|
|
|
// fade out event music and stop
|
|
|
|
|
AudioEngine.tracks['events'].gain.linearRampToValueAtTime(0.0, fadeTime);
|
|
|
|
|
if (AudioEngine.currentEventAudio) {
|
|
|
|
|
AudioEngine.currentEventAudio.stop(fadeTime + 1); // make sure fade has completed
|
|
|
|
|
AudioEngine.currentEventAudio = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// turn up background music
|
|
|
|
|
AudioEngine.tracks[AudioEngine.currentBackgroundChannel].gain.linearRampToValueAtTime(1.0, fadeTime);
|
2020-05-29 13:59:58 -04:00
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
changeMusic: function (src) {
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.loadAudioFile(src)
|
2020-05-29 13:59:58 -04:00
|
|
|
.then(function (buffer) {
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine._fadeTrack(buffer);
|
2020-05-29 13:59:58 -04:00
|
|
|
});
|
2020-05-30 19:48:44 -04:00
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
playEventMusic: function (src) {
|
2020-05-30 19:48:44 -04:00
|
|
|
AudioEngine.loadAudioFile(src)
|
|
|
|
|
.then(function (buffer) {
|
|
|
|
|
AudioEngine._playEvent(buffer);
|
|
|
|
|
});
|
|
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
stopEventMusic: function () {
|
2020-05-30 19:48:44 -04:00
|
|
|
AudioEngine._stopEventMusic();
|
2020-05-29 13:59:58 -04:00
|
|
|
},
|
2020-05-30 22:20:29 -04:00
|
|
|
playSound: function (src) {
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.loadAudioFile(src)
|
2020-05-28 19:10:09 -04:00
|
|
|
.then(function (buffer) {
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine._playSound(buffer);
|
2020-05-28 19:10:09 -04:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
loadAudioFile(src) {
|
2020-06-01 00:42:56 -04:00
|
|
|
if (src.indexOf('http') === -1) {
|
|
|
|
|
src = window.location + src;
|
|
|
|
|
}
|
2020-05-30 17:20:01 -04:00
|
|
|
if (AudioEngine.AUDIO_BUFFER_CACHE[src]) {
|
2020-05-28 19:10:09 -04:00
|
|
|
return new Promise(function (resolve, reject) {
|
2020-05-30 17:20:01 -04:00
|
|
|
resolve(AudioEngine.AUDIO_BUFFER_CACHE[src]);
|
2020-05-28 19:10:09 -04:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
var request = new Request(src);
|
2020-05-30 22:20:29 -04:00
|
|
|
return fetch(request).then(function (response) {
|
2020-05-28 19:10:09 -04:00
|
|
|
return response.arrayBuffer();
|
2020-05-30 22:20:29 -04:00
|
|
|
}).then(function (buffer) {
|
2020-06-01 11:12:00 -04:00
|
|
|
if (buffer.byteLength === 0) {
|
|
|
|
|
console.error('cannot load audio from ' + src);
|
|
|
|
|
return AudioEngine._getMissingAudioBuffer();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-30 22:20:29 -04:00
|
|
|
return AudioEngine.audioContext.decodeAudioData(buffer, function (decodedData) {
|
2020-05-30 17:20:01 -04:00
|
|
|
AudioEngine.AUDIO_BUFFER_CACHE[src] = decodedData;
|
|
|
|
|
return AudioEngine.AUDIO_BUFFER_CACHE[src];
|
2020-05-28 19:10:09 -04:00
|
|
|
});
|
2020-05-30 22:20:29 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
mute: function () {
|
|
|
|
|
AudioEngine.master.gain.linearRampToValueAtTime(
|
|
|
|
|
0.0,
|
|
|
|
|
AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME
|
|
|
|
|
);
|
|
|
|
|
},
|
2020-06-02 16:02:12 -04:00
|
|
|
getVolume: function () {
|
|
|
|
|
return AudioEngine.master.gain.value;
|
|
|
|
|
},
|
|
|
|
|
setVolume: function (volume, s) {
|
2020-05-30 22:20:29 -04:00
|
|
|
if (!AudioEngine.master) return; // master may not be ready yet
|
|
|
|
|
if (!volume) {
|
|
|
|
|
volume = 1.0;
|
2020-05-28 19:10:09 -04:00
|
|
|
}
|
2020-06-02 16:02:12 -04:00
|
|
|
if (!s) {
|
|
|
|
|
s = 1.0;
|
|
|
|
|
}
|
2020-05-30 22:20:29 -04:00
|
|
|
AudioEngine.master.gain.setValueAtTime(
|
|
|
|
|
AudioEngine.master.gain.value,
|
|
|
|
|
AudioEngine.audioContext.currentTime
|
|
|
|
|
);
|
|
|
|
|
AudioEngine.master.gain.linearRampToValueAtTime(
|
|
|
|
|
volume,
|
2020-06-02 16:02:12 -04:00
|
|
|
AudioEngine.audioContext.currentTime + s
|
2020-05-30 22:20:29 -04:00
|
|
|
);
|
|
|
|
|
}
|
2020-05-30 22:27:38 -04:00
|
|
|
};
|