/******************************************************************************* 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 #include #include #include #include #include #include #include "Log.hh" #include "Mippleton.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 Mippleton { OggVorbis_File mOggStream; ALenum mFormat; ALsizei mFreq; std::vector mObjects; public: Buffer(const std::string& name) : Mippleton(name) { mOggStream.datasource = 0; openFile(); } ~Buffer() { while (!mObjects.empty()) { alDeleteBuffers(1, &mObjects.back()); mObjects.pop_back(); } if (mOggStream.datasource) { ov_clear(&mOggStream); } } void openFile() { if (mOggStream.datasource) { ov_clear(&mOggStream); mOggStream.datasource = 0; } std::string filePath = Sound::getPath(getName()); int result = ov_fopen((char*)filePath.c_str(), &mOggStream); if (result < 0) { logWarning("error while loading sound %s", getName().c_str()); throw Exception(Exception::BAD_AUDIO_FORMAT); } vorbis_info* vorbisInfo = ov_info(&mOggStream, -1); mFormat = getAudioFormat(vorbisInfo); mFreq = vorbisInfo->rate; logDebug(" channels: %d", vorbisInfo->channels); logDebug(" frequency: %d", vorbisInfo->rate); } void loadAll(ALuint source) { if (!mOggStream.datasource) openFile(); 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 %s", getName().c_str()); //throw Exception(Exception::FILE_NOT_FOUND); return; } ALuint obj; alGenBuffers(1, &obj); alBufferData(obj, mFormat, data, size, mFreq); mObjects.push_back(obj); alSourcei(source, AL_BUFFER, obj); // don't need this anymore 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) { std::vector::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; 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 STREAM_EOF; alBufferData(buffer, mFormat, data, size, mFreq); return STREAM_OK; } inline 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(); 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; mObjects.pop_back(); } return true; } }; Impl(const std::string& name) : mBuffer(Buffer::getInstance(name)), mIsPlaying(false), mIsLooping(false) { ALfloat zero[] = {0.0f, 0.0f, 0.0f}; alGenSources(1, &mSource); alSourcef(mSource, AL_PITCH, 1.0f); alSourcef(mSource, AL_GAIN, 1.0f); alSourcefv(mSource, AL_POSITION, zero); alSourcefv(mSource, AL_VELOCITY, zero); } ~Impl() { alDeleteSources(1, &mSource); } void play() { ALenum type; alGetSourcei(mSource, AL_SOURCE_TYPE, &type); if (type != AL_STATIC) { mBuffer->loadAll(mSource); } alSourcei(mSource, AL_LOOPING, mIsLooping); alSourcePlay(mSource); mIsPlaying = true; } void stream() { ALenum type; alGetSourcei(mSource, AL_SOURCE_TYPE, &type); alSourcei(mSource, AL_BUFFER, AL_NONE); mBuffer->rewind(); mBuffer->beginStream(mSource); alSourcei(mSource, AL_LOOPING, AL_FALSE); alSourcePlay(mSource); mIsPlaying = true; mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2), 1.0, Timer::REPEAT); } inline void update() { ALint finished = 0; alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &finished); while (finished-- > 0) { ALuint buffer; alSourceUnqueueBuffers(mSource, 1, &buffer); Buffer::StreamStatus status = mBuffer->stream(buffer); if (status == Buffer::STREAM_OK) { alSourceQueueBuffers(mSource, 1, &buffer); } else if (status == Buffer::STREAM_EOF) { if (!mQueue.empty()) { // begin the next buffer in the queue mExpired.push_back(mBuffer); mBuffer = mQueue.front(); mQueue.pop(); mBuffer->beginStream(mSource, 1); } else if (mIsLooping) { // restart from the beginning mBuffer->rewind(); mBuffer->stream(buffer); alSourceQueueBuffers(mSource, 1, &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 if (mIsPlaying && state != AL_PLAYING) { alSourcePlay(mSource); } } inline void clear() { // try to remove expired buffers std::vector::iterator it; for (it = mExpired.end() - 1; it >= mExpired.begin(); --it) { if ((*it)->clear()) mExpired.erase(it); } } void stop() { alSourceStop(mSource); mIsPlaying = false; } inline void pause() { alSourcePause(mSource); mIsPlaying = false; } inline void resume() { alSourcePlay(mSource); mIsPlaying = true; } inline void setSample(const std::string& name) { bool playing = isPlaying(); ALenum type; alGetSourcei(mSource, AL_SOURCE_TYPE, &type); stop(); //alSourcei(mSource, AL_BUFFER, AL_NONE); mBuffer = 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); mQueue.push(buffer); } inline bool isPlaying() const { if (mIsPlaying) return true; ALenum state; alGetSourcei(mSource, AL_SOURCE_STATE, &state); return state == AL_PLAYING; } inline void setLooping(bool looping) { mIsLooping = looping; ALenum type; alGetSourcei(mSource, AL_SOURCE_TYPE, &type); if (type != AL_STREAMING) { alSourcei(mSource, AL_LOOPING, mIsLooping); } } ALuint mSource; BufferP mBuffer; bool mIsPlaying; bool mIsLooping; std::queue mQueue; std::vector mExpired; Timer mStreamTimer; 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 } }; Sound::Sound(const std::string& name) : // pass through mImpl(new Sound::Impl(name)) {} void Sound::play() { // pass through mImpl->play(); } void Sound::stream() { // pass through mImpl->stream(); } void Sound::stop() { // pass through mImpl->stop(); } void Sound::pause() { // pass through mImpl->pause(); } void Sound::resume() { // pass through mImpl->resume(); } void Sound::toggle() { if (mImpl->mIsPlaying) pause(); else resume(); } void Sound::setSample(const std::string& name) { // pass through mImpl->setSample(name); } void Sound::enqueue(const std::string& name) { // pass through mImpl->enqueue(name); } bool Sound::isPlaying() const { // pass through return mImpl->isPlaying(); } void Sound::setPosition(const Vector3& position) { float p[3] = {position[0], position[1], position[2]}; alSourcefv(mImpl->mSource, AL_POSITION, p); } void Sound::setVelocity(const Vector3& velocity) { float v[3] = {velocity[0], velocity[1], velocity[2]}; alSourcefv(mImpl->mSource, AL_VELOCITY, v); } 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) { alListener3f(AL_POSITION, float(position[0]), float(position[1]), float(position[2])); } void Sound::setListenerVelocity(const Vector3& velocity) { alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]), float(velocity[2])); } 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); } std::string Sound::getPath(const std::string& name) { std::string path = Resource::getPath("sounds/" + name + ".ogg"); return path; } } // namespace Mf /** vim: set ts=4 sw=4 tw=80: *************************************************/