+++ /dev/null
-
-/*] 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 <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 "Sound.hh"
-#include "Timer.hh"
-
-#define BUFFER_SIZE (64 * 1024)
-//#define BUFFER_SIZE (5*2048)
-
-namespace Mf {
-
-
-class Sound::Impl
-{
-public:
-
- static ALenum getAudioFormat(const vorbis_info* audioInfo)
- {
- if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
- else return AL_FORMAT_STEREO16;
- }
-
-
- class Buffer;
- typedef boost::shared_ptr<Buffer> BufferP;
-
- class Buffer : public Manager<Buffer>
- {
- public:
-
- Buffer() :
- mBuffer(-1)
- {
- mOggStream.datasource = 0;
- }
-
- ~Buffer()
- {
- if (mOggStream.datasource)
- {
- ov_clear(&mOggStream);
- }
- if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
- }
-
-
- void init(const std::string& name)
- {
- if (mOggStream.datasource)
- {
- ov_clear(&mOggStream);
- mOggStream.datasource = 0;
- }
-
- std::string path(name);
- if (!Sound::getPath(path))
- {
- Error(Error::RESOURCE_NOT_FOUND, path).raise();
- }
-
- if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
- {
- Error(Error::UNKNOWN_AUDIO_FORMAT, path).raise();
- }
-
- vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
- mFormat = getAudioFormat(vorbisInfo);
- mFreq = vorbisInfo->rate;
- }
-
-
- 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;
- }
- }
- if (size == 0)
- {
- 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)
- {
- 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;
-
- alBufferData(buffer, mFormat, data, size, mFreq);
-
- return true;
- }
-
- void rewind()
- {
- if (!mOggStream.datasource) init(getName());
- else ov_raw_seek(&mOggStream, 0);
- }
-
-
- private:
-
- OggVorbis_File mOggStream;
- ALenum mFormat;
- ALsizei mFreq;
- ALuint mBuffer;
- };
-
-
- Impl()
- {
- init();
- }
-
- Impl(const std::string& name)
- {
- init();
- enqueue(name);
- }
-
- void init()
- {
- retainBackend();
-
- mIsLoaded = false;
- mIsPlaying = false;
- mIsLooping = false;
-
- alGenSources(1, &mSource);
-
- 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();
-
- alDeleteSources(1, &mSource);
-
- while (!mBuffers.empty())
- {
- alDeleteBuffers(1, &mBuffers.back());
- mBuffers.pop_back();
- }
-
- 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)
- {
- alSourcei(mSource, AL_LOOPING, false);
- bufferStream();
- }
-
- if (!mStreamTimer.isValid())
- {
- mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
- 1.0, Timer::REPEAT);
- }
-
- alSourcePlay(mSource);
- mIsPlaying = true;
- }
-
- 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(mSource, AL_BUFFERS_PROCESSED, &finished);
-
- while (finished-- > 0)
- {
- ALuint bufferObj;
- alSourceUnqueueBuffers(mSource, 1, &bufferObj);
-
- BufferP buffer = mQueue.front();
- bool streamed = buffer->stream(bufferObj);
-
- 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);
- }
- }
-
-
- 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
- mImpl(new Sound::Impl(name)) {}
-
-
-void Sound::setSample(const std::string& name)
-{
- // pass through
- mImpl->setSample(name);
-}
-
-
-void Sound::play()
-{
- // pass through
- mImpl->play();
-}
-
-void Sound::stop()
-{
- // pass through
- mImpl->stop();
-}
-
-void Sound::pause()
-{
- // pass through
- mImpl->pause();
-}
-
-
-void Sound::toggle()
-{
- if (isPlaying()) pause();
- else play();
-}
-
-bool Sound::isPlaying() const
-{
- // pass through
- return mImpl->isPlaying();
-}
-
-
-void Sound::setPosition(const Vector3& position)
-{
- 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));
-}
-
-void Sound::setLooping(bool looping)
-{
- // pass through
- 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)
-{
- 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);
-}
-
-
-bool Sound::getPath(std::string& name)
-{
- return Resource::getPath(name, "sounds/", "ogg");
-}
-
-
-//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-void SoundStream::enqueue(const std::string& name)
-{
- // pass through
- mImpl->enqueue(name);
-}
-
-
-void SoundStream::play()
-{
- // pass through
- mImpl->playStream();
-}
-
-
-} // namespace Mf
-