added audio
[laserbrain_demo] / src / audio / stream.cc
diff --git a/src/audio/stream.cc b/src/audio/stream.cc
new file mode 100644 (file)
index 0000000..39a147c
--- /dev/null
@@ -0,0 +1,266 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <assert.h>
+#include "stream.h"
+#include "logger.h"
+#include "timer.h"
+
+#include "openal.h"
+
+static ALenum alformat(AudioStreamBuffer *buf)
+{
+       return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
+}
+
+AudioStream::AudioStream()
+{
+       alsrc = 0;
+       poll_interval = 25;
+       done = true;
+       loop = false;
+       volume = 1.0;
+       pitch = 1.0;
+
+       pthread_mutex_init(&mutex, 0);
+}
+
+AudioStream::~AudioStream()
+{
+       stop();
+}
+
+bool AudioStream::open(const char *fname)
+{
+       return false;
+}
+
+void AudioStream::close()
+{
+}
+
+void AudioStream::set_volume(float vol)
+{
+       if(vol < 0.0) vol = 0.0;
+       if(vol > 1.0) vol = 1.0;
+
+       volume = vol;
+
+       pthread_mutex_lock(&mutex);
+       if(alsrc) {
+               alSourcef(alsrc, AL_GAIN, vol);
+       }
+       pthread_mutex_unlock(&mutex);
+}
+
+float AudioStream::get_volume() const
+{
+       return volume;
+}
+void AudioStream::set_pitch(float pitch)
+{
+       if(pitch < 0.0) pitch = 0.0;
+       if(pitch > 1.0) pitch = 1.0;
+
+       this->pitch = pitch;
+
+       pthread_mutex_lock(&mutex);
+       if(alsrc) {
+               alSourcef(alsrc, AL_PITCH, pitch);
+       }
+       pthread_mutex_unlock(&mutex);
+}
+
+float AudioStream::get_pitch() const
+{
+       return pitch;
+}
+
+
+static void *thread_func(void *arg)
+{
+       AudioStream *astr = (AudioStream*)arg;
+       astr->poll_loop();
+       return 0;
+}
+
+void AudioStream::play(AUDIO_PLAYMODE mode)
+{
+       loop = (mode == AUDIO_PLAYMODE_LOOP);
+       done = false;
+
+       if(pthread_create(&play_thread, 0, thread_func, this) != 0) {
+               error_log("failed to create music playback thread\n");
+       }
+}
+
+void AudioStream::stop()
+{
+       pthread_mutex_lock(&mutex);
+
+       if(alsrc) {
+               done = true;
+               //alSourceStop(alsrc);
+               printf("waiting for the music thread to stop\n");
+               pthread_mutex_unlock(&mutex);
+               pthread_join(play_thread, 0);
+       } else {
+               pthread_mutex_unlock(&mutex);
+       }
+}
+
+// gets an array of buffers and returns the index of the one matching id
+static inline int find_buffer(unsigned int id, unsigned int *barr, int num)
+{
+       for(int i=0; i<num; i++) {
+               if(barr[i] == id) {
+                       return i;
+               }
+       }
+       return -1;
+}
+
+
+static int queued_idx_list[AUDIO_NUM_BUFFERS];
+static int queued_idx_head = 0;
+static int queued_idx_tail = 0;
+
+#define BUFQ_UNQUEUE() \
+       do { \
+               queued_idx_tail = (queued_idx_tail + 1) % AUDIO_NUM_BUFFERS; \
+       } while(0)
+
+
+#define BUFQ_QUEUE(idx)        \
+       do { \
+               queued_idx_head = (queued_idx_head + 1) % AUDIO_NUM_BUFFERS; \
+               queued_idx_list[queued_idx_head] = idx; \
+       } while(0)
+
+// thread function
+void AudioStream::poll_loop()
+{
+       long prev_msec = -1000;
+       unsigned int albuf[AUDIO_NUM_BUFFERS];
+
+       pthread_mutex_lock(&mutex);
+       alGenSources(1, &alsrc);
+       alSourcei(alsrc, AL_LOOPING, AL_FALSE);
+       alSourcef(alsrc, AL_GAIN, volume);
+       alSourcef(alsrc, AL_PITCH, pitch);
+       alGenBuffers(AUDIO_NUM_BUFFERS, albuf);
+       AudioStreamBuffer *buf = new AudioStreamBuffer;
+
+       assert(alGetError() == 0);
+       for(int i=0; i<AUDIO_NUM_BUFFERS; i++) {
+               if(more_samples(buf)) {
+                       int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
+                       alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate);
+
+                       if(alGetError()) {
+                               fprintf(stderr, "failed to load sample data into OpenAL buffer\n");
+                       }
+
+                       alSourceQueueBuffers(alsrc, 1, albuf + i);
+                       BUFQ_QUEUE(i);
+
+                       if(alGetError()) {
+                               fprintf(stderr, "failed to start streaming audio buffers\n");
+                       }
+               } else {
+                       break;
+               }
+       }
+
+       // start playback
+       alSourcePlay(alsrc);
+       while(!done) {
+               /* find out how many (if any) of the queued buffers are
+               * done, and free to be reused.
+               */
+               int num_buf_done;
+               alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done);
+               for(int i=0; i<num_buf_done; i++) {
+                       int err;
+                       // unqueue a buffer...
+                       unsigned int buf_id;
+                       alSourceUnqueueBuffers(alsrc, 1, &buf_id);
+
+                       if((err = alGetError())) {
+                               fprintf(stderr, "failed to unqueue used buffer (error: %x)\n", err);
+                               num_buf_done = i;
+                               break;
+                       }
+                       BUFQ_UNQUEUE();
+
+                       // find out which one of our al buffers we just unqueued
+                       int bidx = find_buffer(buf_id, albuf, AUDIO_NUM_BUFFERS);
+                       assert(bidx != -1);
+
+                       int looping;
+
+                       alGetSourcei(alsrc, AL_LOOPING, &looping);
+                       assert(looping == AL_FALSE);
+                       /*if((unsigned int)cur_buf == buf_id) {
+                               continue;
+                       }*/
+
+                       // if there are more data, fill it up and requeue it
+                       if(more_samples(buf)) {
+                               int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
+                               alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate);
+                               if((err = alGetError())) {
+                                       fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err);
+                               }
+
+                               alSourceQueueBuffers(alsrc, 1, &buf_id);
+                               if(alGetError()) {
+                                       fprintf(stderr, "failed to start streaming audio buffers\n");
+                               }
+                               BUFQ_QUEUE(bidx);
+                       } else {
+                               // no more data...
+                               if(loop) {
+                                       debug_log("audio stream looping...\n");
+                                       rewind();
+                               } else {
+                                       done = true;
+                               }
+                       }
+               }
+
+               if(num_buf_done) {
+                       // make sure playback didn't stop
+                       int state;
+                       alGetSourcei(alsrc, AL_SOURCE_STATE, &state);
+                       if(state != AL_PLAYING) {
+                               alSourcePlay(alsrc);
+                       }
+               }
+
+               pthread_mutex_unlock(&mutex);
+               long msec = get_time_msec();
+               long dt = msec - prev_msec;
+               prev_msec = msec;
+
+               if(dt < (long)poll_interval) {
+                       sleep_msec(poll_interval - dt);
+               } else {
+                       sched_yield();
+               }
+               pthread_mutex_lock(&mutex);
+       }
+
+
+       // done with the data, wait for the source to stop playing before cleanup
+       int state;
+       while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) {
+               sched_yield();
+       }
+
+       alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf);
+       alDeleteSources(1, &alsrc);
+       alsrc = 0;
+       pthread_mutex_unlock(&mutex);
+
+       delete buf;
+}