-/*******************************************************************************
-
- Copyright (c) 2009, Charles McGarvey
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-*******************************************************************************/
-
-#include <iostream>
+/*] Copyright (c) 2009-2010, Charles McGarvey [**************************
+**] All rights reserved.
+*
+* vi:ts=4 sw=4 tw=75
+*
+* Distributable under the terms and conditions of the 2-clause BSD license;
+* see the file COPYING for a complete text of the license.
+*
+**************************************************************************/
+
+#include <cstdio>
+#include <deque>
+#include <list>
#include <string>
-#include <SDL/SDL.h>
-#include <SDL/SDL_sound.h>
+#include <boost/algorithm/string.hpp>
+
#include <AL/al.h>
+#include <AL/alc.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
-#include "Mippleton.hh"
+#include "Error.hh"
+#include "Manager.hh"
+#include "Log.hh"
#include "Sound.hh"
+#include "Timer.hh"
-#define BUFFER_SIZE (8 * 4096)
+#define BUFFER_SIZE (64 * 1024)
+//#define BUFFER_SIZE (5*2048)
namespace Mf {
-struct Sound::Impl
+class Sound::Impl
{
+public:
- 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 Manager<Buffer>
{
- Buffer(const std::string& name) :
- Mippleton<Buffer>(name)
+ public:
+
+ Buffer() :
+ mBuffer(-1)
{
- objects[0] = 0;
- objects[1] = 0;
+ mOggStream.datasource = 0;
}
~Buffer()
{
- alDeleteBuffers(2, objects);
-
- if (sound) Sound_FreeSample(sound);
+ if (mOggStream.datasource)
+ {
+ ov_clear(&mOggStream);
+ }
+ if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
}
- void loadFromFile(const std::string& filePath, bool stream)
+ void init(const std::string& name)
{
- if (objects[0] != 0) return;
-
- sound = Sound_NewSampleFromFile(filePath.c_str(),
- NULL, BUFFER_SIZE);
-
- if (!sound)
+ if (mOggStream.datasource)
{
- std::cerr << "could not load sound from file" << std::endl;
- exit(1);
+ ov_clear(&mOggStream);
+ mOggStream.datasource = 0;
}
- if (!stream)
+ std::string path(name);
+ if (!Sound::getPath(path))
{
- unsigned decoded = Sound_DecodeAll(sound);
- if (decoded == 0)
+ Error(Error::RESOURCE_NOT_FOUND, path).raise();
+ }
+
+ if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
{
- std::cout << "decoded no bytes" << std::endl;
- exit(1);
+ Error(Error::UNKNOWN_AUDIO_FORMAT, path).raise();
}
- alGenBuffers(2, objects);
- alBufferData(objects[0], getAudioFormat(sound->actual), sound->buffer,
- sound->buffer_size, sound->actual.rate);
- 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;
+ vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
+ mFormat = getAudioFormat(vorbisInfo);
+ mFreq = vorbisInfo->rate;
+ }
+
- Sound_FreeSample(sound);
- sound = 0;
+ void loadAll(ALuint source)
+ {
+ if (!mOggStream.datasource) init(getName());
+ if (!mOggStream.datasource) return;
+
+ char data[BUFFER_SIZE];
+ int size = 0;
+
+ for (;;)
+ {
+ int section;
+ int result = ov_read(&mOggStream, data + size,
+ BUFFER_SIZE - size, 0, 2, 1, §ion);
+
+ if (result > 0)
+ {
+ size += result;
+ }
+ else
+ {
+ if (result < 0) logWarning("vorbis playback error");
+ break;
+ }
}
- else
+ if (size == 0)
{
- 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(2, objects);
+ logWarning << "decoded no bytes from "
+ << getName() << std::endl;
+ return;
}
+
+ alGenBuffers(1, &mBuffer);
+
+ alBufferData(mBuffer, mFormat, data, size, mFreq);
+ alSourcei(source, AL_BUFFER, mBuffer);
+
+ // don't need to keep this loaded
+ ov_clear(&mOggStream);
+ mOggStream.datasource = 0;
}
bool stream(ALuint buffer)
{
- int bytes = Sound_Decode(sound);
+ char data[BUFFER_SIZE];
+ int size = 0;
+
+ while (size < BUFFER_SIZE)
+ {
+ int section;
+ int result = ov_read(&mOggStream, 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) return false;
- if (bytes < BUFFER_SIZE) return false;
+ alBufferData(buffer, mFormat, data, size, mFreq);
- alBufferData(buffer, getAudioFormat(sound->actual), sound->buffer,
- sound->buffer_size, sound->actual.rate);
- return false;
+ return true;
}
- Sound_Sample* sound;
- ALuint objects[2];
+ void rewind()
+ {
+ if (!mOggStream.datasource) init(getName());
+ else ov_raw_seek(&mOggStream, 0);
+ }
- //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};
+ private:
- //alListenerfv(AL_POSITION, location);
- //alListenerfv(AL_VELOCITY, location);
- //alListenerfv(AL_VELOCITY, orient);
+ OggVorbis_File mOggStream;
+ ALenum mFormat;
+ ALsizei mFreq;
+ ALuint mBuffer;
};
- Impl(const std::string& name, bool stream = false) :
- buffer_(Buffer::retain(name), Buffer::release)
+
+ Impl()
+ {
+ init();
+ }
+
+ Impl(const std::string& name)
+ {
+ init();
+ enqueue(name);
+ }
+
+ void init()
{
- if (!stream) buffer_->loadFromFile(Sound::getPath(name), stream);
- else buffer_->loadFromFile(SoundStream::getPath(name), stream);
+ retainBackend();
+
+ mIsLoaded = false;
+ mIsPlaying = false;
+ mIsLooping = false;
+
+ alGenSources(1, &mSource);
- ALfloat location[] = {0.0f, 0.0f, 0.0f};
-
- alGenSources(1, &source_);
+ ALfloat zero[] = {0.0f, 0.0f, 0.0f};
+ alSourcef(mSource, AL_PITCH, 1.0f);
+ alSourcef(mSource, AL_GAIN, 1.0f);
+ alSourcefv(mSource, AL_POSITION, zero);
+ alSourcefv(mSource, AL_VELOCITY, zero);
+
+ alSourcei(mSource, AL_LOOPING, mIsLooping);
+ }
+
+ ~Impl()
+ {
+ stop();
- 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);
+ alDeleteSources(1, &mSource);
- if (!stream)
+ while (!mBuffers.empty())
{
- alSourcei(source_, AL_BUFFER, buffer_->objects[0]);
+ alDeleteBuffers(1, &mBuffers.back());
+ mBuffers.pop_back();
}
- else
+
+ releaseBackend();
+ }
+
+
+ void play()
+ {
+ if (mQueue.empty()) return;
+
+ if (!mIsLoaded) mQueue.front()->loadAll(mSource);
+
+ alSourcePlay(mSource);
+ mIsLoaded = true;
+ }
+
+
+ void playStream()
+ {
+ if (mQueue.empty()) return;
+
+ if (!mIsPlaying)
{
- buffer_->stream(buffer_->objects[0]);
- buffer_->stream(buffer_->objects[1]);
+ alSourcei(mSource, AL_LOOPING, false);
+ bufferStream();
+ }
- alSourceQueueBuffers(source_, 2, buffer_->objects);
+ if (!mStreamTimer.isValid())
+ {
+ mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
+ 1.0, Timer::REPEAT);
}
+
+ alSourcePlay(mSource);
+ mIsPlaying = true;
}
- ~Impl()
+ void bufferStream()
{
- alDeleteSources(1, &source_);
+ 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()
{
- int finished = 0;
+ 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);
+
+ BufferP buffer = mQueue.front();
+ bool streamed = buffer->stream(bufferObj);
- alSourceUnqueueBuffers(source_, 1, &buffer);
- buffer_->stream(buffer);
- alSourceQueueBuffers(source_, 1, &buffer);
+ if (streamed)
+ {
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
+ }
+ 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
+ mQueue.front()->rewind();
+ mQueue.front()->stream(bufferObj);
+ alSourceQueueBuffers(mSource, 1, &bufferObj);
+ logInfo("loading new buffer");
+
+ // queue up any unused buffers
+ bufferStream();
+ }
+ else if (mIsLooping)
+ {
+ // 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);
+ }
+ }
+ }
+
+ 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 :-(
+ if (mIsPlaying && state != AL_PLAYING)
+ {
+ alSourcePlay(mSource);
}
}
- boost::shared_ptr<Buffer> buffer_;
- ALuint source_;
- bool playing;
+ void stop()
+ {
+ alSourceStop(mSource);
+ mIsPlaying = false;
+
+ mStreamTimer.invalidate();
+ }
+
+ void pause()
+ {
+ alSourcePause(mSource);
+ mIsPlaying = false;
+
+ mStreamTimer.invalidate();
+ }
+
+
+ void setSample(const std::string& name)
+ {
+ stop();
+ alSourcei(mSource, AL_BUFFER, AL_NONE);
+
+ mQueue.clear();
+ mIsLoaded = false;
+
+ enqueue(name);
+
+ while (!mBuffers.empty())
+ {
+ alDeleteBuffers(1, &mBuffers.back());
+ mBuffers.pop_back();
+ }
+ }
+
+ void enqueue(const std::string& name)
+ {
+ BufferP buffer = Buffer::getInstance(name);
+ mQueue.push_back(buffer);
+ }
+
+
+ bool isPlaying() const
+ {
+ if (mIsPlaying) return true;
+
+ ALenum state;
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+
+ return state == AL_PLAYING;
+ }
+
+
+ void setLooping(bool looping)
+ {
+ mIsLooping = looping;
+
+ ALenum type;
+ alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
+
+ if (type != AL_STREAMING)
+ {
+ 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 mSource;
+ std::list<ALuint> mBuffers;
+
+ 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::setSample(const std::string& name)
+{
+ // pass through
+ mImpl->setSample(name);
+}
void Sound::play()
{
- //alSourceRewind(impl_->source_);
- alSourcePlay(impl_->source_);
- impl_->playing = true;
+ // pass through
+ mImpl->play();
+}
+
+void Sound::stop()
+{
+ // pass through
+ mImpl->stop();
}
void Sound::pause()
{
- alSourcePause(impl_->source_);
- impl_->playing = false;
+ // pass through
+ mImpl->pause();
}
-void Sound::togglePlayPause()
+
+void Sound::toggle()
{
- if (impl_->playing) pause();
+ if (isPlaying()) pause();
else play();
}
-void Sound::setGain(Scalar gain)
+bool Sound::isPlaying() const
{
- alSourcef(impl_->source_, AL_GAIN, gain);
+ // pass through
+ return mImpl->isPlaying();
}
-std::string Sound::getPath(const std::string& name)
+void Sound::setPosition(const Vector3& position)
{
- std::string path = Resource::getPath("sounds/" + name + ".ogg");
- return path;
+ float vec[3] = {position[0], position[1], position[2]};
+ alSourcefv(mImpl->mSource, AL_POSITION, vec);
}
+void Sound::setVelocity(const Vector3& velocity)
+{
+ float vec[3] = {velocity[0], velocity[1], velocity[2]};
+ alSourcefv(mImpl->mSource, AL_VELOCITY, vec);
+}
-//##############################################################################
+void Sound::setGain(Scalar gain)
+{
+ alSourcef(mImpl->mSource, AL_GAIN, float(gain));
+}
+void Sound::setPitch(Scalar pitch)
+{
+ alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
+}
-SoundStream::SoundStream(const std::string& name)
+void Sound::setLooping(bool looping)
+{
// pass through
- //impl_(name, true) {}
+ mImpl->setLooping(looping);
+}
+
+
+void Sound::setListenerPosition(const Vector3& position)
+{
+ float vec[] = {position[0], position[1], position[2]};
+ alListenerfv(AL_POSITION, vec);
+}
+
+void Sound::setListenerVelocity(const Vector3& velocity)
{
- impl_ = boost::shared_ptr<Sound::Impl>(new Sound::Impl(name, true));
+ float vec[] = {velocity[0], velocity[1], velocity[2]};
+ alListenerfv(AL_VELOCITY, vec);
+}
+
+void Sound::setListenerOrientation(const Vector3& forward,
+ const Vector3& up)
+{
+ 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 SoundStream::update(Scalar t, Scalar dt)
+bool Sound::getPath(std::string& name)
+{
+ return Resource::getPath(name, "sounds/", "ogg");
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+void SoundStream::enqueue(const std::string& name)
{
// pass through
- impl_->update();
+ mImpl->enqueue(name);
}
-std::string SoundStream::getPath(const std::string& name)
+void SoundStream::play()
{
- std::string path = Resource::getPath("sounds/" + name + ".xm");
- return path;
+ // pass through
+ mImpl->playStream();
}
} // namespace Mf
-/** vim: set ts=4 sw=4 tw=80: *************************************************/
-