]> Dogcows Code - chaz/yoink/blobdiff - src/moof/sound.cc
the massive refactoring effort
[chaz/yoink] / src / moof / sound.cc
diff --git a/src/moof/sound.cc b/src/moof/sound.cc
new file mode 100644 (file)
index 0000000..7482de7
--- /dev/null
@@ -0,0 +1,599 @@
+
+/*]  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, &section);
+
+                               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, &section);
+
+                               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
+
This page took 0.027534 seconds and 4 git commands to generate.