/* Copyright (c) 2013, Slick2D All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Slick2D nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS gAS ISh AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.newdawn.slick; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import org.newdawn.slick.openal.Audio; import org.newdawn.slick.openal.AudioImpl; import org.newdawn.slick.openal.SoundStore; import org.newdawn.slick.util.Log; /** * A piece of music loaded and playable within the game. Only one piece of music can * play at any given time and a channel is reserved so music will always play. * * @author kevin * @author Nathan Sweet */ public class Music { /** The music currently being played or null if none */ private static Music currentMusic; /** * Poll the state of the current music. This causes streaming music * to stream and checks listeners. Note that if you're using a game container * this will be auto-magically called for you. * * @param delta The amount of time since last poll */ public static void poll(int delta) { if (currentMusic != null) { SoundStore.get().poll(delta); if (!SoundStore.get().isMusicPlaying()) { if (!currentMusic.positioning&&!currentMusic.playing) { Music oldMusic = currentMusic; currentMusic = null; oldMusic.fireMusicEnded(); } } else { currentMusic.update(delta); } } } /** The sound from FECK representing this music */ private Audio sound; /** True if the music is playing */ private boolean playing; /** The list of listeners waiting for notification that the music ended */ private ArrayList listeners = new ArrayList(); /** The volume of this music */ private float volume = 1.0f; /** Start gain for fading in/out */ private float fadeStartGain; /** End gain for fading in/out */ private float fadeEndGain; /** Countdown for fading in/out */ private int fadeTime; /** Duration for fading in/out */ private int fadeDuration; /** True if music should be stopped after fading in/out */ private boolean stopAfterFade; /** True if the music is being repositioned and it is therefore normal that it's not playing */ private boolean positioning; /** The position that was requested */ private float requiredPosition = -1; /** * Create and load a piece of music (either OGG or MOD/XM) * * @param ref The location of the music * @throws SlickException */ public Music(String ref) throws SlickException { this(ref, false); } /** * Create and load a piece of music (either OGG or MOD/XM) * * @param ref The location of the music * @throws SlickException */ public Music(URL ref) throws SlickException { this(ref, false); } /** * Create and load a piece of music (either OGG or MOD/XM) * @param in The stream to read the music from * @param ref The symbolic name of this music * @throws SlickException Indicates a failure to read the music from the stream */ public Music(InputStream in, String ref) throws SlickException { SoundStore.get().init(); try { if (ref.toLowerCase().endsWith(".ogg")) { sound = SoundStore.get().getOgg(in); } else if (ref.toLowerCase().endsWith(".wav")) { sound = SoundStore.get().getWAV(in); } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { sound = SoundStore.get().getMOD(in); } else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) { sound = SoundStore.get().getAIF(in); } else { throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported."); } } catch (Exception e) { Log.error(e); throw new SlickException("Failed to load music: "+ref); } } /** * Create and load a piece of music (either OGG or MOD/XM) * * @param url The location of the music * @param streamingHint A hint to indicate whether streaming should be used if possible * @throws SlickException */ public Music(URL url, boolean streamingHint) throws SlickException { SoundStore.get().init(); String ref = url.getFile(); try { if (ref.toLowerCase().endsWith(".ogg") ||ref.toLowerCase().endsWith(".mp3")) { if (streamingHint) { sound = SoundStore.get().getOggStream(url); } else { sound = SoundStore.get().getOgg(url.openStream()); } } else if (ref.toLowerCase().endsWith(".wav")) { sound = SoundStore.get().getWAV(url.openStream()); } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { sound = SoundStore.get().getMOD(url.openStream()); } else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) { sound = SoundStore.get().getAIF(url.openStream()); } else { throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported."); } } catch (Exception e) { Log.error(e); throw new SlickException("Failed to load sound: "+url); } } /** * Create and load a piece of music (either OGG or MOD/XM) * * @param ref The location of the music * @param streamingHint A hint to indicate whether streaming should be used if possible * @throws SlickException */ public Music(String ref, boolean streamingHint) throws SlickException { SoundStore.get().init(); try { if (ref.toLowerCase().endsWith(".ogg") ||ref.toLowerCase().endsWith(".mp3")) { if (streamingHint) { sound = SoundStore.get().getOggStream(ref); } else { sound = SoundStore.get().getOgg(ref); } } else if (ref.toLowerCase().endsWith(".wav")) { sound = SoundStore.get().getWAV(ref); } else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) { sound = SoundStore.get().getMOD(ref); } else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) { sound = SoundStore.get().getAIF(ref); } else { throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported."); } } catch (Exception e) { Log.error(e); throw new SlickException("Failed to load sound: "+ref); } } /** * Add a listener to this music * * @param listener The listener to add */ public void addListener(MusicListener listener) { listeners.add(listener); } /** * Remove a listener from this music * * @param listener The listener to remove */ public void removeListener(MusicListener listener) { listeners.remove(listener); } /** * Fire notifications that this music ended */ private void fireMusicEnded() { playing = false; for (int i=0;i 1.0f) volume = 1.0f; sound.playAsMusic(pitch, volume, loop); playing = true; currentMusic = this; setVolume(volume); if (requiredPosition != -1) { setPosition(requiredPosition); } } /** * Pause the music playback */ public void pause() { playing = false; AudioImpl.pauseMusic(); } /** * Stop the music playing */ public void stop() { sound.stop(); } /** * Resume the music playback */ public void resume() { playing = true; AudioImpl.restartMusic(); } /** * Check if the music is being played * * @return True if the music is being played */ public boolean playing() { return (currentMusic == this) && (playing); } /** * Set the volume of the music as a factor of the global volume setting * * @param volume The volume to play music at. 0 - 1, 1 is Max */ public void setVolume(float volume) { // Bounds check if(volume > 1) { volume = 1; } else if(volume < 0) { volume = 0; } this.volume = volume; // This sound is being played as music if (currentMusic == this) { SoundStore.get().setCurrentMusicVolume(volume); } } /** * Get the individual volume of the music * @return The volume of this music, still effected by global SoundStore volume. 0 - 1, 1 is Max */ public float getVolume() { return volume; } /** * Fade this music to the volume specified * * @param duration Fade time in milliseconds. * @param endVolume The target volume * @param stopAfterFade True if music should be stopped after fading in/out */ public void fade (int duration, float endVolume, boolean stopAfterFade) { this.stopAfterFade = stopAfterFade; fadeStartGain = volume; fadeEndGain = endVolume; fadeDuration = duration; fadeTime = duration; } /** * Update the current music applying any effects that need to updated per * tick. * * @param delta The amount of time in milliseconds thats passed since last update */ void update(int delta) { if (!playing) { return; } if (fadeTime > 0) { fadeTime -= delta; if (fadeTime < 0) { fadeTime = 0; if (stopAfterFade) { stop(); return; } } float offset = (fadeEndGain - fadeStartGain) * (1 - (fadeTime / (float)fadeDuration)); setVolume(fadeStartGain + offset); } } /** * Seeks to a position in the music. For streaming music, seeking before the current position causes * the stream to be reloaded. * * @param position Position in seconds. * @return True if the seek was successful */ public boolean setPosition(float position) { if (playing) { requiredPosition = -1; positioning = true; playing = false; boolean result = sound.setPosition(position); playing = true; positioning = false; return result; } else { requiredPosition = position; return false; } } /** * The position into the sound thats being played * * @return The current position in seconds. */ public float getPosition () { return sound.getPosition(); } }