]> Dogcows Code - chaz/yoink/blobdiff - src/Moof/Sound.cc
improved new vorbisfile compatibility
[chaz/yoink] / src / Moof / Sound.cc
index 368e9388bca4aee4f3665ae40b1abf1afa71f6cd..f912b318d153c2dfa13ecf1b991169f4e87751ee 100644 (file)
 
 *******************************************************************************/
 
+#include <cstdio>
 #include <string>
+#include <queue>
+#include <vector>
 
-#include <SDL/SDL.h>
-#include <SDL/SDL_sound.h>
 #include <AL/al.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
 
 #include "Log.hh"
 #include "Mippleton.hh"
 #include "Sound.hh"
 
-#define BUFFER_SIZE (8 * 4096)
+#define BUFFER_SIZE (64 * 1024)
+//#define BUFFER_SIZE (5*2048)
 
 namespace Mf {
 
@@ -44,142 +48,262 @@ namespace Mf {
 struct Sound::Impl
 {
 
-       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 Mippleton<Buffer>
        {
+               OggVorbis_File                  oggStream;
+               ALenum                                  audioFormat;
+               ALsizei                                 audioFreq;
+               std::vector<ALuint>             objects;
+
+       public:
+
                Buffer(const std::string& name) :
                        Mippleton<Buffer>(name)
                {
-                       objects[0] = 0;
-                       objects[1] = 0;
+                       oggStream.datasource = 0;
+                       openFile();
                }
 
                ~Buffer()
                {
-                       alDeleteBuffers(2, objects);
+                       while (!objects.empty())
+                       {
+                               alDeleteBuffers(1, &objects.back());
+                               objects.pop_back();
+                       }
 
-                       if (sound) Sound_FreeSample(sound);
+                       if (oggStream.datasource)
+                       {
+                               ov_clear(&oggStream);
+                       }
                }
 
 
-               void loadFromFile(const std::string& filePath, bool stream)
+               void openFile()
                {
-                       if (objects[0] != 0) return;
+                       if (oggStream.datasource)
+                       {
+                               ov_clear(&oggStream);
+                               oggStream.datasource = 0;
+                       }
 
-                       sound = Sound_NewSampleFromFile(filePath.c_str(),
-                                       NULL, BUFFER_SIZE);
+                       std::string filePath = Sound::getPath(getName());
+                       int result = ov_fopen((char*)filePath.c_str(), &oggStream);
 
-                       if (!sound)
+                       if (result < 0)
                        {
-                               logWarning("audio not found: %s", getName().c_str());
-                               throw Exception(Exception::FILE_NOT_FOUND);
+                               logWarning("error while loading sound %s",
+                                               getName().c_str());
+                               throw Exception(Exception::BAD_AUDIO_FORMAT);
                        }
 
-                       if (!stream)
+                       vorbis_info* vorbisInfo = ov_info(&oggStream, -1);
+                       audioFormat = getAudioFormat(vorbisInfo);
+                       audioFreq = vorbisInfo->rate;
+
+                       logDebug("   channels: %d", vorbisInfo->channels);
+                       logDebug("  frequency: %d", vorbisInfo->rate);
+               }
+
+
+               void loadAll(ALuint source)
+               {
+                       if (!oggStream.datasource) openFile();
+                       if (!oggStream.datasource) return;
+
+                       char data[BUFFER_SIZE];
+                       int size = 0;
+
+                       for (;;)
                        {
-                       unsigned decoded = Sound_DecodeAll(sound);
-                       if (decoded == 0)
+                               int section;
+                               int result = ov_read(&oggStream, 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)
                        {
-                               logWarning("decoded not bytes from %s", getName().c_str());
-                               throw Exception(Exception::FILE_NOT_FOUND);
+                               logWarning("decoded no bytes from %s", getName().c_str());
+                               //throw Exception(Exception::FILE_NOT_FOUND);
+                               return;
                        }
 
-                       alGenBuffers(2, objects);
-                       alBufferData(objects[0], getAudioFormat(sound->actual), sound->buffer,
-                                       sound->buffer_size, sound->actual.rate);
-                       logDebug("buffer size: %d", sound->buffer_size);
-                       logDebug("   channels: %d", sound->actual.channels);
-                       logDebug("     format: %d", sound->actual.format);
-                       logDebug("  frequency: %d", sound->actual.rate);
+                       ALuint obj;
+                       alGenBuffers(1, &obj);
 
-                       Sound_FreeSample(sound);
-                       sound = 0;
-                       }
-                       else
+                       alBufferData(obj, audioFormat, data, size, audioFreq);
+
+                       objects.push_back(obj);
+
+                       alSourcei(source, AL_BUFFER, obj);
+
+                       // don't need this anymore
+                       ov_clear(&oggStream);
+                       oggStream.datasource = 0;
+               }
+
+
+               void beginStream(ALuint source, int nBuffers = 4)
+               {
+                       if (!oggStream.datasource) openFile();
+                       if (!oggStream.datasource) return;
+
+                       ALuint objs[nBuffers];
+                       alGenBuffers(nBuffers, objs);
+
+                       for (int i = 0; i < nBuffers; ++i)
                        {
-                       logDebug("buffer size: %d", sound->buffer_size);
-                       logDebug("   channels: %d", sound->actual.channels);
-                       logDebug("     format: %d", sound->actual.format);
-                       logDebug("  frequency: %d", sound->actual.rate);
-                               alGenBuffers(2, objects);
+                               objects.push_back(objs[i]);
+                               stream(objs[i]);
                        }
+
+                       alSourceQueueBuffers(source, nBuffers, objs);
                }
 
-               bool stream(ALuint buffer)
+               enum StreamStatus
                {
-                       int bytes = Sound_Decode(sound);
+                       STREAM_OK               = 0,
+                       STREAM_EOF              = 1,
+                       STREAM_WRONG    = 2
+               };
 
-                       if (bytes < BUFFER_SIZE) return false;
+               StreamStatus stream(ALuint buffer)
+               {
+                       std::vector<ALuint>::iterator it =
+                               std::find(objects.begin(), objects.end(), buffer);
+
+                       // that buffer doesn't belong to us
+                       if (it == objects.end()) return STREAM_WRONG;
+
+                       char data[BUFFER_SIZE];
+                       int size = 0;
+
+                       while (size < BUFFER_SIZE)
+                       {
+                               int section;
+                               int result = ov_read(&oggStream, 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 STREAM_EOF;
+
+                       alBufferData(buffer, audioFormat, data, size, audioFreq);
+
+                       return STREAM_OK;
+               }
 
-                       alBufferData(buffer, getAudioFormat(sound->actual), sound->buffer,
-                                       sound->buffer_size, sound->actual.rate);
-                       return false;
+               inline void rewind()
+               {
+                       if (!oggStream.datasource) openFile();
+                       else ov_raw_seek(&oggStream, 0);
                }
 
-               Sound_Sample* sound;
-               ALuint objects[2];
 
-       //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};
+               // delete unused buffers, return true if all buffers deleted
+               inline bool clear()
+               {
+                       // clear any openal errors
+                       alGetError();
+
+                       while (!objects.empty())
+                       {
+                               ALuint buffer = objects.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;
 
+                               objects.pop_back();
+                       }
 
-       //alListenerfv(AL_POSITION, location);
-       //alListenerfv(AL_VELOCITY, location);
-       //alListenerfv(AL_VELOCITY, orient);
+                       return true;
+               }
        };
 
-       Impl(const std::string& name, bool stream = false) :
-               buffer_(Buffer::getInstance(name))
-       {
-               if (!stream) buffer_->loadFromFile(Sound::getPath(name), stream);
-               else         buffer_->loadFromFile(SoundStream::getPath(name), stream);
 
-               ALfloat location[] = {0.0f, 0.0f, 0.0f};
+       Impl(const std::string& name) :
+               buffer_(Buffer::getInstance(name)),
+               playing_(false),
+               looping_(false)
+       {
+               ALfloat zero[] = {0.0f, 0.0f, 0.0f};
                
                alGenSources(1, &source_);
 
                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);
+               alSourcefv(source_, AL_POSITION, zero);
+               alSourcefv(source_, AL_VELOCITY, zero);
+       }
+
+       ~Impl()
+       {
+               alDeleteSources(1, &source_);
+       }
+
 
-               if (!stream)
+       void play()
+       {
+               ALenum type;
+               alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+               if (type != AL_STATIC)
                {
-                       alSourcei(source_, AL_BUFFER, buffer_->objects[0]);
+                       buffer_->loadAll(source_);
                }
-               else
-               {
-                       buffer_->stream(buffer_->objects[0]);
-                       buffer_->stream(buffer_->objects[1]);
 
-                       alSourceQueueBuffers(source_, 2, buffer_->objects);
-               }
+               alSourcei(source_, AL_LOOPING, looping_);
+               alSourcePlay(source_);
+               playing_ = true;
        }
 
-       ~Impl()
+
+       void stream()
        {
-               alDeleteSources(1, &source_);
-       }
+               ALenum type;
+               alGetSourcei(source_, AL_SOURCE_TYPE, &type);
 
+               alSourcei(source_, AL_BUFFER, AL_NONE);
+               buffer_->rewind();
+               buffer_->beginStream(source_);
 
-       void update()
+               alSourcei(source_, AL_LOOPING, AL_FALSE);
+               alSourcePlay(source_);
+               playing_ = true;
+       }
+
+       inline void update()
        {
-               int finished = 0;
+               ALint finished = 0;
 
                alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
 
@@ -188,17 +312,141 @@ struct Sound::Impl
                        ALuint buffer;
 
                        alSourceUnqueueBuffers(source_, 1, &buffer);
-                       buffer_->stream(buffer);
-                       alSourceQueueBuffers(source_, 1, &buffer);
+
+                       Buffer::StreamStatus status = buffer_->stream(buffer);
+
+                       if (status == Buffer::STREAM_OK)
+                       {
+                               alSourceQueueBuffers(source_, 1, &buffer);
+                       }
+                       else if (status == Buffer::STREAM_EOF)
+                       {
+                               if (!queue_.empty())
+                               {
+                                       // begin the next buffer in the queue
+                                       expired_.push_back(buffer_);
+                                       buffer_ = queue_.front();
+                                       queue_.pop();
+                                       buffer_->beginStream(source_, 1);
+                               }
+                               else if (looping_)
+                               {
+                                       // restart from the beginning
+                                       buffer_->rewind();
+                                       buffer_->stream(buffer);
+                                       alSourceQueueBuffers(source_, 1, &buffer);
+                               }
+                       }
+                       else if (status == Buffer::STREAM_WRONG)
+                       {
+                               clear();
+                               buffer_->beginStream(source_, 1);
+                       }
+               }
+
+               ALenum state;
+               alGetSourcei(source_, 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 (playing_ && state != AL_PLAYING)
+               {
+                       alSourcePlay(source_);
                }
        }
 
+       inline void clear()
+       {
+               // try to remove expired buffers
+               std::vector<BufferP>::iterator it;
+               for (it = expired_.end() - 1; it >= expired_.begin(); --it)
+               {
+                       if ((*it)->clear()) expired_.erase(it);
+               }
+       }
 
-       boost::shared_ptr<Buffer>       buffer_;
-       ALuint                                          source_;
-       bool playing;
-};
 
+       void stop()
+       {
+               alSourceStop(source_);
+               playing_ = false;
+       }
+
+       inline void pause()
+       {
+               alSourcePause(source_);
+               playing_ = false;
+       }
+
+       inline void resume()
+       {
+               alSourcePlay(source_);
+               playing_ = true;
+       }
+
+
+       inline void setSample(const std::string& name)
+       {
+               bool playing = isPlaying();
+               ALenum type;
+               alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+               stop();
+
+               //alSourcei(source_, AL_BUFFER, AL_NONE);
+               buffer_ = 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);
+               queue_.push(buffer);
+       }
+
+
+       inline bool isPlaying() const
+       {
+               if (playing_) return true;
+
+               ALenum state;
+               alGetSourcei(source_, AL_SOURCE_STATE, &state);
+
+               return state == AL_PLAYING;
+       }
+
+
+       inline void setLooping(bool looping)
+       {
+               looping_ = looping;
+
+               ALenum type;
+               alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+
+               if (type != AL_STREAMING)
+               {
+                       alSourcei(source_, AL_LOOPING, looping_);
+               }
+       }
+
+
+       ALuint                                  source_;
+       BufferP                                 buffer_;
+
+       bool                                    playing_;
+       bool                                    looping_;
+
+       std::queue<BufferP>             queue_;
+       std::vector<BufferP>    expired_;
+};
 
 Sound::Sound(const std::string& name) :
        // pass through
@@ -207,57 +455,100 @@ Sound::Sound(const std::string& name) :
 
 void Sound::play()
 {
-       //alSourceRewind(impl_->source_);
-       alSourcePlay(impl_->source_);
-       impl_->playing = true;
+       // pass through
+       impl_->play();
 }
 
-void Sound::pause()
+
+void Sound::stream()
 {
-       alSourcePause(impl_->source_);
-       impl_->playing = false;
+       // pass through
+       impl_->stream();
 }
 
-void Sound::togglePlayPause()
+void Sound::update(Scalar t, Scalar dt)
 {
-       if (impl_->playing) pause();
-       else play();
+       // pass through
+       impl_->update();
 }
 
-void Sound::setGain(Scalar gain)
+
+void Sound::stop()
 {
-       alSourcef(impl_->source_, AL_GAIN, gain);
+       // pass through
+       impl_->stop();
 }
 
+void Sound::pause()
+{
+       // pass through
+       impl_->pause();
+}
 
-std::string Sound::getPath(const std::string& name)
+void Sound::resume()
 {
-       std::string path = Resource::getPath("sounds/" + name + ".ogg");
-       return path;
+       // pass through
+       impl_->resume();
 }
 
+void Sound::toggle()
+{
+       if (impl_->playing_) pause();
+       else resume();
+}
+
+
+void Sound::setSample(const std::string& name)
+{
+       // pass through
+       impl_->setSample(name);
+}
 
-//##############################################################################
+void Sound::enqueue(const std::string& name)
+{
+       // pass through
+       impl_->enqueue(name);
+}
 
 
-SoundStream::SoundStream(const std::string& name)
+bool Sound::isPlaying() const
+{
        // pass through
-       //impl_(name, true) {}
+       return impl_->isPlaying();
+}
+
+void Sound::setPosition(Vector3 position)
 {
-       impl_ = boost::shared_ptr<Sound::Impl>(new Sound::Impl(name, true));
+       float p[3] = {position[0], position[1], position[2]};
+       alSourcefv(impl_->source_, AL_POSITION, p);
 }
 
+void Sound::setVelocity(Vector3 velocity)
+{
+       float v[3] = {velocity[0], velocity[1], velocity[2]};
+       alSourcefv(impl_->source_, AL_VELOCITY, v);
+}
 
-void SoundStream::update(Scalar t, Scalar dt)
+void Sound::setGain(Scalar gain)
+{
+       alSourcef(impl_->source_, AL_GAIN, float(gain));
+}
+
+void Sound::setPitch(Scalar pitch)
+{
+       alSourcef(impl_->source_, AL_PITCH, float(pitch));
+}
+
+void Sound::setLooping(bool looping)
 {
        // pass through
-       impl_->update();
+       impl_->setLooping(looping);
 }
 
 
-std::string SoundStream::getPath(const std::string& name)
+std::string Sound::getPath(const std::string& name)
 {
-       std::string path = Resource::getPath("sounds/" + name + ".xm");
+       std::string path = Resource::getPath("sounds/" + name + ".ogg");
        return path;
 }
 
This page took 0.031079 seconds and 4 git commands to generate.