1 /*******************************************************************************
\r
2 * Copyright 2011 See AUTHORS file.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
15 ******************************************************************************/
\r
17 package com.badlogic.gdx.backends.openal;
\r
19 import java.nio.ByteBuffer;
\r
20 import java.nio.IntBuffer;
\r
22 import org.lwjgl.BufferUtils;
\r
23 import org.lwjgl.openal.AL11;
\r
25 import com.badlogic.gdx.audio.Music;
\r
26 import com.badlogic.gdx.files.FileHandle;
\r
27 import com.badlogic.gdx.math.MathUtils;
\r
28 import com.badlogic.gdx.utils.GdxRuntimeException;
\r
30 import static org.lwjgl.openal.AL10.*;
\r
32 /** @author Nathan Sweet */
\r
33 public abstract class OpenALMusic implements Music {
\r
34 static private final int bufferSize = 4096 * 10;
\r
35 static private final int bufferCount = 3;
\r
36 static private final int bytesPerSample = 2;
\r
37 static private final byte[] tempBytes = new byte[bufferSize];
\r
38 static private final ByteBuffer tempBuffer = BufferUtils.createByteBuffer(bufferSize);
\r
40 private final OpenALAudio audio;
\r
41 private IntBuffer buffers;
\r
42 private int sourceID = -1;
\r
43 private int format, sampleRate;
\r
44 private boolean isLooping, isPlaying;
\r
45 private float volume = 1;
\r
46 private float pan = 0;
\r
47 private float renderedSeconds, secondsPerBuffer;
\r
49 protected final FileHandle file;
\r
51 private OnCompletionListener onCompletionListener;
\r
53 public OpenALMusic (OpenALAudio audio, FileHandle file) {
\r
56 if (audio != null) {
\r
57 if (!audio.noDevice) audio.music.add(this);
\r
59 this.onCompletionListener = null;
\r
62 protected void setup (int channels, int sampleRate) {
\r
63 this.format = channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
\r
64 this.sampleRate = sampleRate;
\r
65 secondsPerBuffer = (float)bufferSize / bytesPerSample / channels / sampleRate;
\r
68 public void play () {
\r
69 if (audio.noDevice) return;
\r
70 if (sourceID == -1) {
\r
71 sourceID = audio.obtainSource(true);
\r
72 if (sourceID == -1) return;
\r
73 if (buffers == null) {
\r
74 buffers = BufferUtils.createIntBuffer(bufferCount);
\r
75 alGenBuffers(buffers);
\r
76 if (alGetError() != AL_NO_ERROR) throw new GdxRuntimeException("Unabe to allocate audio buffers.");
\r
78 alSourcei(sourceID, AL_LOOPING, AL_FALSE);
\r
79 setPan(pan, volume);
\r
80 for (int i = 0; i < bufferCount; i++) {
\r
81 int bufferID = buffers.get(i);
\r
82 if (!fill(bufferID)) break;
\r
83 alSourceQueueBuffers(sourceID, bufferID);
\r
85 if (alGetError() != AL_NO_ERROR) {
\r
90 alSourcePlay(sourceID);
\r
94 public void stop () {
\r
95 if (audio.noDevice) return;
\r
96 if (sourceID == -1) return;
\r
98 audio.freeSource(sourceID);
\r
100 renderedSeconds = 0;
\r
104 public void pause () {
\r
105 if (audio.noDevice) return;
\r
106 if (sourceID != -1) alSourcePause(sourceID);
\r
110 public boolean isPlaying () {
\r
111 if (audio.noDevice) return false;
\r
112 if (sourceID == -1) return false;
\r
116 public void setLooping (boolean isLooping) {
\r
117 this.isLooping = isLooping;
\r
120 public boolean isLooping () {
\r
124 public void setVolume (float volume) {
\r
125 this.volume = volume;
\r
126 if (audio.noDevice) return;
\r
127 if (sourceID != -1) alSourcef(sourceID, AL_GAIN, volume);
\r
130 public float getVolume() {
\r
131 return this.volume;
\r
134 public void setPan (float pan, float volume) {
\r
135 this.volume = volume;
\r
137 if (audio.noDevice) return;
\r
138 if (sourceID == -1) return;
\r
139 alSource3f(sourceID, AL_POSITION, MathUtils.cos((pan - 1) * MathUtils.PI / 2), 0,
\r
140 MathUtils.sin((pan + 1) * MathUtils.PI / 2));
\r
141 alSourcef(sourceID, AL_GAIN, volume);
\r
144 public float getPosition () {
\r
145 if (audio.noDevice) return 0;
\r
146 if (sourceID == -1) return 0;
\r
147 return renderedSeconds + alGetSourcef(sourceID, AL11.AL_SEC_OFFSET);
\r
150 /** Fills as much of the buffer as possible and returns the number of bytes filled. Returns <= 0 to indicate the end of the
\r
152 abstract public int read (byte[] buffer);
\r
154 /** Resets the stream to the beginning. */
\r
155 abstract public void reset ();
\r
157 public int getChannels () {
\r
158 return format == AL_FORMAT_STEREO16 ? 2 : 1;
\r
161 public int getRate () {
\r
165 public void update () {
\r
166 if (audio.noDevice) return;
\r
167 if (sourceID == -1) return;
\r
169 boolean end = false;
\r
170 int buffers = alGetSourcei(sourceID, AL_BUFFERS_PROCESSED);
\r
171 while (buffers-- > 0) {
\r
172 int bufferID = alSourceUnqueueBuffers(sourceID);
\r
173 if (bufferID == AL_INVALID_VALUE) break;
\r
174 renderedSeconds += secondsPerBuffer;
\r
176 if (fill(bufferID))
\r
177 alSourceQueueBuffers(sourceID, bufferID);
\r
181 if (end && alGetSourcei(sourceID, AL_BUFFERS_QUEUED) == 0) {
\r
182 if (onCompletionListener != null) onCompletionListener.onCompletion(this);
\r
186 // A buffer underflow will cause the source to stop.
\r
187 if (isPlaying && alGetSourcei(sourceID, AL_SOURCE_STATE) != AL_PLAYING) alSourcePlay(sourceID);
\r
190 private boolean fill (int bufferID) {
\r
191 tempBuffer.clear();
\r
192 int length = read(tempBytes);
\r
196 renderedSeconds = 0;
\r
197 length = read(tempBytes);
\r
198 if (length <= 0) return false;
\r
202 tempBuffer.put(tempBytes, 0, length).flip();
\r
203 alBufferData(bufferID, format, tempBuffer, sampleRate);
\r
207 public void dispose () {
\r
208 if (audio.noDevice) return;
\r
209 if (buffers == null) return;
\r
210 if (sourceID != -1) {
\r
212 audio.music.removeValue(this, true);
\r
213 audio.freeSource(sourceID);
\r
216 alDeleteBuffers(buffers);
\r
218 onCompletionListener = null;
\r
221 public void setOnCompletionListener (OnCompletionListener listener) {
\r
222 onCompletionListener = listener;
\r