ami/src/ami.js/sound.js

287 lines
7.9 KiB
JavaScript

#include utility.js
var AmiSound = function() {
var playing = [];
var isMuted = false,
volume = .8;
var packName = undefined,
packs = new Map,
library = new Map;
var supported = [], fallback = [];
var formats = {
opus: 'audio/ogg;codecs=opus',
ogg: 'audio/ogg;codecs=vorbis',
mp3: 'audio/mpeg;codecs=mp3',
caf: 'audio/x-caf;codecs=opus',
wav: 'audio/wav',
};
var extractKeys = function(map) {
if('Array' in window && 'from' in Array)
return Array.from(map.keys());
var names = [];
map.forEach(function(value, key) {
names.push(key);
});
return names;
};
var createPack = function(name, title, events) {
name = (name || '').toString();
title = (title || '').toString();
events = events || new Map;
return {
getName: function() {
return name;
},
getTitle: function() {
return title;
},
getEventNames: function() {
return extractKeys(events);
},
hasEventSound: function(eventName) {
return events.has(eventName);
},
getEventSound: function(eventName) {
if(!events.has(eventName))
throw 'event not registered';
return events.get(eventName);
},
};
};
var createSound = function(name, title, sources) {
name = (name || '').toString();
title = (title || ('Nameless Sound (' + name + ')')).toString();
sources = sources || {};
return {
getName: function() {
return name;
},
getTitle: function() {
return title;
},
getSources: function() {
return sources;
},
};
};
(function() {
var elem = $e('audio');
for(var name in formats) {
var format = formats[name],
support = elem.canPlayType(format);
if(support === 'probably')
supported.push(name);
else if(support === 'maybe')
fallback.push(name);
}
})();
var findSupportedUrl = function(urls) {
if(typeof urls === 'string')
return urls;
var url = null;
for(var i in supported) {
var type = supported[i];
if(type in urls) {
url = urls[type];
break;
}
}
if(url === null)
for(var i in fallback) {
var type = fallback[i];
if(type in urls) {
url = urls[type];
break;
}
}
return url;
};
var playSound = function(urls, localVolume, rate) {
if(isMuted)
return;
var url = findSupportedUrl(urls);
if(url === undefined || url === null)
return;
// there's a caveat here where if the user changes the volume while a sound is playing
// this adjustment is essentially undone and reset back to 1.0
// it's generally only used to increase volume, but it could cause a Jump Scare for the few instances
// where it is used to lower the volume
if(localVolume === undefined)
localVolume = 1.0;
if(rate === undefined) rate = 1.0;
else rate = Math.min(4.0, Math.max(0.25, rate));
var audio = $e({
tag: 'audio',
attrs: { src: url },
});
audio.volume = Math.max(0.0, Math.min(1.0, volume * localVolume));
audio.muted = isMuted;
audio.preservesPitch = false;
audio.playbackRate = rate;
playing.push(audio);
audio.addEventListener('ended', function() {
$ari(playing, audio);
});
audio.addEventListener('loadeddata', function() {
audio.play();
});
};
var playLibrarySound = function(soundName, localVolume, rate) {
var soundInfo = library.get(soundName);
if(soundInfo === undefined)
return;
playSound(soundInfo.getSources(), localVolume, rate);
};
var pub = {};
pub.findSupportedUrl = findSupportedUrl;
pub.playSound = playSound;
pub.playLibrarySound = playLibrarySound;
pub.playEventSound = function(eventName, localVolume, rate) {
if(packName === undefined || packName === null || isMuted)
return;
var pack = packs.get(packName);
if(pack === undefined)
return;
if(typeof eventName === 'string') {
if(!pack.hasEventSound(eventName))
return;
} else {
var names = eventName;
eventName = null;
for(var i in names) {
var name = names[i];
if(pack.hasEventSound(name)) {
eventName = name;
break;
}
}
if(eventName === null)
return;
}
playLibrarySound(pack.getEventSound(eventName), localVolume, rate);
};
pub.getPackName = function() { return packName; };
pub.setPackName = function(name) { packName = name; };
pub.forEachPack = function(body) {
packs.forEach(function(value) {
body(value);
});
};
pub.hasPacks = function() {
return packs.size > 0;
};
pub.getPacks = function() {
var arr = [];
packs.forEach(function(value) {
arr.push(value);
});
return arr;
};
pub.clearPacks = function() {
packs.clear();
};
var loadSoundFormats = ['opus', 'caf', 'ogg', 'mp3', 'wav'];
pub.registerLibrarySound = function(soundInfo) {
if(typeof soundInfo !== 'object')
throw 'soundInfo must be an object';
if(typeof soundInfo.name !== 'string')
throw 'soundInfo must contain a name field';
if(typeof soundInfo.sources !== 'object')
throw 'soundInfo must contain a sources field';
var sources = {},
sCount = 0;
for(var i in loadSoundFormats) {
var fmt = loadSoundFormats[i];
if(typeof soundInfo.sources[fmt] !== 'string')
continue;
sources[fmt] = soundInfo.sources[fmt];
++sCount;
}
if(sCount < 1)
throw 'soundInfo does not contain any valid sources';
library.set(soundInfo.name, createSound(soundInfo.name, soundInfo.title, sources));
};
pub.registerSoundPack = function(packInfo) {
if(typeof packInfo !== 'object')
throw 'packInfo must be an object';
if(typeof packInfo.name !== 'string')
throw 'packInfo must contain a name field';
if(typeof packInfo.events !== 'object')
throw 'packInfo must contain a events field';
var events = new Map;
for(var eventName in packInfo.events) {
if(typeof packInfo.events[eventName] !== 'string')
continue;
events.set(eventName, packInfo.events[eventName]);
}
if(events.size < 1)
throw 'packInfo does not contain any valid events';
packs.set(packInfo.name, createPack(packInfo.name, packInfo.title, events));
};
pub.hasSoundLibrary = function() {
return library.size > 0;
};
pub.clearSoundLibrary = function() {
library.clear();
};
pub.getVolume = function() { return volume; };
pub.setVolume = function(value) {
volume = Math.max(0.0, Math.min(1.0, value));
for(var i in playing)
playing[i].volume = volume;
};
pub.isMuted = function() {
return isMuted;
};
pub.setMuted = function(mute) {
isMuted = !!mute;
for(var i in playing)
playing[i].muted = isMuted;
};
return pub;
};