alSourceStop was commented out for some reason in AudioStream::stop,
[laserbrain_demo] / src / audio / stream.cc
1 #include <stdio.h>
2 #include <stdint.h>
3 #include <assert.h>
4 #include "stream.h"
5 #include "logger.h"
6 #include "timer.h"
7
8 #include "openal.h"
9
10 static ALenum alformat(AudioStreamBuffer *buf)
11 {
12         return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
13 }
14
15 AudioStream::AudioStream()
16 {
17         alsrc = 0;
18         poll_interval = 25;
19         done = true;
20         loop = false;
21         volume = 1.0;
22         pitch = 1.0;
23
24         pthread_mutex_init(&mutex, 0);
25 }
26
27 AudioStream::~AudioStream()
28 {
29         stop();
30 }
31
32 bool AudioStream::open(const char *fname)
33 {
34         return false;
35 }
36
37 void AudioStream::close()
38 {
39 }
40
41 void AudioStream::set_volume(float vol)
42 {
43         if(vol < 0.0) vol = 0.0;
44         if(vol > 1.0) vol = 1.0;
45
46         volume = vol;
47
48         pthread_mutex_lock(&mutex);
49         if(alsrc) {
50                 alSourcef(alsrc, AL_GAIN, vol);
51         }
52         pthread_mutex_unlock(&mutex);
53 }
54
55 float AudioStream::get_volume() const
56 {
57         return volume;
58 }
59 void AudioStream::set_pitch(float pitch)
60 {
61         if(pitch < 0.0) pitch = 0.0;
62         if(pitch > 1.0) pitch = 1.0;
63
64         this->pitch = pitch;
65
66         pthread_mutex_lock(&mutex);
67         if(alsrc) {
68                 alSourcef(alsrc, AL_PITCH, pitch);
69         }
70         pthread_mutex_unlock(&mutex);
71 }
72
73 float AudioStream::get_pitch() const
74 {
75         return pitch;
76 }
77
78
79 static void *thread_func(void *arg)
80 {
81         AudioStream *astr = (AudioStream*)arg;
82         astr->poll_loop();
83         return 0;
84 }
85
86 void AudioStream::play(AUDIO_PLAYMODE mode)
87 {
88         loop = (mode == AUDIO_PLAYMODE_LOOP);
89         done = false;
90
91         if(pthread_create(&play_thread, 0, thread_func, this) != 0) {
92                 error_log("failed to create music playback thread\n");
93         }
94 }
95
96 void AudioStream::stop()
97 {
98         pthread_mutex_lock(&mutex);
99
100         if(alsrc) {
101                 done = true;
102                 alSourceStop(alsrc);
103                 printf("waiting for the music thread to stop\n");
104                 pthread_mutex_unlock(&mutex);
105                 pthread_join(play_thread, 0);
106         } else {
107                 pthread_mutex_unlock(&mutex);
108         }
109 }
110
111 // gets an array of buffers and returns the index of the one matching id
112 static inline int find_buffer(unsigned int id, unsigned int *barr, int num)
113 {
114         for(int i=0; i<num; i++) {
115                 if(barr[i] == id) {
116                         return i;
117                 }
118         }
119         return -1;
120 }
121
122
123 static int queued_idx_list[AUDIO_NUM_BUFFERS];
124 static int queued_idx_head = 0;
125 static int queued_idx_tail = 0;
126
127 #define BUFQ_UNQUEUE() \
128         do { \
129                 queued_idx_tail = (queued_idx_tail + 1) % AUDIO_NUM_BUFFERS; \
130         } while(0)
131
132
133 #define BUFQ_QUEUE(idx) \
134         do { \
135                 queued_idx_head = (queued_idx_head + 1) % AUDIO_NUM_BUFFERS; \
136                 queued_idx_list[queued_idx_head] = idx; \
137         } while(0)
138
139 // thread function
140 void AudioStream::poll_loop()
141 {
142         long prev_msec = -1000;
143         unsigned int albuf[AUDIO_NUM_BUFFERS];
144
145         pthread_mutex_lock(&mutex);
146         alGenSources(1, &alsrc);
147         alSourcei(alsrc, AL_LOOPING, AL_FALSE);
148         alSourcef(alsrc, AL_GAIN, volume);
149         alSourcef(alsrc, AL_PITCH, pitch);
150         alGenBuffers(AUDIO_NUM_BUFFERS, albuf);
151         AudioStreamBuffer *buf = new AudioStreamBuffer;
152
153         assert(alGetError() == 0);
154         for(int i=0; i<AUDIO_NUM_BUFFERS; i++) {
155                 if(more_samples(buf)) {
156                         int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
157                         alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate);
158
159                         if(alGetError()) {
160                                 fprintf(stderr, "failed to load sample data into OpenAL buffer\n");
161                         }
162
163                         alSourceQueueBuffers(alsrc, 1, albuf + i);
164                         BUFQ_QUEUE(i);
165
166                         if(alGetError()) {
167                                 fprintf(stderr, "failed to start streaming audio buffers\n");
168                         }
169                 } else {
170                         break;
171                 }
172         }
173
174         // start playback
175         alSourcePlay(alsrc);
176         while(!done) {
177                 /* find out how many (if any) of the queued buffers are
178                 * done, and free to be reused.
179                 */
180                 int num_buf_done;
181                 alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done);
182                 for(int i=0; i<num_buf_done; i++) {
183                         int err;
184                         // unqueue a buffer...
185                         unsigned int buf_id;
186                         alSourceUnqueueBuffers(alsrc, 1, &buf_id);
187
188                         if((err = alGetError())) {
189                                 fprintf(stderr, "failed to unqueue used buffer (error: %x)\n", err);
190                                 num_buf_done = i;
191                                 break;
192                         }
193                         BUFQ_UNQUEUE();
194
195                         // find out which one of our al buffers we just unqueued
196                         int bidx = find_buffer(buf_id, albuf, AUDIO_NUM_BUFFERS);
197                         assert(bidx != -1);
198
199                         int looping;
200
201                         alGetSourcei(alsrc, AL_LOOPING, &looping);
202                         assert(looping == AL_FALSE);
203                         /*if((unsigned int)cur_buf == buf_id) {
204                                 continue;
205                         }*/
206
207                         // if there are more data, fill it up and requeue it
208                         if(more_samples(buf)) {
209                                 int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
210                                 alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate);
211                                 if((err = alGetError())) {
212                                         fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err);
213                                 }
214
215                                 alSourceQueueBuffers(alsrc, 1, &buf_id);
216                                 if(alGetError()) {
217                                         fprintf(stderr, "failed to start streaming audio buffers\n");
218                                 }
219                                 BUFQ_QUEUE(bidx);
220                         } else {
221                                 // no more data...
222                                 if(loop) {
223                                         debug_log("audio stream looping...\n");
224                                         rewind();
225                                 } else {
226                                         done = true;
227                                 }
228                         }
229                 }
230
231                 if(num_buf_done) {
232                         // make sure playback didn't stop
233                         int state;
234                         alGetSourcei(alsrc, AL_SOURCE_STATE, &state);
235                         if(state != AL_PLAYING) {
236                                 alSourcePlay(alsrc);
237                         }
238                 }
239
240                 pthread_mutex_unlock(&mutex);
241                 long msec = get_time_msec();
242                 long dt = msec - prev_msec;
243                 prev_msec = msec;
244
245                 if(dt < (long)poll_interval) {
246                         sleep_msec(poll_interval - dt);
247                 } else {
248                         sched_yield();
249                 }
250                 pthread_mutex_lock(&mutex);
251         }
252
253
254         // done with the data, wait for the source to stop playing before cleanup
255         int state;
256         while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) {
257                 sched_yield();
258         }
259
260         alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf);
261         alDeleteSources(1, &alsrc);
262         alsrc = 0;
263         pthread_mutex_unlock(&mutex);
264
265         delete buf;
266 }