*******************************************************************************/
-#include <iostream>
+#include <cstdio>
#include <string>
+#include <queue>
+#include <vector>
-#include <SDL/SDL.h>
-#include <SDL/SDL_sound.h>
#include <AL/al.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+#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
{
- static ALenum getAudioFormat(const Sound_AudioInfo& audioInfo)
+ static ALenum getAudioFormat(const vorbis_info* audioInfo)
{
- if (audioInfo.format == AUDIO_U8 || audioInfo.format == AUDIO_S8)
- {
- if (audioInfo.channels == 1) return AL_FORMAT_MONO8;
- else return AL_FORMAT_STEREO8;
- }
- else
- {
- if (audioInfo.channels == 1) return AL_FORMAT_MONO16;
- else return AL_FORMAT_STEREO16;
- }
+ if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
+ else return AL_FORMAT_STEREO16;
}
- struct Buffer : public Mippleton<Buffer>
+ class Buffer;
+ typedef boost::shared_ptr<Buffer> BufferP;
+
+ class Buffer : public Mippleton<Buffer>
{
+ OggVorbis_File oggStream;
+ ALenum audioFormat;
+ ALsizei audioFreq;
+ std::vector<ALuint> objects;
+
+ public:
+
Buffer(const std::string& name) :
- Mippleton<Buffer>(name),
- object(0)
- {}
+ Mippleton<Buffer>(name)
+ {
+ oggStream.datasource = 0;
+ openFile();
+ }
~Buffer()
{
- alDeleteBuffers(1, &object);
+ while (!objects.empty())
+ {
+ alDeleteBuffers(1, &objects.back());
+ objects.pop_back();
+ }
+
+ if (oggStream.datasource)
+ {
+ ov_clear(&oggStream);
+ }
}
- void loadFromFile(const std::string& filePath, bool stream)
+
+
+ void openFile()
{
- if (object != 0) return;
+ if (oggStream.datasource)
+ {
+ ov_clear(&oggStream);
+ oggStream.datasource = 0;
+ }
- Sound_Sample* sound = Sound_NewSampleFromFile(filePath.c_str(),
- NULL, 8096);
+ std::string filePath = Sound::getPath(getName());
+ int result = ov_fopen((char*)filePath.c_str(), &oggStream);
- if (!sound)
+ if (result < 0)
{
- std::cerr << "could not load sound from file" << std::endl;
- exit(1);
+ logWarning("error while loading sound %s",
+ getName().c_str());
+ throw Exception(Exception::BAD_AUDIO_FORMAT);
}
- unsigned decoded = Sound_DecodeAll(sound);
- if (decoded == 0)
+ vorbis_info* vorbisInfo = ov_info(&oggStream, -1);
+ audioFormat = getAudioFormat(vorbisInfo);
+ audioFreq = vorbisInfo->rate;
+
+ logDebug(" channels: %d", vorbisInfo->channels);
+ logDebug(" frequency: %d", vorbisInfo->rate);
+ }
+
+
+ void loadAll(ALuint source)
+ {
+ if (!oggStream.datasource) openFile();
+ if (!oggStream.datasource) return;
+
+ char data[BUFFER_SIZE];
+ int size = 0;
+
+ for (;;)
+ {
+ int section;
+ int result = ov_read(&oggStream, data + size,
+ BUFFER_SIZE - size, 0, 2, 1, §ion);
+
+ if (result > 0)
+ {
+ size += result;
+ }
+ else
+ {
+ if (result < 0) logWarning("vorbis playback error");
+ break;
+ }
+ }
+ if (size == 0)
+ {
+ logWarning("decoded no bytes from %s", getName().c_str());
+ //throw Exception(Exception::FILE_NOT_FOUND);
+ return;
+ }
+
+ ALuint obj;
+ alGenBuffers(1, &obj);
+
+ alBufferData(obj, audioFormat, data, size, audioFreq);
+
+ objects.push_back(obj);
+
+ alSourcei(source, AL_BUFFER, obj);
+
+ // don't need this anymore
+ ov_clear(&oggStream);
+ oggStream.datasource = 0;
+ }
+
+
+ void beginStream(ALuint source, int nBuffers = 8)
+ {
+ 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)
+ {
+ 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)
{
- std::cout << "decoded no bytes" << std::endl;
- exit(1);
+ int section;
+ int result = ov_read(&oggStream, data + size,
+ BUFFER_SIZE - size, 0, 2, 1, §ion);
+
+ if (result > 0)
+ {
+ size += result;
+ }
+ else
+ {
+ if (result < 0) logWarning("vorbis playback error");
+ break;
+ }
}
- std::cerr << "buffer size: " << sound->buffer_size << std::endl;
- std::cerr << "channels: " << (int)sound->actual.channels << std::endl;
- std::cerr << "format: " << sound->actual.format << std::endl;
- std::cerr << "frequency: " << sound->actual.rate << std::endl;
- alGenBuffers(1, &object);
- alBufferData(object, getAudioFormat(sound->actual), sound->buffer,
- sound->buffer_size, sound->actual.rate);
+ if (size == 0) return STREAM_EOF;
+
+ alBufferData(buffer, audioFormat, data, size, audioFreq);
- Sound_FreeSample(sound);
+ return STREAM_OK;
}
- ALuint object;
+ inline void rewind()
+ {
+ if (!oggStream.datasource) openFile();
+ else ov_raw_seek(&oggStream, 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);
- //ALfloat location[] = {0.0f, 0.0f, 0.0f};
- //ALfloat location2[] = {0.0f, 0.0f, 0.0f};
- //ALfloat orient[] = {0.0f, 0.0f, -1.0f, 0.0, 1.0, 0.0};
+ // 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;
+ objects.pop_back();
+ }
- //alListenerfv(AL_POSITION, location);
- //alListenerfv(AL_VELOCITY, location);
- //alListenerfv(AL_VELOCITY, orient);
+ return true;
+ }
};
- Impl(const std::string& name, bool stream = false) :
- buffer_(Buffer::retain(name), Buffer::release)
- {
- if (!stream) buffer_->loadFromFile(Sound::getPath(name), stream);
- else buffer_->loadFromFile(SoundStream::getPath(name), stream);
- ALfloat location[] = {0.0f, 0.0f, 0.0f};
+ Impl(const std::string& name) :
+ buffer_(Buffer::getInstance(name)),
+ playing_(false),
+ looping_(false)
+ {
+ ALfloat zero[] = {0.0f, 0.0f, 0.0f};
alGenSources(1, &source_);
- alSourcei(source_, AL_BUFFER, buffer_->object);
+
alSourcef(source_, AL_PITCH, 1.0f);
alSourcef(source_, AL_GAIN, 1.0f);
- alSourcefv(source_, AL_POSITION, location);
- alSourcefv(source_, AL_VELOCITY, location);
- alSourcei(source_, AL_LOOPING, AL_FALSE);
+ alSourcefv(source_, AL_POSITION, zero);
+ alSourcefv(source_, AL_VELOCITY, zero);
}
~Impl()
}
- void update()
+ void play()
{
+ ALenum type;
+ alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+ if (type != AL_STATIC)
+ {
+ buffer_->loadAll(source_);
+ }
+
+ alSourcei(source_, AL_LOOPING, looping_);
+ alSourcePlay(source_);
+ playing_ = true;
}
- boost::shared_ptr<Buffer> buffer_;
- ALuint source_;
-};
+ void stream()
+ {
+ ALenum type;
+ alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+ alSourcei(source_, AL_BUFFER, AL_NONE);
+ buffer_->rewind();
+ buffer_->beginStream(source_);
+
+ alSourcei(source_, AL_LOOPING, AL_FALSE);
+ alSourcePlay(source_);
+ playing_ = true;
+
+ streamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2), 1.0,
+ Timer::REPEAT);
+ }
+
+ inline void update()
+ {
+ ALint finished = 0;
+
+ alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
+
+ while (finished-- > 0)
+ {
+ ALuint buffer;
+
+ alSourceUnqueueBuffers(source_, 1, &buffer);
+
+ Buffer::StreamStatus status = buffer_->stream(buffer);
+
+ if (status == Buffer::STREAM_OK)
+ {
+ alSourceQueueBuffers(source_, 1, &buffer);
+ }
+ else if (status == Buffer::STREAM_EOF)
+ {
+ if (!queue_.empty())
+ {
+ // begin the next buffer in the queue
+ expired_.push_back(buffer_);
+ buffer_ = queue_.front();
+ queue_.pop();
+ buffer_->beginStream(source_, 1);
+ }
+ else if (looping_)
+ {
+ // restart from the beginning
+ buffer_->rewind();
+ buffer_->stream(buffer);
+ alSourceQueueBuffers(source_, 1, &buffer);
+ }
+ }
+ else if (status == Buffer::STREAM_WRONG)
+ {
+ clear();
+ buffer_->beginStream(source_, 1);
+ }
+ }
+
+ 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 (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)
+ {
+ if ((*it)->clear()) expired_.erase(it);
+ }
+ }
+
+
+ void stop()
+ {
+ alSourceStop(source_);
+ playing_ = false;
+ }
+
+ inline void pause()
+ {
+ alSourcePause(source_);
+ playing_ = false;
+ }
+
+ inline void resume()
+ {
+ alSourcePlay(source_);
+ playing_ = true;
+ }
+
+
+ inline void setSample(const std::string& name)
+ {
+ bool playing = isPlaying();
+ ALenum type;
+ alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+ stop();
+
+ //alSourcei(source_, AL_BUFFER, AL_NONE);
+ buffer_ = Buffer::getInstance(name);
+
+ if (type == AL_STREAMING)
+ {
+ if (playing) stream();
+ }
+ else
+ {
+ if (playing) play();
+ }
+ }
+
+ inline void enqueue(const std::string& name)
+ {
+ BufferP buffer = Buffer::getInstance(name);
+ queue_.push(buffer);
+ }
+
+
+ inline bool isPlaying() const
+ {
+ if (playing_) return true;
+
+ ALenum state;
+ alGetSourcei(source_, AL_SOURCE_STATE, &state);
+
+ return state == AL_PLAYING;
+ }
+
+
+ inline void setLooping(bool looping)
+ {
+ looping_ = looping;
+
+ ALenum type;
+ alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+ if (type != AL_STREAMING)
+ {
+ alSourcei(source_, AL_LOOPING, looping_);
+ }
+ }
+
+
+ ALuint source_;
+ BufferP buffer_;
+
+ bool playing_;
+ bool looping_;
+
+ std::queue<BufferP> queue_;
+ std::vector<BufferP> expired_;
+
+ Timer streamTimer;
+
+ void streamUpdate(Timer& timer, Scalar t)
+ {
+ // don't let the music die!
+ update();
+ }
+};
Sound::Sound(const std::string& name) :
// pass through
void Sound::play()
{
- alSourceRewind(impl_->source_);
- alSourcePlay(impl_->source_);
+ // pass through
+ impl_->play();
+}
+
+void Sound::stream()
+{
+ // pass through
+ impl_->stream();
}
-std::string Sound::getPath(const std::string& name)
+void Sound::stop()
{
- std::string path = Resource::getPath("sounds/" + name + ".ogg");
- return path;
+ // pass through
+ impl_->stop();
+}
+
+void Sound::pause()
+{
+ // pass through
+ impl_->pause();
+}
+
+void Sound::resume()
+{
+ // pass through
+ impl_->resume();
}
+void Sound::toggle()
+{
+ if (impl_->playing_) pause();
+ else resume();
+}
-//##############################################################################
+void Sound::setSample(const std::string& name)
+{
+ // pass through
+ impl_->setSample(name);
+}
-SoundStream::SoundStream(const std::string& name)
+void Sound::enqueue(const std::string& name)
+{
// pass through
- //impl_(name, true) {}
+ impl_->enqueue(name);
+}
+
+
+bool Sound::isPlaying() const
{
- impl_ = boost::shared_ptr<Sound::Impl>(new Sound::Impl(name, true));
+ // pass through
+ return impl_->isPlaying();
}
+void Sound::setPosition(Vector3 position)
+{
+ float p[3] = {position[0], position[1], position[2]};
+ alSourcefv(impl_->source_, AL_POSITION, p);
+}
-void SoundStream::update(Scalar t, Scalar dt)
+void Sound::setVelocity(Vector3 velocity)
+{
+ float v[3] = {velocity[0], velocity[1], velocity[2]};
+ alSourcefv(impl_->source_, AL_VELOCITY, v);
+}
+
+void Sound::setGain(Scalar gain)
+{
+ alSourcef(impl_->source_, AL_GAIN, float(gain));
+}
+
+void Sound::setPitch(Scalar pitch)
+{
+ alSourcef(impl_->source_, AL_PITCH, float(pitch));
+}
+
+void Sound::setLooping(bool looping)
{
// pass through
- impl_->update();
+ impl_->setLooping(looping);
}
-std::string SoundStream::getPath(const std::string& name)
+std::string Sound::getPath(const std::string& name)
{
- std::string path = Resource::getPath("sounds/" + name + ".xm");
+ std::string path = Resource::getPath("sounds/" + name + ".ogg");
return path;
}