Skip to content

Commit

Permalink
refactor audio engine: simplified gain channels, improve fading betwe…
Browse files Browse the repository at this point in the history
…en audio, rename functions
  • Loading branch information
orsi committed Jun 3, 2020
1 parent fd4fbe6 commit 3b43a27
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 135 deletions.
225 changes: 108 additions & 117 deletions script/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,158 +4,157 @@
var AudioEngine = {
FADE_TIME: 1,
AUDIO_BUFFER_CACHE: {},
_audioPreloaded: false,
audioContext: null,
master: null,
tracks: {
'bg1': null,
'bg2': null,
'events': null,
'sfx': null
},
currentBackgroundChannel: 'bg1',
currentBackgroundAudio: null,
currentEventAudio: null,
init: function (options) {
AudioEngine.initAudioContext();
},
initAudioContext: function () {
_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.createChannels();
AudioEngine._audioContext = new (window.AudioContext || window.webkitAudioContext);
if (AudioEngine._audioContext.state === 'suspended') {
AudioEngine._audioContext.resume().then(function () {
AudioEngine._createMasterChannel();
});
} else {
AudioEngine.createChannels();
AudioEngine._createMasterChannel();
}
},
createChannels: function () {
_createMasterChannel: function () {
// create master
AudioEngine.master = AudioEngine.audioContext.createGain();
AudioEngine.master.gain.setValueAtTime(1.0, AudioEngine.audioContext.currentTime);
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);
AudioEngine._master = AudioEngine._audioContext.createGain();
AudioEngine._master.gain.setValueAtTime(1.0, AudioEngine._audioContext.currentTime);
AudioEngine._master.connect(AudioEngine._audioContext.destination);
},
options: {}, // Nothing for now,
_canPlayAudio: function () {
if (AudioEngine.audioContext.state === 'suspended') {
if (AudioEngine._audioContext.state === 'suspended') {
return false;
}
return true;
},
_getMissingAudioBuffer: function () {
var buffer = AudioEngine.audioContext.createBuffer(
// plays beeping sound to indicate missing audio
var buffer = AudioEngine._audioContext.createBuffer(
1,
AudioEngine.audioContext.sampleRate,
AudioEngine.audioContext.sampleRate
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;
bufferData[i] = Math.sin(i * .05) / 4; // max .25 gain value
}
return buffer;
},
_playSound: function (buffer) {
if (!AudioEngine._canPlayAudio()) return;

var source = AudioEngine.audioContext.createBufferSource();
var source = AudioEngine._audioContext.createBufferSource();
source.buffer = buffer;
source.connect(AudioEngine.tracks['sfx']);
source.start(AudioEngine.audioContext.currentTime);
source.connect(AudioEngine._master);
source.start();

AudioEngine._currentSoundEffectAudio = {
source: source
};
},
_fadeTrack: function (buffer) {
_playBackgroundMusic: function (buffer) {
if (!AudioEngine._canPlayAudio()) return;

var bufferSource = AudioEngine.audioContext.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.loop = true;

// figure out which background track to start on
// in order to do crossfade
var nextBackgroundChannel;
if (AudioEngine.currentBackgroundChannel === 'bg1') {
nextBackgroundChannel = 'bg2';
} else {
nextBackgroundChannel = 'bg1';
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 track
var fadeTime = AudioEngine.audioContext.currentTime + AudioEngine.FADE_TIME;
bufferSource.connect(AudioEngine.tracks[nextBackgroundChannel]);
bufferSource.start(AudioEngine.audioContext.currentTime);
AudioEngine.tracks[nextBackgroundChannel].gain.setValueAtTime(0.0, AudioEngine.audioContext.currentTime);
AudioEngine.tracks[nextBackgroundChannel].gain.linearRampToValueAtTime(1.0, fadeTime);

// fade out old track
AudioEngine.tracks[AudioEngine.currentBackgroundChannel].gain.linearRampToValueAtTime(0.0, fadeTime);
if (AudioEngine.currentBackgroundAudio) {
AudioEngine.currentBackgroundAudio.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);

// switch background track
AudioEngine.currentBackgroundChannel = nextBackgroundChannel;
AudioEngine.currentBackgroundAudio = bufferSource;
// update current background music
AudioEngine._currentBackgroundMusic = {
source: source,
envelope: envelope
};
},
_playEvent: function (buffer) {
_playEventMusic: function (buffer) {
if (!AudioEngine._canPlayAudio()) return;

var bufferSource = AudioEngine.audioContext.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.loop = true;
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;
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);
// 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
bufferSource.connect(AudioEngine.tracks['events']);
bufferSource.start(0);
AudioEngine.currentEventAudio = bufferSource;
source.connect(envelope);
envelope.connect(AudioEngine._master);
source.start();
envelope.gain.linearRampToValueAtTime(1.0, fadeTime);

AudioEngine.tracks['events'].gain.setValueAtTime(0.0, AudioEngine.audioContext.currentTime);
AudioEngine.tracks['events'].gain.linearRampToValueAtTime(1.0, fadeTime);
// update reference
AudioEngine._currentEventAudio = {
source: source,
envelope: envelope
};
},
_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
AudioEngine.tracks['events'].gain.linearRampToValueAtTime(0.0, fadeTime);
if (AudioEngine.currentEventAudio) {
AudioEngine.currentEventAudio.stop(fadeTime + 1); // make sure fade has completed
AudioEngine.currentEventAudio = null;
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
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)
.then(function (buffer) {
AudioEngine._fadeTrack(buffer);
AudioEngine._playBackgroundMusic(buffer);
});
},
playEventMusic: function (src) {
AudioEngine.loadAudioFile(src)
.then(function (buffer) {
AudioEngine._playEvent(buffer);
AudioEngine._playEventMusic(buffer);
});
},
stopEventMusic: function () {
Expand Down Expand Up @@ -185,37 +184,29 @@ var AudioEngine = {
return AudioEngine._getMissingAudioBuffer();
}

return AudioEngine.audioContext.decodeAudioData(buffer, function (decodedData) {
return AudioEngine._audioContext.decodeAudioData(buffer, function (decodedData) {
AudioEngine.AUDIO_BUFFER_CACHE[src] = decodedData;
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) {
if (!AudioEngine.master) return; // master may not be ready yet
if (!volume) {
if (AudioEngine._master == null) return; // master may not be ready yet
if (volume === undefined) {
volume = 1.0;
}
if (!s) {
if (s === undefined) {
s = 1.0;
}
AudioEngine.master.gain.setValueAtTime(
AudioEngine.master.gain.value,
AudioEngine.audioContext.currentTime
);
AudioEngine.master.gain.linearRampToValueAtTime(

// 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
AudioEngine._audioContext.currentTime + s
);
}
};
2 changes: 1 addition & 1 deletion script/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@
if ($SM.get('config.soundOn')) {
$('.volume').text(_('sound on.'));
$SM.set('config.soundOn', false);
AudioEngine.mute();
AudioEngine.setVolume(0.0);
} else {
$('.volume').text(_('sound off.'));
$SM.set('config.soundOn', true);
Expand Down
12 changes: 6 additions & 6 deletions script/outside.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,17 +591,17 @@ var Outside = {
// set music
var numberOfHuts = $SM.get('game.buildings["hut"]', true);
if(numberOfHuts === 0) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_SILENT_FOREST);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SILENT_FOREST);
} else if(numberOfHuts == 1) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_LONELY_HUT);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_LONELY_HUT);
} else if(numberOfHuts <= 4) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_TINY_VILLAGE);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_TINY_VILLAGE);
} else if(numberOfHuts <= 8) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_MODEST_VILLAGE);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_MODEST_VILLAGE);
} else if(numberOfHuts <= 14) {
AudioEngine.changeMusic(AudioLibrary.MUSIC_LARGE_VILLAGE);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_LARGE_VILLAGE);
} else {
AudioEngine.changeMusic(AudioLibrary.MUSIC_RAUCOUS_VILLAGE);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_RAUCOUS_VILLAGE);
}
},

Expand Down
2 changes: 1 addition & 1 deletion script/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ var Path = {
Path.updateOutfitting();
Path.updatePerks(true);

AudioEngine.changeMusic(AudioLibrary.MUSIC_DUSTY_PATH);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_DUSTY_PATH);

Engine.moveStoresView($('#perks'), transition_diff);
},
Expand Down
10 changes: 5 additions & 5 deletions script/room.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,19 +1232,19 @@ var Room = {
var fireValue = $SM.get('game.fire.value');
switch (fireValue) {
case 0:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_DEAD);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_DEAD);
break;
case 1:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_SMOLDERING);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_SMOLDERING);
break;
case 2:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_FLICKERING);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_FLICKERING);
break;
case 3:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_BURNING);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_BURNING);
break;
case 4:
AudioEngine.changeMusic(AudioLibrary.MUSIC_FIRE_ROARING);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_FIRE_ROARING);
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion script/ship.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var Ship = {
Notifications.notify(Ship, _('somewhere above the debris cloud, the wanderer fleet hovers. been on this rock too long.'));
$SM.set('game.spaceShip.seenShip', true);
}
AudioEngine.changeMusic(AudioLibrary.MUSIC_SHIP);
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SHIP);

Engine.moveStoresView(null, transition_diff);
},
Expand Down
Loading

0 comments on commit 3b43a27

Please sign in to comment.