From 6883e2ab44dea92f65676eec81ff36b8b64c75bd Mon Sep 17 00:00:00 2001 From: fd Date: Sun, 8 Feb 2015 23:48:55 -0500 Subject: [PATCH 1/3] MP3 Audio Streaming through modifying some of slick's openal files May rarly crash/not play on next music. May have introduced other unknown bugs. This may be due to loading it async. Also loading of ogg is not loaded async anymore as it seems it doesn't work very well. --- .../opsu/audio/MusicController.java | 39 +- src/itdelatrisu/opsu/states/Game.java | 8 +- src/org/newdawn/slick/Music.java | 428 ++++++++ .../slick/openal/AudioInputStream.java | 87 ++ .../newdawn/slick/openal/Mp3InputStream.java | 176 ++++ .../slick/openal/OpenALStreamPlayer.java | 395 +++++++ src/org/newdawn/slick/openal/SoundStore.java | 992 ++++++++++++++++++ 7 files changed, 2111 insertions(+), 14 deletions(-) create mode 100644 src/org/newdawn/slick/Music.java create mode 100644 src/org/newdawn/slick/openal/AudioInputStream.java create mode 100644 src/org/newdawn/slick/openal/Mp3InputStream.java create mode 100644 src/org/newdawn/slick/openal/OpenALStreamPlayer.java create mode 100644 src/org/newdawn/slick/openal/SoundStore.java diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 12d4a517..28f4878a 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -79,21 +79,24 @@ public class MusicController { switch (OsuParser.getExtension(osu.audioFilename.getName())) { case "ogg": - trackLoader = new Thread() { - @Override - public void run() { + //trackLoader = new Thread() { + // @Override + // public void run() { + //Loading ogg async seems to screw up + //So does mp3, but much less loadTrack(osu.audioFilename, osu.previewTime, loop); - } - }; - trackLoader.start(); + // } + //}; + //trackLoader.start(); break; case "mp3": trackLoader = new Thread() { @Override public void run() { - convertMp3(osu.audioFilename); + loadTrack(osu.audioFilename, osu.previewTime, loop); + //convertMp3(osu.audioFilename); // if (!Thread.currentThread().isInterrupted()) - loadTrack(wavFile, osu.previewTime, loop); + // loadTrack(wavFile, osu.previewTime, loop); } }; trackLoader.start(); @@ -110,7 +113,7 @@ public class MusicController { */ private static void loadTrack(File file, int previewTime, boolean loop) { try { // create a new player - player = new Music(file.getPath()); + player = new Music(file.getPath(),true); player.addListener(new MusicListener() { @Override public void musicEnded(Music music) { trackEnded = true; } @@ -253,7 +256,7 @@ public class MusicController { */ public static int getPosition() { if (isPlaying()) - return Math.max((int) (player.getPosition() * 1000 + Options.getMusicOffset()), 0); + return (int) (player.getPosition() * 1000 + Options.getMusicOffset()); else if (isPaused()) return Math.max((int) (pauseTime * 1000 + Options.getMusicOffset()), 0); else @@ -266,6 +269,17 @@ public class MusicController { public static boolean setPosition(int position) { return (trackExists() && player.setPosition(position / 1000f)); } + + /** + * Plays the current track. + */ + public static boolean play() { + if (trackExists()){ + player.play(); + return true; + } + return false; + } /** * Sets the music volume. @@ -381,8 +395,9 @@ public class MusicController { AL10.alDeleteSources(buf); int exc = AL10.alGetError(); if (exc != AL10.AL_NO_ERROR) { - throw new SlickException( - "Could not clear SoundStore sources, err: " + exc); + //Seems It can't delete mp3 source? + //throw new SlickException( + // "Could not clear SoundStore sources, err: " + exc); } // delete any buffer data stored in memory, too... diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 7daf16ac..f60c03db 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -285,7 +285,7 @@ public class Game extends BasicGameState { } if (isLeadIn()) - trackPosition = leadInTime * -1; // render approach circles during song lead-in + trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in // countdown if (osu.countdown > 0) { // TODO: implement half/double rate settings @@ -376,7 +376,7 @@ public class Game extends BasicGameState { if (isLeadIn()) { // stop updating during song lead-in leadInTime -= delta; if (!isLeadIn()) - MusicController.playAt(0, false); + MusicController.resume(); return; } @@ -694,6 +694,10 @@ public class Game extends BasicGameState { // reset game data resetGameData(); + + //needs to play before we can set position + //so we can resume without lag later + MusicController.play(); MusicController.setPosition(0); MusicController.pause(); diff --git a/src/org/newdawn/slick/Music.java b/src/org/newdawn/slick/Music.java new file mode 100644 index 00000000..a249161c --- /dev/null +++ b/src/org/newdawn/slick/Music.java @@ -0,0 +1,428 @@ +/* +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); + 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(); + } +} diff --git a/src/org/newdawn/slick/openal/AudioInputStream.java b/src/org/newdawn/slick/openal/AudioInputStream.java new file mode 100644 index 00000000..0b5f50f3 --- /dev/null +++ b/src/org/newdawn/slick/openal/AudioInputStream.java @@ -0,0 +1,87 @@ +/* +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.openal; + +import java.io.IOException; + +/** + * The description of an input stream that supplied audio data suitable for + * use in OpenAL buffers + * + * @author kevin + */ +interface AudioInputStream { + /** + * Get the number of channels used by the audio + * + * @return The number of channels used by the audio + */ + public int getChannels(); + + /** + * The play back rate described in the underling audio file + * + * @return The playback rate + */ + public int getRate(); + + /** + * Read a single byte from the stream + * + * @return The single byte read + * @throws IOException Indicates a failure to read the underlying media + * @see java.io.InputStream#read() + */ + public int read() throws IOException; + + /** + * Read up to data.length bytes from the stream + * + * @param data The array to read into + * @return The number of bytes read or -1 to indicate no more bytes are available + * @throws IOException Indicates a failure to read the underlying media + * @see java.io.InputStream#read(byte[]) + */ + public int read(byte[] data) throws IOException; + + /** + * Read up to len bytes from the stream + * + * @param data The array to read into + * @param ofs The offset into the array at which to start writing + * @param len The maximum number of bytes to read + * @return The number of bytes read or -1 to indicate no more bytes are available + * @throws IOException Indicates a failure to read the underlying media + * @see java.io.InputStream#read(byte[], int, int) + */ + public int read(byte[] data, int ofs, int len) throws IOException; + + /** + * Check if the stream is at the end, i.e. end of file or URL + * + * @return True if the stream has no more data available + */ + public boolean atEnd(); + + /** + * Close the stream + * + * @see java.io.InputStream#close() + * @throws IOException Indicates a failure to access the resource + */ + public void close() throws IOException; + + public long skip(long l) throws IOException; + +} diff --git a/src/org/newdawn/slick/openal/Mp3InputStream.java b/src/org/newdawn/slick/openal/Mp3InputStream.java new file mode 100644 index 00000000..b1c7d85d --- /dev/null +++ b/src/org/newdawn/slick/openal/Mp3InputStream.java @@ -0,0 +1,176 @@ +/* +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.openal; + +import java.io.IOException; +import java.io.InputStream; + +import sun.font.EAttribute; +import javazoom.jl.decoder.*; + +public class Mp3InputStream extends InputStream implements AudioInputStream { + + Bitstream bitstream; + Decoder decoder; + Header header; + SampleBuffer buf; + + int channels; + int sampleRate; + + int bufLen = 0; + boolean atEnd=false; + int bpos; //byte pos + + public Mp3InputStream(InputStream resourceAsStream) { + decoder = new Decoder(); + bitstream = new Bitstream(resourceAsStream); + try { + header = bitstream.readFrame(); + } catch (BitstreamException e) { + e.printStackTrace(); + } + + channels = header.mode()==Header.SINGLE_CHANNEL?1:2; + sampleRate = header.frequency(); + + buf = new SampleBuffer(sampleRate, channels); + decoder.setOutputBuffer(buf); + //* + try { + decoder.decodeFrame(header, bitstream); + } catch (DecoderException e) { + e.printStackTrace(); + } + //*/ + bufLen = buf.getBufferLength(); + bitstream.closeFrame(); + } + @Override + public int read() throws IOException { + if(atEnd()) + return -1; + while(bpos/2>=bufLen){ + try { + header = bitstream.readFrame(); + if(header == null){ + buf.clear_buffer(); + + atEnd = true; + return -1; + } + buf.clear_buffer(); + decoder.decodeFrame(header, bitstream); + bufLen = buf.getBufferLength(); + bitstream.closeFrame(); + } catch (DecoderException e) { + e.printStackTrace(); + } catch (BitstreamException e) { + e.printStackTrace(); + } + bpos=0; + } + int npos = bpos/2; + bpos++; + + if(bpos%2==0) + return (buf.getBuffer()[npos]>>8)&0xff; + else + return (buf.getBuffer()[npos])&0xff; + } + @Override + public boolean atEnd() { + return atEnd; + } + @Override + public int getChannels() { + return channels; + } + @Override + public int getRate() { + return sampleRate; + } + /** + * @see java.io.InputStream#read(byte[], int, int) + */ + public int read(byte[] b, int off, int len) throws IOException { + for (int i=0;i= 0) { + b[i] = (byte) value; + } else { + if (i == 0) { + return -1; + } else { + return i; + } + } + } catch (IOException e) { + //Log.error(e); + e.printStackTrace(); + return i; + } + } + + return len; + } + + /** + * @see java.io.InputStream#read(byte[]) + */ + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + @Override + public long skip(long length) { + //System.out.println("skip"+length); + int skiped = 0; + if(bufLen<=0){ + System.out.println("We don't know buf Length yet"); + //throw new Error("We don't know buf Length yet"); + } + while(skiped+bufLen*2=length || bufLen<=0){ + buf.clear_buffer(); + decoder.decodeFrame(header, bitstream); + bufLen = buf.getBufferLength(); + } + skiped+=bufLen*2-bpos; + + bitstream.closeFrame(); + bpos=0; + } catch (BitstreamException e) { + e.printStackTrace(); + }catch (DecoderException e) { + e.printStackTrace(); + } + } + if(bufLen*2-bpos>length-skiped){ + bpos+=length-skiped; + skiped+=length-skiped; + } + + return skiped; + + } +} diff --git a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java new file mode 100644 index 00000000..b83938a0 --- /dev/null +++ b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java @@ -0,0 +1,395 @@ +/* +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.openal; + +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import javax.sound.midi.SysexMessage; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL10; +import org.lwjgl.openal.AL11; +import org.lwjgl.openal.OpenALException; +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +/** + * A generic tool to work on a supplied stream, pulling out PCM data and buffered it to OpenAL + * as required. + * + * @author Kevin Glass + * @author Nathan Sweet + * @author Rockstar play and setPosition cleanup + */ +public class OpenALStreamPlayer { + /** The number of buffers to maintain */ + public static final int BUFFER_COUNT = 9; + /** The size of the sections to stream from the stream */ + private static final int sectionSize = 4096; + + /** The buffer read from the data stream */ + private byte[] buffer = new byte[sectionSize]; + /** Holds the OpenAL buffer names */ + private IntBuffer bufferNames; + /** The byte buffer passed to OpenAL containing the section */ + private ByteBuffer bufferData = BufferUtils.createByteBuffer(sectionSize); + /** The buffer holding the names of the OpenAL buffer thats been fully played back */ + private IntBuffer unqueued = BufferUtils.createIntBuffer(1); + /** The source we're playing back on */ + private int source; + /** The number of buffers remaining */ + private int remainingBufferCount; + /** True if we should loop the track */ + private boolean loop; + /** True if we've completed play back */ + private boolean done = true; + /** The stream we're currently reading from */ + private AudioInputStream audio; + /** The source of the data */ + private String ref; + /** The source of the data */ + private URL url; + /** The pitch of the music */ + private float pitch; + /** Position in seconds of the previously played buffers */ + //private float positionOffset; + + long streamPos = 0; + int sampleRate; + int sampleSize; + long playedPos; + + long musicLength = -1; + + long lastUpdateTime = System.currentTimeMillis(); + /** + * Create a new player to work on an audio stream + * + * @param source The source on which we'll play the audio + * @param ref A reference to the audio file to stream + */ + public OpenALStreamPlayer(int source, String ref) { + this.source = source; + this.ref = ref; + + bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT); + AL10.alGenBuffers(bufferNames); + } + + /** + * Create a new player to work on an audio stream + * + * @param source The source on which we'll play the audio + * @param url A reference to the audio file to stream + */ + public OpenALStreamPlayer(int source, URL url) { + this.source = source; + this.url = url; + + bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT); + AL10.alGenBuffers(bufferNames); + } + + /** + * Initialise our connection to the underlying resource + * + * @throws IOException Indicates a failure to open the underling resource + */ + private void initStreams() throws IOException { + if (audio != null) { + audio.close(); + } + + AudioInputStream audio; + + if (url != null) { + audio = new OggInputStream(url.openStream()); + } else { + if(ref.toLowerCase().endsWith(".mp3")) + audio = new Mp3InputStream(ResourceLoader.getResourceAsStream(ref)); + else + audio = new OggInputStream(ResourceLoader.getResourceAsStream(ref)); + } + + this.audio = audio; + sampleRate = audio.getRate(); + if (audio.getChannels() > 1) { + sampleSize = 4; // AL10.AL_FORMAT_STEREO16 + } else { + sampleSize = 2; // AL10.AL_FORMAT_MONO16 + } + //positionOffset = 0; + streamPos = 0; + + } + + /** + * Get the source of this stream + * + * @return The name of the source of string + */ + public String getSource() { + return (url == null) ? ref : url.toString(); + } + + /** + * Clean up the buffers applied to the sound source + */ + private void removeBuffers() { + IntBuffer buffer = BufferUtils.createIntBuffer(1); + //int queued = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED); + + /*while (queued > 0) + { + AL10.alSourceUnqueueBuffers(source, buffer); + queued--; + }/*/ + + while (AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED) > 0) + { + AL10.alSourceUnqueueBuffers(source, buffer); + buffer.clear(); + }//*/ + + } + + /** + * Start this stream playing + * + * @param loop True if the stream should loop + * @throws IOException Indicates a failure to read from the stream + */ + public void play(boolean loop) throws IOException { + //System.out.println("play "+loop); + this.loop = loop; + initStreams(); + + done = false; + + AL10.alSourceStop(source); + //removeBuffers(); + + startPlayback(); + //AL10.alSourcePlay(source); + } + + /** + * Setup the playback properties + * + * @param pitch The pitch to play back at + */ + public void setup(float pitch) { + this.pitch = pitch; + } + + /** + * Check if the playback is complete. Note this will never + * return true if we're looping + * + * @return True if we're looping + */ + public boolean done() { + return done; + } + + /** + * Poll the bufferNames - check if we need to fill the bufferNames with another + * section. + * + * Most of the time this should be reasonably quick + */ + public void update() { + if (done) { + return; + } + + int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED); + while (processed > 0) { + unqueued.clear(); + AL10.alSourceUnqueueBuffers(source, unqueued); + + int bufferIndex = unqueued.get(0); + + //float bufferLength = (AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE) / sampleSize) / sampleRate; + int bufferLength = AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE); + //positionOffset += bufferLength; + playedPos += bufferLength; + if(musicLength>0 && playedPos>musicLength) + playedPos -= musicLength; + + unqueued.clear(); + unqueued.put(bufferIndex); + unqueued.flip(); + if (stream(bufferIndex)) { + AL10.alSourceQueueBuffers(source, unqueued); + } else { + remainingBufferCount--; + if (remainingBufferCount == 0) { + done = true; + } + } + processed--; + } + + int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE); + + if (state != AL10.AL_PLAYING) { + AL10.alSourcePlay(source); + } + } + + /** + * Stream some data from the audio stream to the buffer indicates by the ID + * + * @param bufferId The ID of the buffer to fill + * @return True if another section was available + */ + public boolean stream(int bufferId) { + //Thread.dumpStack(); + try { + int count = audio.read(buffer); + if (count != -1) { + lastUpdateTime = System.currentTimeMillis(); + streamPos += count; + //bufferData = BufferUtils.createByteBuffer(sectionSize); + bufferData.clear(); + bufferData.put(buffer,0,count); + bufferData.flip(); + + int format = audio.getChannels() > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16; + try { + AL10.alBufferData(bufferId, format, bufferData, audio.getRate()); + } catch (OpenALException e) { + Log.error("Failed to loop buffer: "+bufferId+" "+format+" "+count+" "+audio.getRate(), e); + return false; + } + } else { + if (loop) { + musicLength = streamPos; + initStreams(); + stream(bufferId); + } else { + done = true; + return false; + } + } + + return true; + } catch (IOException e) { + e.printStackTrace(); + Log.error(e); + return false; + } + } + + /** + * Seeks to a position in the music. + * + * @param position Position in seconds. + * @return True if the setting of the position was successful + */ + public boolean setPosition(float position) { + try { + //int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE); + //AL10.alSourceStop(source); + + long samplePos = (long) (position*sampleRate)*sampleSize; + + if(streamPos > samplePos){//(getPosition() > position) { + initStreams(); + } + //if(audio instanceof Mp3InputStream){ + long skiped = audio.skip(samplePos - streamPos); + if(skiped>=0) + streamPos+=skiped; + else{ + System.out.println("Failed to skip?"); + } + //} + while(streamPos+buffer.length < samplePos){ + int count = audio.read(buffer); + if (count != -1) { + streamPos += count; + } else { + if (loop) { + initStreams(); + } else { + done = true; + } + return false; + } + } + /*while(streamPos%sampleSize!=0){ + audio.read(); + streamPos++; + }*/ + playedPos = streamPos; + + startPlayback(); + //if (state != AL10.AL_PLAYING) { + // AL10.alSourcePlay(source); + //} + + return true; + } catch (IOException e) { + Log.error(e); + return false; + } + } + + /** + * Starts the streaming. + */ + private void startPlayback() { + removeBuffers(); + AL10.alSourcei(source, AL10.AL_LOOPING, AL10.AL_FALSE); + AL10.alSourcef(source, AL10.AL_PITCH, pitch); + + remainingBufferCount = BUFFER_COUNT; + + for (int i = 0; i < BUFFER_COUNT; i++) { + stream(bufferNames.get(i)); + } + + AL10.alSourceQueueBuffers(source, bufferNames); + AL10.alSourcePlay(source); + } + + /** + * Return the current playing position in the sound + * + * @return The current position in seconds. + */ + public float getPosition() { + float time = ((float)playedPos/(float)sampleSize)/(float)sampleRate; + float timePosition = time + (System.currentTimeMillis()-lastUpdateTime)/1000f; + //System.out.println(playedPos +" "+streamPos+" "+AL10.alGetSourcef(source, AL11.AL_SAMPLE_OFFSET)+" "+System.currentTimeMillis()+" "+time+" "+sampleRate+" "+sampleSize+" "+timePosition); + return timePosition;//AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET); + } + + public void pausing() { + //System.out.println("Pasuing "); + + } + + public void resuming() { + //System.out.println("Resuming "); + lastUpdateTime = System.currentTimeMillis(); + } +} + diff --git a/src/org/newdawn/slick/openal/SoundStore.java b/src/org/newdawn/slick/openal/SoundStore.java new file mode 100644 index 00000000..678a22fe --- /dev/null +++ b/src/org/newdawn/slick/openal/SoundStore.java @@ -0,0 +1,992 @@ +/* +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.openal; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; + +import org.lwjgl.BufferUtils; +import org.lwjgl.Sys; +import org.lwjgl.openal.AL; +import org.lwjgl.openal.AL10; +import org.lwjgl.openal.OpenALException; +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +/** + * Responsible for holding and playing the sounds used in the game. + * + * @author Kevin Glass + * @author Rockstar setVolume cleanup + */ +public class SoundStore { + + /** The single instance of this class */ + private static SoundStore store = new SoundStore(); + + /** True if sound effects are turned on */ + private boolean sounds; + /** True if music is turned on */ + private boolean music; + /** True if sound initialisation succeeded */ + private boolean soundWorks; + /** The number of sound sources enabled - default 8 */ + private int sourceCount; + /** The map of references to IDs of previously loaded sounds */ + private HashMap loaded = new HashMap(); + /** The ID of the buffer containing the music currently being played */ + private int currentMusic = -1; + /** The OpenGL AL sound sources in use */ + private IntBuffer sources; + /** The next source to be used for sound effects */ + private int nextSource; + /** True if the sound system has been initialise */ + private boolean inited = false; + /** The MODSound to be updated */ + private MODSound mod; + /** The stream to be updated */ + private OpenALStreamPlayer stream; + + /** The global music volume setting */ + private float musicVolume = 1.0f; + /** The global sound fx volume setting */ + private float soundVolume = 1.0f; + /** The volume given for the last current music */ + private float lastCurrentMusicVolume = 1.0f; + + /** True if the music is paused */ + private boolean paused; + /** True if we're returning deferred versions of resources */ + private boolean deferred; + + /** The buffer used to set the velocity of a source */ + private FloatBuffer sourceVel = BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f }); + /** The buffer used to set the position of a source */ + private FloatBuffer sourcePos = BufferUtils.createFloatBuffer(3); + + /** The maximum number of sources */ + private int maxSources = 64; + + /** + * Create a new sound store + */ + private SoundStore() { + } + + /** + * Clear out the sound store contents + */ + public void clear() { + store = new SoundStore(); + } + + /** + * Disable use of the Sound Store + */ + public void disable() { + inited = true; + } + + /** + * True if we should only record the request to load in the intention + * of loading the sound later + * + * @param deferred True if the we should load a token + */ + public void setDeferredLoading(boolean deferred) { + this.deferred = deferred; + } + + /** + * Check if we're using deferred loading + * + * @return True if we're loading deferred sounds + */ + public boolean isDeferredLoading() { + return deferred; + } + + /** + * Inidicate whether music should be playing + * + * @param music True if music should be played + */ + public void setMusicOn(boolean music) { + if (soundWorks) { + this.music = music; + if (music) { + restartLoop(); + setMusicVolume(musicVolume); + } else { + pauseLoop(); + } + } + } + + /** + * Check if music should currently be playing + * + * @return True if music is currently playing + */ + public boolean isMusicOn() { + return music; + } + + /** + * Set the music volume + * + * @param volume The volume for music + */ + public void setMusicVolume(float volume) { + if (volume < 0) { + volume = 0; + } + if (volume > 1) { + volume = 1; + } + + musicVolume = volume; + if (soundWorks) { + AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume); + } + } + + /** + * Get the volume scalar of the music that is currently playing. + * + * @return The volume of the music currently playing + */ + public float getCurrentMusicVolume() { + return lastCurrentMusicVolume; + } + + /** + * Set the music volume of the current playing music. Does NOT affect the global volume + * + * @param volume The volume for the current playing music + */ + public void setCurrentMusicVolume(float volume) { + if (volume < 0) { + volume = 0; + } + if (volume > 1) { + volume = 1; + } + + if (soundWorks) { + lastCurrentMusicVolume = volume; + AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume); + } + } + + /** + * Set the sound volume + * + * @param volume The volume for sound fx + */ + public void setSoundVolume(float volume) { + if (volume < 0) { + volume = 0; + } + soundVolume = volume; + } + + /** + * Check if sound works at all + * + * @return True if sound works at all + */ + public boolean soundWorks() { + return soundWorks; + } + + /** + * Check if music is currently enabled + * + * @return True if music is currently enabled + */ + public boolean musicOn() { + return music; + } + + /** + * Get the volume for sounds + * + * @return The volume for sounds + */ + public float getSoundVolume() { + return soundVolume; + } + + /** + * Get the volume for music + * + * @return The volume for music + */ + public float getMusicVolume() { + return musicVolume; + } + + /** + * Get the ID of a given source + * + * @param index The ID of a given source + * @return The ID of the given source + */ + public int getSource(int index) { + if (!soundWorks) { + return -1; + } + if (index < 0) { + return -1; + } + return sources.get(index); + } + + /** + * Indicate whether sound effects should be played + * + * @param sounds True if sound effects should be played + */ + public void setSoundsOn(boolean sounds) { + if (soundWorks) { + this.sounds = sounds; + } + } + + /** + * Check if sound effects are currently enabled + * + * @return True if sound effects are currently enabled + */ + public boolean soundsOn() { + return sounds; + } + + /** + * Set the maximum number of concurrent sound effects that will be + * attempted + * + * @param max The maximum number of sound effects/music to mix + */ + public void setMaxSources(int max) { + this.maxSources = max; + } + + /** + * Initialise the sound effects stored. This must be called + * before anything else will work + */ + public void init() { + if (inited) { + return; + } + Log.info("Initialising sounds.."); + inited = true; + + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + AL.create(); + soundWorks = true; + sounds = true; + music = true; + Log.info("- Sound works"); + } catch (Exception e) { + Log.error("Sound initialisation failure."); + Log.error(e); + soundWorks = false; + sounds = false; + music = false; + } + + return null; + }}); + + if (soundWorks) { + sourceCount = 0; + sources = BufferUtils.createIntBuffer(maxSources); + while (AL10.alGetError() == AL10.AL_NO_ERROR) { + IntBuffer temp = BufferUtils.createIntBuffer(1); + + try { + AL10.alGenSources(temp); + + if (AL10.alGetError() == AL10.AL_NO_ERROR) { + sourceCount++; + sources.put(temp.get(0)); + if (sourceCount > maxSources-1) { + break; + } + } + } catch (OpenALException e) { + // expected at the end + break; + } + } + Log.info("- "+sourceCount+" OpenAL source available"); + + if (AL10.alGetError() != AL10.AL_NO_ERROR) { + sounds = false; + music = false; + soundWorks = false; + Log.error("- AL init failed"); + } else { + FloatBuffer listenerOri = BufferUtils.createFloatBuffer(6).put( + new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f }); + FloatBuffer listenerVel = BufferUtils.createFloatBuffer(3).put( + new float[] { 0.0f, 0.0f, 0.0f }); + FloatBuffer listenerPos = BufferUtils.createFloatBuffer(3).put( + new float[] { 0.0f, 0.0f, 0.0f }); + listenerPos.flip(); + listenerVel.flip(); + listenerOri.flip(); + AL10.alListener(AL10.AL_POSITION, listenerPos); + AL10.alListener(AL10.AL_VELOCITY, listenerVel); + AL10.alListener(AL10.AL_ORIENTATION, listenerOri); + + Log.info("- Sounds source generated"); + } + } + } + + /** + * Stop a particular sound source + * + * @param index The index of the source to stop + */ + void stopSource(int index) { + AL10.alSourceStop(sources.get(index)); + } + + /** + * Play the specified buffer as a sound effect with the specified + * pitch and gain. + * + * @param buffer The ID of the buffer to play + * @param pitch The pitch to play at + * @param gain The gain to play at + * @param loop True if the sound should loop + * @return source The source that will be used + */ + int playAsSound(int buffer,float pitch,float gain,boolean loop) { + return playAsSoundAt(buffer, pitch, gain, loop, 0, 0, 0); + } + + /** + * Play the specified buffer as a sound effect with the specified + * pitch and gain. + * + * @param buffer The ID of the buffer to play + * @param pitch The pitch to play at + * @param gain The gain to play at + * @param loop True if the sound should loop + * @param x The x position to play the sound from + * @param y The y position to play the sound from + * @param z The z position to play the sound from + * @return source The source that will be used + */ + int playAsSoundAt(int buffer,float pitch,float gain,boolean loop,float x, float y, float z) { + gain *= soundVolume; + if (gain == 0) { + gain = 0.001f; + } + if (soundWorks) { + if (sounds) { + int nextSource = findFreeSource(); + if (nextSource == -1) { + return -1; + } + + AL10.alSourceStop(sources.get(nextSource)); + + AL10.alSourcei(sources.get(nextSource), AL10.AL_BUFFER, buffer); + AL10.alSourcef(sources.get(nextSource), AL10.AL_PITCH, pitch); + AL10.alSourcef(sources.get(nextSource), AL10.AL_GAIN, gain); + AL10.alSourcei(sources.get(nextSource), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE); + + sourcePos.clear(); + sourceVel.clear(); + sourceVel.put(new float[] { 0, 0, 0 }); + sourcePos.put(new float[] { x, y, z }); + sourcePos.flip(); + sourceVel.flip(); + AL10.alSource(sources.get(nextSource), AL10.AL_POSITION, sourcePos); + AL10.alSource(sources.get(nextSource), AL10.AL_VELOCITY, sourceVel); + + AL10.alSourcePlay(sources.get(nextSource)); + + return nextSource; + } + } + + return -1; + } + /** + * Check if a particular source is playing + * + * @param index The index of the source to check + * @return True if the source is playing + */ + boolean isPlaying(int index) { + int state = AL10.alGetSourcei(sources.get(index), AL10.AL_SOURCE_STATE); + + return (state == AL10.AL_PLAYING); + } + + /** + * Find a free sound source + * + * @return The index of the free sound source + */ + private int findFreeSource() { + for (int i=1;i 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16, ogg.data, ogg.rate); + + loaded.put(ref,new Integer(buf.get(0))); + + buffer = buf.get(0); + } catch (Exception e) { + Log.error(e); + Sys.alert("Error","Failed to load: "+ref+" - "+e.getMessage()); + throw new IOException("Unable to load: "+ref); + } + } + + if (buffer == -1) { + throw new IOException("Unable to load: "+ref); + } + + return new AudioImpl(this, buffer); + } + + /** + * Set the mod thats being streamed if any + * + * @param sound The mod being streamed + */ + void setMOD(MODSound sound) { + if (!soundWorks) { + return; + } + + currentMusic = sources.get(0); + stopSource(0); + + this.mod = sound; + if (sound != null) { + this.stream = null; + } + paused = false; + } + + /** + * Set the stream being played + * + * @param stream The stream being streamed + */ + void setStream(OpenALStreamPlayer stream) { + if (!soundWorks) { + return; + } + + currentMusic = sources.get(0); + this.stream = stream; + if (stream != null) { + this.mod = null; + } + paused = false; + } + + /** + * Poll the streaming system + * + * @param delta The amount of time passed since last poll (in milliseconds) + */ + public void poll(int delta) { + if (!soundWorks) { + return; + } + if (paused) { + return; + } + + if (music) { + if (mod != null) { + try { + mod.poll(); + } catch (OpenALException e) { + Log.error("Error with OpenGL MOD Player on this this platform"); + Log.error(e); + mod = null; + } + } + if (stream != null) { + try { + stream.update(); + } catch (OpenALException e) { + Log.error("Error with OpenGL Streaming Player on this this platform"); + Log.error(e); + mod = null; + } + } + } + } + + /** + * Check if the music is currently playing + * + * @return True if the music is playing + */ + public boolean isMusicPlaying() + { + if (!soundWorks) { + return false; + } + + int state = AL10.alGetSourcei(sources.get(0), AL10.AL_SOURCE_STATE); + return ((state == AL10.AL_PLAYING) || (state == AL10.AL_PAUSED)); + } + + /** + * Get the single instance of this class + * + * @return The single instnace of this class + */ + public static SoundStore get() { + return store; + } + + /** + * Stop a playing sound identified by the ID returned from playing. This utility method + * should only be used when needing to stop sound effects that may have been played + * more than once and need to be explicitly stopped. + * + * @param id The ID of the underlying OpenAL source as returned from playAsSoundEffect + */ + public void stopSoundEffect(int id) { + AL10.alSourceStop(id); + } + + /** + * Retrieve the number of OpenAL sound sources that have been + * determined at initialisation. + * + * @return The number of sources available + */ + public int getSourceCount() { + return sourceCount; + } +} From 08406ac0386d4261e153574fa6a1d2a12852c48a Mon Sep 17 00:00:00 2001 From: fd Date: Mon, 9 Feb 2015 21:33:37 -0500 Subject: [PATCH 2/3] Changed some methods in OpenALStreamPlayer to synchornized methods. Seems to work with ogg now too. Hopefully this fixes it, but I don't actually know much about concurrency. However it stills throws "Could not clear SoundStore sources, err" and so it remains removed. --- .../opsu/audio/MusicController.java | 25 ++++++++++++------- src/org/newdawn/slick/Music.java | 4 +-- .../slick/openal/OpenALStreamPlayer.java | 11 +++++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 28f4878a..7517b1b8 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -79,15 +79,15 @@ public class MusicController { switch (OsuParser.getExtension(osu.audioFilename.getName())) { case "ogg": - //trackLoader = new Thread() { - // @Override - // public void run() { + trackLoader = new Thread() { + @Override + public void run() { //Loading ogg async seems to screw up //So does mp3, but much less loadTrack(osu.audioFilename, osu.previewTime, loop); - // } - //}; - //trackLoader.start(); + } + }; + trackLoader.start(); break; case "mp3": trackLoader = new Thread() { @@ -95,7 +95,7 @@ public class MusicController { public void run() { loadTrack(osu.audioFilename, osu.previewTime, loop); //convertMp3(osu.audioFilename); -// if (!Thread.currentThread().isInterrupted()) + //if (!Thread.currentThread().isInterrupted()) // loadTrack(wavFile, osu.previewTime, loop); } }; @@ -133,13 +133,14 @@ public class MusicController { public static void playAt(final int position, final boolean loop) { if (trackExists()) { setVolume(Options.getMusicVolume() * Options.getMasterVolume()); - player.setPosition(position / 1000f); trackEnded = false; pauseTime = 0f; if (loop) player.loop(); else player.play(); + player.setPosition(position / 1000f); + } } @@ -339,8 +340,14 @@ public class MusicController { // TODO: properly interrupt instead of using deprecated Thread.stop(); // interrupt the conversion/track loading if (isTrackLoading()) + try { + trackLoader.join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } // trackLoader.interrupt(); - trackLoader.stop(); + //trackLoader.stop(); trackLoader = null; // delete temporary WAV file diff --git a/src/org/newdawn/slick/Music.java b/src/org/newdawn/slick/Music.java index a249161c..16c61a7c 100644 --- a/src/org/newdawn/slick/Music.java +++ b/src/org/newdawn/slick/Music.java @@ -278,14 +278,14 @@ public class Music { currentMusic.fireMusicSwapped(this); } - playing = true; - currentMusic = this; if (volume < 0.0f) volume = 0.0f; if (volume > 1.0f) volume = 1.0f; sound.playAsMusic(pitch, volume, loop); + playing = true; + currentMusic = this; setVolume(volume); if (requiredPosition != -1) { setPosition(requiredPosition); diff --git a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java index b83938a0..400bbb95 100644 --- a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java +++ b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java @@ -150,7 +150,7 @@ public class OpenALStreamPlayer { /** * Clean up the buffers applied to the sound source */ - private void removeBuffers() { + private synchronized void removeBuffers() { IntBuffer buffer = BufferUtils.createIntBuffer(1); //int queued = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED); @@ -213,7 +213,7 @@ public class OpenALStreamPlayer { * * Most of the time this should be reasonably quick */ - public void update() { + public synchronized void update() { if (done) { return; } @@ -259,7 +259,7 @@ public class OpenALStreamPlayer { * @param bufferId The ID of the buffer to fill * @return True if another section was available */ - public boolean stream(int bufferId) { + public synchronized boolean stream(int bufferId) { //Thread.dumpStack(); try { int count = audio.read(buffer); @@ -303,13 +303,14 @@ public class OpenALStreamPlayer { * @param position Position in seconds. * @return True if the setting of the position was successful */ - public boolean setPosition(float position) { + public synchronized boolean setPosition(float position) { try { //int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE); //AL10.alSourceStop(source); long samplePos = (long) (position*sampleRate)*sampleSize; + //System.out.println("offset:"+samplePos%sampleSize); if(streamPos > samplePos){//(getPosition() > position) { initStreams(); } @@ -334,6 +335,8 @@ public class OpenALStreamPlayer { return false; } } + //System.out.println("offset2:"+samplePos%sampleSize); + /*while(streamPos%sampleSize!=0){ audio.read(); streamPos++; From a0bf2293cf82cdf1baa9d5a2eee10ea1bf1c5a26 Mon Sep 17 00:00:00 2001 From: fd Date: Wed, 11 Feb 2015 22:41:20 -0500 Subject: [PATCH 3/3] Clean up and it doesn't throw that slick exception anymore. --- .../opsu/audio/MusicController.java | 21 ++-- src/org/newdawn/slick/Music.java | 2 +- .../slick/openal/OpenALStreamPlayer.java | 103 +++++++----------- 3 files changed, 49 insertions(+), 77 deletions(-) diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 7517b1b8..dd6c4366 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -82,8 +82,6 @@ public class MusicController { trackLoader = new Thread() { @Override public void run() { - //Loading ogg async seems to screw up - //So does mp3, but much less loadTrack(osu.audioFilename, osu.previewTime, loop); } }; @@ -94,9 +92,6 @@ public class MusicController { @Override public void run() { loadTrack(osu.audioFilename, osu.previewTime, loop); - //convertMp3(osu.audioFilename); - //if (!Thread.currentThread().isInterrupted()) - // loadTrack(wavFile, osu.previewTime, loop); } }; trackLoader.start(); @@ -339,15 +334,18 @@ public class MusicController { // TODO: properly interrupt instead of using deprecated Thread.stop(); // interrupt the conversion/track loading - if (isTrackLoading()) + // Not sure if the interrupt does anything + // And the join kind of defeats the purpose of threading it. + // But is needed since bad things happen when OpenALStreamPlayer source is released asynchronously I think. + if (isTrackLoading()){ + //trackLoader.stop(); + trackLoader.interrupt(); try { trackLoader.join(); } catch (InterruptedException e) { - // TODO Auto-generated catch block e.printStackTrace(); } -// trackLoader.interrupt(); - //trackLoader.stop(); + } trackLoader = null; // delete temporary WAV file @@ -402,9 +400,8 @@ public class MusicController { AL10.alDeleteSources(buf); int exc = AL10.alGetError(); if (exc != AL10.AL_NO_ERROR) { - //Seems It can't delete mp3 source? - //throw new SlickException( - // "Could not clear SoundStore sources, err: " + exc); + throw new SlickException( + "Could not clear SoundStore sources, err: " + exc); } // delete any buffer data stored in memory, too... diff --git a/src/org/newdawn/slick/Music.java b/src/org/newdawn/slick/Music.java index 16c61a7c..0f99c2b8 100644 --- a/src/org/newdawn/slick/Music.java +++ b/src/org/newdawn/slick/Music.java @@ -283,9 +283,9 @@ public class Music { if (volume > 1.0f) volume = 1.0f; - sound.playAsMusic(pitch, volume, loop); playing = true; currentMusic = this; + sound.playAsMusic(pitch, volume, loop); setVolume(volume); if (requiredPosition != -1) { setPosition(requiredPosition); diff --git a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java index 400bbb95..70d69b2f 100644 --- a/src/org/newdawn/slick/openal/OpenALStreamPlayer.java +++ b/src/org/newdawn/slick/openal/OpenALStreamPlayer.java @@ -19,11 +19,8 @@ import java.net.URL; import java.nio.ByteBuffer; import java.nio.IntBuffer; -import javax.sound.midi.SysexMessage; - import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL10; -import org.lwjgl.openal.AL11; import org.lwjgl.openal.OpenALException; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; @@ -38,7 +35,7 @@ import org.newdawn.slick.util.ResourceLoader; */ public class OpenALStreamPlayer { /** The number of buffers to maintain */ - public static final int BUFFER_COUNT = 9; + public static final int BUFFER_COUNT = 20; /** The size of the sections to stream from the stream */ private static final int sectionSize = 4096; @@ -151,20 +148,15 @@ public class OpenALStreamPlayer { * Clean up the buffers applied to the sound source */ private synchronized void removeBuffers() { + AL10.alSourceStop(source); IntBuffer buffer = BufferUtils.createIntBuffer(1); - //int queued = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED); - /*while (queued > 0) - { - AL10.alSourceUnqueueBuffers(source, buffer); - queued--; - }/*/ while (AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED) > 0) { AL10.alSourceUnqueueBuffers(source, buffer); buffer.clear(); - }//*/ + } } @@ -175,17 +167,14 @@ public class OpenALStreamPlayer { * @throws IOException Indicates a failure to read from the stream */ public void play(boolean loop) throws IOException { - //System.out.println("play "+loop); this.loop = loop; initStreams(); done = false; AL10.alSourceStop(source); - //removeBuffers(); startPlayback(); - //AL10.alSourcePlay(source); } /** @@ -225,32 +214,30 @@ public class OpenALStreamPlayer { int bufferIndex = unqueued.get(0); - //float bufferLength = (AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE) / sampleSize) / sampleRate; int bufferLength = AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE); - //positionOffset += bufferLength; + playedPos += bufferLength; + lastUpdateTime = System.currentTimeMillis(); + if(musicLength>0 && playedPos>musicLength) playedPos -= musicLength; - unqueued.clear(); - unqueued.put(bufferIndex); - unqueued.flip(); - if (stream(bufferIndex)) { - AL10.alSourceQueueBuffers(source, unqueued); - } else { - remainingBufferCount--; - if (remainingBufferCount == 0) { - done = true; - } - } - processed--; + if (stream(bufferIndex)) { + AL10.alSourceQueueBuffers(source, unqueued); + } else { + remainingBufferCount--; + if (remainingBufferCount == 0) { + done = true; + } + } + processed--; } int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE); - - if (state != AL10.AL_PLAYING) { - AL10.alSourcePlay(source); - } + + if (state != AL10.AL_PLAYING) { + AL10.alSourcePlay(source); + } } /** @@ -260,13 +247,11 @@ public class OpenALStreamPlayer { * @return True if another section was available */ public synchronized boolean stream(int bufferId) { - //Thread.dumpStack(); try { int count = audio.read(buffer); if (count != -1) { - lastUpdateTime = System.currentTimeMillis(); streamPos += count; - //bufferData = BufferUtils.createByteBuffer(sectionSize); + bufferData.clear(); bufferData.put(buffer,0,count); bufferData.flip(); @@ -291,7 +276,6 @@ public class OpenALStreamPlayer { return true; } catch (IOException e) { - e.printStackTrace(); Log.error(e); return false; } @@ -305,23 +289,20 @@ public class OpenALStreamPlayer { */ public synchronized boolean setPosition(float position) { try { - //int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE); - //AL10.alSourceStop(source); long samplePos = (long) (position*sampleRate)*sampleSize; - //System.out.println("offset:"+samplePos%sampleSize); - if(streamPos > samplePos){//(getPosition() > position) { + if(streamPos > samplePos){ initStreams(); } - //if(audio instanceof Mp3InputStream){ - long skiped = audio.skip(samplePos - streamPos); - if(skiped>=0) - streamPos+=skiped; - else{ - System.out.println("Failed to skip?"); - } - //} + + long skiped = audio.skip(samplePos - streamPos); + if(skiped>=0) + streamPos+=skiped; + else{ + System.out.println("Failed to skip?"); + } + while(streamPos+buffer.length < samplePos){ int count = audio.read(buffer); if (count != -1) { @@ -335,18 +316,10 @@ public class OpenALStreamPlayer { return false; } } - //System.out.println("offset2:"+samplePos%sampleSize); - /*while(streamPos%sampleSize!=0){ - audio.read(); - streamPos++; - }*/ playedPos = streamPos; startPlayback(); - //if (state != AL10.AL_PLAYING) { - // AL10.alSourcePlay(source); - //} return true; } catch (IOException e) { @@ -371,6 +344,8 @@ public class OpenALStreamPlayer { AL10.alSourceQueueBuffers(source, bufferNames); AL10.alSourcePlay(source); + lastUpdateTime = System.currentTimeMillis(); + } /** @@ -379,20 +354,20 @@ public class OpenALStreamPlayer { * @return The current position in seconds. */ public float getPosition() { - float time = ((float)playedPos/(float)sampleSize)/(float)sampleRate; - float timePosition = time + (System.currentTimeMillis()-lastUpdateTime)/1000f; - //System.out.println(playedPos +" "+streamPos+" "+AL10.alGetSourcef(source, AL11.AL_SAMPLE_OFFSET)+" "+System.currentTimeMillis()+" "+time+" "+sampleRate+" "+sampleSize+" "+timePosition); - return timePosition;//AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET); + float playedTime = ((float)playedPos/(float)sampleSize)/(float)sampleRate; + float timePosition = playedTime + + (System.currentTimeMillis()-lastUpdateTime)/1000f; + //+ AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET); + return timePosition; } + long offsetTime = 0; public void pausing() { - //System.out.println("Pasuing "); - + offsetTime = System.currentTimeMillis()-lastUpdateTime; } public void resuming() { - //System.out.println("Resuming "); - lastUpdateTime = System.currentTimeMillis(); + lastUpdateTime = System.currentTimeMillis()-offsetTime; } }