OSDN Git Service

Merge branch 'origin/master'
[mikumikustudio/libgdx-mikumikustudio.git] / backends / gdx-openal / src / com / badlogic / gdx / backends / openal / OpenALMusic.java
1 /*******************************************************************************\r
2  * Copyright 2011 See AUTHORS file.\r
3  * \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
7  * \r
8  *   http://www.apache.org/licenses/LICENSE-2.0\r
9  * \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
16 \r
17 package com.badlogic.gdx.backends.openal;\r
18 \r
19 import java.nio.ByteBuffer;\r
20 import java.nio.IntBuffer;\r
21 \r
22 import org.lwjgl.BufferUtils;\r
23 import org.lwjgl.openal.AL11;\r
24 \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
29 \r
30 import static org.lwjgl.openal.AL10.*;\r
31 \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
39 \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
48 \r
49         protected final FileHandle file;\r
50         \r
51         private OnCompletionListener onCompletionListener;\r
52 \r
53         public OpenALMusic (OpenALAudio audio, FileHandle file) {\r
54                 this.audio = audio;\r
55                 this.file = file;\r
56                 if (audio != null) {\r
57                         if (!audio.noDevice) audio.music.add(this);\r
58                 }\r
59                 this.onCompletionListener = null;\r
60         }\r
61 \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
66         }\r
67 \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
77                         }\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
84                         }\r
85                         if (alGetError() != AL_NO_ERROR) {\r
86                                 stop();\r
87                                 return;\r
88                         }\r
89                 }\r
90                 alSourcePlay(sourceID);\r
91                 isPlaying = true;\r
92         }\r
93 \r
94         public void stop () {\r
95                 if (audio.noDevice) return;\r
96                 if (sourceID == -1) return;\r
97                 reset();\r
98                 audio.freeSource(sourceID);\r
99                 sourceID = -1;\r
100                 renderedSeconds = 0;\r
101                 isPlaying = false;\r
102         }\r
103 \r
104         public void pause () {\r
105                 if (audio.noDevice) return;\r
106                 if (sourceID != -1) alSourcePause(sourceID);\r
107                 isPlaying = false;\r
108         }\r
109 \r
110         public boolean isPlaying () {\r
111                 if (audio.noDevice) return false;\r
112                 if (sourceID == -1) return false;\r
113                 return isPlaying;\r
114         }\r
115 \r
116         public void setLooping (boolean isLooping) {\r
117                 this.isLooping = isLooping;\r
118         }\r
119 \r
120         public boolean isLooping () {\r
121                 return isLooping;\r
122         }\r
123 \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
128         }\r
129         \r
130         public float getVolume() {\r
131                 return this.volume;\r
132         }\r
133         \r
134         public void setPan (float pan, float volume) {\r
135                 this.volume = volume;\r
136                 this.pan = pan;\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
142         }\r
143 \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
148         }\r
149 \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
151          * stream. */\r
152         abstract public int read (byte[] buffer);\r
153 \r
154         /** Resets the stream to the beginning. */\r
155         abstract public void reset ();\r
156 \r
157         public int getChannels () {\r
158                 return format == AL_FORMAT_STEREO16 ? 2 : 1;\r
159         }\r
160 \r
161         public int getRate () {\r
162                 return sampleRate;\r
163         }\r
164 \r
165         public void update () {\r
166                 if (audio.noDevice) return;\r
167                 if (sourceID == -1) return;\r
168 \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
175                         if (end) continue;\r
176                         if (fill(bufferID))\r
177                                 alSourceQueueBuffers(sourceID, bufferID);\r
178                         else\r
179                                 end = true;\r
180                 }\r
181                 if (end && alGetSourcei(sourceID, AL_BUFFERS_QUEUED) == 0) {\r
182                         if (onCompletionListener != null) onCompletionListener.onCompletion(this);\r
183                         stop();\r
184                 }\r
185 \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
188         }\r
189 \r
190         private boolean fill (int bufferID) {\r
191                 tempBuffer.clear();\r
192                 int length = read(tempBytes);\r
193                 if (length <= 0) {\r
194                         if (isLooping) {\r
195                                 reset();\r
196                                 renderedSeconds = 0;\r
197                                 length = read(tempBytes);\r
198                                 if (length <= 0) return false;\r
199                         } else\r
200                                 return false;\r
201                 }\r
202                 tempBuffer.put(tempBytes, 0, length).flip();\r
203                 alBufferData(bufferID, format, tempBuffer, sampleRate);\r
204                 return true;\r
205         }\r
206 \r
207         public void dispose () {\r
208                 if (audio.noDevice) return;\r
209                 if (buffers == null) return;\r
210                 if (sourceID != -1) {\r
211                         reset();\r
212                         audio.music.removeValue(this, true);\r
213                         audio.freeSource(sourceID);\r
214                         sourceID = -1;\r
215                 }\r
216                 alDeleteBuffers(buffers);\r
217                 buffers = null;\r
218                 onCompletionListener = null;\r
219         }\r
220         \r
221         public void setOnCompletionListener (OnCompletionListener listener) {\r
222                 onCompletionListener = listener;\r
223         }\r
224 }