*
**************************************************************************/
-#include <cstdio>
#include <deque>
-#include <list>
#include <stdexcept>
-#include <string>
#include <boost/algorithm/string.hpp>
-
+#include <boost/cstdint.hpp>
+#include <boost/noncopyable.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 "resource.hh"
+#include "runloop.hh"
+#include "thread.hh"
#include "timer.hh"
-#define BUF_SIZE (64 * 1024)
-//#define BUF_SIZE (5*2048)
+
+#ifndef BUF_SIZE
+#define BUF_SIZE (4096)
+#endif
+
+#define NUM_SAMPLE_BITS (16)
+
namespace moof {
-class sound::impl
+thread stream_thread_;
+
+
+/*] Sound backend
+ *************************************************************************/
+
+class sound_backend
{
public:
- static ALenum getAudioFormat(const vorbis_info* audioInfo)
+ 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)
{
- if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
- else return AL_FORMAT_STEREO16;
+ ++retain_count;
}
-
- class buffer;
- typedef boost::shared_ptr<buffer> buffer_ptr;
-
- class buffer : public manager<buffer>
+ sound_backend& operator=(const sound_backend& backend)
{
- public:
+ ++retain_count;
+ return *this;
+ }
- buffer() :
- buffer_(-1)
+ ~sound_backend()
+ {
+ if (--retain_count == 0)
{
- mOggStream.datasource = 0;
+ alcMakeContextCurrent(0);
+ alcDestroyContext(al_context);
+ alcCloseDevice(al_device);
}
+ }
+
- ~buffer()
+ 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_resource> sound_handle;
+
+MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds);
+
+
+/*] Sound resource
+ *************************************************************************/
+
+class sound_resource : public boost::noncopyable
+{
+public:
+
+ sound_resource(const std::string& path) :
+ buffer_(AL_NONE),
+ sample_(0)
+ {
+ if (ov_fopen((char*)path.c_str(), &file_) < 0)
{
- if (mOggStream.datasource)
- {
- ov_clear(&mOggStream);
- }
- if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_);
+ throw std::runtime_error("problem reading audio: " + path);
}
+ }
+
+ ~sound_resource()
+ {
+ ov_clear(&file_);
+ if (buffer_) alDeleteBuffers(1, &buffer_);
+ }
- void init(const std::string& name)
+ ALuint read_all() const
+ {
+ if (buffer_) return buffer_;
+
+ if (ov_pcm_seek(&file_, 0) != 0)
{
- if (mOggStream.datasource)
- {
- ov_clear(&mOggStream);
- mOggStream.datasource = 0;
- }
+ log_warning("vorbis seek error");
+ return AL_NONE;
+ }
+
+ ogg_int64_t samples = ov_pcm_total(&file_, 0);
+
+ char data[2 * samples * (NUM_SAMPLE_BITS / 8)];
+ size_t size = 0;
+
+ while (size < sizeof(data))
+ {
+ int section;
+ int result = ov_read(&file_,
+ data + size, sizeof(data) - size,
+ 0, 2, 1, §ion);
- std::string path(name);
- if (!sound::find_path(path))
+ if (0 < result)
{
- throw std::runtime_error("cannot find resource: " + name);
+ size += result;
}
+ else if (result == 0 && size > 0)
+ {
+ vorbis_info* info = ov_info(&file_, section);
+
+ ALuint buffer;
+ alGenBuffers(1, &buffer);
+ alBufferData(buffer, get_audio_format(info),
+ data, size, info->rate);
- if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
+ buffer_ = buffer;
+ return buffer;
+ }
+ else
{
- throw std::runtime_error("problem reading audio: " + name);
+ log_warning("vorbis playback error");
+ break;
}
-
- vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
- mFormat = getAudioFormat(vorbisInfo);
- mFreq = vorbisInfo->rate;
}
+ return AL_NONE;
+ }
- void load_all(ALuint source)
+ bool read(ALuint buffer, uint64_t& sample) const
+ {
+ if ((sample == sample_ && ov_pcm_seek_lap(&file_, sample) != 0) ||
+ (sample != sample_ && ov_pcm_seek(&file_, sample) != 0))
{
- if (!mOggStream.datasource) init(name());
- if (!mOggStream.datasource) return;
+ return false;
+ }
- char data[BUF_SIZE];
- int size = 0;
+ char data[BUF_SIZE];
+ int section;
+ int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion);
- for (;;)
- {
- int section;
- int result = ov_read(&mOggStream, data + size,
- BUF_SIZE - size, 0, 2, 1, §ion);
+ if (0 < result)
+ {
+ vorbis_info* info = ov_info(&file_, section);
+ alBufferData(buffer, get_audio_format(info),
+ data, result, info->rate);
+ sample_ = sample = ov_pcm_tell(&file_);
+ return true;
+ }
+ else if (result < 0) log_warning("vorbis playback error");
- 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;
- }
+ return false;
+ }
- alGenBuffers(1, &buffer_);
+ bool read(ALuint buffer, uint64_t& sample, sound_handle other) const
+ {
+ ov_crosslap(&other->file_, &file_);
- alBufferData(buffer_, mFormat, data, size, mFreq);
- alSourcei(source, AL_BUFFER, buffer_);
+ char data[BUF_SIZE];
+ int section;
+ int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion);
- // don't need to keep this loaded
- ov_clear(&mOggStream);
- mOggStream.datasource = 0;
+ if (0 < result)
+ {
+ vorbis_info* info = ov_info(&file_, section);
+ alBufferData(buffer, get_audio_format(info),
+ data, result, info->rate);
+ sample_ = sample = ov_pcm_tell(&file_);
+ return true;
}
+ else if (result < 0) log_warning("vorbis playback error");
- bool stream(ALuint buffer)
- {
- char data[BUF_SIZE];
- int size = 0;
+ return false;
+ }
- 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;
- }
- }
+ // determines the correct number of fixed-size buffers needed to hold a
+ // given number of seconds of PCM audio.
+ int num_buffers(scalar seconds) const
+ {
+ vorbis_info* info = ov_info(&file_, -1);
+ int count = scalar(info->rate) * // sample rate
+ scalar(info->channels) * // channels
+ scalar(NUM_SAMPLE_BITS) * // sample size
+ seconds * // time
+ SCALAR(0.125) / // bits to bytes
+ scalar(BUF_SIZE); // individual buffer size
+ return count;
+ }
- if (size == 0) return false;
- alBufferData(buffer, mFormat, data, size, mFreq);
+ static ALenum get_audio_format(const vorbis_info* info)
+ {
+ if (info->channels == 1) return AL_FORMAT_MONO16;
+ else if (info->channels == 2) return AL_FORMAT_STEREO16;
- return true;
- }
+ log_error("unsupported number of channels:", info->channels);
+ return 0;
+ }
- void rewind()
- {
- if (!mOggStream.datasource) init(name());
- else ov_raw_seek(&mOggStream, 0);
- }
+private:
- private:
+ mutable OggVorbis_File file_;
+ mutable ALuint buffer_;
+ mutable uint64_t sample_;
- OggVorbis_File mOggStream;
- ALenum mFormat;
- ALsizei mFreq;
- ALuint buffer_;
- };
+ sound_backend backend_;
+};
+/*] Sound class
+ *************************************************************************/
+
+class sound::impl
+{
+public:
+
impl()
{
init();
impl(const std::string& name)
{
init();
- enqueue(name);
+ sample(name);
}
void init()
{
- retain_backend();
-
- is_loaded_ = false;
is_playing_ = false;
is_looping_ = false;
+ sample_ = 0;
+ buffer_size_ = SCALAR(1.0);
+
alGenSources(1, &source_);
ALfloat zero[] = {0.0f, 0.0f, 0.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();
+ deplete_stream();
alDeleteSources(1, &source_);
+ }
- while (!buffers_.empty())
+
+ void deplete_stream()
+ {
+ if (is_streaming_)
{
- alDeleteBuffers(1, &buffers_.back());
- buffers_.pop_back();
+ ALint queued = 0;
+ alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+ ALuint buffers[queued];
+ alSourceUnqueueBuffers(source_, queued, buffers);
+ alDeleteBuffers(queued, buffers);
}
-
- release_backend();
+ else alSourcei(source_, AL_BUFFER, AL_NONE);
}
-
void play()
{
- if (queue_.empty()) return;
+ if (queue_.empty() || is_playing_) return;
+
+ ALenum state;
+ alGetSourcei(source_, AL_SOURCE_STATE, &state);
+ switch (state)
+ {
+ case AL_INITIAL:
+ case AL_STOPPED:
- if (!is_loaded_) queue_.front()->load_all(source_);
+ if (is_streaming_)
+ {
+ start_update_timer();
+ num_buffers_ = queue_.front()->num_buffers(buffer_size_);
+ fill_stream();
+ is_playing_ = true;
+ alSourcei(source_, AL_LOOPING, false);
+ }
+ else
+ {
+ ALuint buffer = queue_.front()->read_all();
+ alSourcei(source_, AL_BUFFER, buffer);
+ alSourcei(source_, AL_LOOPING, is_looping_);
+ }
+ break;
+
+ case AL_PAUSED:
+
+ if (is_streaming_)
+ {
+ start_update_timer();
+ is_playing_ = true;
+ }
+ break;
+ }
alSourcePlay(source_);
- is_loaded_ = true;
}
- void play_stream()
+ void start_update_timer()
{
- if (queue_.empty()) return;
+ stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
+ SCALAR(0.1), timer::repeat);
- if (!is_playing_)
+ //thread thread;
+ //thread.runloop().add_timer(stream_timer_);
+ if (!stream_thread_.is_valid())
{
- alSourcei(source_, AL_LOOPING, false);
- buffer_stream();
+ stream_thread_ = thread::detach(stream_timer_);
}
-
- 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()
+ void fill_stream()
{
- ALuint buffer;
- for (int i = buffers_.size(); i <= 8; ++i)
+ ALenum type;
+ alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+ ASSERT(type != AL_STATIC && "source shouldn't be static");
+
+ ALint processed = 0;
+ ALint queued = 0;
+ alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+ alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
+
+ int target_diff = num_buffers_ - queued + processed;
+
+ ALuint bufs[processed];
+ alSourceUnqueueBuffers(source_, processed, bufs);
+ for (int i = 0; i < processed; ++i)
{
- alGenBuffers(1, &buffer);
+ ALuint& buf = bufs[i];
- if (queue_.front()->stream(buffer))
+ if (0 < target_diff)
{
- alSourceQueueBuffers(source_, 1, &buffer);
- buffers_.push_back(buffer);
+ if (queue_buffer(buf)) --target_diff;
+ else
+ {
+ alDeleteBuffers(processed - i - 1, &bufs[i+1]);
+ break;
+ }
}
- else
+ else alDeleteBuffers(1, &buf);
+ }
+
+ if (stream_timer_.mode() != timer::invalid && 0 < target_diff)
+ {
+ for (int i = 0; i < target_diff; ++i)
{
- alDeleteBuffers(1, &buffer);
- break;
+ if (!queue_buffer(AL_NONE)) break;
}
}
- }
+ if (is_playing_)
+ {
+ ALenum state;
+ alGetSourcei(source_, AL_SOURCE_STATE, &state);
+ if (state != AL_PLAYING)
+ {
+ // restart playing if we're stopped but supposed to be
+ // playing... this means we didn't queue enough...
+ alSourcePlay(source_);
+ log_warning("not enough audio buffered");
+ }
+ }
+ }
- void update()
+ bool queue_buffer(ALuint buffer)
{
- ALint finished = 0;
-
- alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
+ if (buffer == AL_NONE) alGenBuffers(1, &buffer);
- while (finished-- > 0)
+ bool looped_once = false;
+
+ while (0 < queue_.size())
{
- ALuint bufferObj;
- alSourceUnqueueBuffers(source_, 1, &bufferObj);
+ sound_handle sound = queue_.front();
- buffer_ptr buffer = queue_.front();
- bool streamed = buffer->stream(bufferObj);
-
- if (streamed)
+ bool result = sound->read(buffer, sample_);
+ if (result)
{
- alSourceQueueBuffers(source_, 1, &bufferObj);
+ alSourceQueueBuffers(source_, 1, &buffer);
+ return true;
}
else
{
- // the buffer couldn't be streamed, so get rid of it
+ sample_ = 0;
queue_.pop_front();
- if (!queue_.empty())
+ if (0 < queue_.size())
{
- // 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();
+ sound_handle new_sound = queue_.front();
+ result = new_sound->read(buffer, sample_, sound);
+ if (result)
+ {
+ queue_.push_back(new_sound);
+ alSourceQueueBuffers(source_, 1, &buffer);
+ num_buffers_ = new_sound->num_buffers(buffer_size_);
+ return true;
+ }
+ else
+ {
+ queue_.pop_front();
+ queue_.push_front(sound);
+ }
}
- 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
+ else if (is_looping_ && !looped_once)
{
- // nothing more to play, stopping...
- is_playing_ = false;
- std::remove(buffers_.begin(), buffers_.end(),
- bufferObj);
+ queue_.push_back(sound);
+ looped_once = true;
}
+ else break;
}
}
- 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_);
- }
+ alDeleteBuffers(1, &buffer);
+ is_playing_ = false;
+ stream_timer_.invalidate();
+ return false;
}
is_playing_ = false;
stream_timer_.invalidate();
+
+ sample_ = 0;
}
void pause()
void sample(const std::string& name)
{
stop();
- alSourcei(source_, AL_BUFFER, AL_NONE);
+ deplete_stream();
queue_.clear();
- is_loaded_ = false;
-
- enqueue(name);
-
- while (!buffers_.empty())
- {
- alDeleteBuffers(1, &buffers_.back());
- buffers_.pop_back();
- }
+ queue(name);
+ is_streaming_ = false;
}
- void enqueue(const std::string& name)
+ void queue(const std::string& name)
{
- buffer_ptr buffer = buffer::instance(name);
- queue_.push_back(buffer);
+ sound_handle handle = resource::load(name, "ogg");
+ if (handle) queue_.push_back(handle);
+ is_streaming_ = true;
}
bool is_playing() const
{
- if (is_playing_) return true;
-
ALenum state;
alGetSourcei(source_, AL_SOURCE_STATE, &state);
- return state == AL_PLAYING;
+ if (state == AL_PLAYING) return true;
+ else return is_playing_;
}
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_);
- }
+ log_debug("blaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhrrrrrg");
+ fill_stream();
}
- ALuint source_;
- std::list<ALuint> buffers_;
+ ALuint source_;
- bool is_loaded_;
- bool is_playing_;
- bool is_looping_;
+ bool is_playing_;
+ bool is_looping_;
+ bool is_streaming_;
- std::deque<buffer_ptr> queue_;
+ std::deque<sound_handle> queue_;
+ uint64_t sample_;
+ int num_buffers_;
+ scalar buffer_size_;
- timer stream_timer_;
+ timer stream_timer_;
- static unsigned retain_count_;
- static ALCdevice* al_device_;
- static ALCcontext* al_context_;
+ sound_backend backend_;
};
-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) :
+sound::sound(const std::string& path) :
// pass through
- impl_(new sound::impl(name)) {}
+ impl_(new sound::impl(path)) {}
+
+void sound::sample(const std::string& path)
+{
+ // pass through
+ impl_->sample(path);
+}
-void sound::sample(const std::string& name)
+void sound::queue(const std::string& path)
{
// pass through
- impl_->sample(name);
+ impl_->queue(path);
}
impl_->pause();
}
-
void sound::toggle()
{
if (is_playing()) pause();
return impl_->is_playing();
}
+void sound::buffer_size(scalar seconds)
+{
+ impl_->buffer_size_ = seconds;
+}
+
void sound::position(const vector3& position)
{
}
void sound::listener_orientation(const vector3& forward,
- const vector3& up)
+ const vector3& up)
{
float vec[6];
vec[0] = float(forward[0]);
}
-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