--- /dev/null
+
+/*] Copyright (c) 2009-2010, Charles McGarvey [**************************
+**] All rights reserved.
+*
+* vi:ts=4 sw=4 tw=75
+*
+* Distributable under the terms and conditions of the 2-clause BSD license;
+* see the file COPYING for a complete text of the license.
+*
+**************************************************************************/
+
+#include <cstdio>
+#include <deque>
+#include <list>
+#include <stdexcept>
+#include <string>
+
+#include <boost/algorithm/string.hpp>
+
+#include <AL/al.h>
+#include <AL/alc.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+
+#include "log.hh"
+#include "manager.hh"
+#include "sound.hh"
+#include "timer.hh"
+
+#define BUF_SIZE (64 * 1024)
+//#define BUF_SIZE (5*2048)
+
+namespace moof {
+
+
+class sound::impl
+{
+public:
+
+ static ALenum getAudioFormat(const vorbis_info* audioInfo)
+ {
+ if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
+ else return AL_FORMAT_STEREO16;
+ }
+
+
+ class buffer;
+ typedef boost::shared_ptr<buffer> buffer_ptr;
+
+ class buffer : public manager<buffer>
+ {
+ public:
+
+ buffer() :
+ buffer_(-1)
+ {
+ mOggStream.datasource = 0;
+ }
+
+ ~buffer()
+ {
+ if (mOggStream.datasource)
+ {
+ ov_clear(&mOggStream);
+ }
+ if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_);
+ }
+
+
+ void init(const std::string& name)
+ {
+ if (mOggStream.datasource)
+ {
+ ov_clear(&mOggStream);
+ mOggStream.datasource = 0;
+ }
+
+ std::string path(name);
+ if (!sound::find_path(path))
+ {
+ throw std::runtime_error("cannot find resource: " + name);
+ }
+
+ if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
+ {
+ throw std::runtime_error("problem reading audio: " + name);
+ }
+
+ vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
+ mFormat = getAudioFormat(vorbisInfo);
+ mFreq = vorbisInfo->rate;
+ }
+
+
+ void load_all(ALuint source)
+ {
+ if (!mOggStream.datasource) init(name());
+ if (!mOggStream.datasource) return;
+
+ char data[BUF_SIZE];
+ int size = 0;
+
+ for (;;)
+ {
+ int section;
+ int result = ov_read(&mOggStream, data + size,
+ BUF_SIZE - size, 0, 2, 1, §ion);
+
+ if (result > 0)
+ {
+ size += result;
+ }
+ else
+ {
+ if (result < 0) log_warning("vorbis playback error");
+ break;
+ }
+ }
+ if (size == 0)
+ {
+ log_warning("decoded no bytes from", name());
+ return;
+ }
+
+ alGenBuffers(1, &buffer_);
+
+ alBufferData(buffer_, mFormat, data, size, mFreq);
+ alSourcei(source, AL_BUFFER, buffer_);
+
+ // don't need to keep this loaded
+ ov_clear(&mOggStream);
+ mOggStream.datasource = 0;
+ }
+
+ bool stream(ALuint buffer)
+ {
+ char data[BUF_SIZE];
+ int size = 0;
+
+ while (size < BUF_SIZE)
+ {
+ int section;
+ int result = ov_read(&mOggStream, data + size,
+ BUF_SIZE - size, 0, 2, 1, §ion);
+
+ if (result > 0)
+ {
+ size += result;
+ }
+ else
+ {
+ if (result < 0) log_warning("vorbis playback error");
+ break;
+ }
+ }
+
+ if (size == 0) return false;
+
+ alBufferData(buffer, mFormat, data, size, mFreq);
+
+ return true;
+ }
+
+ void rewind()
+ {
+ if (!mOggStream.datasource) init(name());
+ else ov_raw_seek(&mOggStream, 0);
+ }
+
+
+ private:
+
+ OggVorbis_File mOggStream;
+ ALenum mFormat;
+ ALsizei mFreq;
+ ALuint buffer_;
+ };
+
+
+ impl()
+ {
+ init();
+ }
+
+ impl(const std::string& name)
+ {
+ init();
+ enqueue(name);
+ }
+
+ void init()
+ {
+ retain_backend();
+
+ is_loaded_ = false;
+ is_playing_ = false;
+ is_looping_ = false;
+
+ alGenSources(1, &source_);
+
+ ALfloat zero[] = {0.0f, 0.0f, 0.0f};
+ alSourcef(source_, AL_PITCH, 1.0f);
+ alSourcef(source_, AL_GAIN, 1.0f);
+ alSourcefv(source_, AL_POSITION, zero);
+ alSourcefv(source_, AL_VELOCITY, zero);
+
+ alSourcei(source_, AL_LOOPING, is_looping_);
+ }
+
+ ~impl()
+ {
+ stop();
+
+ alDeleteSources(1, &source_);
+
+ while (!buffers_.empty())
+ {
+ alDeleteBuffers(1, &buffers_.back());
+ buffers_.pop_back();
+ }
+
+ release_backend();
+ }
+
+
+ void play()
+ {
+ if (queue_.empty()) return;
+
+ if (!is_loaded_) queue_.front()->load_all(source_);
+
+ alSourcePlay(source_);
+ is_loaded_ = true;
+ }
+
+
+ void play_stream()
+ {
+ if (queue_.empty()) return;
+
+ if (!is_playing_)
+ {
+ alSourcei(source_, AL_LOOPING, false);
+ buffer_stream();
+ }
+
+ if (!stream_timer_.is_valid())
+ {
+ stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
+ 1.0, timer::repeat);
+ }
+
+ alSourcePlay(source_);
+ is_playing_ = true;
+ }
+
+ void buffer_stream()
+ {
+ ALuint buffer;
+ for (int i = buffers_.size(); i <= 8; ++i)
+ {
+ alGenBuffers(1, &buffer);
+
+ if (queue_.front()->stream(buffer))
+ {
+ alSourceQueueBuffers(source_, 1, &buffer);
+ buffers_.push_back(buffer);
+ }
+ else
+ {
+ alDeleteBuffers(1, &buffer);
+ break;
+ }
+ }
+ }
+
+
+ void update()
+ {
+ ALint finished = 0;
+
+ alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
+
+ while (finished-- > 0)
+ {
+ ALuint bufferObj;
+ alSourceUnqueueBuffers(source_, 1, &bufferObj);
+
+ buffer_ptr buffer = queue_.front();
+ bool streamed = buffer->stream(bufferObj);
+
+ if (streamed)
+ {
+ alSourceQueueBuffers(source_, 1, &bufferObj);
+ }
+ else
+ {
+ // the buffer couldn't be streamed, so get rid of it
+ queue_.pop_front();
+
+ if (!queue_.empty())
+ {
+ // begin the next buffer in the queue
+ queue_.front()->rewind();
+ queue_.front()->stream(bufferObj);
+ alSourceQueueBuffers(source_, 1, &bufferObj);
+ log_info("loading new buffer");
+
+ // queue up any unused buffers
+ buffer_stream();
+ }
+ else if (is_looping_)
+ {
+ // reload the same buffer
+ queue_.push_back(buffer);
+ buffer->rewind();
+ buffer->stream(bufferObj);
+ alSourceQueueBuffers(source_, 1, &bufferObj);
+ log_info("looping same buffer");
+ }
+ else
+ {
+ // nothing more to play, stopping...
+ is_playing_ = false;
+ std::remove(buffers_.begin(), buffers_.end(),
+ bufferObj);
+ }
+ }
+ }
+
+ ALenum state;
+ alGetSourcei(source_, AL_SOURCE_STATE, &state);
+
+ // restart playing if we're stopped but supposed to be playing...
+ // this means we didn't queue enough and the audio skipped :-(
+ if (is_playing_ && state != AL_PLAYING)
+ {
+ alSourcePlay(source_);
+ }
+ }
+
+
+ void stop()
+ {
+ alSourceStop(source_);
+ is_playing_ = false;
+
+ stream_timer_.invalidate();
+ }
+
+ void pause()
+ {
+ alSourcePause(source_);
+ is_playing_ = false;
+
+ stream_timer_.invalidate();
+ }
+
+
+ void sample(const std::string& name)
+ {
+ stop();
+ alSourcei(source_, AL_BUFFER, AL_NONE);
+
+ queue_.clear();
+ is_loaded_ = false;
+
+ enqueue(name);
+
+ while (!buffers_.empty())
+ {
+ alDeleteBuffers(1, &buffers_.back());
+ buffers_.pop_back();
+ }
+ }
+
+ void enqueue(const std::string& name)
+ {
+ buffer_ptr buffer = buffer::instance(name);
+ queue_.push_back(buffer);
+ }
+
+
+ bool is_playing() const
+ {
+ if (is_playing_) return true;
+
+ ALenum state;
+ alGetSourcei(source_, AL_SOURCE_STATE, &state);
+
+ return state == AL_PLAYING;
+ }
+
+
+ void loop(bool looping)
+ {
+ is_looping_ = looping;
+
+ ALenum type;
+ alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+ if (type != AL_STREAMING)
+ {
+ alSourcei(source_, AL_LOOPING, is_looping_);
+ }
+ }
+
+
+ void stream_update(timer& timer, scalar t)
+ {
+ update();
+ // TODO: might be nice to also allow using threads for streaming
+ // rather than a timer, probably as a compile-time option
+ }
+
+
+ static void retain_backend()
+ {
+ if (retain_count_++ == 0)
+ {
+ al_device_ = alcOpenDevice(0);
+ al_context_ = alcCreateContext(al_device_, 0);
+ if (!al_device_ || !al_context_)
+ {
+ const char* error = alcGetString(al_device_,
+ alcGetError(al_device_));
+ log_error("audio subsystem initialization failure", error);
+ }
+ else
+ {
+ alcMakeContextCurrent(al_context_);
+ log_info << "opened sound device `"
+ << alcGetString(al_device_,
+ ALC_DEFAULT_DEVICE_SPECIFIER)
+ << "'" << std::endl;
+ }
+ }
+ }
+
+ static void release_backend()
+ {
+ if (--retain_count_ == 0)
+ {
+ alcMakeContextCurrent(0);
+ alcDestroyContext(al_context_);
+ alcCloseDevice(al_device_);
+ }
+ }
+
+
+ ALuint source_;
+ std::list<ALuint> buffers_;
+
+ bool is_loaded_;
+ bool is_playing_;
+ bool is_looping_;
+
+ std::deque<buffer_ptr> queue_;
+
+ timer stream_timer_;
+
+ static unsigned retain_count_;
+ static ALCdevice* al_device_;
+ static ALCcontext* al_context_;
+};
+
+unsigned sound::impl::retain_count_ = 0;
+ALCdevice* sound::impl::al_device_ = 0;
+ALCcontext* sound::impl::al_context_ = 0;
+
+
+sound::sound() :
+ // pass through
+ impl_(new sound::impl) {}
+
+sound::sound(const std::string& name) :
+ // pass through
+ impl_(new sound::impl(name)) {}
+
+
+void sound::sample(const std::string& name)
+{
+ // pass through
+ impl_->sample(name);
+}
+
+
+void sound::play()
+{
+ // pass through
+ impl_->play();
+}
+
+void sound::stop()
+{
+ // pass through
+ impl_->stop();
+}
+
+void sound::pause()
+{
+ // pass through
+ impl_->pause();
+}
+
+
+void sound::toggle()
+{
+ if (is_playing()) pause();
+ else play();
+}
+
+bool sound::is_playing() const
+{
+ // pass through
+ return impl_->is_playing();
+}
+
+
+void sound::position(const vector3& position)
+{
+ float vec[3] = {position[0], position[1], position[2]};
+ alSourcefv(impl_->source_, AL_POSITION, vec);
+}
+
+void sound::velocity(const vector3& velocity)
+{
+ float vec[3] = {velocity[0], velocity[1], velocity[2]};
+ alSourcefv(impl_->source_, AL_VELOCITY, vec);
+}
+
+void sound::gain(scalar gain)
+{
+ alSourcef(impl_->source_, AL_GAIN, float(gain));
+}
+
+void sound::pitch(scalar pitch)
+{
+ alSourcef(impl_->source_, AL_PITCH, float(pitch));
+}
+
+void sound::loop(bool looping)
+{
+ // pass through
+ impl_->loop(looping);
+}
+
+
+void sound::listener_position(const vector3& position)
+{
+ float vec[] = {position[0], position[1], position[2]};
+ alListenerfv(AL_POSITION, vec);
+}
+
+void sound::listener_velocity(const vector3& velocity)
+{
+ float vec[] = {velocity[0], velocity[1], velocity[2]};
+ alListenerfv(AL_VELOCITY, vec);
+}
+
+void sound::listener_orientation(const vector3& forward,
+ const vector3& up)
+{
+ float vec[6];
+ vec[0] = float(forward[0]);
+ vec[1] = float(forward[1]);
+ vec[2] = float(forward[2]);
+ vec[3] = float(up[0]);
+ vec[4] = float(up[1]);
+ vec[5] = float(up[2]);
+ alListenerfv(AL_ORIENTATION, vec);
+}
+
+
+bool sound::find_path(std::string& name)
+{
+ return resource::find_path(name, "sounds/", "ogg");
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+void sound_stream::enqueue(const std::string& name)
+{
+ // pass through
+ impl_->enqueue(name);
+}
+
+
+void sound_stream::play()
+{
+ // pass through
+ impl_->play_stream();
+}
+
+
+} // namespace moof
+