*******************************************************************************/
#include <cstdio>
+#include <deque>
#include <string>
-#include <queue>
#include <vector>
#include <AL/al.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
+#include "Exception.hh"
+#include "Library.hh"
#include "Log.hh"
-#include "Mippleton.hh"
#include "Sound.hh"
#include "Timer.hh"
class Buffer;
typedef boost::shared_ptr<Buffer> BufferP;
- class Buffer : public Mippleton<Buffer>
+ class Buffer : public Library<Buffer>
{
- OggVorbis_File mOggStream;
- ALenum mFormat;
- ALsizei mFreq;
- std::vector<ALuint> mObjects;
-
public:
Buffer(const std::string& name) :
- Mippleton<Buffer>(name)
+ Library<Buffer>(name),
+ mBuffer(-1)
{
mOggStream.datasource = 0;
openFile();
~Buffer()
{
- while (!mObjects.empty())
- {
- alDeleteBuffers(1, &mObjects.back());
- mObjects.pop_back();
- }
-
if (mOggStream.datasource)
{
ov_clear(&mOggStream);
}
+ if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
}
{
logWarning("error while loading sound %s",
getName().c_str());
- throw Exception(Exception::BAD_AUDIO_FORMAT);
+ throw Exception(ErrorCode::UNKNOWN_AUDIO_FORMAT);
}
vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
if (size == 0)
{
logWarning("decoded no bytes from %s", getName().c_str());
- //throw Exception(Exception::FILE_NOT_FOUND);
+ //throw Exception("file_not_found");
return;
}
- ALuint obj;
- alGenBuffers(1, &obj);
-
- alBufferData(obj, mFormat, data, size, mFreq);
-
- mObjects.push_back(obj);
+ alGenBuffers(1, &mBuffer);
- alSourcei(source, AL_BUFFER, obj);
+ alBufferData(mBuffer, mFormat, data, size, mFreq);
+ alSourcei(source, AL_BUFFER, mBuffer);
- // don't need this anymore
+ // don't need to keep this loaded
ov_clear(&mOggStream);
mOggStream.datasource = 0;
}
-
- void beginStream(ALuint source, int nBuffers = 8)
- {
- if (!mOggStream.datasource) openFile();
- if (!mOggStream.datasource) return;
-
- ALuint objs[nBuffers];
- alGenBuffers(nBuffers, objs);
-
- for (int i = 0; i < nBuffers; ++i)
- {
- mObjects.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(mObjects.begin(), mObjects.end(), buffer);
-
- // that buffer doesn't belong to us
- if (it == mObjects.end()) return STREAM_WRONG;
-
char data[BUFFER_SIZE];
int size = 0;
}
}
- if (size == 0) return STREAM_EOF;
+ if (size == 0) return false;
alBufferData(buffer, mFormat, data, size, mFreq);
- return STREAM_OK;
+ return true;
}
- inline void rewind()
+ void rewind()
{
if (!mOggStream.datasource) openFile();
else ov_raw_seek(&mOggStream, 0);
}
- // delete unused buffers, return true if all buffers deleted
- inline bool clear()
- {
- // clear any openal errors
- alGetError();
+ private:
- while (!mObjects.empty())
- {
- ALuint buffer = mObjects.back();
- alDeleteBuffers(1, &buffer);
-
- // 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;
+ };
- mObjects.pop_back();
- }
- return true;
- }
- };
+ Impl()
+ {
+ init();
+ }
+ Impl(const std::string& name)
+ {
+ init();
+ enqueue(name);
+ }
- Impl(const std::string& name) :
- mBuffer(Buffer::getInstance(name)),
- mIsPlaying(false),
- mIsLooping(false)
+ void init()
{
ALfloat zero[] = {0.0f, 0.0f, 0.0f};
alSourcef(mSource, AL_GAIN, 1.0f);
alSourcefv(mSource, AL_POSITION, zero);
alSourcefv(mSource, AL_VELOCITY, zero);
+
+ mIsPlaying = false;
+ mIsLooping = false;
}
~Impl()
{
+ stop();
+
alDeleteSources(1, &mSource);
+
+ while (!mBufferObjects.empty())
+ {
+ alDeleteBuffers(1, &mBufferObjects.back());
+ mBufferObjects.pop_back();
+ }
}
void play()
{
+ if (mQueue.empty()) return;
+
ALenum type;
alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
if (type != AL_STATIC)
{
- mBuffer->loadAll(mSource);
+ mQueue.front()->loadAll(mSource);
}
alSourcei(mSource, AL_LOOPING, mIsLooping);
void stream()
{
- ALenum type;
- alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
+ stop();
alSourcei(mSource, AL_BUFFER, AL_NONE);
- mBuffer->rewind();
- mBuffer->beginStream(mSource);
+ mQueue.front()->rewind();
+ beginStream();
alSourcei(mSource, AL_LOOPING, AL_FALSE);
alSourcePlay(mSource);
Timer::REPEAT);
}
- inline void update()
+ void beginStream()
+ {
+ ALuint buffer;
+ for (int i = mBufferObjects.size(); i < 4; ++i)
+ {
+ alGenBuffers(1, &buffer);
+ mBufferObjects.push_back(buffer);
+ }
+ for (int i = 0; i < 4; ++i)
+ {
+ buffer = mBufferObjects[i];
+ mQueue.front()->stream(buffer);
+ alSourceQueueBuffers(mSource, 1, &buffer);
+ }
+ }
+
+
+ void update()
{
ALint finished = 0;
while (finished-- > 0)
{
- ALuint buffer;
+ ALuint bufferObj;
+ alSourceUnqueueBuffers(mSource, 1, &bufferObj);
- alSourceUnqueueBuffers(mSource, 1, &buffer);
+ BufferP buffer = mQueue.front();
+ bool streamed = buffer->stream(bufferObj);
- Buffer::StreamStatus status = mBuffer->stream(buffer);
-
- if (status == Buffer::STREAM_OK)
+ if (streamed)
{
- alSourceQueueBuffers(mSource, 1, &buffer);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
}
- else if (status == Buffer::STREAM_EOF)
+ else
{
+ // the buffer couldn't be streamed, so get rid of it
+ mQueue.pop_front();
+
if (!mQueue.empty())
{
// begin the next buffer in the queue
- mExpired.push_back(mBuffer);
- mBuffer = mQueue.front();
- mQueue.pop();
- mBuffer->beginStream(mSource, 1);
+ mQueue.front()->rewind();
+ mQueue.front()->stream(bufferObj);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
+ logInfo("loading new buffer");
}
else if (mIsLooping)
{
- // restart from the beginning
- mBuffer->rewind();
- mBuffer->stream(buffer);
- alSourceQueueBuffers(mSource, 1, &buffer);
+ // reload the same buffer
+ mQueue.push_back(buffer);
+ buffer->rewind();
+ buffer->stream(bufferObj);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
+ logInfo("looping same buffer");
}
}
- else if (status == Buffer::STREAM_WRONG)
- {
- clear();
- mBuffer->beginStream(mSource, 1);
- }
}
ALenum 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
+ // means we didn't queue enough and the audio skipped :-(
if (mIsPlaying && state != AL_PLAYING)
{
alSourcePlay(mSource);
}
}
- inline void clear()
- {
- // try to remove expired buffers
- std::vector<BufferP>::iterator it;
- for (it = mExpired.end() - 1; it >= mExpired.begin(); --it)
- {
- if ((*it)->clear()) mExpired.erase(it);
- }
- }
-
void stop()
{
alSourceStop(mSource);
mIsPlaying = false;
+
+ mStreamTimer.invalidate();
}
- inline void pause()
+ void pause()
{
alSourcePause(mSource);
mIsPlaying = false;
+
+ mStreamTimer.invalidate();
}
- inline void resume()
+ void resume()
{
alSourcePlay(mSource);
mIsPlaying = true;
+
+ ALenum type;
+ alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
+
+ if (type == AL_STREAMING)
+ {
+ mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
+ 1.0, Timer::REPEAT);
+ }
}
- inline void setSample(const std::string& name)
+ void setSample(const std::string& name)
{
bool playing = isPlaying();
ALenum type;
alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
stop();
+ mQueue.clear();
//alSourcei(mSource, AL_BUFFER, AL_NONE);
- mBuffer = Buffer::getInstance(name);
+ enqueue(name);
- if (type == AL_STREAMING)
- {
- if (playing) stream();
- }
- else
+ if (playing)
{
- if (playing) play();
+ if (type == AL_STREAMING) stream();
+ else play();
}
}
- inline void enqueue(const std::string& name)
+ void enqueue(const std::string& name)
{
BufferP buffer = Buffer::getInstance(name);
- mQueue.push(buffer);
+ mQueue.push_back(buffer);
}
- inline bool isPlaying() const
+ bool isPlaying() const
{
if (mIsPlaying) return true;
}
- inline void setLooping(bool looping)
+ void setLooping(bool looping)
{
mIsLooping = looping;
}
- ALuint mSource;
- BufferP mBuffer;
-
- bool mIsPlaying;
- bool mIsLooping;
-
- std::queue<BufferP> mQueue;
- std::vector<BufferP> mExpired;
-
- Timer mStreamTimer;
-
void streamUpdate(Timer& timer, Scalar t)
{
// don't let the music die!
// TODO - might be nice to also allow using threads for streaming rather
// than a timer, probably as a compile-time option
}
+
+
+ ALuint mSource;
+ std::vector<ALuint> mBufferObjects;
+
+ bool mIsPlaying;
+ bool mIsLooping;
+
+ std::deque<BufferP> mQueue;
+
+ Timer mStreamTimer;
};
+
+Sound::Sound() :
+ // pass through
+ mImpl(new Sound::Impl) {}
+
Sound::Sound(const std::string& name) :
// pass through
mImpl(new Sound::Impl(name)) {}