*
**************************************************************************/
-#include <cstdio>
#include <deque>
-#include <list>
#include <stdexcept>
#include <boost/algorithm/string.hpp>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
-#include "hash.hh"
#include "log.hh"
#include "sound.hh"
#include "resource.hh"
#ifndef BUF_SIZE
-#define BUF_SIZE (4096 * 64)
+#define BUF_SIZE (4096)
#endif
-#define NUM_BUFFERS (4)
+#define NUM_SAMPLE_BITS (16)
namespace moof {
MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds);
-/*] Sound buffer
- *************************************************************************/
-
-class buffer
-{
-public:
-
- typedef hash<ALuint,int,hash_function> 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;
- }
-
- 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();
- }
- }
-
- static buffer unqueue(ALuint source)
- {
- ALuint buf = (ALuint)-1;
- alSourceUnqueueBuffers(source, 1, &buf);
- return buffer(buf);
- }
-
- void set(ALuint source) const
- {
- 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);
- }
- }
-
-
- ALuint buffer_;
- sound_backend backend_;
-
- static retcount_lookup retain_counts_;
-};
-
-buffer::retcount_lookup buffer::retain_counts_;
-
-
-
/*] Sound resource
*************************************************************************/
{
public:
- sound_resource(const std::string& path)
+ sound_resource(const std::string& path) :
+ buffer_(AL_NONE),
+ sample_(0)
{
if (ov_fopen((char*)path.c_str(), &file_) < 0)
{
~sound_resource()
{
ov_clear(&file_);
+ if (buffer_) alDeleteBuffers(1, &buffer_);
}
- bool read(buffer& buf) const
+ ALuint read_all() const
{
- if (buffer_)
- {
- buf = buffer_;
- return true;
- }
+ if (buffer_) return buffer_;
- if (ov_pcm_seek_lap(&file_, 0) != 0)
+ if (ov_pcm_seek(&file_, 0) != 0)
{
log_warning("vorbis seek error");
- return false;
+ return AL_NONE;
}
- char data[64*BUF_SIZE];
+ 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))
data + size, sizeof(data) - size,
0, 2, 1, §ion);
- if (result > 0)
+ if (0 < result)
{
size += result;
- continue;
}
else if (result == 0 && size > 0)
{
vorbis_info* info = ov_info(&file_, section);
- buffer_ = buffer(data, size,
- get_audio_format(info), info->rate);
- buf = buffer_;
- return true;
+
+ ALuint buffer;
+ alGenBuffers(1, &buffer);
+ alBufferData(buffer, get_audio_format(info),
+ data, size, info->rate);
+
+ buffer_ = buffer;
+ return buffer;
}
else
{
}
}
- if (size >= sizeof(data)) log_warning("sample is too big to play");
- return false;
+ return AL_NONE;
}
-
- bool read(buffer& buf, uint64_t& sample) const
+ bool read(ALuint buffer, uint64_t& sample) const
{
- if (ov_pcm_seek_lap(&file_, sample) != 0)
+ if ((sample == sample_ && ov_pcm_seek_lap(&file_, sample) != 0) ||
+ (sample != sample_ && ov_pcm_seek(&file_, sample) != 0))
{
- log_warning("vorbis seek error");
return false;
}
int section;
int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion);
- if (result > 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");
+
+ return false;
+ }
+
+ bool read(ALuint buffer, uint64_t& sample, sound_handle other) const
+ {
+ ov_crosslap(&other->file_, &file_);
+
+ char data[BUF_SIZE];
+ int section;
+ int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion);
+
+ if (0 < result)
{
vorbis_info* info = ov_info(&file_, section);
- buf = buffer(data, result, get_audio_format(info), info->rate);
- sample = ov_pcm_tell(&file_);
+ 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) log_warning("vorbis playback error");
return false;
}
+ // 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;
+ }
+
+
static ALenum get_audio_format(const vorbis_info* info)
{
- if (info->channels == 1) return AL_FORMAT_MONO16;
- else return AL_FORMAT_STEREO16;
+ if (info->channels == 1) return AL_FORMAT_MONO16;
+ else if (info->channels == 2) return AL_FORMAT_STEREO16;
+
+ log_error("unsupported number of channels:", info->channels);
+ return 0;
}
private:
mutable OggVorbis_File file_;
- mutable buffer buffer_;
+ mutable ALuint buffer_;
+ mutable uint64_t sample_;
+
+ sound_backend backend_;
};
impl(const std::string& name)
{
init();
- enqueue(name);
+ sample(name);
}
void init()
is_looping_ = false;
sample_ = 0;
+ buffer_size_ = SCALAR(1.0);
alGenSources(1, &source_);
~impl()
{
stop();
+ deplete_stream();
+
alDeleteSources(1, &source_);
}
- void play()
+ void deplete_stream()
{
- if (queue_.empty()) return;
-
- sound_handle handle = queue_.front();
- buffer buf;
-
- if (handle->read(buf))
+ if (is_streaming_)
{
- buf.set(source_);
- alSourcei(source_, AL_LOOPING, is_looping_);
- alSourcePlay(source_);
+ ALint queued = 0;
+ alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+ ALuint buffers[queued];
+ alSourceUnqueueBuffers(source_, queued, buffers);
+ alDeleteBuffers(queued, buffers);
}
+ else alSourcei(source_, AL_BUFFER, AL_NONE);
}
- void stream()
+ void play()
{
- if (queue_.empty()) return;
+ if (queue_.empty() || is_playing_) return;
- if (!is_playing_)
+ ALenum state;
+ alGetSourcei(source_, AL_SOURCE_STATE, &state);
+ switch (state)
{
- alSourcei(source_, AL_LOOPING, false);
+ case AL_INITIAL:
+ case AL_STOPPED:
- sound_handle handle = queue_.front();
+ 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;
- for (int i = 0; i < NUM_BUFFERS; ++i)
- {
- buffer buf;
- if (handle->read(buf, sample_))
+ case AL_PAUSED:
+
+ if (is_streaming_)
{
- buf.queue(source_);
+ start_update_timer();
+ is_playing_ = true;
}
+ break;
+ }
+
+ alSourcePlay(source_);
+ }
+
+ void start_update_timer()
+ {
+ stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
+ SCALAR(0.1), timer::repeat);
+ }
+
+ void fill_stream()
+ {
+ 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)
+ {
+ ALuint& buf = bufs[i];
+
+ if (0 < target_diff)
+ {
+ if (queue_buffer(buf)) --target_diff;
else
{
- log_error("failed to start stream");
+ alDeleteBuffers(processed - i - 1, &bufs[i+1]);
break;
}
-
- ALint queued = 0;
- alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
}
+ else alDeleteBuffers(1, &buf);
}
- if (!stream_timer_.is_valid())
+ if (stream_timer_.is_valid() && 0 < target_diff)
{
- stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
- SCALAR(0.5), timer::repeat);
+ for (int i = 0; i < target_diff; ++i)
+ {
+ if (!queue_buffer(AL_NONE)) break;
+ }
}
- alSourcePlay(source_);
- is_playing_ = true;
+ 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())
{
- 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)
+ sound_handle sound = queue_.front();
+
+ bool result = sound->read(buffer, sample_);
+ if (result)
{
- buf.queue(source_);
+ alSourceQueueBuffers(source_, 1, &buffer);
+ return true;
}
else
{
- // the buffer couldn't be streamed, so get rid of it
- queue_.pop_front();
sample_ = 0;
+ queue_.pop_front();
- if (!queue_.empty())
- {
- // begin the next buffer in the queue
- handle->read(buf, sample_);
- buf.queue(source_);
- }
- else if (is_looping_)
+ if (0 < queue_.size())
{
- // reload the same buffer
- queue_.push_back(handle);
- handle->read(buf, sample_);
- buf.queue(source_);
+ 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
+ else if (is_looping_ && !looped_once)
{
- // nothing more to play, stopping...
- stop();
- queue_.push_back(handle);
+ 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;
}
stream_timer_.invalidate();
- // TODO: clear buffers if streaming
+ sample_ = 0;
}
void pause()
stream_timer_.invalidate();
}
- void rewind()
- {
- alSourceRewind(source_);
- sample_ = 0;
- }
-
void sample(const std::string& name)
{
stop();
- alSourcei(source_, AL_BUFFER, AL_NONE);
+ deplete_stream();
queue_.clear();
-
- enqueue(name);
+ queue(name);
+ is_streaming_ = false;
}
- void enqueue(const std::string& name)
+ void queue(const std::string& name)
{
sound_handle handle = resource::load(name, "ogg");
- queue_.push_back(handle);
+ if (handle) queue_.push_back(handle);
+ is_streaming_ = true;
}
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();
+ //log_error("blaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhrrrrrg");
+ fill_stream();
// TODO: might be nice to also allow using threads for streaming
// rather than a timer, probably as a compile-time option
}
bool is_playing_;
bool is_looping_;
+ bool is_streaming_;
std::deque<sound_handle> queue_;
uint64_t sample_;
+ int num_buffers_;
+ scalar buffer_size_;
timer stream_timer_;
impl_->sample(path);
}
-void sound::enqueue(const std::string& path)
+void sound::queue(const std::string& path)
{
// pass through
- impl_->enqueue(path);
+ impl_->queue(path);
}
void sound::play()
{
// pass through
- impl_->stream();
+ impl_->play();
}
void sound::stop()
impl_->pause();
}
-void sound::rewind()
-{
- // pass through
- impl_->rewind();
-}
-
-
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)
{