[added] Sound stop.
[fixed] Music stopping on buffer underflow.
[fixed] How music/sound obtains IDs so they can't stomp each other (sound just lets the stream stop in its own, music explicitly stops it).
isPrepared = false;\r
}\r
\r
+ public float getPosition () {\r
+ return player.getCurrentPosition() * 1000;\r
+ }\r
}\r
soundPool.play(soundId, volume, volume, 1, 0, 1);\r
}\r
\r
+ public void stop () {\r
+ soundPool.stop(soundId);\r
+ }\r
}\r
}\r
}\r
}\r
+\r
+ public float getPosition () {\r
+ throw new UnsupportedOperationException("Upgrade Angle to OpenAL.");\r
+ }\r
}\r
audio.enqueueSound(this, volume);\r
}\r
\r
+ public void stop () {\r
+ throw new UnsupportedOperationException("Upgrade Angle to OpenAL.");\r
+ }\r
+\r
/**\r
* @return the {@link AudioFormat} of the audio data\r
*/\r
public float[] getAudioData () {\r
return samples;\r
}\r
-\r
}\r
import com.badlogic.gdx.utils.GdxRuntimeException;\r
import com.badlogic.gdx.utils.IntArray;\r
import com.badlogic.gdx.utils.ObjectMap;\r
+import com.badlogic.gdx.utils.Pool;\r
\r
import static org.lwjgl.openal.AL10.*;\r
\r
* @author Nathan Sweet\r
*/\r
public class OpenALAudio implements Audio {\r
- private int[] streams;\r
+ private IntArray idleStreams, allStreams;\r
private ObjectMap<String, Class<? extends OpenALSound>> extensionToSoundClass = new ObjectMap();\r
private ObjectMap<String, Class<? extends OpenALMusic>> extensionToMusicClass = new ObjectMap();\r
\r
throw new GdxRuntimeException("Error initializing OpenAL.", ex);\r
}\r
\r
- IntArray streams = new IntArray(false, simultaneousStreams);\r
+ allStreams = new IntArray(false, simultaneousStreams);\r
for (int i = 0; i < simultaneousStreams; i++) {\r
int streamID = alGenSources();\r
if (alGetError() != AL_NO_ERROR) break;\r
- streams.add(streamID);\r
+ allStreams.add(streamID);\r
}\r
- this.streams = streams.items;\r
+ idleStreams = new IntArray(allStreams);\r
\r
FloatBuffer orientation = (FloatBuffer)BufferUtils.createFloatBuffer(6)\r
.put(new float[] {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}).flip();\r
}\r
}\r
\r
- int getIdleStreamID () {\r
- for (int i = 0, n = streams.length; i < n; i++) {\r
- int streamID = streams[i];\r
+ int obtainStream (boolean isMusic) {\r
+ for (int i = 0, n = idleStreams.size; i < n; i++) {\r
+ int streamID = idleStreams.get(i);\r
int state = alGetSourcei(streamID, AL_SOURCE_STATE);\r
- if (state != AL_PLAYING && state != AL_PAUSED) return streamID;\r
+ if (state != AL_PLAYING && state != AL_PAUSED) {\r
+ if (isMusic) idleStreams.removeIndex(i);\r
+ return streamID;\r
+ }\r
}\r
return -1;\r
}\r
\r
+ void freeStream (int streamID) {\r
+ idleStreams.add(streamID);\r
+ }\r
+\r
public void update () {\r
for (int i = 0; i < music.size; i++)\r
music.items[i].update();\r
}\r
\r
public void dispose () {\r
- for (int i = 0, n = streams.length; i < n; i++) {\r
- int streamID = streams[i];\r
+ for (int i = 0, n = allStreams.size; i < n; i++) {\r
+ int streamID = allStreams.get(i);\r
int state = alGetSourcei(streamID, AL_SOURCE_STATE);\r
if (state != AL_STOPPED) alSourceStop(streamID);\r
alDeleteSources(streamID);\r
import java.nio.IntBuffer;\r
\r
import org.lwjgl.BufferUtils;\r
+import org.lwjgl.openal.AL11;\r
\r
import com.badlogic.gdx.audio.Music;\r
import com.badlogic.gdx.files.FileHandle;\r
public abstract class OpenALMusic implements Music {\r
static private final int bufferSize = 4096 * 10;\r
static private final int bufferCount = 3;\r
+ static private final int bytesPerSample = 2;\r
static private final byte[] tempBytes = new byte[bufferSize];\r
static private final ByteBuffer tempBuffer = BufferUtils.createByteBuffer(bufferSize);\r
\r
private IntBuffer buffers;\r
private int streamID = -1;\r
private int format, sampleRate;\r
- private boolean isLooping;\r
+ private boolean isLooping, isPlaying;\r
private float volume = 1;\r
+ private float renderedSeconds, secondsPerBuffer;\r
\r
protected final FileHandle file;\r
\r
protected void setup (int channels, int sampleRate) {\r
this.format = channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;\r
this.sampleRate = sampleRate;\r
+ secondsPerBuffer = (float)bufferSize / bytesPerSample / channels / sampleRate;\r
}\r
\r
public void play () {\r
if (streamID == -1) {\r
- streamID = audio.getIdleStreamID();\r
+ streamID = audio.obtainStream(true);\r
if (streamID == -1) return;\r
+ alSourceStop(streamID);\r
+ alSourcei(streamID, AL_BUFFER, 0);\r
alSourcei(streamID, AL_LOOPING, AL_FALSE);\r
alSourcef(streamID, AL_GAIN, volume);\r
for (int i = 0; i < bufferCount; i++)\r
fill(buffers.get(i));\r
alSourceQueueBuffers(streamID, buffers);\r
+ if (alGetError() != AL_NO_ERROR) {\r
+ stop();\r
+ return;\r
+ }\r
}\r
alSourcePlay(streamID);\r
+ isPlaying = true;\r
}\r
\r
public void stop () {\r
reset();\r
alSourceStop(streamID);\r
alSourcei(streamID, AL_BUFFER, 0);\r
+ audio.freeStream(streamID);\r
streamID = -1;\r
+ renderedSeconds = 0;\r
+ isPlaying = false;\r
}\r
\r
public void pause () {\r
if (streamID != -1) alSourcePause(streamID);\r
+ isPlaying = false;\r
}\r
\r
public boolean isPlaying () {\r
if (streamID == -1) return false;\r
- return alGetSourcei(streamID, AL_SOURCE_STATE) == AL_PLAYING;\r
+ return isPlaying;\r
}\r
\r
public void setLooping (boolean isLooping) {\r
if (streamID != -1) alSourcef(streamID, AL_GAIN, volume);\r
}\r
\r
+ public float getPosition () {\r
+ if (streamID == -1) return 0;\r
+ return renderedSeconds + alGetSourcef(streamID, AL11.AL_SEC_OFFSET);\r
+ }\r
+\r
/**\r
* Fills as much of the buffer as possible and returns the number of bytes filled. Returns <= 0 to indicate the end of the\r
* stream.\r
\r
public void update () {\r
if (streamID == -1) return;\r
+ // A buffer underflow will cause the source to stop.\r
+ if (isPlaying && alGetSourcei(streamID, AL_SOURCE_STATE) != AL_PLAYING) alSourcePlay(streamID);\r
int buffers = alGetSourcei(streamID, AL_BUFFERS_PROCESSED);\r
while (buffers-- > 0) {\r
int bufferID = alSourceUnqueueBuffers(streamID);\r
if (bufferID == AL_INVALID_VALUE) break;\r
+ renderedSeconds += secondsPerBuffer;\r
if (!fill(bufferID)) return;\r
alSourceQueueBuffers(streamID, bufferID);\r
}\r
if (length <= 0) {\r
if (isLooping) {\r
reset();\r
+ renderedSeconds = 0;\r
length = read(tempBytes);\r
if (length <= 0) return false;\r
- } else\r
+ } else {\r
+ stop();\r
return false;\r
+ }\r
}\r
tempBuffer.put(tempBytes, 0, length).flip();\r
alBufferData(bufferID, format, tempBuffer, sampleRate);\r
audio.music.removeValue(this, true);\r
alSourceStop(streamID);\r
alSourcei(streamID, AL_BUFFER, 0);\r
+ audio.freeStream(streamID);\r
streamID = -1;\r
}\r
alDeleteBuffers(buffers);\r
import java.nio.ByteBuffer;\r
import java.nio.ByteOrder;\r
\r
+import org.lwjgl.openal.AL11;\r
+\r
import com.badlogic.gdx.audio.Sound;\r
\r
import static org.lwjgl.openal.AL10.*;\r
}\r
\r
public void play (float volume) {\r
- streamID = audio.getIdleStreamID();\r
+ streamID = audio.obtainStream(false);\r
if (streamID == -1) return;\r
alSourceStop(streamID);\r
+ alSourcei(streamID, AL_BUFFER, 0);\r
alSourcei(streamID, AL_BUFFER, bufferID);\r
alSourcei(streamID, AL_LOOPING, AL_FALSE);\r
alSourcef(streamID, AL_GAIN, volume);\r
}\r
\r
public void loop () {\r
- streamID = audio.getIdleStreamID();\r
+ streamID = audio.obtainStream(false);\r
if (streamID == -1) return;\r
alSourceStop(streamID);\r
+ alSourcei(streamID, AL_BUFFER, 0);\r
alSourcei(streamID, AL_BUFFER, bufferID);\r
alSourcei(streamID, AL_LOOPING, AL_TRUE);\r
alSourcePlay(streamID);\r
}\r
\r
public void stop () {\r
- if (streamID != -1) alSourceStop(streamID);\r
+ if (streamID == -1) return;\r
+ alSourceStop(streamID);\r
+ alSourcei(streamID, AL_BUFFER, 0);\r
+ streamID = -1;\r
}\r
\r
public void dispose () {\r
public void stop ();\r
\r
/**\r
- * @return whether this music stream is playing or not\r
+ * @return whether this music stream is playing\r
*/\r
public boolean isPlaying ();\r
\r
/**\r
- * Sets whether the music stream is looping or not. This can be called at any time, whether the stream is playing or not.\r
+ * Sets whether the music stream is looping. This can be called at any time, whether the stream is playing.\r
* \r
- * @param isLooping whether to loop the stream or not\r
+ * @param isLooping whether to loop the stream\r
*/\r
public void setLooping (boolean isLooping);\r
\r
/**\r
- * @return whether the music stream is playing or not.\r
+ * @return whether the music stream is playing.\r
*/\r
public boolean isLooping ();\r
\r
public void setVolume (float volume);\r
\r
/**\r
+ * Returns the playback position in milliseconds.\r
+ */\r
+ public float getPosition ();\r
+\r
+ /**\r
* Needs to be called when the Music is no longer needed.\r
*/\r
public void dispose ();\r
*/\r
public interface Sound extends Disposable {\r
/**\r
- * Plays the sound, you can call this repeatedly to play the same sound a couple of times with a bit of lag introduced.\r
+ * Plays the sound. If the sound is already playing, it will be played again, concurrently.\r
*/\r
public void play ();\r
\r
/**\r
- * Plays the sound, you can call this repeatedly to play the same sound a couple of times with a bit of lag introduced.\r
- * \r
+ * Plays the sound. If the sound is already playing, it will be played again, concurrently.\r
* @param volume the volume in the range [0,1]\r
*/\r
public void play (float volume);\r
\r
/**\r
+ * Stops the sound.\r
+ */\r
+ public void stop ();\r
+\r
+ /**\r
* Releases all the resources.\r
*/\r
public void dispose ();\r
\r
import com.badlogic.gdx.Files.FileType;\r
import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.Input;\r
import com.badlogic.gdx.InputProcessor;\r
import com.badlogic.gdx.audio.Music;\r
import com.badlogic.gdx.audio.Sound;\r
+import com.badlogic.gdx.graphics.GL10;\r
+import com.badlogic.gdx.graphics.g2d.BitmapFont;\r
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;\r
import com.badlogic.gdx.tests.utils.GdxTest;\r
\r
public class SoundTest extends GdxTest implements InputProcessor {\r
Music music;\r
float volume = 0.5f;\r
\r
+ BitmapFont font;\r
+ SpriteBatch batch;\r
+\r
@Override public void render () {\r
+ Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);\r
+ batch.begin();\r
+ font.draw(batch, "Position: " + music.getPosition(), 30, 146);\r
+ batch.end();\r
}\r
\r
@Override public void create () {\r
-// sound = Gdx.audio.newSound(Gdx.files.getFileHandle("data/shotgun.wav", FileType.Internal));\r
+ // sound = Gdx.audio.newSound(Gdx.files.getFileHandle("data/shotgun.wav", FileType.Internal));\r
sound = Gdx.audio.newSound(Gdx.files.getFileHandle("data/sell_buy_item.wav", FileType.Internal));\r
\r
-// music = Gdx.audio.newMusic(Gdx.files.internal("data/cloudconnected.ogg"));\r
+ // music = Gdx.audio.newMusic(Gdx.files.internal("data/cloudconnected.ogg"));\r
music = Gdx.audio.newMusic(Gdx.files.getFileHandle("data/threeofaperfectpair.mp3", FileType.Internal));\r
music.setVolume(volume);\r
music.play();\r
music.setLooping(true);\r
Gdx.input.setInputProcessor(this);\r
+\r
+ batch = new SpriteBatch();\r
+ font = new BitmapFont(Gdx.files.internal("data/verdana39.fnt"), Gdx.files.internal("data/verdana39.png"), false);\r
}\r
\r
@Override public boolean keyDown (int keycode) {\r
}\r
\r
@Override public boolean keyUp (int keycode) {\r
-\r
+ if (keycode != Input.Keys.KEYCODE_SPACE) return false;\r
+ if (music.isPlaying())\r
+ music.pause();\r
+ else\r
+ music.play();\r
return false;\r
}\r
\r
@Override public boolean touchDown (int x, int y, int pointer, int newParam) {\r
sound.play(1f);\r
+ if (music.isPlaying())\r
+ music.stop();\r
+ else\r
+ music.play();\r
return false;\r
}\r
\r