/*] 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 #include #include #include #include #include #include #include #include #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 BufferP; class Buffer : public Manager { 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 mBuffers; bool mIsLoaded; bool mIsPlaying; bool mIsLooping; std::deque 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