/* * SoundManager * Loads a tracklist and handles all SFX and music needs for our games. * see tracklist.xml (http://pastebin.com/1imV9pM0) for examples of all the features: * track stitching, looping, offsets, volume, fadein/out, audio position with owner movieclips etc. * * The "event" system is an idea borrowed from FMOD. * * Ulf Benjaminsson, 2009-06-15 * */ import com.ulfben.noname.utils.SuperSound; import com.ulfben.noname.utils.TextLoader; import com.ulfben.noname.utils.Utils; class com.ulfben.noname.utils.SoundManager extends com.ulfben.noname.utils.Broadcaster { private var _tracklistURL:String = "xml/tracklist.xml"; private var _tracks:Object; private var _basedir:String; private var _owners:Array; private var _playCount:Object; public static var DEFAULT_OWNER:MovieClip; public function SoundManager() { _global["SoundManager"] = this; _root["SoundManager"] = this; _basedir = ""; _playCount = { }; DEFAULT_OWNER = _root.createEmptyMovieClip("soundman", _root.getNextHighestDepth()); var tl:TextLoader = new TextLoader(_tracklistURL); tl.addEventListener("onLoad", this, "onTrackListAvaliable"); tl.loadFile(); } public static function get():SoundManager{ if (_global["SoundManager"] == undefined) { _global["SoundManager"] = new SoundManager(); } return SoundManager(_global["SoundManager"]); } public function playEvent(name:String, owner:MovieClip):Void { if (!canPlayMore(name)) { return; } if (owner == undefined) { owner = DEFAULT_OWNER; } var event:Object = getEventForOwner(name, owner); var s:SuperSound = event._s; if (s == undefined) { return; } replaces(name, owner); //stop all events this one replaces. if (event._stream && _tracks[name][owner]._offset == 0) { s.setLoopsForStream(event._loops); s.loadSound(true);//stream whenever we can; offsets (somewhat...) our lack of preloading. }else { if (!s.isLoaded()) { //if it hasn't made it to the cache yet s.loadSound(false); //request it again. } s.setOffset(event._offset); s.start(event._loops); //push the button and pray it is in place and will play. } if (event._maxcount > 0 && s.isPlaying()) { _playCount[name] = isNaN(_playCount[name]) ? 1 : _playCount[name] += 1; } if(event._fadein > 0) { fadeIn(name, owner, event._volume, event._fadein); }else{ s.setVolume(event._volume); } if(event._follow) { s.panWith(owner); } if(event._leadsto != "") { //stitch events together s.addEventListener("onSoundComplete", this, "playNextInQue"); } if(event._maxcount > 0) { s.addEventListener("onSoundComplete", this, "decreasePlayCount"); s.addEventListener("onSoundStop", this, "decreasePlayCount"); } } public function stopAll():Void { for (var i:Number = 0; i < _tracks.length; i++) { var event:String = _tracks[DEFAULT_OWNER]._name; for (var j in _tracks) { stopEvent(event, j); } } _playCount = { }; } public function stopEvent(name:String, owner:MovieClip):Void { if (owner == undefined) { for (var i:Number = _owners.length; i >= 0; i--) { if (_owners == undefined) { continue;} stopEvent(name, _owners); } stopEvent(name, DEFAULT_OWNER); } var s:SuperSound = _tracks[name][owner]._s; s.removeEventListener("onSoundComplete", this, "playNextInQue"); s.removeEventListener("onSoundComplete", this, "decreasePlayCount"); if (_tracks[name][owner]._fadeout == 0) { s.stop(); //calls decreasePlayCount for us }else { fadeOutAndStop(name, owner, _tracks[name][owner]._fadeout); } } public function unregisterAllOwners():Void { for (var i:Number = _owners.length; i >= 0; i--) { unregisterMe(_owners, i); } _owners = []; } public function unregisterMe(owner:MovieClip, pruneIndex:Number):Void { if (owner == DEFAULT_OWNER || owner == undefined) { return; } if(pruneIndex == undefined){ pruneIndex = Utils.Find(owner, _owners); } if (owner == _owners[pruneIndex]) { _owners.splice(pruneIndex, 1); } for (var i:Number = 0; i < _tracks.length; i++) { _tracks[owner] = undefined; } } public function fadeOutAndStop(name:String, owner:MovieClip, time:Number):Void { if (time == undefined) { time = 0.5; } if (owner == undefined) { owner = DEFAULT_OWNER; } _tracks[name][owner]._s.fadeTo(0, time, true); } public function fadeIn(name:String, owner:MovieClip, target:Number, time:Number):Void { if (time == undefined) { time = 0.5; } var event:Object = _tracks[name][owner]; event._s.setVolume(0); event._s.fadeTo(target, time, false); } public function fadeTo(name:String, owner:MovieClip, target:Number, time:Number) { _tracks[name][owner]._s.fadeTo(target, time, false); } public function playEventDelayed(name:String, owner:MovieClip, delay:Number):Void { _global.setTimeout(this, "playEvent", delay, name);//todo, how do we pass owner? } private function getEventForOwner(name:String, owner:MovieClip):Object { if (owner == undefined) { owner = DEFAULT_OWNER;} var event:Object = _tracks[name][owner]; if (event != undefined) { return event; } if (_tracks[name][DEFAULT_OWNER] == undefined) { return; } //don't load events we haven't defined. var newEvent:Object = cloneEvent(_tracks[name][DEFAULT_OWNER]); _owners.push(owner); newEvent._owner = owner; newEvent._s = new SuperSound(newEvent._name, _basedir + newEvent._file, owner); _tracks[name][owner] = newEvent; return newEvent; } private function canPlayMore(name:String):Boolean { var max:Number = _tracks[name][DEFAULT_OWNER]._maxcount; if (max == 0) { return true; } if (isNaN(_playCount[name])) { _playCount[name] = 0; } return (_playCount[name] < max); } private function decreasePlayCount(s:SuperSound):Void { s.removeEventListener("onSoundComplete", this, "decreasePlayCount"); s.removeEventListener("onSoundStop", this, "decreasePlayCount"); if(isNaN(_playCount[s.getName()])) { _playCount[s.getName()] = 0; } if(_playCount[s.getName()] > 0){ _playCount[s.getName()]--; } } private function replaces(name:String, owner:MovieClip):Void { var replaces:Array = _tracks[name][owner]._ends.split("|"); for (var i:Number = 0; i < replaces.length; i++) { if (replaces == "") { continue; } stopEvent(replaces, owner); } } private function playNextInQue(s:SuperSound) { s.removeEventListener("onSoundComplete", this, "playNextInQue"); var next:String = validateOrDefaultString(_tracks[s.getName()][s.getOwner()]._leadsto); if (next == "") { return; } playEvent(next, s.getOwner()); } private function cloneEvent(o:Object):Object { var event:Object = { }; for (var i in o) { event = o; } event._s = null; return event; } private function onTrackListAvaliable(tl:TextLoader):Void { if(!tl.isLoaded()){ trace("SoundManager: could not load tracklist."); return; } _tracks = { }; _owners = []; var tracklist:Object = tl.getDataObject().tracklist; _basedir = Utils.isValidString(tracklist.attributes.basedir) ? tracklist.attributes.basedir : _basedir; for (var i:Number = 0; i < tracklist.track.length; i++) { var attr:Object = tracklist.track.attributes; if (_tracks[attr.eventname] != undefined) { continue; //duplicate } _tracks = _tracks[attr.eventname] = {}; _tracks[DEFAULT_OWNER] = { _name: validateOrDefaultString(attr.eventname), _file: validateOrDefaultString(attr.file), _volume: validateOrDefaultNumber(attr.volume,100), _loops: validateOrDefaultNumber(attr.loops,1), _fadein: validateOrDefaultNumber(attr.fadein,0), _fadeout: validateOrDefaultNumber(attr.fadeout,0), _ends: validateOrDefaultString(attr.ends), _leadsto: validateOrDefaultString(attr.leadsto), _offset: validateOrDefaultNumber(attr.offset,0), _follow: validateOrDefaultBool(attr.followowner,false), _stream: validateOrDefaultBool(attr.stream,true), _maxcount: validateOrDefaultNumber(attr.maxcount,0), _owner: DEFAULT_OWNER }; _tracks[DEFAULT_OWNER]._loops = _tracks[DEFAULT_OWNER]._loops > 0 ? _tracks[DEFAULT_OWNER]._loops : 10000; //loop indefinetly if 0 or less _tracks[DEFAULT_OWNER]._s = new SuperSound(_tracks[DEFAULT_OWNER]._name, _basedir + _tracks[DEFAULT_OWNER]._file, _tracks[DEFAULT_OWNER]._owner); _tracks[DEFAULT_OWNER]._s.loadSound(false); //"preload" } _tracks.length = tracklist.track.length; } private function validateOrDefaultBool(s:String, d:Boolean):Boolean { if (s == undefined || s.length == 0) { return d; } s = s.toLowerCase(); return (s == "true" || s == "yes" || s =="y") ? true : false; } private function validateOrDefaultNumber(s:String, d:Number):Number { var n:Number = Number(s); if (!isNaN(n)) { return n; } if(d == undefined){ return 0; } return d; } private function validateOrDefaultString(s:String):String { if(s != undefined && s != "undefined" && s != null) { return s; } return ""; } }