rev |
line source |
nuclear@0
|
1 #include <stdio.h>
|
nuclear@0
|
2 #include <stdint.h>
|
nuclear@0
|
3 #include <assert.h>
|
nuclear@0
|
4 #include "openal.h"
|
nuclear@0
|
5 #include "stream.h"
|
nuclear@0
|
6 #include "logger.h"
|
nuclear@0
|
7 #include "timer.h"
|
nuclear@0
|
8 #include "kiss_fft.h"
|
nuclear@0
|
9
|
nuclear@0
|
10 struct FFTState {
|
nuclear@0
|
11 kiss_fft_cfg kiss;
|
nuclear@0
|
12 kiss_fft_cpx *inbuf, *outbuf;
|
nuclear@0
|
13 int nsamples;
|
nuclear@0
|
14 };
|
nuclear@0
|
15
|
nuclear@0
|
16 static ALenum alformat(AudioStreamBuffer *buf)
|
nuclear@0
|
17 {
|
nuclear@0
|
18 return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
nuclear@0
|
19 }
|
nuclear@0
|
20
|
nuclear@0
|
21 AudioStream::AudioStream()
|
nuclear@0
|
22 {
|
nuclear@0
|
23 alsrc = 0;
|
nuclear@0
|
24 poll_interval = 25;
|
nuclear@0
|
25 done = true;
|
nuclear@0
|
26 loop = false;
|
nuclear@0
|
27 volume = 1.0;
|
nuclear@0
|
28
|
nuclear@0
|
29 freqhist = 0;
|
nuclear@0
|
30
|
nuclear@0
|
31 // by default disable FFT processing
|
nuclear@0
|
32 use_fft = false;
|
nuclear@0
|
33
|
nuclear@0
|
34 pthread_mutex_init(&mutex, 0);
|
nuclear@0
|
35 }
|
nuclear@0
|
36
|
nuclear@0
|
37 AudioStream::~AudioStream()
|
nuclear@0
|
38 {
|
nuclear@0
|
39 stop();
|
nuclear@0
|
40 }
|
nuclear@0
|
41
|
nuclear@0
|
42 void AudioStream::enable_fft()
|
nuclear@0
|
43 {
|
nuclear@0
|
44 use_fft = true;
|
nuclear@0
|
45 }
|
nuclear@0
|
46
|
nuclear@0
|
47 void AudioStream::disable_fft()
|
nuclear@0
|
48 {
|
nuclear@0
|
49 use_fft = false;
|
nuclear@0
|
50 }
|
nuclear@0
|
51
|
nuclear@0
|
52 bool AudioStream::is_fft_enabled() const
|
nuclear@0
|
53 {
|
nuclear@0
|
54 return use_fft;
|
nuclear@0
|
55 }
|
nuclear@0
|
56
|
nuclear@0
|
57 bool AudioStream::open(const char *fname)
|
nuclear@0
|
58 {
|
nuclear@0
|
59 return false;
|
nuclear@0
|
60 }
|
nuclear@0
|
61
|
nuclear@0
|
62 void AudioStream::close()
|
nuclear@0
|
63 {
|
nuclear@0
|
64 }
|
nuclear@0
|
65
|
nuclear@0
|
66 void AudioStream::set_volume(float vol)
|
nuclear@0
|
67 {
|
nuclear@0
|
68 volume = vol;
|
nuclear@0
|
69
|
nuclear@0
|
70 pthread_mutex_lock(&mutex);
|
nuclear@0
|
71 if(alsrc) {
|
nuclear@0
|
72 alSourcef(alsrc, AL_GAIN, vol);
|
nuclear@0
|
73 }
|
nuclear@0
|
74 pthread_mutex_unlock(&mutex);
|
nuclear@0
|
75 }
|
nuclear@0
|
76
|
nuclear@0
|
77 float AudioStream::get_volume() const
|
nuclear@0
|
78 {
|
nuclear@0
|
79 return volume;
|
nuclear@0
|
80 }
|
nuclear@0
|
81
|
nuclear@0
|
82 static void *thread_func(void *arg)
|
nuclear@0
|
83 {
|
nuclear@0
|
84 AudioStream *astr = (AudioStream*)arg;
|
nuclear@0
|
85 astr->poll_loop();
|
nuclear@0
|
86 return 0;
|
nuclear@0
|
87 }
|
nuclear@0
|
88
|
nuclear@0
|
89 void AudioStream::play(AUDIO_PLAYMODE mode)
|
nuclear@0
|
90 {
|
nuclear@0
|
91 loop = (mode == AUDIO_PLAYMODE_LOOP);
|
nuclear@0
|
92 done = false;
|
nuclear@0
|
93
|
nuclear@0
|
94 if(pthread_create(&play_thread, 0, thread_func, this) != 0) {
|
nuclear@0
|
95 error_log("failed to create music playback thread\n");
|
nuclear@0
|
96 }
|
nuclear@0
|
97 }
|
nuclear@0
|
98
|
nuclear@0
|
99 void AudioStream::stop()
|
nuclear@0
|
100 {
|
nuclear@0
|
101 pthread_mutex_lock(&mutex);
|
nuclear@0
|
102
|
nuclear@0
|
103 if(alsrc) {
|
nuclear@0
|
104 done = true;
|
nuclear@0
|
105 alSourceStop(alsrc);
|
nuclear@0
|
106 printf("waiting for the music thread to stop\n");
|
nuclear@0
|
107 pthread_mutex_unlock(&mutex);
|
nuclear@0
|
108 pthread_join(play_thread, 0);
|
nuclear@0
|
109 } else {
|
nuclear@0
|
110 pthread_mutex_unlock(&mutex);
|
nuclear@0
|
111 }
|
nuclear@0
|
112 }
|
nuclear@0
|
113
|
nuclear@0
|
114 // gets an array of buffers and returns the index of the one matching id
|
nuclear@0
|
115 static inline int find_buffer(unsigned int id, unsigned int *barr, int num)
|
nuclear@0
|
116 {
|
nuclear@0
|
117 for(int i=0; i<num; i++) {
|
nuclear@0
|
118 if(barr[i] == id) {
|
nuclear@0
|
119 return i;
|
nuclear@0
|
120 }
|
nuclear@0
|
121 }
|
nuclear@0
|
122 return -1;
|
nuclear@0
|
123 }
|
nuclear@0
|
124
|
nuclear@0
|
125
|
nuclear@0
|
126 static int queued_idx_list[AUDIO_NUM_BUFFERS];
|
nuclear@0
|
127 static int queued_idx_head = 0;
|
nuclear@0
|
128 static int queued_idx_tail = 0;
|
nuclear@0
|
129
|
nuclear@0
|
130 #define BUFQ_UNQUEUE() \
|
nuclear@0
|
131 do { \
|
nuclear@0
|
132 queued_idx_tail = (queued_idx_tail + 1) % AUDIO_NUM_BUFFERS; \
|
nuclear@0
|
133 } while(0)
|
nuclear@0
|
134
|
nuclear@0
|
135
|
nuclear@0
|
136 #define BUFQ_QUEUE(idx) \
|
nuclear@0
|
137 do { \
|
nuclear@0
|
138 queued_idx_head = (queued_idx_head + 1) % AUDIO_NUM_BUFFERS; \
|
nuclear@0
|
139 queued_idx_list[queued_idx_head] = idx; \
|
nuclear@0
|
140 } while(0)
|
nuclear@0
|
141
|
nuclear@0
|
142 // thread function
|
nuclear@0
|
143 void AudioStream::poll_loop()
|
nuclear@0
|
144 {
|
nuclear@0
|
145 long prev_msec = -1000;
|
nuclear@0
|
146 unsigned int albuf[AUDIO_NUM_BUFFERS];
|
nuclear@0
|
147 int freqbins[AUDIO_NUM_BUFFERS][AUDIO_FFT_BINS];
|
nuclear@0
|
148
|
nuclear@0
|
149 pthread_mutex_lock(&mutex);
|
nuclear@0
|
150 alGenSources(1, &alsrc);
|
nuclear@0
|
151 alSourcei(alsrc, AL_LOOPING, AL_FALSE);
|
nuclear@0
|
152 alSourcef(alsrc, AL_GAIN, volume);
|
nuclear@0
|
153 alGenBuffers(AUDIO_NUM_BUFFERS, albuf);
|
nuclear@0
|
154 AudioStreamBuffer *buf = new AudioStreamBuffer;
|
nuclear@0
|
155
|
nuclear@0
|
156 FFTState fft;
|
nuclear@0
|
157 fft.kiss = kiss_fft_alloc(AUDIO_FFT_SAMPLES, 0, 0, 0);
|
nuclear@0
|
158 assert(fft.kiss);
|
nuclear@0
|
159 fft.inbuf = new kiss_fft_cpx[AUDIO_FFT_SAMPLES];
|
nuclear@0
|
160 fft.outbuf = new kiss_fft_cpx[AUDIO_FFT_SAMPLES];
|
nuclear@0
|
161 assert(fft.inbuf && fft.outbuf);
|
nuclear@0
|
162 fft.nsamples = AUDIO_FFT_SAMPLES;
|
nuclear@0
|
163
|
nuclear@0
|
164 // zero out the inbuf array to get rid of the imaginary parts
|
nuclear@0
|
165 memset(fft.inbuf, 0, AUDIO_FFT_SAMPLES * sizeof *fft.inbuf);
|
nuclear@0
|
166
|
nuclear@0
|
167 for(int i=0; i<AUDIO_NUM_BUFFERS; i++) {
|
nuclear@0
|
168 if(more_samples(buf)) {
|
nuclear@0
|
169 int bufsz = buf->num_samples * buf->channels * 2; // 2 is for 16bit samples
|
nuclear@0
|
170 alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate);
|
nuclear@0
|
171
|
nuclear@0
|
172 if(alGetError()) {
|
nuclear@0
|
173 fprintf(stderr, "failed to load sample data into OpenAL buffer\n");
|
nuclear@0
|
174 }
|
nuclear@0
|
175
|
nuclear@0
|
176 alSourceQueueBuffers(alsrc, 1, albuf + i);
|
nuclear@0
|
177 BUFQ_QUEUE(i);
|
nuclear@0
|
178
|
nuclear@0
|
179 if(alGetError()) {
|
nuclear@0
|
180 fprintf(stderr, "failed to start streaming audio buffers\n");
|
nuclear@0
|
181 }
|
nuclear@0
|
182
|
nuclear@0
|
183 // also calculate the frequencies
|
nuclear@0
|
184 calc_freq(buf, freqbins[i], &fft);
|
nuclear@0
|
185 } else {
|
nuclear@0
|
186 break;
|
nuclear@0
|
187 }
|
nuclear@0
|
188 }
|
nuclear@0
|
189
|
nuclear@0
|
190 // start playback
|
nuclear@0
|
191 alSourcePlay(alsrc);
|
nuclear@0
|
192 while(!done) {
|
nuclear@0
|
193 // XXX this doesn't work
|
nuclear@0
|
194 /*
|
nuclear@0
|
195 // first let's figure out which buffer is currently playing
|
nuclear@0
|
196 int cur_buf;
|
nuclear@0
|
197 alGetSourcei(alsrc, AL_BUFFER, &cur_buf);
|
nuclear@0
|
198 int cur_buf_idx = find_buffer(cur_buf, albuf, AUDIO_NUM_BUFFERS);
|
nuclear@0
|
199
|
nuclear@0
|
200 // make the fft histogram pointer point to the correct frequency bin array
|
nuclear@0
|
201 freqhist = cur_buf_idx != -1 ? freqbins[cur_buf_idx] : 0;
|
nuclear@0
|
202 if(!freqhist) {
|
nuclear@0
|
203 debug_log("skata\n");
|
nuclear@0
|
204 }
|
nuclear@0
|
205 */
|
nuclear@0
|
206
|
nuclear@0
|
207 /* find out how many (if any) of the queued buffers are
|
nuclear@0
|
208 * done, and free to be reused.
|
nuclear@0
|
209 */
|
nuclear@0
|
210 int num_buf_done;
|
nuclear@0
|
211 alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done);
|
nuclear@0
|
212 for(int i=0; i<num_buf_done; i++) {
|
nuclear@0
|
213 int err;
|
nuclear@0
|
214 // unqueue a buffer...
|
nuclear@0
|
215 unsigned int buf_id;
|
nuclear@0
|
216 alSourceUnqueueBuffers(alsrc, 1, &buf_id);
|
nuclear@0
|
217
|
nuclear@0
|
218 if((err = alGetError())) {
|
nuclear@0
|
219 fprintf(stderr, "failed to unqueue used buffer (error: %x)\n", err);
|
nuclear@0
|
220 num_buf_done = i;
|
nuclear@0
|
221 break;
|
nuclear@0
|
222 }
|
nuclear@0
|
223 BUFQ_UNQUEUE();
|
nuclear@0
|
224
|
nuclear@0
|
225 // find out which one of our al buffers we just unqueued
|
nuclear@0
|
226 int bidx = find_buffer(buf_id, albuf, AUDIO_NUM_BUFFERS);
|
nuclear@0
|
227 assert(bidx != -1);
|
nuclear@0
|
228
|
nuclear@0
|
229 int looping;
|
nuclear@0
|
230
|
nuclear@0
|
231 alGetSourcei(alsrc, AL_LOOPING, &looping);
|
nuclear@0
|
232 assert(looping == AL_FALSE);
|
nuclear@0
|
233 /*if((unsigned int)cur_buf == buf_id) {
|
nuclear@0
|
234 continue;
|
nuclear@0
|
235 }*/
|
nuclear@0
|
236
|
nuclear@0
|
237 // if there are more data, fill it up and requeue it
|
nuclear@0
|
238 if(more_samples(buf)) {
|
nuclear@0
|
239 int bufsz = buf->num_samples * buf->channels * 2; // 2 is for 16bit samples
|
nuclear@0
|
240 alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate);
|
nuclear@0
|
241 if((err = alGetError())) {
|
nuclear@0
|
242 fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err);
|
nuclear@0
|
243 }
|
nuclear@0
|
244
|
nuclear@0
|
245 alSourceQueueBuffers(alsrc, 1, &buf_id);
|
nuclear@0
|
246 if(alGetError()) {
|
nuclear@0
|
247 fprintf(stderr, "failed to start streaming audio buffers\n");
|
nuclear@0
|
248 }
|
nuclear@0
|
249 BUFQ_QUEUE(bidx);
|
nuclear@0
|
250
|
nuclear@0
|
251 // also calculate the frequencies if required
|
nuclear@0
|
252 if(use_fft) {
|
nuclear@0
|
253 calc_freq(buf, freqbins[bidx], &fft);
|
nuclear@0
|
254 }
|
nuclear@0
|
255 } else {
|
nuclear@0
|
256 // no more data...
|
nuclear@0
|
257 if(loop) {
|
nuclear@0
|
258 rewind();
|
nuclear@0
|
259 } else {
|
nuclear@0
|
260 done = true;
|
nuclear@0
|
261 }
|
nuclear@0
|
262 }
|
nuclear@0
|
263 }
|
nuclear@0
|
264 if(use_fft) {
|
nuclear@0
|
265 freqhist = freqbins[queued_idx_list[queued_idx_tail]];
|
nuclear@0
|
266 }
|
nuclear@0
|
267
|
nuclear@0
|
268 if(num_buf_done) {
|
nuclear@0
|
269 // make sure playback didn't stop
|
nuclear@0
|
270 int state;
|
nuclear@0
|
271 alGetSourcei(alsrc, AL_SOURCE_STATE, &state);
|
nuclear@0
|
272 if(state != AL_PLAYING) {
|
nuclear@0
|
273 alSourcePlay(alsrc);
|
nuclear@0
|
274 }
|
nuclear@0
|
275 }
|
nuclear@0
|
276
|
nuclear@0
|
277 pthread_mutex_unlock(&mutex);
|
nuclear@0
|
278 long msec = get_time_msec();
|
nuclear@0
|
279 long dt = msec - prev_msec;
|
nuclear@0
|
280 prev_msec = msec;
|
nuclear@0
|
281
|
nuclear@0
|
282 if(dt < poll_interval - 5) {
|
nuclear@0
|
283 sleep_msec(poll_interval - dt);
|
nuclear@0
|
284 } else {
|
nuclear@0
|
285 sched_yield();
|
nuclear@0
|
286 }
|
nuclear@0
|
287 pthread_mutex_lock(&mutex);
|
nuclear@0
|
288 }
|
nuclear@0
|
289
|
nuclear@0
|
290
|
nuclear@0
|
291 // done with the data, wait for the source to stop playing before cleanup
|
nuclear@0
|
292 int state;
|
nuclear@0
|
293 while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) {
|
nuclear@0
|
294 sched_yield();
|
nuclear@0
|
295 }
|
nuclear@0
|
296
|
nuclear@0
|
297 freqhist = 0;
|
nuclear@0
|
298
|
nuclear@0
|
299 alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf);
|
nuclear@0
|
300 alDeleteSources(1, &alsrc);
|
nuclear@0
|
301 alsrc = 0;
|
nuclear@0
|
302 pthread_mutex_unlock(&mutex);
|
nuclear@0
|
303
|
nuclear@0
|
304 delete buf;
|
nuclear@0
|
305
|
nuclear@0
|
306 delete [] fft.inbuf;
|
nuclear@0
|
307 delete [] fft.outbuf;
|
nuclear@0
|
308 kiss_fft_free(fft.kiss);
|
nuclear@0
|
309 }
|
nuclear@0
|
310
|
nuclear@0
|
311 int AudioStream::freq_count(int bin) const
|
nuclear@0
|
312 {
|
nuclear@0
|
313 if(!freqhist || !use_fft || bin < 0 || bin >= AUDIO_BUFFER_SAMPLES) {
|
nuclear@0
|
314 return 0;
|
nuclear@0
|
315 }
|
nuclear@0
|
316 return freqhist[bin];
|
nuclear@0
|
317 }
|
nuclear@0
|
318
|
nuclear@0
|
319 #define NORM_FACTOR (1.0f / (float)AUDIO_FFT_SAMPLES)
|
nuclear@0
|
320 float AudioStream::freq_normalized(int bin) const
|
nuclear@0
|
321 {
|
nuclear@0
|
322 // TODO remove the fudge factor
|
nuclear@0
|
323 return freq_count(bin) * NORM_FACTOR * 0.25;
|
nuclear@0
|
324 }
|
nuclear@0
|
325
|
nuclear@0
|
326 // frequency range in hertz
|
nuclear@0
|
327 int AudioStream::freq_count(int range_start, int range_end) const
|
nuclear@0
|
328 {
|
nuclear@0
|
329 // NOTE this will probably be something like sampling freq / num-bins Hz per bin...
|
nuclear@0
|
330 return 0; // TODO
|
nuclear@0
|
331 }
|
nuclear@0
|
332
|
nuclear@0
|
333 // TODO ok this might be inefficient, copying the data around a lot, optimize later
|
nuclear@0
|
334 void AudioStream::calc_freq(AudioStreamBuffer *buf, int *bins, FFTState *fft)
|
nuclear@0
|
335 {
|
nuclear@0
|
336 kiss_fft_cpx *inptr = fft->inbuf;
|
nuclear@0
|
337 int16_t *samples = (int16_t*)buf->samples;
|
nuclear@0
|
338 for(int i=0; i<AUDIO_BUFFER_SAMPLES; i++) {
|
nuclear@0
|
339
|
nuclear@0
|
340 inptr->i = 0;
|
nuclear@0
|
341 if(i < buf->num_samples) {
|
nuclear@0
|
342 int left = samples[i * 2];
|
nuclear@0
|
343 int right = samples[i * 2 + 1];
|
nuclear@0
|
344
|
nuclear@0
|
345 (inptr++)->r = (left + right) / 2;
|
nuclear@0
|
346 } else {
|
nuclear@0
|
347 (inptr++)->r = 0;
|
nuclear@0
|
348 }
|
nuclear@0
|
349 }
|
nuclear@0
|
350
|
nuclear@0
|
351 kiss_fft(fft->kiss, fft->inbuf, fft->outbuf);
|
nuclear@0
|
352
|
nuclear@0
|
353 // then copy all the relevant data to the bins array
|
nuclear@0
|
354 int num_out_samples = AUDIO_BUFFER_SAMPLES / 2;
|
nuclear@0
|
355 int samples_per_bin = num_out_samples / AUDIO_FFT_BINS;
|
nuclear@0
|
356
|
nuclear@0
|
357 long abins[AUDIO_FFT_BINS];
|
nuclear@0
|
358
|
nuclear@0
|
359 int prev_bidx = -1;
|
nuclear@0
|
360 // ignore the DC bin (0)
|
nuclear@0
|
361 for(int i=1; i<num_out_samples; i++) {
|
nuclear@0
|
362 int bidx = i * AUDIO_FFT_BINS / num_out_samples;
|
nuclear@0
|
363 float x = fft->outbuf[i].r;
|
nuclear@0
|
364 float y = fft->outbuf[i].i;
|
nuclear@0
|
365 int val = x * x + y * y;
|
nuclear@0
|
366
|
nuclear@0
|
367 if(bidx != prev_bidx) {
|
nuclear@0
|
368 abins[bidx] = val;
|
nuclear@0
|
369 prev_bidx = bidx;
|
nuclear@0
|
370 } else {
|
nuclear@0
|
371 abins[bidx] += val;
|
nuclear@0
|
372 }
|
nuclear@0
|
373 }
|
nuclear@0
|
374
|
nuclear@0
|
375 for(int i=0; i<AUDIO_FFT_BINS; i++) {
|
nuclear@0
|
376 long res = abins[i] / (long)samples_per_bin;
|
nuclear@0
|
377 bins[i] = res;
|
nuclear@0
|
378 assert(bins[i] == res);
|
nuclear@0
|
379 }
|
nuclear@0
|
380 }
|