/*] 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 #include "hash.hh" #include "log.hh" #include "manager.hh" #include "sound.hh" #include "resource.hh" #include "timer.hh" #ifndef BUF_SIZE #define BUF_SIZE (4096) #endif #define NUM_BUFFERS (16) namespace moof { class sound_backend { public: sound_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; } } } sound_backend(const sound_backend& backend) { ++retain_count; } sound_backend& operator=(const sound_backend& backend) { ++retain_count; return *this; } ~sound_backend() { if (--retain_count == 0) { alcMakeContextCurrent(0); alcDestroyContext(al_context); alcCloseDevice(al_device); log_info("unloaded sound device ALSA"); } } static int retain_count; static ALCdevice* al_device; static ALCcontext* al_context; }; int sound_backend::retain_count = 0; ALCdevice* sound_backend::al_device; ALCcontext* sound_backend::al_context; class sound_resource; typedef resource_handle sound_handle; class sound_resource_loader { public: sound_resource_loader() { resource::register_type("ogg"); } ~sound_resource_loader() { resource::unregister_type("ogg"); } }; static sound_resource_loader loader; // SOUND BUFFER class buffer { public: typedef hash retcount_lookup; buffer() : buffer_((ALuint)-1) {} buffer(const void* data, ALsizei size, ALenum format, ALsizei freq) { alGenBuffers(1, &buffer_); alBufferData(buffer_, format, data, size, freq); retain_counts_[buffer_] = 1; log_warning("ctor buffer:", buffer_); } buffer(const buffer& buf) { buffer_ = buf.buffer_; retain(); } buffer& operator = (const buffer& buf) { buffer_ = buf.buffer_; retain(); return *this; } ~buffer() { release(); } void queue(ALuint source) const { if (*this) { alSourceQueueBuffers(source, 1, &buffer_); retain(); log_warning("queued buffer:", buffer_); } } static buffer unqueue(ALuint source) { ALuint buf = (ALuint)-1; alSourceUnqueueBuffers(source, 1, &buf); log_warning("unqueued buffer:", buf); return buffer(buf); } void set(ALuint source) const { log_warning("set buffer:", buffer_); if (*this) alSourcei(source, AL_BUFFER, buffer_); } operator bool () const { return buffer_ != (ALuint)-1; } private: explicit buffer(ALuint buf) : buffer_(buf) {} void retain() const { retcount_lookup::iterator it = retain_counts_.find(buffer_); if (it.valid()) ++it->second; } void release() const { retcount_lookup::iterator it = retain_counts_.find(buffer_); if (it.valid() && --it->second <= 0) { alDeleteBuffers(1, &buffer_); retain_counts_.erase(it); log_warning("kill buffer:", buffer_); } } ALuint buffer_; sound_backend backend_; static retcount_lookup retain_counts_; }; buffer::retcount_lookup buffer::retain_counts_; // SOUND RESOURCE class sound_resource { public: sound_resource(const std::string& path) { log_info("audio path is", path); if (ov_fopen((char*)path.c_str(), &file_) < 0) { throw std::runtime_error("problem reading audio: " + path); } } ~sound_resource() { ov_clear(&file_); } bool read(buffer& buf) { if (buffer_) { buf = buffer_; return true; } if (ov_pcm_seek_lap(&file_, 0) != 0) { log_warning("vorbis seek error"); return false; } char data[64*BUF_SIZE]; size_t size = 0; while (size < sizeof(data)) { int section; int result = ov_read(&file_, data + size, sizeof(data) - size, 0, 2, 1, §ion); if (result > 0) { size += result; continue; } else if (result == 0 && size > 0) { log_info("loaded", size, "bytes from vorbis"); vorbis_info* info = ov_info(&file_, section); buffer_ = buffer(data, size, get_audio_format(info), info->rate); buf = buffer_; log_info("this section is", section); log_info("audio format is", get_audio_format(info)); log_info("audio freq is", info->rate); return true; } else { log_warning("vorbis playback error"); break; } } if (size >= sizeof(data)) log_warning("sample is too big to play"); return false; } bool read(buffer& buf, uint64_t& sample) { if (ov_pcm_seek_lap(&file_, sample) != 0) { log_warning("vorbis seek error"); return false; } char data[BUF_SIZE]; int section; int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion); if (result > 0) { log_info("loaded", result, "bytes from vorbis"); vorbis_info* info = ov_info(&file_, section); buf = buffer(data, result, get_audio_format(info), info->rate); sample = ov_pcm_tell(&file_); log_info("this section is", section); log_info("next sample is", sample); log_info("audio format is", get_audio_format(info)); log_info("audio freq is", info->rate); return true; } if (result < 0) log_warning("vorbis playback error"); return false; } static ALenum get_audio_format(const vorbis_info* info) { if (info->channels == 1) return AL_FORMAT_MONO16; else return AL_FORMAT_STEREO16; } private: OggVorbis_File file_; buffer buffer_; }; class sound::impl { public: impl() { init(); } impl(const std::string& path) { log_info("sound::impl constructor"); init(); enqueue(path); } void init() { is_playing_ = false; is_looping_ = false; sample_ = 0; alGenSources(1, &source_); log_error("alGenSources:", alGetError()); 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); log_error("init:", alGetError()); } ~impl() { stop(); alDeleteSources(1, &source_); } void play() { if (queue_.empty()) return; sound_handle handle = queue_.front(); buffer buf; if (handle->read(buf)) { log_info("playing source..."); buf.set(source_); alSourcei(source_, AL_LOOPING, is_looping_); alSourcePlay(source_); } } void stream() { if (queue_.empty()) return; if (!is_playing_) { alSourcei(source_, AL_LOOPING, false); log_error("set not looping:", alGetError()); sound_handle handle = queue_.front(); for (int i = 0; i < NUM_BUFFERS; ++i) { buffer buf; if (handle->read(buf, sample_)) { buf.queue(source_); } else { log_error("failed to start stream"); break; } ALint queued = 0; alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued); log_info("buffers queued:", queued); } } if (!stream_timer_.is_valid()) { stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2), 0.01, timer::repeat); } log_info("streaming source..."); alSourcePlay(source_); log_error("playing:", alGetError()); is_playing_ = true; } void update() { ALint finished = 0; alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished); while (finished-- > 0) { buffer::unqueue(source_); bool streamed = false; //if (handle->is_loaded()) //{ //streamed = handle->read(buf); //} //else //{ buffer buf; sound_handle handle = queue_.front(); streamed = handle->read(buf, sample_); //} if (streamed) { buf.queue(source_); } else { // the buffer couldn't be streamed, so get rid of it queue_.pop_front(); sample_ = 0; if (!queue_.empty()) { // begin the next buffer in the queue handle->read(buf, sample_); buf.queue(source_); log_info("loading new buffer"); } else if (is_looping_) { // reload the same buffer log_info("looping same buffer"); queue_.push_back(handle); handle->read(buf, sample_); buf.queue(source_); } else { // nothing more to play, stopping... stop(); queue_.push_back(handle); } } } 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(); // TODO: clear buffers if streaming } void pause() { alSourcePause(source_); is_playing_ = false; stream_timer_.invalidate(); } void rewind() { alSourceRewind(source_); sample_ = 0; } void sample(const std::string& path) { stop(); alSourcei(source_, AL_BUFFER, AL_NONE); queue_.clear(); enqueue(path); } void enqueue(const std::string& path) { sound_handle handle = resource::load(path); queue_.push_back(handle); } bool is_playing() const { ALenum state; alGetSourcei(source_, AL_SOURCE_STATE, &state); if (state == AL_PLAYING) return true; else return is_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 } ALuint source_; bool is_playing_; bool is_looping_; std::deque queue_; uint64_t sample_; timer stream_timer_; sound_backend backend_; }; 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::enqueue(const std::string& path) { // pass through impl_->enqueue(path); } void sound::play() { // pass through impl_->play(); } void sound::stream() { // pass through impl_->stream(); } void sound::stop() { // pass through impl_->stop(); } void sound::pause() { // pass through impl_->pause(); } void sound::rewind() { // pass through impl_->rewind(); } void sound::toggle() { if (is_playing()) pause(); else play(); // TODO: what about streaming sources? } 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); } } // namespace moof