diff --git a/sound/outputs/input_filter.h b/sound/outputs/input_filter.h new file mode 100644 index 000000000..455a60e14 --- /dev/null +++ b/sound/outputs/input_filter.h @@ -0,0 +1,80 @@ +#ifndef MANGLE_INPUT_FILTER_H +#define MANGLE_INPUT_FILTER_H + +#include "../sound.h" + +#include + +namespace Mangle { +namespace Sound { + +/** + @brief This filter class adds file loading capabilities to a + Sound::Manager class, by associating an InputManager with it. + + The class takes an existing Manager able to load streams, and + associates an InputManager with it. The combined class is able to + load files directly. + + Example: + \code + + // Add FFmpeg input to an OpenAL soud output manager. OpenAL cannot + // decode sound files on its own. + InputFilter mg(new OpenAL_Manager, new FFM_InputManager); + + // We can now load filenames directly. + mg.load("file1.mp3"); + \endcode +*/ +class InputFilter : public Manager +{ + protected: + Manager *snd; + InputManager *inp; + + public: + /// Empty constructor + InputFilter() {} + + /// Assign an input manager and a sound manager to this object + InputFilter(Manager *_snd, InputManager *_inp) + { set(_snd, _inp); } + + /// Assign an input manager and a sound manager to this object + void set(Manager *_snd, InputManager *_inp) + { + inp = _inp; + snd = _snd; + + // Set capabilities + needsUpdate = snd->needsUpdate; + has3D = snd->has3D; + canRepeatStream = snd->canRepeatStream; + canLoadStream = inp->canLoadStream; + + // Both these should be true, or the use of this class is pretty + // pointless + canLoadSource = snd->canLoadSource; + canLoadFile = canLoadSource; + assert(canLoadSource && canLoadFile); + } + + virtual Sound *load(const std::string &file, bool stream=false) + { return load(inp->load(file), stream); } + + virtual Sound *load(Stream::Stream *input, bool stream=false) + { return load(inp->load(input), stream); } + + virtual Sound *load(InputSource *input, bool stream=false) + { return snd->load(input, stream); } + + virtual void update() { snd->update(); } + virtual void setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) + { snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); } +}; + +}} +#endif diff --git a/sound/outputs/openal_audiere.h b/sound/outputs/openal_audiere.h new file mode 100644 index 000000000..65947b22f --- /dev/null +++ b/sound/outputs/openal_audiere.h @@ -0,0 +1,29 @@ +#ifndef MANGLE_FFMPEG_OPENAL_H +#define MANGLE_FFMPEG_OPENAL_H + +#include "input_filter.h" +#include "input_audiere.h" +#include "output_openal.h" + +namespace Mangle { +namespace Sound { + +/// A InputFilter that adds audiere decoding to OpenAL. Audiere has +/// it's own output, but OpenAL sports 3D and other advanced features. +class OpenAL_Audiere_Manager : public InputFilter +{ + public: + OpenAL_Audiere_Manager() + { + set(new OpenAL_Manager, + new AudiereInput); + } + ~OpenAL_Audiere_Manager() + { + delete snd; + delete inp; + } +}; + +}} +#endif diff --git a/sound/outputs/openal_out.cpp b/sound/outputs/openal_out.cpp new file mode 100644 index 000000000..5d3633e75 --- /dev/null +++ b/sound/outputs/openal_out.cpp @@ -0,0 +1,283 @@ +#include "openal_out.h" +#include + +// ALuint bufferID; + +using namespace Mangle::Sound; + +// ---- Helper functions and classes ---- + +class OpenAL_Exception : public std::exception +{ + std::string msg; + + public: + + OpenAL_Exception(const std::string &m) : msg(m) {} + ~OpenAL_Exception() throw() {} + virtual const char* what() const throw() { return msg.c_str(); } +}; + +static void fail(const std::string &msg) +{ + throw OpenAL_Exception("OpenAL exception: " + msg); +} + +static void checkALError(const std::string &msg) +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + fail("\"" + std::string(alGetString(err)) + "\" while " + msg); +} + +static void getALFormat(InputStream *inp, int &fmt, int &rate) +{ + int ch, bits; + inp->getInfo(&rate, &ch, &bits); + + fmt = 0; + + if(bits == 8) + { + if(ch == 1) fmt = AL_FORMAT_MONO8; + if(ch == 2) fmt = AL_FORMAT_STEREO8; + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8"); + if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8"); + } + } + if(bits == 16) + { + if(ch == 1) fmt = AL_FORMAT_MONO16; + if(ch == 2) fmt = AL_FORMAT_STEREO16; + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); + if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16"); + } + } + + if(fmt == 0) + fail("Unsupported input format"); +} + +// ---- OpenAL_Sound ---- + +OpenAL_Sound::OpenAL_Sound(SampleSource *input) +{ + +} + +void OpenAL_Sound::play() +{ + alSourcePlay(inst); + checkALError("starting playback"); +} + +void OpenAL_Sound::stop() +{ + alSourceStop(inst); + checkALError("stopping"); +} + +void OpenAL_Sound::pause() +{ + alSourcePause(inst); + checkALError("pausing"); +} + +bool OpenAL_Sound::isPlaying() +{ + ALint state; + alGetSourcei(inst, AL_SOURCE_STATE, &state); + + return state == AL_PLAYING; +} + +void OpenAL_Sound::setVolume(float volume) +{ + if(volume > 1.0) volume = 1.0; + if(volume < 0.0) volume = 0.0; + alSourcef(inst, AL_GAIN, volume); + checkALError("setting volume"); +} + +void OpenAL_Sound::setPos(float x, float y, float z) +{ + alSource3f(inst, AL_POSITION, x, y, z); + checkALError("setting position"); +} + + + + + + + + + + + + + + + + + + + + +Instance *OpenAL_Sound::getInstance(bool is3d, bool repeat) +{ + assert((!repeat || !stream) && "OpenAL implementation does not support looping streams"); + + if(stream) + return new OpenAL_Stream_Instance(source->getStream(), owner); + + // Load the buffer if it hasn't been done already + if(bufferID == 0) + { + // Get an input stream and load the file from it + InputStream *inp = source->getStream(); + + std::vector buffer; + + // Add 32 kb at each increment + const int ADD = 32*1024; + + // Fill the buffer. We increase the buffer until it's large + // enough to fit all the data. + while(true) + { + // Increase the buffer + int oldlen = buffer.size(); + buffer.resize(oldlen+ADD); + + // Read the data + size_t len = inp->getData(&buffer[oldlen], ADD); + + // If we read less than requested, we're done. + if(len < ADD) + { + // Downsize the buffer to the right size + buffer.resize(oldlen+len); + break; + } + } + + // Get the format + int fmt, rate; + getALFormat(inp, fmt, rate); + + // We don't need the file anymore + inp->drop(); + source->drop(); + source = NULL; + + // Move the data into OpenAL + alGenBuffers(1, &bufferID); + alBufferData(bufferID, fmt, &buffer[0], buffer.size(), rate); + checkALError("loading sound buffer"); + } // End of buffer loading + + // At this point, the file data has been loaded into the buffer + // in 'bufferID', and we should be ready to go. + assert(bufferID != 0); + + return new OpenAL_Simple_Instance(bufferID); +} + + +// ---- OpenAL_Simple_Instance ---- + +OpenAL_Simple_Instance::OpenAL_Simple_Instance(ALuint buf) +{ + // Create instance and associate buffer + alGenSources(1, &inst); + alSourcei(inst, AL_BUFFER, buf); +} + +OpenAL_Simple_Instance::~OpenAL_Simple_Instance() +{ + // Stop + alSourceStop(inst); + + // Return sound + alDeleteSources(1, &inst); +} + + +// ---- OpenAL_Stream_Instance ---- + +OpenAL_Stream_Instance::OpenAL_Stream_Instance(InputStream *_stream, + OpenAL_Manager *_owner) + : stream(_stream), owner(_owner) +{ + // Deduce the file format from the stream info + getALFormat(stream, fmt, rate); + + // Create the buffers and the sound instance + alGenBuffers(BUFS, bufs); + alGenSources(1, &inst); + + checkALError("initializing"); + + // Fill the buffers and que them + for(int i=0; iadd_stream(this); +} + +void OpenAL_Stream_Instance::queueBuffer(ALuint bId) +{ + char buf[SIZE]; + + // Get the data + int len = stream->getData(buf, SIZE); + if(len == 0) + return; + + // .. and stash it + alBufferData(bId, fmt, buf, len, rate); + alSourceQueueBuffers(inst, 1, &bId); +} + +OpenAL_Stream_Instance::~OpenAL_Stream_Instance() +{ + // Remove ourselves from streaming list + owner->remove_stream(lit); + + // Stop + alSourceStop(inst); + + // Kill the input stream + stream->drop(); + + // Return sound + alDeleteSources(1, &inst); + + // Delete buffers + alDeleteBuffers(BUFS, bufs); +} + +void OpenAL_Stream_Instance::update() +{ + ALint count; + alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count); + + for(int i = 0;i < count;i++) + { + // Unque a finished buffer + ALuint bId; + alSourceUnqueueBuffers(inst, 1, &bId); + + // Queue a new buffer + queueBuffer(bId); + } +} diff --git a/sound/outputs/openal_out.h b/sound/outputs/openal_out.h new file mode 100644 index 000000000..95d27845c --- /dev/null +++ b/sound/outputs/openal_out.h @@ -0,0 +1,109 @@ +#ifndef MANGLE_SOUND_OPENAL_OUT_H +#define MANGLE_SOUND_OPENAL_OUT_H + +#include "../output.h" + +#include +#include +#include + +namespace Mangle { +namespace Sound { + +/// OpenAL sound output +class OpenAL_Sound : public Sound +{ + protected: + ALuint inst; + + public: + OpenAL_Sound(SampleSource *input); + ~OpenAL_Sound(); + + /// Play or resume the sound + void play(); + + /// Stop the sound + void stop(); + + /// Pause the sound, may be resumed later + void pause(); + + /// Check if the sound is still playing + bool isPlaying(); + + /// Set the volume. The parameter must be between 0.0 and 1.0. + void setVolume(float); + + /// Set the 3D position. + void setPos(float x, float y, float z); +}; + +class OpenALFactory : public SoundFactory +{ + ALCdevice *Device; + ALCcontext *Context; + bool didSetup; + + public: + /// Initialize object. Pass true (default) if you want the + /// constructor to set up the current ALCdevice and ALCcontext for + /// you. + OpenALFactory(bool doSetup = true) + : didSetup(doSetup) + { + needsUpdate = false; + has3D = true; + canRepeatStream = false; + canLoadFile = false; + canLoadStream = false; + canLoadSource = true; + + if(doSetup) + { + // Set up sound system + Device = alcOpenDevice(NULL); + Context = alcCreateContext(Device, NULL); + + if(!Device || !Context) + fail("Failed to initialize context or device"); + + alcMakeContextCurrent(Context); + } + } + + ~OpenALFactory() + { + // Deinitialize sound system + if(didSetup) + { + alcMakeContextCurrent(NULL); + if(Context) alcDestroyContext(Context); + if(Device) alcCloseDevice(Device); + } + } + + Sound *load(const std::string &file, bool stream=false) { assert(0); } + Sound *load(Stream::Stream *input, bool stream=false) { assert(0); } + Sound *load(SampleSource* input, bool stream=false) + { return new OpenAL_Sound(input); } + + void update() {} + setListenerPos(float x, float y, float z, + float fx, float fy, float fz, + float ux, float uy, float uz) + { + ALfloat orient[6]; + orient[0] = fx; + orient[1] = fy; + orient[2] = fz; + orient[3] = ux; + orient[4] = uy; + orient[5] = uz; + alListener3f(AL_POSITION, x, y, z); + alListenerfv(AL_ORIENTATION, orient); + } +}; + +}} // namespaces +#endif