package org.flixel; import org.flixel.system.gdx.audio.GdxMusic; import org.flixel.system.gdx.audio.GdxSound; import flash.events.Event; import flash.events.IEventListener; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; /** * This is the universal flixel sound object, used for streaming, music, and sound effects. * * @author Ka Wing Chin * @author Thomas Weston */ public class FlxSound extends FlxBasic { /** * Automatically determine the type of file. */ static public final int AUTO = 0; /** * A short audio clip. */ static public final int SFX = 1; /** * A large music file. */ static public final int MUSIC = 2; /** * The X position of this sound in world coordinates. * Only really matters if you are doing proximity/panning stuff. */ public float x; /** * The Y position of this sound in world coordinates. * Only really matters if you are doing proximity/panning stuff. */ public float y; /** * Whether or not this sound should be automatically destroyed when you switch states. */ public boolean survive; /** * The ID3 song name. Defaults to null. Currently only works for streamed sounds. */ public String name; /** * The ID3 artist name. Defaults to null. Currently only works for streamed sounds. */ public String artist; /** * Stores the average wave amplitude of both stereo channels */ public float amplitude; /** * Just the amplitude of the left stereo channel */ public float amplitudeLeft; /** * Just the amplitude of the left stereo channel */ public float amplitudeRight; /** * Whether to call destroy() when the sound has finished. */ public boolean autoDestroy; /** * Internal tracker for a Flash sound object. */ protected Sound _sound; /** * Internal tracker for a Flash sound channel object. */ protected SoundChannel _channel; /** * Internal tracker for a Flash sound transform object. */ protected SoundTransform _transform; /** * Internal tracker for the position in runtime of the music playback. */ protected float _position; /** * Internal tracker for how loud the sound is. */ protected float _volume; /** * Internal tracker for total volume adjustment. */ protected float _volumeAdjust; /** * Internal tracker for how fast or how slow the sound is. */ private float _pitch; /** * Internal tracker for whether the sound is looping or not. */ protected boolean _looped; /** * Internal tracker for whether the sound is paused by focus lost. */ boolean _isPausedOnFocusLost; /** * Internal tracker for the sound's "target" (for proximity and panning). */ protected FlxObject _target; /** * Internal tracker for the maximum effective radius of this sound (for proximity and panning). */ protected float _radius; /** * Internal tracker for whether to pan the sound left and right. Default is false. */ protected boolean _pan; /** * Internal timer used to keep track of requests to fade out the sound playback. */ protected float _fadeOutTimer; /** * Internal helper for fading out sounds. */ protected float _fadeOutTotal; /** * Internal flag for whether to pause or stop the sound when it's done fading out. */ protected boolean _pauseOnFadeOut; /** * Internal timer for fading in the sound playback. */ protected float _fadeInTimer; /** * Internal helper for fading in sounds. */ protected float _fadeInTotal; /** * The FlxSound constructor gets all the variables initialized, but NOT ready to play a sound yet. */ public FlxSound() { super(); createSound(); } /** * An internal function for clearing all the variables used by sounds. */ protected void createSound() { destroy(); x = 0; y = 0; if(_transform == null) _transform = new SoundTransform(); _transform.pan = 0f; _sound = null; _position = 0; _volume = 1.0f; _volumeAdjust = 1.0f; _pitch = 1.0f; _looped = false; _target = null; _radius = 0; _pan = false; _fadeOutTimer = 0; _fadeOutTotal = 0; _pauseOnFadeOut = false; _fadeInTimer = 0; _fadeInTotal = 0; exists = false; active = false; visible = false; name = null; artist = null; amplitude = 0; amplitudeLeft = 0; amplitudeRight = 0; autoDestroy = false; survive = false; } /** * Clean up memory. */ public void destroy() { kill(); _transform = null; _sound = null; _channel = null; _target = null; name = null; artist = null; super.destroy(); } /** * Handles fade out, fade in, panning, proximity, and amplitude operations each frame. */ @Override public void update() { float radial = 1.0f; float fade = 1.0f; //Distance-based volume control if(_target != null) { radial = 1 - FlxU.getDistance(new FlxPoint(_target.x,_target.y),new FlxPoint(x,y))/_radius; if(radial < 0) radial = 0; if(radial > 1) radial = 1; if(_pan) { float d = (x-_target.x)/_radius; if(d < -1) d = -1; else if(d > 1) d = 1; _transform.pan = d; } } //Cross-fading volume control if(_fadeOutTimer > 0) { _fadeOutTimer -= FlxG.elapsed; if(_fadeOutTimer <= 0) { if(_pauseOnFadeOut) pause(); else stop(); } fade = _fadeOutTimer/_fadeOutTotal; if(fade < 0) fade = 0; } else if(_fadeInTimer > 0) { _fadeInTimer -= FlxG.elapsed; fade = _fadeInTimer/_fadeInTotal; if(fade < 0) fade = 0; fade = 1 - fade; } _volumeAdjust = radial*fade; updateTransform(); if((_transform.volume > 0) && (_channel != null)) { amplitudeLeft = _channel.getLeftPeak()/_transform.volume; amplitudeRight = _channel.getRightPeak()/_transform.volume; amplitude = (amplitudeLeft+amplitudeRight)*0.5f; } } @Override public void kill() { super.kill(); if(_channel != null) stop(); } /** * One of two main setup functions for sounds, this function loads a sound from an embedded MP3. * * @param EmbeddedSound An embedded Class object representing an MP3 file. * @param Looped Whether or not this sound should loop endlessly. * @param AutoDestroy Whether or not this <code>FlxSound</code> instance should be destroyed when the sound finishes playing. Default value is false, but FlxG.play() and FlxG.stream() will set it to true by default. * @param Type Whether this sound is a sound effect or a music track. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ public FlxSound loadEmbedded(String EmbeddedSound, boolean Looped, boolean AutoDestroy, int Type) { stop(); createSound(); switch(Type) { case SFX: _sound = new GdxSound(EmbeddedSound); break; case MUSIC: _sound = new GdxMusic(EmbeddedSound); break; case AUTO: default: //If the type is not specified, make a guess based on the file size. FileHandle file = Gdx.files.internal(EmbeddedSound); Type = file.length() < 24576 ? SFX : MUSIC; return loadEmbedded(EmbeddedSound, Looped, AutoDestroy, Type); } //NOTE: can't pull ID3 info from embedded sound currently _looped = Looped; autoDestroy = AutoDestroy; updateTransform(); exists = true; return this; } /** * One of two main setup functions for sounds, this function loads a sound from an embedded MP3. * * @param EmbeddedSound An embedded Class object representing an MP3 file. * @param Looped Whether or not this sound should loop endlessly. * @param AutoDestroy Whether or not this <code>FlxSound</code> instance should be destroyed when the sound finishes playing. Default value is false, but FlxG.play() and FlxG.stream() will set it to true by default. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ public FlxSound loadEmbedded(String EmbeddedSound, boolean Looped, boolean AutoDestroy) { return loadEmbedded(EmbeddedSound, Looped, AutoDestroy, AUTO); } /** * One of two main setup functions for sounds, this function loads a sound from an embedded MP3. * * @param EmbeddedSound An embedded Class object representing an MP3 file. * @param Looped Whether or not this sound should loop endlessly. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ public FlxSound loadEmbedded(String EmbeddedSound, boolean Looped) { return loadEmbedded(EmbeddedSound, Looped, false, AUTO); } /** * One of two main setup functions for sounds, this function loads a sound from an embedded MP3. * * @param EmbeddedSound An embedded Class object representing an MP3 file. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ public FlxSound loadEmbedded(String EmbeddedSound) { return loadEmbedded(EmbeddedSound, false, false, AUTO); } /** * One of two main setup functions for sounds, this function loads a sound from a URL. * * @param SoundURL A string representing the URL of the MP3 file you want to play. * @param Looped Whether or not this sound should loop endlessly. * @param AutoDestroy Whether or not this <code>FlxSound</code> instance should be destroyed when the sound finishes playing. Default value is false, but FlxG.play() and FlxG.stream() will set it to true by default. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ // TODO: Load a sound from an URL public FlxSound loadStream(String SoundURL, boolean Looped, boolean AutoDestroy) { stop(); createSound(); // _sound = new Sound(); // _sound.addEventListener(Event.ID3, gotID3); // _sound.load(new URLRequest(SoundURL)); _looped = Looped; autoDestroy = AutoDestroy; updateTransform(); exists = true; return this; } /** * One of two main setup functions for sounds, this function loads a sound from a URL. * * @param SoundURL A string representing the URL of the MP3 file you want to play. * @param Looped Whether or not this sound should loop endlessly. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ public FlxSound loadStream(String SoundURL, boolean Looped) { return loadStream(SoundURL, Looped, false); } /** * One of two main setup functions for sounds, this function loads a sound from a URL. * * @param SoundURL A string representing the URL of the MP3 file you want to play. * * @return This <code>FlxSound</code> instance (nice for chaining stuff together, if you're into that). */ public FlxSound loadStream(String SoundURL) { return loadStream(SoundURL, false, false); } /** * Call this function if you want this sound's volume to change * based on distance from a particular FlxCore object. * * @param X The X position of the sound. * @param Y The Y position of the sound. * @param Object The object you want to track. * @param Radius The maximum distance this sound can travel. * @param Pan Whether the sound should pan in addition to the volume changes (default: true). * * @return This FlxSound instance (nice for chaining stuff together, if you're into that). */ public FlxSound proximity(float X,float Y,FlxObject Object,float Radius,boolean Pan) { x = X; y = Y; _target = Object; _radius = Radius; _pan = Pan; return this; } /** * Call this function if you want this sound's volume to change * based on distance from a particular FlxCore object. * * @param X The X position of the sound. * @param Y The Y position of the sound. * @param Object The object you want to track. * @param Radius The maximum distance this sound can travel. * @param Pan Whether the sound should pan in addition to the volume changes (default: true). * * @return This FlxSound instance (nice for chaining stuff together, if you're into that). */ public FlxSound proximity(float X,float Y,FlxObject Object,float Radius) { return proximity(X,Y,Object,Radius,true); } /** * Call this function to play the sound - also works on paused sounds. * * @param ForceRestart Whether to start the sound over or not. Default value is false, meaning if the sound is already playing or was paused when you call <code>play()</code>, it will continue playing from its current position, NOT start again from the beginning. */ public void play(boolean ForceRestart) { if(_position < 0) return; if(ForceRestart) { boolean oldAutoDestroy = autoDestroy; autoDestroy = false; stop(); autoDestroy = oldAutoDestroy; } if(_looped) { if(_position == 0) { if(_channel == null) _channel = _sound.play(0f,9999,_transform); if(_channel == null) exists = false; else _channel.addEventListener(Event.SOUND_COMPLETE, stoppedListener); } else _channel.resume(); } else { if(_position == 0) { if(_channel == null) { _channel = _sound.play(0f,0,_transform); if(_channel == null) exists = false; else _channel.addEventListener(Event.SOUND_COMPLETE, stoppedListener); } } else _channel.resume(); } active = (_channel != null); _position = 0; } /** * Call this function to play the sound - also works on paused sounds. */ public void play() { play(false); } /** * Unpause a sound. Only works on sounds that have been paused. */ public void resume() { if(_position <= 0) return; _channel.resume(); active = (_channel != null); } /** * Call this function to pause this sound. */ public void pause() { if(_channel == null) { _position = -1; return; } _position = 1; _channel.pause(); active = false; } /** * Call this function to stop this sound. */ public void stop() { _position = 0; if(_channel != null) { _channel.stop(); stopped(); } } /** * Call this function to make this sound fade out over a certain time interval. * * @param Seconds The amount of time the fade out operation should take. * @param PauseInstead Tells the sound to pause on fadeout, instead of stopping. */ public void fadeOut(float Seconds,boolean PauseInstead) { _pauseOnFadeOut = PauseInstead; _fadeInTimer = 0; _fadeOutTimer = Seconds; _fadeOutTotal = _fadeOutTimer; } /** * Call this function to make this sound fade out over a certain time interval. * * @param Seconds The amount of time the fade out operation should take. */ public void fadeOut(float Seconds) { fadeOut(Seconds,false); } /** * Call this function to make a sound fade in over a certain * time interval (calls <code>play()</code> automatically). * * @param Seconds The amount of time the fade-in operation should take. */ public void fadeIn(float Seconds) { _fadeOutTimer = 0; _fadeInTimer = Seconds; _fadeInTotal = _fadeInTimer; play(); } /** * Set <code>volume</code> to a value between 0 and 1 to change how this sound is. */ public float getVolume() { return _volume; } /** * Set <code>volume</code> to a value between 0 and 1 to change how this sound is. * * @param Volume The volume of the sound. */ public void setVolume(float Volume) { _volume = Volume; if(_volume < 0) _volume = 0; else if(_volume > 1) _volume = 1; updateTransform(); } /** * Returns the currently selected "real" volume of the sound (takes fades * and proximity into account). * * @return The adjusted volume of the sound. */ public float getActualVolume() { return _volume*_volumeAdjust; } /** * Set the pitch multiplier, 1 == default, >1 == faster, <1 == slower. * The value has to be between 0.5 and 2.0. * * @param Pitch The pitch multiplier. */ public void setPitch(float Pitch) { _pitch = Pitch; if(_pitch < 0) _pitch = 0; else if(_pitch > 2) _pitch = 2; updateTransform(); } /** * Returns the current pitch of the sound. * * @return The pitch of the sound. */ public float getPitch() { return _pitch; } /** * Call after adjusting the volume to update the sound channel's settings. */ protected void updateTransform() { _transform.volume = (FlxG.mute?0:1)*FlxG.getVolume()*_volume*_volumeAdjust; _transform.pitch = _pitch; if(_channel != null) _channel.setSoundTransform(_transform); } /** * An internal helper function used to help Flash clean up and re-use finished sounds. */ protected void stopped() { _channel.removeEventListener(Event.SOUND_COMPLETE,stoppedListener); _channel = null; active = false; _isPausedOnFocusLost = false; if(autoDestroy) destroy(); } /** * Internal event handler for ID3 info (i.e. fetching the song name). */ // TODO: ID3 info protected void gotID3() { /* * FlxG.log("got ID3 info!"); if(_sound.id3.songName.length > 0) name = * _sound.id3.songName; if(_sound.id3.artist.length > 0) artist = * _sound.id3.artist; _sound.removeEventListener(Event.ID3, gotID3); */ } /** * Internal event listener. */ protected final IEventListener stoppedListener = new IEventListener() { @Override public void onEvent(Event e) { stopped(); } }; }