+#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;
+}