*******************************************************************************/
#include <cstdio>
+#include <deque>
+#include <list>
#include <string>
-#include <queue>
-#include <vector>
+
+#include <boost/algorithm/string.hpp>
#include <AL/al.h>
+#include <AL/alc.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
+#include "Error.hh"
+#include "Manager.hh"
#include "Log.hh"
-#include "Mippleton.hh"
#include "Sound.hh"
+#include "Timer.hh"
#define BUFFER_SIZE (64 * 1024)
//#define BUFFER_SIZE (5*2048)
namespace Mf {
-struct Sound::Impl
+class Sound::Impl
{
+public:
static ALenum getAudioFormat(const vorbis_info* audioInfo)
{
else return AL_FORMAT_STEREO16;
}
+
class Buffer;
typedef boost::shared_ptr<Buffer> BufferP;
- class Buffer : public Mippleton<Buffer>
+ class Buffer : public Manager<Buffer>
{
- OggVorbis_File oggStream;
- ALenum audioFormat;
- ALsizei audioFreq;
- std::vector<ALuint> objects;
-
public:
- Buffer(const std::string& name) :
- Mippleton<Buffer>(name)
+ Buffer() :
+ mBuffer(-1)
{
- oggStream.datasource = 0;
- openFile();
+ mOggStream.datasource = 0;
}
~Buffer()
{
- while (!objects.empty())
- {
- alDeleteBuffers(1, &objects.back());
- objects.pop_back();
- }
-
- if (oggStream.datasource)
+ if (mOggStream.datasource)
{
- ov_clear(&oggStream);
+ ov_clear(&mOggStream);
}
+ if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
}
- void openFile()
+ void init(const std::string& name)
{
- if (oggStream.datasource)
+ if (mOggStream.datasource)
{
- ov_clear(&oggStream);
- oggStream.datasource = 0;
+ ov_clear(&mOggStream);
+ mOggStream.datasource = 0;
}
- std::string filePath = Sound::getPath(getName());
- int result = ov_fopen((char*)filePath.c_str(), &oggStream);
+ std::string path = Sound::getPath(name);
+ int result = ov_fopen((char*)path.c_str(), &mOggStream);
if (result < 0)
{
- logWarning("error while loading sound %s",
- getName().c_str());
- throw Exception(Exception::BAD_AUDIO_FORMAT);
+ logWarning << "couldn't load sound: " << path << std::endl;
+ throw Error(Error::UNKNOWN_AUDIO_FORMAT, path);
}
- vorbis_info* vorbisInfo = ov_info(&oggStream, -1);
- audioFormat = getAudioFormat(vorbisInfo);
- audioFreq = vorbisInfo->rate;
-
- logDebug(" channels: %d", vorbisInfo->channels);
- logDebug(" frequency: %d", vorbisInfo->rate);
+ vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
+ mFormat = getAudioFormat(vorbisInfo);
+ mFreq = vorbisInfo->rate;
}
void loadAll(ALuint source)
{
- if (!oggStream.datasource) openFile();
- if (!oggStream.datasource) return;
+ if (!mOggStream.datasource) init(getName());
+ if (!mOggStream.datasource) return;
char data[BUFFER_SIZE];
int size = 0;
for (;;)
{
int section;
- int result = ov_read(&oggStream, data + size,
+ int result = ov_read(&mOggStream, data + size,
BUFFER_SIZE - size, 0, 2, 1, §ion);
if (result > 0)
}
if (size == 0)
{
- logWarning("decoded no bytes from %s", getName().c_str());
- //throw Exception(Exception::FILE_NOT_FOUND);
+ logWarning << "decoded no bytes from "
+ << getName() << std::endl;
return;
}
- ALuint obj;
- alGenBuffers(1, &obj);
-
- alBufferData(obj, audioFormat, data, size, audioFreq);
+ alGenBuffers(1, &mBuffer);
- objects.push_back(obj);
+ alBufferData(mBuffer, mFormat, data, size, mFreq);
+ alSourcei(source, AL_BUFFER, mBuffer);
- alSourcei(source, AL_BUFFER, obj);
-
- // don't need this anymore
- ov_clear(&oggStream);
- oggStream.datasource = 0;
+ // don't need to keep this loaded
+ ov_clear(&mOggStream);
+ mOggStream.datasource = 0;
}
-
- void beginStream(ALuint source, int nBuffers = 4)
- {
- if (!oggStream.datasource) openFile();
- if (!oggStream.datasource) return;
-
- ALuint objs[nBuffers];
- alGenBuffers(nBuffers, objs);
-
- for (int i = 0; i < nBuffers; ++i)
- {
- objects.push_back(objs[i]);
- stream(objs[i]);
- }
-
- alSourceQueueBuffers(source, nBuffers, objs);
- }
-
- enum StreamStatus
- {
- STREAM_OK = 0,
- STREAM_EOF = 1,
- STREAM_WRONG = 2
- };
-
- StreamStatus stream(ALuint buffer)
+ bool stream(ALuint buffer)
{
- std::vector<ALuint>::iterator it =
- std::find(objects.begin(), objects.end(), buffer);
-
- // that buffer doesn't belong to us
- if (it == objects.end()) return STREAM_WRONG;
-
char data[BUFFER_SIZE];
int size = 0;
while (size < BUFFER_SIZE)
{
int section;
- int result = ov_read(&oggStream, data + size,
+ int result = ov_read(&mOggStream, data + size,
BUFFER_SIZE - size, 0, 2, 1, §ion);
if (result > 0)
}
}
- if (size == 0) return STREAM_EOF;
+ if (size == 0) return false;
- alBufferData(buffer, audioFormat, data, size, audioFreq);
+ alBufferData(buffer, mFormat, data, size, mFreq);
- return STREAM_OK;
+ return true;
}
- inline void rewind()
+ void rewind()
{
- if (!oggStream.datasource) openFile();
- else ov_raw_seek(&oggStream, 0);
+ if (!mOggStream.datasource) init(getName());
+ else ov_raw_seek(&mOggStream, 0);
}
- // delete unused buffers, return true if all buffers deleted
- inline bool clear()
- {
- // clear any openal errors
- alGetError();
-
- while (!objects.empty())
- {
- ALuint buffer = objects.back();
- alDeleteBuffers(1, &buffer);
+ private:
- // if an error occured, the buffer was not deleted because it's
- // still in use by some source
- if (alGetError() != AL_NO_ERROR) return false;
+ OggVorbis_File mOggStream;
+ ALenum mFormat;
+ ALsizei mFreq;
+ ALuint mBuffer;
+ };
- objects.pop_back();
- }
- return true;
- }
- };
+ Impl()
+ {
+ init();
+ }
+ Impl(const std::string& name)
+ {
+ init();
+ enqueue(name);
+ }
- Impl(const std::string& name) :
- buffer_(Buffer::getInstance(name)),
- playing_(false),
- looping_(false)
+ void init()
{
+ retainBackend();
+
+ mIsLoaded = false;
+ mIsPlaying = false;
+ mIsLooping = false;
+
+ alGenSources(1, &mSource);
+
ALfloat zero[] = {0.0f, 0.0f, 0.0f};
-
- alGenSources(1, &source_);
+ alSourcef(mSource, AL_PITCH, 1.0f);
+ alSourcef(mSource, AL_GAIN, 1.0f);
+ alSourcefv(mSource, AL_POSITION, zero);
+ alSourcefv(mSource, AL_VELOCITY, zero);
- alSourcef(source_, AL_PITCH, 1.0f);
- alSourcef(source_, AL_GAIN, 1.0f);
- alSourcefv(source_, AL_POSITION, zero);
- alSourcefv(source_, AL_VELOCITY, zero);
+ alSourcei(mSource, AL_LOOPING, mIsLooping);
}
~Impl()
{
- alDeleteSources(1, &source_);
+ stop();
+
+ alDeleteSources(1, &mSource);
+
+ while (!mBuffers.empty())
+ {
+ alDeleteBuffers(1, &mBuffers.back());
+ mBuffers.pop_back();
+ }
+
+ releaseBackend();
}
void play()
{
- ALenum type;
- alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+ if (mQueue.empty()) return;
- if (type != AL_STATIC)
- {
- buffer_->loadAll(source_);
- }
+ if (!mIsLoaded) mQueue.front()->loadAll(mSource);
- alSourcei(source_, AL_LOOPING, looping_);
- alSourcePlay(source_);
- playing_ = true;
+ alSourcePlay(mSource);
+ mIsLoaded = true;
}
- void stream()
+ void playStream()
{
- ALenum type;
- alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+ if (mQueue.empty()) return;
- alSourcei(source_, AL_BUFFER, AL_NONE);
- buffer_->rewind();
- buffer_->beginStream(source_);
+ if (!mIsPlaying)
+ {
+ alSourcei(mSource, AL_LOOPING, false);
+ bufferStream();
+ }
- alSourcei(source_, AL_LOOPING, AL_FALSE);
- alSourcePlay(source_);
- playing_ = true;
+ if (!mStreamTimer.isValid())
+ {
+ mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
+ 1.0, Timer::REPEAT);
+ }
+
+ alSourcePlay(mSource);
+ mIsPlaying = true;
}
- inline void update()
+ void bufferStream()
+ {
+ ALuint buffer;
+ for (int i = mBuffers.size(); i <= 8; ++i)
+ {
+ alGenBuffers(1, &buffer);
+
+ if (mQueue.front()->stream(buffer))
+ {
+ alSourceQueueBuffers(mSource, 1, &buffer);
+ mBuffers.push_back(buffer);
+ }
+ else
+ {
+ alDeleteBuffers(1, &buffer);
+ break;
+ }
+ }
+ }
+
+
+ void update()
{
ALint finished = 0;
- alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
+ alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &finished);
while (finished-- > 0)
{
- ALuint buffer;
+ ALuint bufferObj;
+ alSourceUnqueueBuffers(mSource, 1, &bufferObj);
- alSourceUnqueueBuffers(source_, 1, &buffer);
+ BufferP buffer = mQueue.front();
+ bool streamed = buffer->stream(bufferObj);
- Buffer::StreamStatus status = buffer_->stream(buffer);
-
- if (status == Buffer::STREAM_OK)
+ if (streamed)
{
- alSourceQueueBuffers(source_, 1, &buffer);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
}
- else if (status == Buffer::STREAM_EOF)
+ else
{
- if (!queue_.empty())
+ // the buffer couldn't be streamed, so get rid of it
+ mQueue.pop_front();
+
+ if (!mQueue.empty())
{
// begin the next buffer in the queue
- expired_.push_back(buffer_);
- buffer_ = queue_.front();
- queue_.pop();
- buffer_->beginStream(source_, 1);
+ mQueue.front()->rewind();
+ mQueue.front()->stream(bufferObj);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
+ logInfo("loading new buffer");
+
+ // queue up any unused buffers
+ bufferStream();
}
- else if (looping_)
+ else if (mIsLooping)
{
- // restart from the beginning
- buffer_->rewind();
- buffer_->stream(buffer);
- alSourceQueueBuffers(source_, 1, &buffer);
+ // reload the same buffer
+ mQueue.push_back(buffer);
+ buffer->rewind();
+ buffer->stream(bufferObj);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
+ logInfo("looping same buffer");
+ }
+ else
+ {
+ // nothing more to play, stopping...
+ mIsPlaying = false;
+ std::remove(mBuffers.begin(), mBuffers.end(), bufferObj);
}
- }
- else if (status == Buffer::STREAM_WRONG)
- {
- clear();
- buffer_->beginStream(source_, 1);
}
}
ALenum state;
- alGetSourcei(source_, AL_SOURCE_STATE, &state);
+ alGetSourcei(mSource, 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 (playing_ && state != AL_PLAYING)
- {
- alSourcePlay(source_);
- }
- }
-
- inline void clear()
- {
- // try to remove expired buffers
- std::vector<BufferP>::iterator it;
- for (it = expired_.end() - 1; it >= expired_.begin(); --it)
+ // means we didn't queue enough and the audio skipped :-(
+ if (mIsPlaying && state != AL_PLAYING)
{
- if ((*it)->clear()) expired_.erase(it);
+ alSourcePlay(mSource);
}
}
void stop()
{
- alSourceStop(source_);
- playing_ = false;
- }
+ alSourceStop(mSource);
+ mIsPlaying = false;
- inline void pause()
- {
- alSourcePause(source_);
- playing_ = false;
+ mStreamTimer.invalidate();
}
- inline void resume()
+ void pause()
{
- alSourcePlay(source_);
- playing_ = true;
+ alSourcePause(mSource);
+ mIsPlaying = false;
+
+ mStreamTimer.invalidate();
}
- inline void setSample(const std::string& name)
+ void setSample(const std::string& name)
{
- bool playing = isPlaying();
- ALenum type;
- alGetSourcei(source_, AL_SOURCE_TYPE, &type);
-
stop();
+ alSourcei(mSource, AL_BUFFER, AL_NONE);
- //alSourcei(source_, AL_BUFFER, AL_NONE);
- buffer_ = Buffer::getInstance(name);
+ mQueue.clear();
+ mIsLoaded = false;
- if (type == AL_STREAMING)
- {
- if (playing) stream();
- }
- else
+ enqueue(name);
+
+ while (!mBuffers.empty())
{
- if (playing) play();
+ alDeleteBuffers(1, &mBuffers.back());
+ mBuffers.pop_back();
}
}
- inline void enqueue(const std::string& name)
+ void enqueue(const std::string& name)
{
BufferP buffer = Buffer::getInstance(name);
- queue_.push(buffer);
+ mQueue.push_back(buffer);
}
- inline bool isPlaying() const
+ bool isPlaying() const
{
- if (playing_) return true;
+ if (mIsPlaying) return true;
ALenum state;
- alGetSourcei(source_, AL_SOURCE_STATE, &state);
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
- inline void setLooping(bool looping)
+ void setLooping(bool looping)
{
- looping_ = looping;
+ mIsLooping = looping;
ALenum type;
- alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+ alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
if (type != AL_STREAMING)
{
- alSourcei(source_, AL_LOOPING, looping_);
+ alSourcei(mSource, AL_LOOPING, mIsLooping);
+ }
+ }
+
+
+ void streamUpdate(Timer& timer, Scalar t)
+ {
+ // don't let the music die!
+ update();
+ // TODO - might be nice to also allow using threads for streaming rather
+ // than a timer, probably as a compile-time option
+ }
+
+ static void retainBackend()
+ {
+ if (gRetainCount++ == 0)
+ {
+ gAlDevice = alcOpenDevice(0);
+ gAlContext = alcCreateContext(gAlDevice, 0);
+ if (!gAlDevice || !gAlContext)
+ {
+ const char* error = alcGetString(gAlDevice,
+ alcGetError(gAlDevice));
+ logError << "audio subsystem initialization failure: "
+ << error << std::endl;
+ }
+ else
+ {
+ alcMakeContextCurrent(gAlContext);
+ logInfo << "opened sound device `"
+ << alcGetString(gAlDevice, ALC_DEFAULT_DEVICE_SPECIFIER)
+ << "'" << std::endl;
+ }
}
}
+ static void releaseBackend()
+ {
+ if (--gRetainCount == 0)
+ {
+ alcMakeContextCurrent(0);
+ alcDestroyContext(gAlContext);
+ alcCloseDevice(gAlDevice);
+ }
+ }
- ALuint source_;
- BufferP buffer_;
- bool playing_;
- bool looping_;
+ ALuint mSource;
+ std::list<ALuint> mBuffers;
- std::queue<BufferP> queue_;
- std::vector<BufferP> expired_;
+ bool mIsLoaded;
+ bool mIsPlaying;
+ bool mIsLooping;
+
+ std::deque<BufferP> mQueue;
+
+ Timer mStreamTimer;
+
+ static unsigned gRetainCount;
+ static ALCdevice* gAlDevice;
+ static ALCcontext* gAlContext;
};
+unsigned Sound::Impl::gRetainCount = 0;
+ALCdevice* Sound::Impl::gAlDevice = 0;
+ALCcontext* Sound::Impl::gAlContext = 0;
+
+
+Sound::Sound() :
+ // pass through
+ mImpl(new Sound::Impl) {}
+
Sound::Sound(const std::string& name) :
// pass through
- impl_(new Sound::Impl(name)) {}
+ mImpl(new Sound::Impl(name)) {}
-void Sound::play()
+void Sound::setSample(const std::string& name)
{
// pass through
- impl_->play();
+ mImpl->setSample(name);
}
-void Sound::stream()
+void Sound::play()
{
// pass through
- impl_->stream();
+ mImpl->play();
}
-void Sound::update(Scalar t, Scalar dt)
+void Sound::stop()
{
// pass through
- impl_->update();
+ mImpl->stop();
}
-
-void Sound::stop()
+void Sound::pause()
{
// pass through
- impl_->stop();
+ mImpl->pause();
}
-void Sound::pause()
+
+void Sound::toggle()
{
- // pass through
- impl_->pause();
+ if (isPlaying()) pause();
+ else play();
}
-void Sound::resume()
+bool Sound::isPlaying() const
{
// pass through
- impl_->resume();
+ return mImpl->isPlaying();
}
-void Sound::toggle()
+
+void Sound::setPosition(const Vector3& position)
{
- if (impl_->playing_) pause();
- else resume();
+ float vec[3] = {position[0], position[1], position[2]};
+ alSourcefv(mImpl->mSource, AL_POSITION, vec);
}
-
-void Sound::setSample(const std::string& name)
+void Sound::setVelocity(const Vector3& velocity)
{
- // pass through
- impl_->setSample(name);
+ float vec[3] = {velocity[0], velocity[1], velocity[2]};
+ alSourcefv(mImpl->mSource, AL_VELOCITY, vec);
}
-void Sound::enqueue(const std::string& name)
+void Sound::setGain(Scalar gain)
{
- // pass through
- impl_->enqueue(name);
+ alSourcef(mImpl->mSource, AL_GAIN, float(gain));
}
+void Sound::setPitch(Scalar pitch)
+{
+ alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
+}
-bool Sound::isPlaying() const
+void Sound::setLooping(bool looping)
{
// pass through
- return impl_->isPlaying();
+ mImpl->setLooping(looping);
}
-void Sound::setPosition(Vector3 position)
+
+void Sound::setListenerPosition(const Vector3& position)
{
- float p[3] = {position[0], position[1], position[2]};
- alSourcefv(impl_->source_, AL_POSITION, p);
+ //alListener3f(AL_POSITION, float(position[0]), float(position[1]),
+ //float(position[2]));
+ float vec[] = {position[0], position[1], position[2]};
+ alListenerfv(AL_POSITION, vec);
}
-void Sound::setVelocity(Vector3 velocity)
+void Sound::setListenerVelocity(const Vector3& velocity)
{
- float v[3] = {velocity[0], velocity[1], velocity[2]};
- alSourcefv(impl_->source_, AL_VELOCITY, v);
+ //alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]),
+ //float(velocity[2]));
+ float vec[] = {velocity[0], velocity[1], velocity[2]};
+ alListenerfv(AL_VELOCITY, vec);
}
-void Sound::setGain(Scalar gain)
+void Sound::setListenerOrientation(const Vector3& forward, const Vector3& up)
{
- alSourcef(impl_->source_, AL_GAIN, float(gain));
+ 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::setPitch(Scalar pitch)
+
+std::string Sound::getPath(const std::string& name)
{
- alSourcef(impl_->source_, AL_PITCH, float(pitch));
+ if (boost::find_last(name, ".ogg"))
+ {
+ return Resource::getPath(name);
+ }
+ else
+ {
+ std::string path("sounds/");
+ path += name;
+ path += ".ogg";
+ return Resource::getPath(path);
+ }
}
-void Sound::setLooping(bool looping)
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+void SoundStream::enqueue(const std::string& name)
{
// pass through
- impl_->setLooping(looping);
+ mImpl->enqueue(name);
}
-std::string Sound::getPath(const std::string& name)
+void SoundStream::play()
{
- std::string path = Resource::getPath("sounds/" + name + ".ogg");
- return path;
+ // pass through
+ mImpl->playStream();
}