/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.backends.openal; import java.nio.ByteBuffer; import java.nio.IntBuffer; import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL11; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.GdxRuntimeException; import static org.lwjgl.openal.AL10.*; /** @author Nathan Sweet */ public abstract class OpenALMusic implements Music { static private final int bufferSize = 4096 * 10; static private final int bufferCount = 3; static private final int bytesPerSample = 2; static private final byte[] tempBytes = new byte[bufferSize]; static private final ByteBuffer tempBuffer = BufferUtils.createByteBuffer(bufferSize); private final OpenALAudio audio; private IntBuffer buffers; private int sourceID = -1; private int format, sampleRate; private boolean isLooping, isPlaying; private float volume = 1; private float renderedSeconds, secondsPerBuffer; protected final FileHandle file; public OpenALMusic(OpenALAudio audio, FileHandle file) { this.audio = audio; this.file = file; if (audio != null) { if (!audio.noDevice) audio.music.add(this); } } protected void setup(int channels, int sampleRate) { this.format = channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; this.sampleRate = sampleRate; secondsPerBuffer = (float) bufferSize / bytesPerSample / channels / sampleRate; } public void play() { if (audio.noDevice) return; if (sourceID == -1) { sourceID = audio.obtainSource(true); if (sourceID == -1) return; if (buffers == null) { buffers = BufferUtils.createIntBuffer(bufferCount); alGenBuffers(buffers); if (alGetError() != AL_NO_ERROR) throw new GdxRuntimeException("Unabe to allocate audio buffers."); } alSourcei(sourceID, AL_LOOPING, AL_FALSE); alSourcef(sourceID, AL_GAIN, volume); for (int i = 0; i < bufferCount; i++) { int bufferID = buffers.get(i); if (!fill(bufferID)) break; alSourceQueueBuffers(sourceID, bufferID); } if (alGetError() != AL_NO_ERROR) { stop(); return; } } alSourcePlay(sourceID); isPlaying = true; } public void stop() { if (audio.noDevice) return; if (sourceID == -1) return; reset(); audio.freeSource(sourceID); sourceID = -1; renderedSeconds = 0; isPlaying = false; } public void pause() { if (audio.noDevice) return; if (sourceID != -1) alSourcePause(sourceID); isPlaying = false; } public boolean isPlaying() { if (audio.noDevice) return false; if (sourceID == -1) return false; return isPlaying; } public void setLooping(boolean isLooping) { this.isLooping = isLooping; } public boolean isLooping() { return isLooping; } public void setVolume(float volume) { this.volume = volume; if (audio.noDevice) return; if (sourceID != -1) alSourcef(sourceID, AL_GAIN, volume); } public float getPosition() { if (audio.noDevice) return 0; if (sourceID == -1) return 0; return renderedSeconds + alGetSourcef(sourceID, AL11.AL_SEC_OFFSET); } /** * Fills as much of the buffer as possible and returns the number of bytes filled. Returns <= 0 to indicate the end * of the stream. */ abstract public int read(byte[] buffer); /** Resets the stream to the beginning. */ abstract public void reset(); public int getChannels() { return format == AL_FORMAT_STEREO16 ? 2 : 1; } public int getRate() { return sampleRate; } public void update() { if (audio.noDevice) return; if (sourceID == -1) return; boolean end = false; int buffers = alGetSourcei(sourceID, AL_BUFFERS_PROCESSED); while (buffers-- > 0) { int bufferID = alSourceUnqueueBuffers(sourceID); if (bufferID == AL_INVALID_VALUE) break; renderedSeconds += secondsPerBuffer; if (end) continue; if (fill(bufferID)) alSourceQueueBuffers(sourceID, bufferID); else end = true; } if (end && alGetSourcei(sourceID, AL_BUFFERS_QUEUED) == 0) stop(); // A buffer underflow will cause the source to stop. if (isPlaying && alGetSourcei(sourceID, AL_SOURCE_STATE) != AL_PLAYING) alSourcePlay(sourceID); } private boolean fill(int bufferID) { tempBuffer.clear(); int length = read(tempBytes); if (length <= 0) { if (isLooping) { reset(); renderedSeconds = 0; length = read(tempBytes); if (length <= 0) return false; } else return false; } tempBuffer.put(tempBytes, 0, length).flip(); alBufferData(bufferID, format, tempBuffer, sampleRate); return true; } public void dispose() { if (audio.noDevice) return; if (buffers == null) return; if (sourceID != -1) { reset(); audio.music.removeValue(this, true); audio.freeSource(sourceID); sourceID = -1; } alDeleteBuffers(buffers); buffers = null; } }