nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include "openal.h" nuclear@0: #include "stream.h" nuclear@0: #include "logger.h" nuclear@0: #include "timer.h" nuclear@0: #include "kiss_fft.h" nuclear@0: nuclear@0: struct FFTState { nuclear@0: kiss_fft_cfg kiss; nuclear@0: kiss_fft_cpx *inbuf, *outbuf; nuclear@0: int nsamples; nuclear@0: }; nuclear@0: nuclear@0: static ALenum alformat(AudioStreamBuffer *buf) nuclear@0: { nuclear@0: return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; nuclear@0: } nuclear@0: nuclear@0: AudioStream::AudioStream() nuclear@0: { nuclear@0: alsrc = 0; nuclear@0: poll_interval = 25; nuclear@0: done = true; nuclear@0: loop = false; nuclear@0: volume = 1.0; nuclear@0: nuclear@0: freqhist = 0; nuclear@0: nuclear@0: // by default disable FFT processing nuclear@0: use_fft = false; nuclear@0: nuclear@0: pthread_mutex_init(&mutex, 0); nuclear@0: } nuclear@0: nuclear@0: AudioStream::~AudioStream() nuclear@0: { nuclear@0: stop(); nuclear@0: } nuclear@0: nuclear@0: void AudioStream::enable_fft() nuclear@0: { nuclear@0: use_fft = true; nuclear@0: } nuclear@0: nuclear@0: void AudioStream::disable_fft() nuclear@0: { nuclear@0: use_fft = false; nuclear@0: } nuclear@0: nuclear@0: bool AudioStream::is_fft_enabled() const nuclear@0: { nuclear@0: return use_fft; nuclear@0: } nuclear@0: nuclear@0: bool AudioStream::open(const char *fname) nuclear@0: { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: void AudioStream::close() nuclear@0: { nuclear@0: } nuclear@0: nuclear@0: void AudioStream::set_volume(float vol) nuclear@0: { nuclear@0: volume = vol; nuclear@0: nuclear@0: pthread_mutex_lock(&mutex); nuclear@0: if(alsrc) { nuclear@0: alSourcef(alsrc, AL_GAIN, vol); nuclear@0: } nuclear@0: pthread_mutex_unlock(&mutex); nuclear@0: } nuclear@0: nuclear@0: float AudioStream::get_volume() const nuclear@0: { nuclear@0: return volume; nuclear@0: } nuclear@0: nuclear@0: static void *thread_func(void *arg) nuclear@0: { nuclear@0: AudioStream *astr = (AudioStream*)arg; nuclear@0: astr->poll_loop(); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: void AudioStream::play(AUDIO_PLAYMODE mode) nuclear@0: { nuclear@0: loop = (mode == AUDIO_PLAYMODE_LOOP); nuclear@0: done = false; nuclear@0: nuclear@0: if(pthread_create(&play_thread, 0, thread_func, this) != 0) { nuclear@0: error_log("failed to create music playback thread\n"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: void AudioStream::stop() nuclear@0: { nuclear@0: pthread_mutex_lock(&mutex); nuclear@0: nuclear@0: if(alsrc) { nuclear@0: done = true; nuclear@0: alSourceStop(alsrc); nuclear@0: printf("waiting for the music thread to stop\n"); nuclear@0: pthread_mutex_unlock(&mutex); nuclear@0: pthread_join(play_thread, 0); nuclear@0: } else { nuclear@0: pthread_mutex_unlock(&mutex); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // gets an array of buffers and returns the index of the one matching id nuclear@0: static inline int find_buffer(unsigned int id, unsigned int *barr, int num) nuclear@0: { nuclear@0: for(int i=0; inum_samples * buf->channels * 2; // 2 is for 16bit samples nuclear@0: alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate); nuclear@0: nuclear@0: if(alGetError()) { nuclear@0: fprintf(stderr, "failed to load sample data into OpenAL buffer\n"); nuclear@0: } nuclear@0: nuclear@0: alSourceQueueBuffers(alsrc, 1, albuf + i); nuclear@0: BUFQ_QUEUE(i); nuclear@0: nuclear@0: if(alGetError()) { nuclear@0: fprintf(stderr, "failed to start streaming audio buffers\n"); nuclear@0: } nuclear@0: nuclear@0: // also calculate the frequencies nuclear@0: calc_freq(buf, freqbins[i], &fft); nuclear@0: } else { nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // start playback nuclear@0: alSourcePlay(alsrc); nuclear@0: while(!done) { nuclear@0: // XXX this doesn't work nuclear@0: /* nuclear@0: // first let's figure out which buffer is currently playing nuclear@0: int cur_buf; nuclear@0: alGetSourcei(alsrc, AL_BUFFER, &cur_buf); nuclear@0: int cur_buf_idx = find_buffer(cur_buf, albuf, AUDIO_NUM_BUFFERS); nuclear@0: nuclear@0: // make the fft histogram pointer point to the correct frequency bin array nuclear@0: freqhist = cur_buf_idx != -1 ? freqbins[cur_buf_idx] : 0; nuclear@0: if(!freqhist) { nuclear@0: debug_log("skata\n"); nuclear@0: } nuclear@0: */ nuclear@0: nuclear@0: /* find out how many (if any) of the queued buffers are nuclear@0: * done, and free to be reused. nuclear@0: */ nuclear@0: int num_buf_done; nuclear@0: alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done); nuclear@0: for(int i=0; inum_samples * buf->channels * 2; // 2 is for 16bit samples nuclear@0: alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate); nuclear@0: if((err = alGetError())) { nuclear@0: fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err); nuclear@0: } nuclear@0: nuclear@0: alSourceQueueBuffers(alsrc, 1, &buf_id); nuclear@0: if(alGetError()) { nuclear@0: fprintf(stderr, "failed to start streaming audio buffers\n"); nuclear@0: } nuclear@0: BUFQ_QUEUE(bidx); nuclear@0: nuclear@0: // also calculate the frequencies if required nuclear@0: if(use_fft) { nuclear@0: calc_freq(buf, freqbins[bidx], &fft); nuclear@0: } nuclear@0: } else { nuclear@0: // no more data... nuclear@0: if(loop) { nuclear@0: rewind(); nuclear@0: } else { nuclear@0: done = true; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: if(use_fft) { nuclear@0: freqhist = freqbins[queued_idx_list[queued_idx_tail]]; nuclear@0: } nuclear@0: nuclear@0: if(num_buf_done) { nuclear@0: // make sure playback didn't stop nuclear@0: int state; nuclear@0: alGetSourcei(alsrc, AL_SOURCE_STATE, &state); nuclear@0: if(state != AL_PLAYING) { nuclear@0: alSourcePlay(alsrc); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: pthread_mutex_unlock(&mutex); nuclear@0: long msec = get_time_msec(); nuclear@0: long dt = msec - prev_msec; nuclear@0: prev_msec = msec; nuclear@0: nuclear@0: if(dt < poll_interval - 5) { nuclear@0: sleep_msec(poll_interval - dt); nuclear@0: } else { nuclear@0: sched_yield(); nuclear@0: } nuclear@0: pthread_mutex_lock(&mutex); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // done with the data, wait for the source to stop playing before cleanup nuclear@0: int state; nuclear@0: while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) { nuclear@0: sched_yield(); nuclear@0: } nuclear@0: nuclear@0: freqhist = 0; nuclear@0: nuclear@0: alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf); nuclear@0: alDeleteSources(1, &alsrc); nuclear@0: alsrc = 0; nuclear@0: pthread_mutex_unlock(&mutex); nuclear@0: nuclear@0: delete buf; nuclear@0: nuclear@0: delete [] fft.inbuf; nuclear@0: delete [] fft.outbuf; nuclear@0: kiss_fft_free(fft.kiss); nuclear@0: } nuclear@0: nuclear@0: int AudioStream::freq_count(int bin) const nuclear@0: { nuclear@0: if(!freqhist || !use_fft || bin < 0 || bin >= AUDIO_BUFFER_SAMPLES) { nuclear@0: return 0; nuclear@0: } nuclear@0: return freqhist[bin]; nuclear@0: } nuclear@0: nuclear@0: #define NORM_FACTOR (1.0f / (float)AUDIO_FFT_SAMPLES) nuclear@0: float AudioStream::freq_normalized(int bin) const nuclear@0: { nuclear@0: // TODO remove the fudge factor nuclear@0: return freq_count(bin) * NORM_FACTOR * 0.25; nuclear@0: } nuclear@0: nuclear@0: // frequency range in hertz nuclear@0: int AudioStream::freq_count(int range_start, int range_end) const nuclear@0: { nuclear@0: // NOTE this will probably be something like sampling freq / num-bins Hz per bin... nuclear@0: return 0; // TODO nuclear@0: } nuclear@0: nuclear@0: // TODO ok this might be inefficient, copying the data around a lot, optimize later nuclear@0: void AudioStream::calc_freq(AudioStreamBuffer *buf, int *bins, FFTState *fft) nuclear@0: { nuclear@0: kiss_fft_cpx *inptr = fft->inbuf; nuclear@0: int16_t *samples = (int16_t*)buf->samples; nuclear@0: for(int i=0; ii = 0; nuclear@0: if(i < buf->num_samples) { nuclear@0: int left = samples[i * 2]; nuclear@0: int right = samples[i * 2 + 1]; nuclear@0: nuclear@0: (inptr++)->r = (left + right) / 2; nuclear@0: } else { nuclear@0: (inptr++)->r = 0; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: kiss_fft(fft->kiss, fft->inbuf, fft->outbuf); nuclear@0: nuclear@0: // then copy all the relevant data to the bins array nuclear@0: int num_out_samples = AUDIO_BUFFER_SAMPLES / 2; nuclear@0: int samples_per_bin = num_out_samples / AUDIO_FFT_BINS; nuclear@0: nuclear@0: long abins[AUDIO_FFT_BINS]; nuclear@0: nuclear@0: int prev_bidx = -1; nuclear@0: // ignore the DC bin (0) nuclear@0: for(int i=1; ioutbuf[i].r; nuclear@0: float y = fft->outbuf[i].i; nuclear@0: int val = x * x + y * y; nuclear@0: nuclear@0: if(bidx != prev_bidx) { nuclear@0: abins[bidx] = val; nuclear@0: prev_bidx = bidx; nuclear@0: } else { nuclear@0: abins[bidx] += val; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: for(int i=0; i