//-----------------------------------------------------------------------------
// AudioManager
//
// The static class that handles BGM, BGS, ME and SE.
/**
* The static class that handles BGM, BGS, ME and SE.
*
* @namespace
*/
function AudioManager() {
throw new Error("This is a static class");
}
AudioManager._bgmVolume = 100;
AudioManager._bgsVolume = 100;
AudioManager._meVolume = 100;
AudioManager._seVolume = 100;
AudioManager._currentBgm = null;
AudioManager._currentBgs = null;
AudioManager._bgmBuffer = null;
AudioManager._bgsBuffer = null;
AudioManager._meBuffer = null;
AudioManager._seBuffers = [];
AudioManager._staticBuffers = [];
AudioManager._replayFadeTime = 0.5;
AudioManager._path = "audio/";
/**
* The volume of the BGM audio
*
* @type number
* @static
* @name AudioManager.bgmVolume
*/
Object.defineProperty(AudioManager, "bgmVolume", {
get: function() {
return this._bgmVolume;
},
set: function(value) {
this._bgmVolume = value;
this.updateBgmParameters(this._currentBgm);
},
configurable: true
});
/**
* The volume of the BGS audio
*
* @type number
* @static
* @name AudioManager.bgsVolume
*/
Object.defineProperty(AudioManager, "bgsVolume", {
get: function() {
return this._bgsVolume;
},
set: function(value) {
this._bgsVolume = value;
this.updateBgsParameters(this._currentBgs);
},
configurable: true
});
/**
* The volume of the ME audio
*
* @type number
* @static
* @name AudioManager.meVolume
*/
Object.defineProperty(AudioManager, "meVolume", {
get: function() {
return this._meVolume;
},
set: function(value) {
this._meVolume = value;
this.updateMeParameters(this._currentMe);
},
configurable: true
});
/**
* The volume of the SE audio
*
* @type number
* @static
* @name AudioManager.seVolume
*/
Object.defineProperty(AudioManager, "seVolume", {
get: function() {
return this._seVolume;
},
set: function(value) {
this._seVolume = value;
},
configurable: true
});
/**
* Plays the BGM beginning at a position in the file
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} bgm - the bgm object to play
* @param {number} pos - position in the audio file to start at
*/
AudioManager.playBgm = function(bgm, pos) {
if (this.isCurrentBgm(bgm)) {
this.updateBgmParameters(bgm);
} else {
this.stopBgm();
if (bgm.name) {
this._bgmBuffer = this.createBuffer("bgm/", bgm.name);
this.updateBgmParameters(bgm);
if (!this._meBuffer) {
this._bgmBuffer.play(true, pos || 0);
}
}
}
this.updateCurrentBgm(bgm, pos);
};
/**
* Replays the given bgm from where it's last position was
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number, pos: number}} bgm - the bgm object to replay
*/
AudioManager.replayBgm = function(bgm) {
if (this.isCurrentBgm(bgm)) {
this.updateBgmParameters(bgm);
} else {
this.playBgm(bgm, bgm.pos);
if (this._bgmBuffer) {
this._bgmBuffer.fadeIn(this._replayFadeTime);
}
}
};
/**
* Checks if the given bgm is currently playing
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number, pos: number}} bgm - compares the bgm name to currently playing bgm name
*/
AudioManager.isCurrentBgm = function(bgm) {
return (
this._currentBgm &&
this._bgmBuffer &&
this._currentBgm.name === bgm.name
);
};
/**
* Updates BGM parameters: volume, pitch, and pan
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number, pos: number}} bgm - bgm object to update parameters from
*/
AudioManager.updateBgmParameters = function(bgm) {
this.updateBufferParameters(this._bgmBuffer, this._bgmVolume, bgm);
};
/**
* Updates the currently playing BGM's volume, pitch, pan, pos, and name
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} bgm - bgm object to set as the current bgm
* @param {number} pos - position in the audio file to begin playing from
*/
AudioManager.updateCurrentBgm = function(bgm, pos) {
this._currentBgm = {
name: bgm.name,
volume: bgm.volume,
pitch: bgm.pitch,
pan: bgm.pan,
pos: pos
};
};
/**
* Stops playing the current BGM
* @static
*/
AudioManager.stopBgm = function() {
if (this._bgmBuffer) {
this._bgmBuffer.destroy();
this._bgmBuffer = null;
this._currentBgm = null;
}
};
/**
* Fades out the current BGM
*
* @static
* @param {number} duration - How long the fade out effect lasts (in seconds)
*/
AudioManager.fadeOutBgm = function(duration) {
if (this._bgmBuffer && this._currentBgm) {
this._bgmBuffer.fadeOut(duration);
this._currentBgm = null;
}
};
/**
* Fades in the current BGM
*
* @static
* @param {number} duration - How long the fade in effect lasts (in seconds)
*/
AudioManager.fadeInBgm = function(duration) {
if (this._bgmBuffer && this._currentBgm) {
this._bgmBuffer.fadeIn(duration);
}
};
/**
* Plays the BGS beginning at a position in the file
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} bgs - the bgs object to play
* @param {number} pos - position in the audio file to start at
*/
AudioManager.playBgs = function(bgs, pos) {
if (this.isCurrentBgs(bgs)) {
this.updateBgsParameters(bgs);
} else {
this.stopBgs();
if (bgs.name) {
this._bgsBuffer = this.createBuffer("bgs/", bgs.name);
this.updateBgsParameters(bgs);
this._bgsBuffer.play(true, pos || 0);
}
}
this.updateCurrentBgs(bgs, pos);
};
/**
* Replays the given bgs from where it's last position was
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number, pos: number}} bgs - the bgs object to replay
*/
AudioManager.replayBgs = function(bgs) {
if (this.isCurrentBgs(bgs)) {
this.updateBgsParameters(bgs);
} else {
this.playBgs(bgs, bgs.pos);
if (this._bgsBuffer) {
this._bgsBuffer.fadeIn(this._replayFadeTime);
}
}
};
/**
* Checks if the given bgs is currently playing
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number, pos: number}} bgs - compares the bgs name to currently playing bgs name
*/
AudioManager.isCurrentBgs = function(bgs) {
return (
this._currentBgs &&
this._bgsBuffer &&
this._currentBgs.name === bgs.name
);
};
/**
* Updates BGS parameters: volume, pitch, and pan
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number, pos: number}} bgs - bgs object to update parameters from
*/
AudioManager.updateBgsParameters = function(bgs) {
this.updateBufferParameters(this._bgsBuffer, this._bgsVolume, bgs);
};
/**
* Updates the currently playing BGS's volume, pitch, pan, pos, and name
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} bgs - bgs object to set as the current bgs
* @param {number} pos - position in the audio file to begin playing from
*/
AudioManager.updateCurrentBgs = function(bgs, pos) {
this._currentBgs = {
name: bgs.name,
volume: bgs.volume,
pitch: bgs.pitch,
pan: bgs.pan,
pos: pos
};
};
/**
* Stops playing the current BGS
* @static
*/
AudioManager.stopBgs = function() {
if (this._bgsBuffer) {
this._bgsBuffer.destroy();
this._bgsBuffer = null;
this._currentBgs = null;
}
};
/**
* Fades out the current BGS
*
* @static
* @param {number} duration - How long the fade out effect lasts (in seconds)
*/
AudioManager.fadeOutBgs = function(duration) {
if (this._bgsBuffer && this._currentBgs) {
this._bgsBuffer.fadeOut(duration);
this._currentBgs = null;
}
};
/**
* Fades in the current BGS
*
* @static
* @param {number} duration - How long the fade in effect lasts (in seconds)
*/
AudioManager.fadeInBgs = function(duration) {
if (this._bgsBuffer && this._currentBgs) {
this._bgsBuffer.fadeIn(duration);
}
};
/**
* Plays the given ME.
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} me - the me object to play
*/
AudioManager.playMe = function(me) {
this.stopMe();
if (me.name) {
if (this._bgmBuffer && this._currentBgm) {
this._currentBgm.pos = this._bgmBuffer.seek();
this._bgmBuffer.stop();
}
this._meBuffer = this.createBuffer("me/", me.name);
this.updateMeParameters(me);
this._meBuffer.play(false);
this._meBuffer.addStopListener(this.stopMe.bind(this));
}
};
/**
* Updates ME parameters: volume, pitch, and pan
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} me - me object to update parameters from
*/
AudioManager.updateMeParameters = function(me) {
this.updateBufferParameters(this._meBuffer, this._meVolume, me);
};
/**
* Fades out the current ME
*
* @static
* @param {number} duration - How long the fade out effect lasts (in seconds)
*/
AudioManager.fadeOutMe = function(duration) {
if (this._meBuffer) {
this._meBuffer.fadeOut(duration);
}
};
/**
* Stops playing the current ME
* @static
*/
AudioManager.stopMe = function() {
if (this._meBuffer) {
this._meBuffer.destroy();
this._meBuffer = null;
if (
this._bgmBuffer &&
this._currentBgm &&
!this._bgmBuffer.isPlaying()
) {
this._bgmBuffer.play(true, this._currentBgm.pos);
this._bgmBuffer.fadeIn(this._replayFadeTime);
}
}
};
/**
* Plays the given SE.
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} se - the se object to play
*/
AudioManager.playSe = function(se) {
if (se.name) {
// [Note] Do not play the same sound in the same frame.
const latestBuffers = this._seBuffers.filter(
buffer => buffer.frameCount === Graphics.frameCount
);
if (latestBuffers.find(buffer => buffer.name === se.name)) {
return;
}
const buffer = this.createBuffer("se/", se.name);
this.updateSeParameters(buffer, se);
buffer.play(false);
this._seBuffers.push(buffer);
this.cleanupSe();
}
};
/**
* Updates SE parameters: volume, pitch, and pan
*
* @static
* @param {WebAudio} buffer - the se buffer to update
* @param {{name: string, volume: number, pan: number, pitch: number}} se - se object to update parameters from
*/
AudioManager.updateSeParameters = function(buffer, se) {
this.updateBufferParameters(buffer, this._seVolume, se);
};
/**
* Removes any SE buffers that are not currently playing
* @static
*/
AudioManager.cleanupSe = function() {
for (const buffer of this._seBuffers) {
if (!buffer.isPlaying()) {
buffer.destroy();
}
}
this._seBuffers = this._seBuffers.filter(buffer => buffer.isPlaying());
};
/**
* Stops all SEs that are currently playing
* @static
*/
AudioManager.stopSe = function() {
for (const buffer of this._seBuffers) {
buffer.destroy();
}
this._seBuffers = [];
};
/**
* Plays the given static SE. Static SEs do not stop when stopSe is called.
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} se - the se object to play
*/
AudioManager.playStaticSe = function(se) {
if (se.name) {
this.loadStaticSe(se);
for (const buffer of this._staticBuffers) {
if (buffer.name === se.name) {
buffer.stop();
this.updateSeParameters(buffer, se);
buffer.play(false);
break;
}
}
}
};
/**
* Creates a buffer and pushes it to the static se buffer stack
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} se - the se object to load
*/
AudioManager.loadStaticSe = function(se) {
if (se.name && !this.isStaticSe(se)) {
const buffer = this.createBuffer("se/", se.name);
this._staticBuffers.push(buffer);
}
};
/**
* Checks if the given SE is a static SE by name
*
* @static
* @param {{name: string, volume: number, pan: number, pitch: number}} se - the se object to compare
*/
AudioManager.isStaticSe = function(se) {
for (const buffer of this._staticBuffers) {
if (buffer.name === se.name) {
return true;
}
}
return false;
};
/**
* Stops ME, SE, BGM, and BGS
* @static
*/
AudioManager.stopAll = function() {
this.stopMe();
this.stopBgm();
this.stopBgs();
this.stopSe();
};
/**
* Saves the currently playing BGM
*
* @static
* @return {{name: string, volume: number, pan: number, pitch: number, pos: number}} The currently playing BGM, or an empty audio object is no current BGM exists
*/
AudioManager.saveBgm = function() {
if (this._currentBgm) {
const bgm = this._currentBgm;
return {
name: bgm.name,
volume: bgm.volume,
pitch: bgm.pitch,
pan: bgm.pan,
pos: this._bgmBuffer ? this._bgmBuffer.seek() : 0
};
} else {
return this.makeEmptyAudioObject();
}
};
/**
* Saves the currently playing BGS
*
* @static
* @return {{name: string, volume: number, pan: number, pitch: number, pos: number}} The currently playing BGS, or an empty audio object is no current BGS exists
*/
AudioManager.saveBgs = function() {
if (this._currentBgs) {
const bgs = this._currentBgs;
return {
name: bgs.name,
volume: bgs.volume,
pitch: bgs.pitch,
pan: bgs.pan,
pos: this._bgsBuffer ? this._bgsBuffer.seek() : 0
};
} else {
return this.makeEmptyAudioObject();
}
};
/**
* Creates an empty audio object
*
* @static
* @return {{name: string, volume: number, pitch: number}} An empty audio object with an empty string name, 0 volume and 0 pitch
*/
AudioManager.makeEmptyAudioObject = function() {
return { name: "", volume: 0, pitch: 0 };
};
/**
* Creates a WebAudio buffer from the file found at the folder and filename location given
*
* @static
* @param {string} folder - The folder to find the audio file
* @param {string} name - The name of the audio file (not including file extension)
* @return {WebAudio} A WebAudio buffer object
*/
AudioManager.createBuffer = function(folder, name) {
const ext = this.audioFileExt();
const url = this._path + folder + Utils.encodeURI(name) + ext;
const buffer = new WebAudio(url);
buffer.name = name;
buffer.frameCount = Graphics.frameCount;
return buffer;
};
/**
* Updates a buffer's parameters for volume, pitch, and pan from a given audio object
*
* @static
* @param {WebAudio} buffer - The WebAudio buffer to update
* @param {number} configVolume - The volume setting from the player's audio settings
* @param {{volume: number, pitch: number, pan: number} audio - The audio object to update volume, pitch, and pan from
*/
AudioManager.updateBufferParameters = function(buffer, configVolume, audio) {
if (buffer && audio) {
buffer.volume = (configVolume * (audio.volume || 0)) / 10000;
buffer.pitch = (audio.pitch || 0) / 100;
buffer.pan = (audio.pan || 0) / 100;
}
};
/**
* Returns the file extension to use for audio files
*
* @static
* @return {string} The file extension to use for audio files
*/
AudioManager.audioFileExt = function() {
return ".ogg";
};
/**
* Checks if any buffers could not load their audio file
*
* @static
*/
AudioManager.checkErrors = function() {
const buffers = [this._bgmBuffer, this._bgsBuffer, this._meBuffer];
buffers.push(...this._seBuffers);
buffers.push(...this._staticBuffers);
for (const buffer of buffers) {
if (buffer && buffer.isError()) {
this.throwLoadError(buffer);
}
}
};
/**
* Throws an error screen with retry button if audio file could not be loaded.
*
* @static
* @param {WebAudio} webAudio - The buffer that could not be loaded.
* @throws Will throw a retry screen error
*/
AudioManager.throwLoadError = function(webAudio) {
const retry = webAudio.retry.bind(webAudio);
throw ["LoadError", webAudio.url, retry];
};