refactor audio engine: simplified gain channels, improve fading between audio, rename functions

This commit is contained in:
jorsi
2020-06-03 17:44:27 -04:00
parent fd4fbe6bd4
commit 3b43a2754a
8 changed files with 124 additions and 133 deletions
+106 -115
View File
@@ -4,158 +4,157 @@
var AudioEngine = { var AudioEngine = {
FADE_TIME: 1, FADE_TIME: 1,
AUDIO_BUFFER_CACHE: {}, AUDIO_BUFFER_CACHE: {},
_audioPreloaded: false, _audioContext: null,
audioContext: null, _master: null,
master: null, _currentBackgroundMusic: null,
tracks: { _currentEventAudio: null,
'bg1': null, _currentSoundEffectAudio: null,
'bg2': null, init: function () {
'events': null, AudioEngine._initAudioContext();
'sfx': null
}, },
currentBackgroundChannel: 'bg1', _initAudioContext: function () {
currentBackgroundAudio: null,
currentEventAudio: null,
init: function (options) {
AudioEngine.initAudioContext();
},
initAudioContext: function () {
// for legacy browsers // for legacy browsers
AudioEngine.audioContext = new (window.AudioContext || window.webkitAudioContext); AudioEngine._audioContext = new (window.AudioContext || window.webkitAudioContext);
if (AudioEngine._audioContext.state === 'suspended') {
if (AudioEngine.audioContext.state === 'suspended') { AudioEngine._audioContext.resume().then(function () {
AudioEngine.audioContext.resume().then(function () { AudioEngine._createMasterChannel();
AudioEngine.createChannels();
}); });
} else { } else {
AudioEngine.createChannels(); AudioEngine._createMasterChannel();
} }
}, },
createChannels: function () { _createMasterChannel: function () {
// create master // create master
AudioEngine.master = AudioEngine.audioContext.createGain(); AudioEngine._master = AudioEngine._audioContext.createGain();
AudioEngine.master.gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime); AudioEngine._master.gain.setValueAtTime(1.0, AudioEngine._audioContext.currentTime);
AudioEngine.master.connect(AudioEngine.audioContext.destination); AudioEngine._master.connect(AudioEngine._audioContext.destination);
// create 4 tracks to output to master
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,
_canPlayAudio: function () { _canPlayAudio: function () {
if (AudioEngine.audioContext.state === 'suspended') { if (AudioEngine._audioContext.state === 'suspended') {
return false; return false;
} }
return true; return true;
}, },
_getMissingAudioBuffer: function () { _getMissingAudioBuffer: function () {
var buffer = AudioEngine.audioContext.createBuffer( // plays beeping sound to indicate missing audio
var buffer = AudioEngine._audioContext.createBuffer(
1, 1,
AudioEngine.audioContext.sampleRate, AudioEngine._audioContext.sampleRate,
AudioEngine.audioContext.sampleRate AudioEngine._audioContext.sampleRate
); );
// Fill the buffer // Fill the buffer
var bufferData = buffer.getChannelData(0); var bufferData = buffer.getChannelData(0);
for (var i = 0; i < buffer.length / 2; i++) { for (var i = 0; i < buffer.length / 2; i++) {
bufferData[i] = Math.sin(i * .05) / 2; bufferData[i] = Math.sin(i * .05) / 4; // max .25 gain value
} }
return buffer; return buffer;
}, },
_playSound: function (buffer) { _playSound: function (buffer) {
if (!AudioEngine._canPlayAudio()) return; if (!AudioEngine._canPlayAudio()) return;
var source = AudioEngine.audioContext.createBufferSource(); var source = AudioEngine._audioContext.createBufferSource();
source.buffer = buffer; source.buffer = buffer;
source.connect(AudioEngine.tracks['sfx']); source.connect(AudioEngine._master);
source.start(AudioEngine.audioContext.currentTime); source.start();
AudioEngine._currentSoundEffectAudio = {
source: source
};
}, },
_fadeTrack: function (buffer) { _playBackgroundMusic: function (buffer) {
if (!AudioEngine._canPlayAudio()) return; if (!AudioEngine._canPlayAudio()) return;
var bufferSource = AudioEngine.audioContext.createBufferSource(); var source = AudioEngine._audioContext.createBufferSource();
bufferSource.buffer = buffer; source.buffer = buffer;
bufferSource.loop = true; source.loop = true;
// figure out which background track to start on var envelope = AudioEngine._audioContext.createGain();
// in order to do crossfade envelope.gain.setValueAtTime(0.0, AudioEngine._audioContext.currentTime);
var nextBackgroundChannel;
if (AudioEngine.currentBackgroundChannel === 'bg1') { var fadeTime = AudioEngine._audioContext.currentTime + AudioEngine.FADE_TIME;
nextBackgroundChannel = 'bg2';
} else { // fade out current background music
nextBackgroundChannel = 'bg1'; 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 track // fade in new backgorund music
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME; source.connect(envelope);
bufferSource.connect(AudioEngine.tracks[nextBackgroundChannel]); envelope.connect(AudioEngine._master);
bufferSource.start(AudioEngine.audioContext.currentTime); source.start();
AudioEngine.tracks[nextBackgroundChannel].gain.setValueAtTime(0.0, AudioEngine.audioContext.currentTime); envelope.gain.linearRampToValueAtTime(1.0, fadeTime);
AudioEngine.tracks[nextBackgroundChannel].gain.linearRampToValueAtTime(1.0, fadeTime);
// fade out old track // update current background music
AudioEngine.tracks[AudioEngine.currentBackgroundChannel].gain.linearRampToValueAtTime(0.0, fadeTime); AudioEngine._currentBackgroundMusic = {
if (AudioEngine.currentBackgroundAudio) { source: source,
AudioEngine.currentBackgroundAudio.stop(fadeTime + 0.3); // make sure fade has completed envelope: envelope
} };
// switch background track
AudioEngine.currentBackgroundChannel = nextBackgroundChannel;
AudioEngine.currentBackgroundAudio = bufferSource;
}, },
_playEvent: function (buffer) { _playEventMusic: function (buffer) {
if (!AudioEngine._canPlayAudio()) return; if (!AudioEngine._canPlayAudio()) return;
var bufferSource = AudioEngine.audioContext.createBufferSource(); var source = AudioEngine._audioContext.createBufferSource();
bufferSource.buffer = buffer; source.buffer = buffer;
bufferSource.loop = true; source.loop = true;
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME * 2; var envelope = AudioEngine._audioContext.createGain();
envelope.gain.setValueAtTime(0.0, AudioEngine._audioContext.currentTime);
// turn down background music var fadeTime = AudioEngine._audioContext.currentTime + AudioEngine.FADE_TIME * 2;
AudioEngine.tracks['bg1'].gain.linearRampToValueAtTime(0.2, fadeTime);
AudioEngine.tracks['bg2'].gain.linearRampToValueAtTime(0.2, fadeTime); // 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 // fade in event music
bufferSource.connect(AudioEngine.tracks['events']); source.connect(envelope);
bufferSource.start(0); envelope.connect(AudioEngine._master);
AudioEngine.currentEventAudio = bufferSource; source.start();
envelope.gain.linearRampToValueAtTime(1.0, fadeTime);
AudioEngine.tracks['events'].gain.setValueAtTime(0.0, AudioEngine.audioContext.currentTime); // update reference
AudioEngine.tracks['events'].gain.linearRampToValueAtTime(1.0, fadeTime); AudioEngine._currentEventAudio = {
source: source,
envelope: envelope
};
}, },
_stopEventMusic: function () { _stopEventMusic: function () {
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME * 2; var fadeTime = AudioEngine._audioContext.currentTime + AudioEngine.FADE_TIME * 2;
// fade out event music and stop // fade out event music and stop
AudioEngine.tracks['events'].gain.linearRampToValueAtTime(0.0, fadeTime); if (AudioEngine._currentEventAudio) {
if (AudioEngine.currentEventAudio) { var currentEventGainValue = AudioEngine._currentEventAudio.envelope.gain.value;
AudioEngine.currentEventAudio.stop(fadeTime + 1); // make sure fade has completed AudioEngine._currentEventAudio.envelope.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime);
AudioEngine.currentEventAudio = null; 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 // turn up background music
AudioEngine.tracks[AudioEngine.currentBackgroundChannel].gain.linearRampToValueAtTime(1.0, fadeTime); 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);
}, },
changeMusic: function (src) { playBackgroundMusic: function (src) {
AudioEngine.loadAudioFile(src) AudioEngine.loadAudioFile(src)
.then(function (buffer) { .then(function (buffer) {
AudioEngine._fadeTrack(buffer); AudioEngine._playBackgroundMusic(buffer);
}); });
}, },
playEventMusic: function (src) { playEventMusic: function (src) {
AudioEngine.loadAudioFile(src) AudioEngine.loadAudioFile(src)
.then(function (buffer) { .then(function (buffer) {
AudioEngine._playEvent(buffer); AudioEngine._playEventMusic(buffer);
}); });
}, },
stopEventMusic: function () { stopEventMusic: function () {
@@ -185,37 +184,29 @@ var AudioEngine = {
return AudioEngine._getMissingAudioBuffer(); return AudioEngine._getMissingAudioBuffer();
} }
return AudioEngine.audioContext.decodeAudioData(buffer, function (decodedData) { return AudioEngine._audioContext.decodeAudioData(buffer, function (decodedData) {
AudioEngine.AUDIO_BUFFER_CACHE[src] = decodedData; AudioEngine.AUDIO_BUFFER_CACHE[src] = decodedData;
return AudioEngine.AUDIO_BUFFER_CACHE[src]; return AudioEngine.AUDIO_BUFFER_CACHE[src];
}); });
}); });
} }
}, },
mute: function () {
AudioEngine.master.gain.linearRampToValueAtTime(
0.0,
AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME
);
},
getVolume: function () {
return AudioEngine.master.gain.value;
},
setVolume: function (volume, s) { setVolume: function (volume, s) {
if (!AudioEngine.master) return; // master may not be ready yet if (AudioEngine._master == null) return; // master may not be ready yet
if (!volume) { if (volume === undefined) {
volume = 1.0; volume = 1.0;
} }
if (!s) { if (s === undefined) {
s = 1.0; s = 1.0;
} }
AudioEngine.master.gain.setValueAtTime(
AudioEngine.master.gain.value, // cancel any current schedules and then ramp
AudioEngine.audioContext.currentTime var currentGainValue = AudioEngine._master.gain.value;
); AudioEngine._master.gain.cancelScheduledValues(AudioEngine._audioContext.currentTime);
AudioEngine.master.gain.linearRampToValueAtTime( AudioEngine._master.gain.setValueAtTime(currentGainValue, AudioEngine._audioContext.currentTime);
AudioEngine._master.gain.linearRampToValueAtTime(
volume, volume,
AudioEngine.audioContext.currentTime + s AudioEngine._audioContext.currentTime + s
); );
} }
}; };
+1 -1
View File
@@ -805,7 +805,7 @@
if ($SM.get('config.soundOn')) { if ($SM.get('config.soundOn')) {
$('.volume').text(_('sound on.')); $('.volume').text(_('sound on.'));
$SM.set('config.soundOn', false); $SM.set('config.soundOn', false);
AudioEngine.mute(); AudioEngine.setVolume(0.0);
} else { } else {
$('.volume').text(_('sound off.')); $('.volume').text(_('sound off.'));
$SM.set('config.soundOn', true); $SM.set('config.soundOn', true);
+6 -6
View File
@@ -591,17 +591,17 @@ var Outside = {
// set music // set music
var numberOfHuts = $SM.get('game.buildings["hut"]', true); var numberOfHuts = $SM.get('game.buildings["hut"]', true);
if(numberOfHuts === 0) { if(numberOfHuts === 0) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_SILENT_FOREST); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SILENT_FOREST);
} else if(numberOfHuts == 1) { } else if(numberOfHuts == 1) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_LONELY_HUT); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_LONELY_HUT);
} else if(numberOfHuts <= 4) { } else if(numberOfHuts <= 4) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_TINY_VILLAGE); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_TINY_VILLAGE);
} else if(numberOfHuts <= 8) { } else if(numberOfHuts <= 8) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_MODEST_VILLAGE); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_MODEST_VILLAGE);
} else if(numberOfHuts <= 14) { } else if(numberOfHuts <= 14) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_LARGE_VILLAGE); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_LARGE_VILLAGE);
} else { } else {
AudioEngine.changeMusic(AudioLibrary.MUSIC_RAUCOUS_VILLAGE); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_RAUCOUS_VILLAGE);
} }
}, },
+1 -1
View File
@@ -304,7 +304,7 @@ var Path = {
Path.updateOutfitting(); Path.updateOutfitting();
Path.updatePerks(true); Path.updatePerks(true);
AudioEngine.changeMusic(AudioLibrary.MUSIC_DUSTY_PATH); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_DUSTY_PATH);
Engine.moveStoresView($('#perks'), transition_diff); Engine.moveStoresView($('#perks'), transition_diff);
}, },
+5 -5
View File
@@ -1232,19 +1232,19 @@ var Room = {
var fireValue = $SM.get('game.fire.value'); var fireValue = $SM.get('game.fire.value');
switch (fireValue) { switch (fireValue) {
case 0: case 0:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_DEAD); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_DEAD);
break; break;
case 1: case 1:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_SMOLDERING); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_SMOLDERING);
break; break;
case 2: case 2:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_FLICKERING); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_FLICKERING);
break; break;
case 3: case 3:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_BURNING); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_BURNING);
break; break;
case 4: case 4:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_ROARING); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_ROARING);
break; break;
} }
} }
+1 -1
View File
@@ -90,7 +90,7 @@ var Ship = {
Notifications.notify(Ship, _('somewhere above the debris cloud, the wanderer fleet hovers. been on this rock too long.')); Notifications.notify(Ship, _('somewhere above the debris cloud, the wanderer fleet hovers. been on this rock too long.'));
$SM.set('game.spaceShip.seenShip', true); $SM.set('game.spaceShip.seenShip', true);
} }
AudioEngine.changeMusic(AudioLibrary.MUSIC_SHIP); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SHIP);
Engine.moveStoresView(null, transition_diff); Engine.moveStoresView(null, transition_diff);
}, },
+3 -3
View File
@@ -53,7 +53,7 @@ var Space = {
Space.hull = Ship.getMaxHull(); Space.hull = Ship.getMaxHull();
Space.altitude = 0; Space.altitude = 0;
Space.setTitle(); Space.setTitle();
AudioEngine.changeMusic(AudioLibrary.MUSIC_SPACE); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SPACE);
Space.updateHull(); Space.updateHull();
Space.up = Space.up =
@@ -68,7 +68,7 @@ var Space = {
Space.startAscent(); Space.startAscent();
Space._shipTimer = setInterval(Space.moveShip, 33); Space._shipTimer = setInterval(Space.moveShip, 33);
Space._volumeTimer = setInterval(Space.lowerVolume, 1000); Space._volumeTimer = setInterval(Space.lowerVolume, 1000);
AudioEngine.changeMusic(AudioLibrary.MUSIC_SPACE); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SPACE);
}, },
setTitle: function() { setTitle: function() {
@@ -401,7 +401,7 @@ var Space = {
delete Outside._popTimeout; delete Outside._popTimeout;
AudioEngine.setVolume(1.0); AudioEngine.setVolume(1.0);
AudioEngine.changeMusic(AudioLibrary.MUSIC_ENDING); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_ENDING);
$('#hullRemaining', Space.panel).animate({opacity: 0}, 500, 'linear'); $('#hullRemaining', Space.panel).animate({opacity: 0}, 500, 'linear');
Space.ship.animate({ Space.ship.animate({
top: '350px', top: '350px',
+1 -1
View File
@@ -1012,7 +1012,7 @@ var World = {
World.curPos = World.copyPos(World.VILLAGE_POS); World.curPos = World.copyPos(World.VILLAGE_POS);
World.drawMap(); World.drawMap();
World.setTitle(); World.setTitle();
AudioEngine.changeMusic(AudioLibrary.MUSIC_WORLD); AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_WORLD);
World.dead = false; World.dead = false;
$('div#bagspace-world > div').empty(); $('div#bagspace-world > div').empty();
World.updateSupplies(); World.updateSupplies();