/**
* Global plugin for playing back audio from files.
* @memberof module:sound~
*/
class Sound {
/**
* @param options - An options object for this plugin. Passed via the game config.
* @param options.key - The key to use for localStorage. Used to store wether this plugin is muted or not.
*/
constructor ({
key = 'verf-sound'
} = {})
{
this.key = key;
this._isOn = true;
let savedSetting = localStorage.getItem(this.key);
if (savedSetting === 'off') {
this._isOn = false;
}
this.tracks = {};
}
/**
* Wether this sound plugin is active or on (not muted).
*
* @type {boolean}
* @default true
*/
get isOn() {
return this._isOn;
}
set isOn(value) {
this._isOn = value;
localStorage.setItem(this.key, (value) ? 'on' : 'off');
}
createAndStartBufferSource({
audioBuffer = undefined,
start = 0,
duration = undefined,
name = '',
loop = false
}= {})
{
if (audioBuffer == undefined) {
return;
}
let audioCtx = this.engine.assets.audioCtx;
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
let trackSource = audioCtx.createBufferSource();
trackSource.buffer = audioBuffer;
trackSource.loop = loop;
trackSource.connect(audioCtx.destination);
trackSource.onended = () => {
if (this.tracks[name]) {
this.tracks[name] = undefined;
}
};
trackSource.start(0, start, duration);
return trackSource;
}
/**
* Plays an audio file. Does nothing if it is already playing.
*
* @param {string} name - The asset name.
*/
play(name)
{
if (!this.isOn) {
return;
}
if (this.engine.assets.audioCtx) {
if (this.tracks[name]) {
return;
}
this.tracks[name] = this.createAndStartBufferSource({
audioBuffer: this.engine.assets.getAudioBufferByName(name),
name: name
});
} else {
let asset = this.engine.assets.getByName(name);
if (asset) {
asset.loop = false;
asset.play();
}
}
}
/**
* Stops an audio file that is playing.
*
* @param {string} name - The asset name.
*/
stop(name)
{
if (this.tracks[name]) {
this.tracks[name].stop();
return;
}
let asset = this.engine.assets.getByName(name);
if (asset) {
asset.pause();
asset.currentTime = 0;
}
}
/**
* Plays an audio file and keeps looping it. Does nothing if the sound is already playing.
*
* @param {string} name - The asset name.
*/
loop(name)
{
if (!this.isOn) {
return;
}
if (this.engine.assets.audioCtx) {
if (this.tracks[name]) {
this.tracks[name].loop = true;
return;
}
this.tracks[name] = this.createAndStartBufferSource({
audioBuffer: this.engine.assets.getAudioBufferByName(name),
name: name,
loop: true
});
} else {
let asset = this.engine.assets.getByName(name);
if (asset) {
asset.loop = true;
asset.play();
}
}
}
/**
* Plays a random chunk from the audio file. If no chunks are defined it will play the complete sound.
*
* @param {string} name - The asset name.
*/
playRandom(name)
{
if (!this.isOn) {
return;
}
let asset = (this.engine.assets.audioCtx) ? this.engine.assets.getAudioBufferByName(name) : this.engine.assets.getByName(name);
if (asset) {
if (!asset.chunks) {
this.play(name);
return;
}
asset.chunkIndex = Math.floor(Math.random() * asset.chunks.length);
this.playChunk(asset, name);
}
}
/**
* Plays the next chunk from the audio file. If your chunks are notes that go up, this will create a positive feeling.
*
* @param {string} name - The asset name.
*/
playUp(name)
{
if (!this.isOn) {
return;
}
let asset = (this.engine.assets.audioCtx) ? this.engine.assets.getAudioBufferByName(name) : this.engine.assets.getByName(name);
if (asset) {
if (!asset.chunks) {
this.play(name);
return;
}
asset.chunkIndex += 1;
if (asset.chunkIndex >= asset.chunks.length) {
asset.chunkIndex = 0;
}
this.playChunk(asset, name);
}
}
/**
* Plays the previous chunk from the audio file. If your chunks are notes that go up, this will create a negative vibe as the notes will sound like they are going down.
*
* @param {string} name - The asset name.
*/
playDown(name)
{
if (!this.isOn) {
return;
}
let asset = (this.engine.assets.audioCtx) ? this.engine.assets.getAudioBufferByName(name) : this.engine.assets.getByName(name);
if (asset) {
if (!asset.chunks) {
this.play(name);
return;
}
asset.chunkIndex -= 1;
if (asset.chunkIndex < 0) {
asset.chunkIndex = asset.chunks.length - 1;
}
this.playChunk(asset, name);
}
}
/**
* Internal function that plays a chunk.
*
* @param {object} asset - The asset.
* @param {string} name - The asset name.
*/
playChunk(asset, name)
{
let end = asset.chunks[asset.chunkIndex].end;
let start = asset.chunks[asset.chunkIndex].start;
let duration = (end - start);
if (this.engine.assets.audioCtx) {
this.createAndStartBufferSource({
audioBuffer: asset,
name: name,
start: start,
duration: duration
});
} else {
let clone = new Audio(this.engine.assets.getBlobByName(name));
let playPromise = clone.play();
if (playPromise) {
playPromise.then(_ => {
clone.currentTime = start;
setTimeout(() => {
clone.pause();
}, duration * 1000);
});
} else {
clone.currentTime = start;
setTimeout(() => {
clone.pause();
}, duration * 1000);
}
}
}
}
export default Sound;