/*] 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 #include #include #include #include #include #include #include #include #include #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 impl { public: impl() { resource::register_type("ogg"); } ~impl() { resource::unregister_type("ogg"); } }; static impl impl; class sound::impl { public: static ALenum get_audio_format(const vorbis_info* audioInfo) { if (audioInfo->channels == 1) return AL_FORMAT_MONO16; else return AL_FORMAT_STEREO16; } class buffer; typedef boost::shared_ptr buffer_ptr; class buffer : public manager { 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& path) { log_info("initializing audio buffer..."); if (mOggStream.datasource) { ov_clear(&mOggStream); mOggStream.datasource = 0; } if (ov_fopen((char*)path.c_str(), &mOggStream) < 0) { throw std::runtime_error("problem reading audio: " + path); } vorbis_info* vorbisInfo = ov_info(&mOggStream, -1); mFormat = get_audio_format(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& path) { log_info("sound::impl constructor"); init(); enqueue(path); } 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& path) { stop(); alSourcei(source_, AL_BUFFER, AL_NONE); queue_.clear(); is_loaded_ = false; enqueue(path); while (!buffers_.empty()) { alDeleteBuffers(1, &buffers_.back()); buffers_.pop_back(); } } void enqueue(const std::string& path) { buffer_ptr buffer = buffer::instance(path); 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 buffers_; bool is_loaded_; bool is_playing_; bool is_looping_; std::deque 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& path) : // pass through impl_(new sound::impl(path)) { log_info("sound constructor"); } void sound::sample(const std::string& path) { // pass through impl_->sample(path); } 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); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void sound_stream::enqueue(const std::string& path) { // pass through impl_->enqueue(path); } void sound_stream::play() { // pass through impl_->play_stream(); } } // namespace moof