]> Dogcows Code - chaz/yoink/blobdiff - src/Moof/Sound.cc
port to NetBSD
[chaz/yoink] / src / Moof / Sound.cc
index 9a632ebf3ace404b31787b16f227c70fecba4d60..4c60e328085f60d7d282c2e70144079c49c5dbc2 100644 (file)
 
 *******************************************************************************/
 
-#include <iostream>
+#include <cstdio>
+#include <deque>
 #include <string>
+#include <vector>
 
-#include <SDL/SDL.h>
-#include <SDL/SDL_sound.h>
 #include <AL/al.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
 
-#include "Mippleton.hh"
+#include "Exception.hh"
+#include "Library.hh"
+#include "Log.hh"
 #include "Sound.hh"
+#include "Timer.hh"
 
+#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 Library<Buffer>
        {
+       public:
+
                Buffer(const std::string& name) :
-                       Mippleton<Buffer>(name),
-                       object(0)
-               {}
+                       Library<Buffer>(name),
+                       mBuffer(-1)
+               {
+                       mOggStream.datasource = 0;
+                       openFile();
+               }
 
                ~Buffer()
                {
-                       alDeleteBuffers(1, &object);
+                       if (mOggStream.datasource)
+                       {
+                               ov_clear(&mOggStream);
+                       }
+                       if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
                }
-               void loadFromFile(const std::string& filePath, bool stream)
+
+
+               void openFile()
                {
-                       if (object != 0) return;
+                       if (mOggStream.datasource)
+                       {
+                               ov_clear(&mOggStream);
+                               mOggStream.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(), &mOggStream);
 
-                       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(ErrorCode::UNKNOWN_AUDIO_FORMAT);
                        }
 
-                       unsigned decoded = Sound_DecodeAll(sound);
-                       if (decoded == 0)
+                       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, &section);
+
+                               if (result > 0)
+                               {
+                                       size += result;
+                               }
+                               else
+                               {
+                                       if (result < 0) logWarning("vorbis playback error");
+                                       break;
+                               }
+                       }
+                       if (size == 0)
                        {
-                               std::cout << "decoded no bytes" << std::endl;
-                               exit(1);
+                               logWarning("decoded no bytes from %s", getName().c_str());
+                               //throw Exception("file_not_found");
+                               return;
                        }
-                       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);
+                       alGenBuffers(1, &mBuffer);
 
-                       Sound_FreeSample(sound);
+                       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, &section);
+
+                               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;
                }
 
-               ALuint object;
+               void rewind()
+               {
+                       if (!mOggStream.datasource) openFile();
+                       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)
        {
-               if (!stream) buffer_->loadFromFile(Sound::getPath(name), stream);
-               else         buffer_->loadFromFile(SoundStream::getPath(name), stream);
+               init();
+               enqueue(name);
+       }
 
-               ALfloat location[] = {0.0f, 0.0f, 0.0f};
+       void init()
+       {
+               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);
+               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);
+
+               mIsPlaying = false;
+               mIsLooping = false;
        }
 
        ~Impl()
        {
-               alDeleteSources(1, &source_);
+               stop();
+
+               alDeleteSources(1, &mSource);
+
+               while (!mBufferObjects.empty())
+               {
+                       alDeleteBuffers(1, &mBufferObjects.back());
+                       mBufferObjects.pop_back();
+               }
+       }
+
+
+       void play()
+       {
+               if (mQueue.empty()) return;
+
+               ALenum type;
+               alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
+
+               if (type != AL_STATIC)
+               {
+                       mQueue.front()->loadAll(mSource);
+               }
+
+               alSourcei(mSource, AL_LOOPING, mIsLooping);
+               alSourcePlay(mSource);
+               mIsPlaying = true;
+       }
+
+
+       void stream()
+       {
+               stop();
+
+               alSourcei(mSource, AL_BUFFER, AL_NONE);
+               mQueue.front()->rewind();
+               beginStream();
+
+               alSourcei(mSource, AL_LOOPING, AL_FALSE);
+               alSourcePlay(mSource);
+               mIsPlaying = true;
+
+               mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2), 1.0,
+                               Timer::REPEAT);
+       }
+
+       void beginStream()
+       {
+               ALuint buffer;
+               for (int i = mBufferObjects.size(); i < 8; ++i)
+               {
+                       alGenBuffers(1, &buffer);
+                       mBufferObjects.push_back(buffer);
+               }
+               for (int i = 0; i < 8; ++i)
+               {
+                       buffer = mBufferObjects[i];
+                       mQueue.front()->stream(buffer);
+                       alSourceQueueBuffers(mSource, 1, &buffer);
+               }
        }
 
 
        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");
+                               }
+                               else if (mIsLooping)
+                               {
+                                       // reload the same buffer
+                                       mQueue.push_back(buffer);
+                                       buffer->rewind();
+                                       buffer->stream(bufferObj);
+                                       alSourceQueueBuffers(mSource, 1, &bufferObj);
+                                       logInfo("looping same buffer");
+                               }
+                       }
+               }
+
+               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 resume()
+       {
+               alSourcePlay(mSource);
+               mIsPlaying = true;
+
+               ALenum type;
+               alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
+
+               if (type == AL_STREAMING)
+               {
+                       mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
+                                       1.0, Timer::REPEAT);
+               }
+       }
+
+
+       void setSample(const std::string& name)
+       {
+               bool playing = isPlaying();
+               ALenum type;
+               alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
+
+               stop();
+               mQueue.clear();
+
+               //alSourcei(mSource, AL_BUFFER, AL_NONE);
+               enqueue(name);
+
+               if (playing)
+               {
+                       if (type == AL_STREAMING) stream();
+                       else                      play();
+               }
+       }
+
+       void enqueue(const std::string& name)
+       {
+               BufferP buffer = Buffer::getInstance(name);
+               mQueue.push_back(buffer);
        }
 
 
-       boost::shared_ptr<Buffer>       buffer_;
-       ALuint                                          source_;
+       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
+       }
+
+
+       ALuint                                  mSource;
+       std::vector<ALuint>             mBufferObjects;
+
+       bool                                    mIsPlaying;
+       bool                                    mIsLooping;
+
+       std::deque<BufferP>             mQueue;
+
+       Timer                                   mStreamTimer;
 };
 
 
+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::play()
 {
-       alSourceRewind(impl_->source_);
-       alSourcePlay(impl_->source_);
+       // pass through
+       mImpl->play();
 }
 
+void Sound::stream()
+{
+       // pass through
+       mImpl->stream();
+}
 
-std::string Sound::getPath(const std::string& name)
+
+void Sound::stop()
 {
-       std::string path = Resource::getPath("sounds/" + name + ".ogg");
-       return path;
+       // 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);
+}
 
 
-SoundStream::SoundStream(const std::string& name)
+bool Sound::isPlaying() const
+{
        // pass through
-       //impl_(name, true) {}
+       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)
 {
-       impl_ = boost::shared_ptr<Sound::Impl>(new Sound::Impl(name, true));
+       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 SoundStream::update(Scalar t, Scalar dt)
+void Sound::setLooping(bool looping)
 {
        // pass through
-       impl_->update();
+       mImpl->setLooping(looping);
 }
 
 
-std::string SoundStream::getPath(const std::string& name)
+void Sound::setListenerPosition(const Vector3& position)
 {
-       std::string path = Resource::getPath("sounds/" + name + ".xm");
+       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;
 }
 
This page took 0.033157 seconds and 4 git commands to generate.