Merge pull request #15 from fluddokt/omaster
MP3 Audio Streaming through modifying some of slick's openal files
This commit is contained in:
commit
800014ed4c
|
@ -91,9 +91,7 @@ public class MusicController {
|
|||
trackLoader = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
convertMp3(osu.audioFilename);
|
||||
// if (!Thread.currentThread().isInterrupted())
|
||||
loadTrack(wavFile, osu.previewTime, loop);
|
||||
loadTrack(osu.audioFilename, osu.previewTime, loop);
|
||||
}
|
||||
};
|
||||
trackLoader.start();
|
||||
|
@ -110,7 +108,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; }
|
||||
|
@ -130,13 +128,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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +252,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 +265,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.
|
||||
|
@ -324,9 +334,18 @@ public class MusicController {
|
|||
|
||||
// TODO: properly interrupt instead of using deprecated Thread.stop();
|
||||
// interrupt the conversion/track loading
|
||||
if (isTrackLoading())
|
||||
// trackLoader.interrupt();
|
||||
trackLoader.stop();
|
||||
// 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) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
trackLoader = null;
|
||||
|
||||
// delete temporary WAV file
|
||||
|
|
|
@ -286,7 +286,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
|
||||
|
@ -377,7 +377,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;
|
||||
}
|
||||
|
||||
|
@ -695,6 +695,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();
|
||||
|
||||
|
|
428
src/org/newdawn/slick/Music.java
Normal file
428
src/org/newdawn/slick/Music.java
Normal file
|
@ -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 <EFBFBD>gAS IS<EFBFBD>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 <misc@n4te.com>
|
||||
*/
|
||||
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<listeners.size();i++) {
|
||||
((MusicListener) listeners.get(i)).musicEnded(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire notifications that this music was swapped out
|
||||
*
|
||||
* @param newMusic The new music that will be played
|
||||
*/
|
||||
private void fireMusicSwapped(Music newMusic) {
|
||||
playing = false;
|
||||
for (int i=0;i<listeners.size();i++) {
|
||||
((MusicListener) listeners.get(i)).musicSwapped(this, newMusic);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Loop the music
|
||||
*/
|
||||
public void loop() {
|
||||
loop(1.0f, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the music
|
||||
*/
|
||||
public void play() {
|
||||
play(1.0f, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the music at a given pitch and volume
|
||||
*
|
||||
* @param pitch The pitch to play the music at (1.0 = default)
|
||||
* @param volume The volume to play the music at (1.0 = default)
|
||||
*/
|
||||
public void play(float pitch, float volume) {
|
||||
startMusic(pitch, volume, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the music at a given pitch and volume
|
||||
*
|
||||
* @param pitch The pitch to play the music at (1.0 = default)
|
||||
* @param volume The volume to play the music at (1.0 = default)
|
||||
*/
|
||||
public void loop(float pitch, float volume) {
|
||||
startMusic(pitch, volume, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* play or loop the music at a given pitch and volume
|
||||
* @param pitch The pitch to play the music at (1.0 = default)
|
||||
* @param volume The volume to play the music at (1.0 = default)
|
||||
* @param loop if false the music is played once, the music is looped otherwise
|
||||
*/
|
||||
private void startMusic(float pitch, float volume, boolean loop) {
|
||||
if (currentMusic != null) {
|
||||
currentMusic.stop();
|
||||
currentMusic.fireMusicSwapped(this);
|
||||
}
|
||||
|
||||
if (volume < 0.0f)
|
||||
volume = 0.0f;
|
||||
if (volume > 1.0f)
|
||||
volume = 1.0f;
|
||||
|
||||
playing = true;
|
||||
currentMusic = this;
|
||||
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();
|
||||
}
|
||||
}
|
87
src/org/newdawn/slick/openal/AudioInputStream.java
Normal file
87
src/org/newdawn/slick/openal/AudioInputStream.java
Normal file
|
@ -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 <EFBFBD>gAS IS<EFBFBD>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;
|
||||
|
||||
}
|
176
src/org/newdawn/slick/openal/Mp3InputStream.java
Normal file
176
src/org/newdawn/slick/openal/Mp3InputStream.java
Normal file
|
@ -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 <EFBFBD>gAS IS<EFBFBD>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<len;i++) {
|
||||
try {
|
||||
int value = read();
|
||||
if (value >= 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){
|
||||
try {
|
||||
header = bitstream.readFrame();
|
||||
if(header == null){
|
||||
//System.out.println("Header is null");
|
||||
atEnd = true;
|
||||
return -1;
|
||||
}
|
||||
//last frame that won't be skiped so better read it
|
||||
if(skiped+bufLen*2*4 >=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;
|
||||
|
||||
}
|
||||
}
|
373
src/org/newdawn/slick/openal/OpenALStreamPlayer.java
Normal file
373
src/org/newdawn/slick/openal/OpenALStreamPlayer.java
Normal file
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
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 <EFBFBD>gAS IS<EFBFBD>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 org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
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 <misc@n4te.com>
|
||||
* @author Rockstar play and setPosition cleanup
|
||||
*/
|
||||
public class OpenALStreamPlayer {
|
||||
/** The number of buffers to maintain */
|
||||
public static final int BUFFER_COUNT = 20;
|
||||
/** 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 synchronized void removeBuffers() {
|
||||
AL10.alSourceStop(source);
|
||||
IntBuffer buffer = BufferUtils.createIntBuffer(1);
|
||||
|
||||
|
||||
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 {
|
||||
this.loop = loop;
|
||||
initStreams();
|
||||
|
||||
done = false;
|
||||
|
||||
AL10.alSourceStop(source);
|
||||
|
||||
startPlayback();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 synchronized 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);
|
||||
|
||||
int bufferLength = AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE);
|
||||
|
||||
playedPos += bufferLength;
|
||||
lastUpdateTime = System.currentTimeMillis();
|
||||
|
||||
if(musicLength>0 && playedPos>musicLength)
|
||||
playedPos -= musicLength;
|
||||
|
||||
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 synchronized boolean stream(int bufferId) {
|
||||
try {
|
||||
int count = audio.read(buffer);
|
||||
if (count != -1) {
|
||||
streamPos += count;
|
||||
|
||||
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) {
|
||||
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 synchronized boolean setPosition(float position) {
|
||||
try {
|
||||
|
||||
long samplePos = (long) (position*sampleRate)*sampleSize;
|
||||
|
||||
if(streamPos > samplePos){
|
||||
initStreams();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
playedPos = streamPos;
|
||||
|
||||
startPlayback();
|
||||
|
||||
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);
|
||||
lastUpdateTime = System.currentTimeMillis();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current playing position in the sound
|
||||
*
|
||||
* @return The current position in seconds.
|
||||
*/
|
||||
public float getPosition() {
|
||||
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() {
|
||||
offsetTime = System.currentTimeMillis()-lastUpdateTime;
|
||||
}
|
||||
|
||||
public void resuming() {
|
||||
lastUpdateTime = System.currentTimeMillis()-offsetTime;
|
||||
}
|
||||
}
|
||||
|
992
src/org/newdawn/slick/openal/SoundStore.java
Normal file
992
src/org/newdawn/slick/openal/SoundStore.java
Normal file
|
@ -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 <EFBFBD>gAS IS<EFBFBD>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<sourceCount-1;i++) {
|
||||
int state = AL10.alGetSourcei(sources.get(i), AL10.AL_SOURCE_STATE);
|
||||
|
||||
if ((state != AL10.AL_PLAYING) && (state != AL10.AL_PAUSED)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified buffer as music (i.e. use the music channel)
|
||||
*
|
||||
* @param buffer The buffer to be played
|
||||
* @param pitch The pitch to play the music at
|
||||
* @param gain The gaing to play the music at
|
||||
* @param loop True if we should loop the music
|
||||
*/
|
||||
void playAsMusic(int buffer,float pitch,float gain, boolean loop) {
|
||||
paused = false;
|
||||
|
||||
setMOD(null);
|
||||
|
||||
if (soundWorks) {
|
||||
if (currentMusic != -1) {
|
||||
AL10.alSourceStop(sources.get(0));
|
||||
}
|
||||
|
||||
getMusicSource();
|
||||
|
||||
AL10.alSourcei(sources.get(0), AL10.AL_BUFFER, buffer);
|
||||
AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
|
||||
AL10.alSourcei(sources.get(0), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE);
|
||||
|
||||
currentMusic = sources.get(0);
|
||||
|
||||
if (!music) {
|
||||
pauseLoop();
|
||||
} else {
|
||||
AL10.alSourcePlay(sources.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OpenAL source used for music
|
||||
*
|
||||
* @return The open al source used for music
|
||||
*/
|
||||
private int getMusicSource() {
|
||||
return sources.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pitch at which the current music is being played
|
||||
*
|
||||
* @param pitch The pitch at which the current music is being played
|
||||
*/
|
||||
public void setMusicPitch(float pitch) {
|
||||
if (soundWorks) {
|
||||
AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the music loop that is currently playing
|
||||
*/
|
||||
public void pauseLoop() {
|
||||
if ((soundWorks) && (currentMusic != -1)){
|
||||
paused = true;
|
||||
if(stream!=null)
|
||||
stream.pausing();
|
||||
AL10.alSourcePause(currentMusic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the music loop that is currently paused
|
||||
*/
|
||||
public void restartLoop() {
|
||||
if ((music) && (soundWorks) && (currentMusic != -1)){
|
||||
paused = false;
|
||||
AL10.alSourcePlay(currentMusic);
|
||||
if(stream!=null)
|
||||
stream.resuming();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the supplied player is currently being polled by this
|
||||
* sound store.
|
||||
*
|
||||
* @param player The player to check
|
||||
* @return True if this player is currently in use by this sound store
|
||||
*/
|
||||
boolean isPlaying(OpenALStreamPlayer player) {
|
||||
return stream == player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a MOD sound (mod/xm etc)
|
||||
*
|
||||
* @param ref The refernece to the mod to load
|
||||
* @return The sound for play back
|
||||
* @throws IOException Indicates a failure to read the data
|
||||
*/
|
||||
public Audio getMOD(String ref) throws IOException {
|
||||
return getMOD(ref, ResourceLoader.getResourceAsStream(ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a MOD sound (mod/xm etc)
|
||||
*
|
||||
* @param in The stream to the MOD to load
|
||||
* @return The sound for play back
|
||||
* @throws IOException Indicates a failure to read the data
|
||||
*/
|
||||
public Audio getMOD(InputStream in) throws IOException {
|
||||
return getMOD(in.toString(), in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a MOD sound (mod/xm etc)
|
||||
*
|
||||
* @param ref The stream to the MOD to load
|
||||
* @param in The stream to the MOD to load
|
||||
* @return The sound for play back
|
||||
* @throws IOException Indicates a failure to read the data
|
||||
*/
|
||||
public Audio getMOD(String ref, InputStream in) throws IOException {
|
||||
if (!soundWorks) {
|
||||
return new NullAudio();
|
||||
}
|
||||
if (!inited) {
|
||||
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
|
||||
}
|
||||
if (deferred) {
|
||||
return new DeferredSound(ref, in, DeferredSound.MOD);
|
||||
}
|
||||
|
||||
return new MODSound(this, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified AIF file
|
||||
*
|
||||
* @param ref The reference to the AIF file in the classpath
|
||||
* @return The Sound read from the AIF file
|
||||
* @throws IOException Indicates a failure to load the AIF
|
||||
*/
|
||||
public Audio getAIF(String ref) throws IOException {
|
||||
return getAIF(ref, ResourceLoader.getResourceAsStream(ref));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified AIF file
|
||||
*
|
||||
* @param in The stream to the MOD to load
|
||||
* @return The Sound read from the AIF file
|
||||
* @throws IOException Indicates a failure to load the AIF
|
||||
*/
|
||||
public Audio getAIF(InputStream in) throws IOException {
|
||||
return getAIF(in.toString(), in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified AIF file
|
||||
*
|
||||
* @param ref The reference to the AIF file in the classpath
|
||||
* @param in The stream to the AIF to load
|
||||
* @return The Sound read from the AIF file
|
||||
* @throws IOException Indicates a failure to load the AIF
|
||||
*/
|
||||
public Audio getAIF(String ref, InputStream in) throws IOException {
|
||||
in = new BufferedInputStream(in);
|
||||
|
||||
if (!soundWorks) {
|
||||
return new NullAudio();
|
||||
}
|
||||
if (!inited) {
|
||||
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
|
||||
}
|
||||
if (deferred) {
|
||||
return new DeferredSound(ref, in, DeferredSound.AIF);
|
||||
}
|
||||
|
||||
int buffer = -1;
|
||||
|
||||
if (loaded.get(ref) != null) {
|
||||
buffer = ((Integer) loaded.get(ref)).intValue();
|
||||
} else {
|
||||
try {
|
||||
IntBuffer buf = BufferUtils.createIntBuffer(1);
|
||||
|
||||
AiffData data = AiffData.create(in);
|
||||
AL10.alGenBuffers(buf);
|
||||
AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);
|
||||
|
||||
loaded.put(ref,new Integer(buf.get(0)));
|
||||
buffer = buf.get(0);
|
||||
} catch (Exception e) {
|
||||
Log.error(e);
|
||||
IOException x = new IOException("Failed to load: "+ref);
|
||||
x.initCause(e);
|
||||
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer == -1) {
|
||||
throw new IOException("Unable to load: "+ref);
|
||||
}
|
||||
|
||||
return new AudioImpl(this, buffer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified WAV file
|
||||
*
|
||||
* @param ref The reference to the WAV file in the classpath
|
||||
* @return The Sound read from the WAV file
|
||||
* @throws IOException Indicates a failure to load the WAV
|
||||
*/
|
||||
public Audio getWAV(String ref) throws IOException {
|
||||
return getWAV(ref, ResourceLoader.getResourceAsStream(ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified WAV file
|
||||
*
|
||||
* @param in The stream to the WAV to load
|
||||
* @return The Sound read from the WAV file
|
||||
* @throws IOException Indicates a failure to load the WAV
|
||||
*/
|
||||
public Audio getWAV(InputStream in) throws IOException {
|
||||
return getWAV(in.toString(), in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified WAV file
|
||||
*
|
||||
* @param ref The reference to the WAV file in the classpath
|
||||
* @param in The stream to the WAV to load
|
||||
* @return The Sound read from the WAV file
|
||||
* @throws IOException Indicates a failure to load the WAV
|
||||
*/
|
||||
public Audio getWAV(String ref, InputStream in) throws IOException {
|
||||
if (!soundWorks) {
|
||||
return new NullAudio();
|
||||
}
|
||||
if (!inited) {
|
||||
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
|
||||
}
|
||||
if (deferred) {
|
||||
return new DeferredSound(ref, in, DeferredSound.WAV);
|
||||
}
|
||||
|
||||
int buffer = -1;
|
||||
|
||||
if (loaded.get(ref) != null) {
|
||||
buffer = ((Integer) loaded.get(ref)).intValue();
|
||||
} else {
|
||||
try {
|
||||
IntBuffer buf = BufferUtils.createIntBuffer(1);
|
||||
|
||||
WaveData data = WaveData.create(in);
|
||||
AL10.alGenBuffers(buf);
|
||||
AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);
|
||||
|
||||
loaded.put(ref,new Integer(buf.get(0)));
|
||||
buffer = buf.get(0);
|
||||
} catch (Exception e) {
|
||||
Log.error(e);
|
||||
IOException x = new IOException("Failed to load: "+ref);
|
||||
x.initCause(e);
|
||||
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer == -1) {
|
||||
throw new IOException("Unable to load: "+ref);
|
||||
}
|
||||
|
||||
return new AudioImpl(this, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified OGG file
|
||||
*
|
||||
* @param ref The reference to the OGG file in the classpath
|
||||
* @return The Sound read from the OGG file
|
||||
* @throws IOException Indicates a failure to load the OGG
|
||||
*/
|
||||
public Audio getOggStream(String ref) throws IOException {
|
||||
if (!soundWorks) {
|
||||
return new NullAudio();
|
||||
}
|
||||
|
||||
setMOD(null);
|
||||
setStream(null);
|
||||
|
||||
if (currentMusic != -1) {
|
||||
AL10.alSourceStop(sources.get(0));
|
||||
}
|
||||
|
||||
getMusicSource();
|
||||
currentMusic = sources.get(0);
|
||||
|
||||
return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified OGG file
|
||||
*
|
||||
* @param ref The reference to the OGG file in the classpath
|
||||
* @return The Sound read from the OGG file
|
||||
* @throws IOException Indicates a failure to load the OGG
|
||||
*/
|
||||
public Audio getOggStream(URL ref) throws IOException {
|
||||
if (!soundWorks) {
|
||||
return new NullAudio();
|
||||
}
|
||||
|
||||
setMOD(null);
|
||||
setStream(null);
|
||||
|
||||
if (currentMusic != -1) {
|
||||
AL10.alSourceStop(sources.get(0));
|
||||
}
|
||||
|
||||
getMusicSource();
|
||||
currentMusic = sources.get(0);
|
||||
|
||||
return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified OGG file
|
||||
*
|
||||
* @param ref The reference to the OGG file in the classpath
|
||||
* @return The Sound read from the OGG file
|
||||
* @throws IOException Indicates a failure to load the OGG
|
||||
*/
|
||||
public Audio getOgg(String ref) throws IOException {
|
||||
return getOgg(ref, ResourceLoader.getResourceAsStream(ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified OGG file
|
||||
*
|
||||
* @param in The stream to the OGG to load
|
||||
* @return The Sound read from the OGG file
|
||||
* @throws IOException Indicates a failure to load the OGG
|
||||
*/
|
||||
public Audio getOgg(InputStream in) throws IOException {
|
||||
return getOgg(in.toString(), in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sound based on a specified OGG file
|
||||
*
|
||||
* @param ref The reference to the OGG file in the classpath
|
||||
* @param in The stream to the OGG to load
|
||||
* @return The Sound read from the OGG file
|
||||
* @throws IOException Indicates a failure to load the OGG
|
||||
*/
|
||||
public Audio getOgg(String ref, InputStream in) throws IOException {
|
||||
if (!soundWorks) {
|
||||
return new NullAudio();
|
||||
}
|
||||
if (!inited) {
|
||||
throw new RuntimeException("Can't load sounds until SoundStore is init(). Use the container init() method.");
|
||||
}
|
||||
if (deferred) {
|
||||
return new DeferredSound(ref, in, DeferredSound.OGG);
|
||||
}
|
||||
|
||||
int buffer = -1;
|
||||
|
||||
if (loaded.get(ref) != null) {
|
||||
buffer = ((Integer) loaded.get(ref)).intValue();
|
||||
} else {
|
||||
try {
|
||||
IntBuffer buf = BufferUtils.createIntBuffer(1);
|
||||
|
||||
OggDecoder decoder = new OggDecoder();
|
||||
OggData ogg = decoder.getData(in);
|
||||
|
||||
AL10.alGenBuffers(buf);
|
||||
AL10.alBufferData(buf.get(0), ogg.channels > 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user