1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-12-27 23:35:51 +00:00
Aquaria/BBGE/FmodOpenALBridge.cpp

1551 lines
43 KiB
C++

/*
Copyright (C) 2007, 2010 - Bit-Blot
This file is part of Aquaria.
Aquaria is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// This file implements just enough of the FMOD library with OpenAL to suit
// the needs of the existing game code without having to actually ship FMOD.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef BBGE_BUILD_UNIX
#include <signal.h>
#endif
#include "Base.h"
#include "Core.h"
#include "FmodOpenALBridge.h"
#include "al.h"
#include "alc.h"
#include "ogg/ogg.h"
#include "vorbis/vorbisfile.h"
#include "FileAPI.h"
#include "MT.h"
#ifndef _DEBUG
//#define _DEBUG 1
#endif
///////////////////////////////////////////////////////////////////////////
// Decoder implementation for streamed Ogg Vorbis audio.
class OggDecoder {
public:
// Create a decoder that streams from a file.
OggDecoder(VFILE *fp);
// Create a decoder that streams from a memory buffer.
OggDecoder(const void *data, long data_size);
~OggDecoder();
// Start playing on the given channel, with optional looping.
bool start(ALuint source, bool loop);
// Decode audio into any free buffers. Must be called periodically
// on systems without threads; may be called without harm on systems
// with threads (the function does nothing in that case).
void update();
// Terminate playback.
void stop();
// Return the current playback position in seconds.
double position();
// Memory buffer I/O callback functions for libvorbisfile.
static size_t mem_read(void *ptr, size_t size, size_t nmemb, void *datasource);
static int mem_seek(void *datasource, ogg_int64_t offset, int whence);
static long mem_tell(void *datasource);
static void startDecoderThread();
static void stopDecoderThread();
private:
void _stop();
// Decoding loop, run in a separate thread (if threads are available).
static void decode_loop(OggDecoder *this_);
// Decode and queue PCM data for one buffer; does nothing if the end
// of the stream has already been reached or an unrecoverable error
// has occurred during decoding. If looping, the audio will instead
// restart at the beginning of the stream after reaching the end,
// but will still stop on an unrecoverable error.
void queue(ALuint buffer);
static const int NUM_BUFFERS = 8;
static const int BUFFER_LENGTH = 4096; // In samples (arbitrary)
char pcm_buffer[BUFFER_LENGTH * 4]; // Temporary buffer for decoding
ALuint buffers[NUM_BUFFERS];
ALuint source;
// Data source. If fp != NULL, the source is that file; otherwise, the
// source is the buffer pointed to by "data" with size "data_size" bytes.
VFILE *fp;
const char *data;
long data_size;
long data_pos; // Current read position for memory buffers
OggVorbis_File vf;
ALenum format;
int freq;
bool thread; // true if played by background thread
bool playing;
bool loop;
bool eof; // End of file _or_ unrecoverable error encountered
bool stopped; // true if enqueued deletion
unsigned int samples_done; // Number of samples played and dequeued
#ifdef BBGE_BUILD_SDL
static SDL_Thread *decoderThread;
static LockedQueue<OggDecoder*> decoderQ;
static volatile bool stop_thread;
static std::list<OggDecoder*> decoderList; // used by decoder thread only
#else
#warning Threads not supported, music may cut out on area changes!
// ... because the stream runs out of decoded data while the area is
// still loading, so OpenAL aborts playback.
#endif
static void detachDecoder(OggDecoder *);
};
// File I/O callback set (OV_CALLBACKS_NOCLOSE from libvorbis 1.2.0).
// It might be better to just update libogg/libvorbis to the current
// versions so we don't have to worry about identifier collisions --
// we can then drop all this and use OV_CALLBACKS_NOCLOSE in the
// ov_open_callbacks() call. Note that we rename the fseek() wrapper
// to avoid an identifier collision when building with more recent
// versions of libvorbis.
static int BBGE_ov_header_fseek_wrap(VFILE *f,ogg_int64_t off,int whence){
if(f==NULL)return(-1);
return vfseek(f,(long int)off,whence); // no ogg file is larger than 4 GB, int-cast should be ok
}
static int noclose(FILE *f) {return 0;}
static const ov_callbacks local_OV_CALLBACKS_NOCLOSE = {
(size_t (*)(void *, size_t, size_t, void *)) vfread,
(int (*)(void *, ogg_int64_t, int)) BBGE_ov_header_fseek_wrap,
(int (*)(void *)) noclose, // NULL doesn't work in libvorbis-1.1.2
(long (*)(void *)) vftell
};
// Memory I/O callback set.
static const ov_callbacks ogg_memory_callbacks = {
OggDecoder::mem_read,
OggDecoder::mem_seek,
(int (*)(void *))noclose,
OggDecoder::mem_tell
};
#ifdef BBGE_BUILD_SDL
SDL_Thread *OggDecoder::decoderThread = NULL;
LockedQueue<OggDecoder*> OggDecoder::decoderQ;
volatile bool OggDecoder::stop_thread;
std::list<OggDecoder*> OggDecoder::decoderList;
#endif
void OggDecoder::startDecoderThread()
{
#ifdef BBGE_BUILD_SDL
stop_thread = false;
decoderThread = SDL_CreateThread((int (*)(void *))decode_loop, NULL);
if (!decoderThread)
{
debugLog("Failed to create Ogg Vorbis decode thread: "
+ std::string(SDL_GetError()));
}
#endif
}
void OggDecoder::stopDecoderThread()
{
#ifdef BBGE_BUILD_SDL
if (decoderThread)
{
stop_thread = true;
debugLog("Waiting for decoder thread to exit...");
SDL_WaitThread(decoderThread, NULL);
decoderThread = NULL;
}
#endif
}
void OggDecoder::detachDecoder(OggDecoder *ogg)
{
#ifdef BBGE_BUILD_SDL
if(decoderThread)
{
ogg->thread = true;
decoderQ.push(ogg);
}
#endif
}
void OggDecoder::decode_loop(OggDecoder *this_)
{
while (!this_->stop_thread)
{
#ifdef BBGE_BUILD_SDL
SDL_Delay(10);
#endif
// Transfer decoder to this background thread
OggDecoder *ogg;
while(decoderQ.pop(ogg))
decoderList.push_back(ogg);
for(std::list<OggDecoder*>::iterator it = decoderList.begin(); it != decoderList.end(); )
{
ogg = *it;
if (ogg->playing)
{
int processed = 0;
alGetSourcei(ogg->source, AL_BUFFERS_PROCESSED, &processed);
for (int i = 0; i < processed; i++)
{
ogg->samples_done += BUFFER_LENGTH;
ALuint buffer = 0;
alSourceUnqueueBuffers(ogg->source, 1, &buffer);
if (buffer)
ogg->queue(buffer);
}
++it;
}
else
{
delete ogg;
decoderList.erase(it++);
}
}
core->dbg_numThreadDecoders = decoderList.size();
}
}
OggDecoder::OggDecoder(VFILE *fp)
{
for (int i = 0; i < NUM_BUFFERS; i++)
{
buffers[i] = 0;
}
this->source = 0;
this->fp = fp;
this->data = NULL;
this->data_size = 0;
this->data_pos = 0;
this->thread = false;
this->playing = false;
this->loop = false;
this->eof = false;
this->samples_done = 0;
this->stopped = false;
}
OggDecoder::OggDecoder(const void *data, long data_size)
{
for (int i = 0; i < NUM_BUFFERS; i++)
{
buffers[i] = 0;
}
this->source = 0;
this->fp = NULL;
this->data = (const char *)data;
this->data_size = data_size;
this->data_pos = 0;
this->thread = false;
this->playing = false;
this->loop = false;
this->eof = false;
this->samples_done = 0;
this->stopped = false;
}
OggDecoder::~OggDecoder()
{
_stop();
for (int i = 0; i < NUM_BUFFERS; i++)
{
if (buffers[i])
alDeleteBuffers(1, &buffers[i]);
}
}
bool OggDecoder::start(ALuint source, bool loop)
{
this->source = source;
this->loop = loop;
if (fp) {
if (ov_open_callbacks(fp, &vf, NULL, 0, local_OV_CALLBACKS_NOCLOSE) != 0)
{
debugLog("ov_open() failed for file");
return false;
}
}
else
{
data_pos = 0;
if (ov_open_callbacks(this, &vf, NULL, 0, ogg_memory_callbacks) != 0)
{
debugLog("ov_open() failed for memory buffer");
return false;
}
}
vorbis_info *info = ov_info(&vf, -1);
if (!info)
{
debugLog("ov_info() failed");
ov_clear(&vf);
return false;
}
if (info->channels == 1)
format = AL_FORMAT_MONO16;
else if (info->channels == 2)
format = AL_FORMAT_STEREO16;
else
{
std::ostringstream os;
os << "Bad channel count " << info->channels;
debugLog(os.str());
ov_clear(&vf);
return false;
}
freq = info->rate;
/* NOTE: The failure to use alGetError() here and elsewhere is
* intentional -- since alGetError() writes to a global buffer and
* is thus not thread-safe, we can't use it either in the decoding
* threads _or_ here in the main thread. In this case, we rely on
* the specification that failing OpenAL calls do not modify return
* parameters to detect failure; for functions that do not return
* values, we have no choice but to hope for the best. (From a
* multithreading point of view, the insistence on using a global
* error buffer instead of returning success/failure or error codes
* from functions is a remarkably poor design decision. Not that a
* mere library user has much choice except to live with it...)
* --achurch */
buffers[0] = 0;
alGenBuffers(NUM_BUFFERS, buffers);
if (!buffers[0])
{
debugLog("Failed to generate OpenAL buffers");
ov_clear(&vf);
return false;
}
playing = true;
eof = false;
samples_done = 0;
for (int i = 0; i < NUM_BUFFERS; i++)
queue(buffers[i]);
detachDecoder(this);
return true;
}
void OggDecoder::update()
{
if (!playing || thread)
return;
int processed = 0;
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
for (int i = 0; i < processed; i++)
{
samples_done += BUFFER_LENGTH;
ALuint buffer;
alSourceUnqueueBuffers(source, 1, &buffer);
queue(buffer);
}
}
void OggDecoder::stop()
{
if (thread)
playing = false; // The background thread will take care of deletion then.
else
delete this;
}
void OggDecoder::_stop()
{
playing = false;
if (stopped)
return;
ov_clear(&vf);
alSourceStop(source);
int queued = 0;
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
for (int i = 0; i < queued; i++)
{
ALuint buffer;
alSourceUnqueueBuffers(source, 1, &buffer);
}
for (int i = 0; i < NUM_BUFFERS; i++)
{
alDeleteBuffers(1, &buffers[i]);
buffers[i] = 0;
}
stopped = true;
}
double OggDecoder::position()
{
ALint samples_played = 0;
alGetSourcei(source, AL_SAMPLE_OFFSET, &samples_played);
samples_played += samples_done;
return (double)samples_played / (double)freq;
}
#if (defined(BBGE_BUILD_SDL) && (SDL_BYTEORDER == SDL_BIG_ENDIAN))
#define BBGE_BIGENDIAN 1
#else
#define BBGE_BIGENDIAN 0
#endif
void OggDecoder::queue(ALuint buffer)
{
if (!playing || eof)
return;
const int channels = (format == AL_FORMAT_STEREO16 ? 2 : 1);
const int buffer_size = BUFFER_LENGTH * channels * 2;
int pcm_size = 0;
bool just_looped = false; // Avoid infinite loops on empty files.
while (pcm_size < buffer_size && !eof)
{
int bitstream_unused;
const int nread = ov_read(
&vf, pcm_buffer + pcm_size, buffer_size - pcm_size,
/*bigendianp*/ BBGE_BIGENDIAN, /*word*/ 2, /*sgned*/ 1,
&bitstream_unused
);
if (nread == 0 || nread == OV_EOF)
{
if (loop && !just_looped)
{
just_looped = true;
samples_done = 0;
ov_pcm_seek(&vf, 0);
}
else
{
eof = true;
}
}
else if (nread == OV_HOLE)
{
debugLog("Warning: decompression error, data dropped");
}
else if (nread < 0)
{
std::ostringstream os;
os << "Decompression error: " << nread;
debugLog(os.str());
eof = true;
}
else
{
pcm_size += nread;
just_looped = false;
}
}
if (pcm_size > 0)
{
alBufferData(buffer, format, pcm_buffer, pcm_size, freq);
alSourceQueueBuffers(source, 1, &buffer);
}
}
size_t OggDecoder::mem_read(void *ptr, size_t size, size_t nmemb, void *datasource)
{
OggDecoder *this_ = (OggDecoder *)datasource;
long to_read = size * nmemb;
if (to_read > this_->data_size - this_->data_pos)
to_read = this_->data_size - this_->data_pos;
if (to_read < 0)
to_read = 0;
memcpy(ptr, this_->data + this_->data_pos, to_read);
this_->data_pos += to_read;
return to_read / size;
}
int OggDecoder::mem_seek(void *datasource, ogg_int64_t offset, int whence)
{
OggDecoder *this_ = (OggDecoder *)datasource;
if (whence == SEEK_CUR)
offset += this_->data_pos;
else if (whence == SEEK_END)
offset += this_->data_size;
if (offset < 0)
offset = 0;
else if (offset > this_->data_size)
offset = this_->data_size;
this_->data_pos = offset;
return 0;
}
long OggDecoder::mem_tell(void *datasource)
{
OggDecoder *this_ = (OggDecoder *)datasource;
return this_->data_pos;
}
///////////////////////////////////////////////////////////////////////////
/* for porting purposes... */
#ifndef STUBBED
#ifndef _DEBUG
#define STUBBED(x)
#else
#define STUBBED(x) { \
static bool first_time = true; \
if (first_time) { \
first_time = false; \
fprintf(stderr, "STUBBED: %s (%s, %s:%d)\n", x, __FUNCTION__, __FILE__, __LINE__); \
} \
}
#endif
#endif
namespace FMOD {
#if _DEBUG
#ifdef _MSC_VER
#define bbgeDebugBreak _CrtDbgBreak
#elif defined(__GNUC__) && ((__i386__) || (__x86_64__))
#define bbgeDebugBreak() __asm__ __volatile__ ( "int $3\n\t" )
#else
#define bbgeDebugBreak() raise(SIGTRAP)
#endif
#define SANITY_CHECK_OPENAL_CALL() { \
const ALenum err = alGetError(); \
if (err != AL_NONE) { \
fprintf(stderr, "WARNING: OpenAL error %s:%d: 0x%X\n", \
__FILE__, __LINE__, (int) err); \
bbgeDebugBreak(); \
} \
}
#else
#define SANITY_CHECK_OPENAL_CALL()
#endif
// HACK: works fairly well without it. Annoying to be thrown into the debugger because all channels are full.
#undef SANITY_CHECK_OPENAL_CALL
#define SANITY_CHECK_OPENAL_CALL()
// simply nasty.
#define ALBRIDGE(cls,method,params,args) \
FMOD_RESULT cls::method params { \
if (!this) return FMOD_ERR_INTERNAL; \
return ((OpenAL##cls *) this)->method args; \
}
// FMOD::Sound implementation ...
class OpenALSound
{
public:
OpenALSound(VFILE *_fp, const bool _looping); // ctor for ogg streamed from file
OpenALSound(void *_data, long _size, const bool _looping); // ctor for ogg streamed from memory
OpenALSound(ALuint _bid, const bool _looping); // ctor for raw samples already assigned an opanAL buffer ID
VFILE *getFile() const { return fp; }
const void *getData() const { return data; }
long getSize() const { return size; }
bool isLooping() const { return looping; }
bool isRaw() const { return raw; }
FMOD_RESULT release();
void reference() { refcount++; }
ALuint getBufferName() const { return bid; }
private:
VFILE * const fp;
void * const data; // Only used if fp==NULL
const long size; // Only used if fp==NULL
const bool looping;
int refcount;
const bool raw; // true if buffer holds raw PCM data
ALuint bid; // only used if raw == true
};
OpenALSound::OpenALSound(VFILE *_fp, const bool _looping)
: fp(_fp)
, data(NULL)
, size(0)
, looping(_looping)
, refcount(1)
, raw(false)
, bid(0)
{
}
OpenALSound::OpenALSound(void *_data, long _size, const bool _looping)
: fp(NULL)
, data(_data)
, size(_size)
, looping(_looping)
, refcount(1)
, raw(false)
, bid(0)
{
}
OpenALSound::OpenALSound(ALuint _bid, const bool _looping)
: fp(NULL)
, data(NULL)
, size(0)
, looping(_looping)
, refcount(1)
, raw(true)
, bid(_bid)
{
}
ALBRIDGE(Sound,release,(),())
FMOD_RESULT OpenALSound::release()
{
refcount--;
if (refcount <= 0)
{
if(raw)
{
alDeleteBuffers(1, &bid);
}
else
{
if (fp)
vfclose(fp);
else
free(data);
}
delete this;
}
return FMOD_OK;
}
class OpenALChannelGroup;
class OpenALChannel
{
public:
OpenALChannel();
FMOD_RESULT setVolume(const float _volume, const bool setstate=true);
FMOD_RESULT setPaused(const bool _paused, const bool setstate=true);
FMOD_RESULT setFrequency(const float _frequency);
FMOD_RESULT setPriority(int _priority);
FMOD_RESULT getPosition(unsigned int *position, FMOD_TIMEUNIT postype);
FMOD_RESULT getVolume(float *_volume);
FMOD_RESULT isPlaying(bool *isplaying);
FMOD_RESULT setChannelGroup(ChannelGroup *channelgroup);
FMOD_RESULT stop();
FMOD_RESULT setPan(const float pan);
void setGroupVolume(const float _volume);
void setSourceName(const ALuint _sid) { sid = _sid; }
ALuint getSourceName() const { return sid; }
bool start(OpenALSound *sound);
void update();
void reacquire();
bool isInUse() const { return inuse; }
void setSound(OpenALSound *sound);
private:
ALuint sid; // source id.
float groupvolume;
float volume;
bool paused;
int priority;
float frequency;
OpenALChannelGroup *group;
OpenALSound *sound;
OggDecoder *decoder;
bool inuse;
bool initial;
};
class OpenALChannelGroup
{
public:
OpenALChannelGroup(const char *_name);
~OpenALChannelGroup();
FMOD_RESULT stop();
FMOD_RESULT addDSP(DSP *dsp, DSPConnection **connection);
FMOD_RESULT getPaused(bool *_paused);
FMOD_RESULT setPaused(const bool _paused);
FMOD_RESULT getVolume(float *_volume);
FMOD_RESULT setVolume(const float _volume);
bool attachChannel(OpenALChannel *channel);
void detachChannel(OpenALChannel *channel);
private:
const char *name;
bool paused;
int channel_count;
OpenALChannel **channels;
float volume;
};
// FMOD::Channel implementation...
OpenALChannel::OpenALChannel()
: sid(0)
, groupvolume(1.0f)
, volume(1.0f)
, paused(false)
, priority(0)
, frequency(1.0f)
, group(NULL)
, sound(NULL)
, decoder(NULL)
, inuse(false)
, initial(true)
{
}
void OpenALChannel::reacquire()
{
assert(!inuse);
inuse = true;
volume = 1.0f;
paused = true;
priority = 0;
frequency = 1.0f;
sound = NULL;
initial = true;
}
void OpenALChannel::setGroupVolume(const float _volume)
{
groupvolume = _volume;
alSourcef(sid, AL_GAIN, volume * groupvolume);
SANITY_CHECK_OPENAL_CALL();
}
bool OpenALChannel::start(OpenALSound *sound)
{
if (decoder)
delete decoder;
if (sound->isRaw())
{
alSourcei(sid, AL_BUFFER, sound->getBufferName());
alSourcei(sid, AL_LOOPING, sound->isLooping() ? AL_TRUE : AL_FALSE);
}
else
{
if (sound->getFile())
decoder = new OggDecoder(sound->getFile());
else
decoder = new OggDecoder(sound->getData(), sound->getSize());
if (!decoder->start(sid, sound->isLooping()))
{
delete decoder;
decoder = NULL;
return false;
}
}
return true;
}
void OpenALChannel::update()
{
if (inuse)
{
if (decoder)
decoder->update();
ALint state = 0;
alGetSourceiv(sid, AL_SOURCE_STATE, &state);
SANITY_CHECK_OPENAL_CALL();
if (state == AL_STOPPED)
stop();
}
}
ALBRIDGE(Channel,setVolume,(float volume),(volume))
FMOD_RESULT OpenALChannel::setVolume(const float _volume, const bool setstate)
{
if (setstate)
volume = _volume;
alSourcef(sid, AL_GAIN, _volume * groupvolume);
SANITY_CHECK_OPENAL_CALL();
return FMOD_OK;
}
ALBRIDGE(Channel,getPosition,(unsigned int *position, FMOD_TIMEUNIT postype),(position,postype))
FMOD_RESULT OpenALChannel::getPosition(unsigned int *position, FMOD_TIMEUNIT postype)
{
assert(postype == FMOD_TIMEUNIT_MS);
if (decoder)
{
*position = (unsigned int) (decoder->position() * 1000.0);
}
else
{
ALfloat secs = 0.0f;
alGetSourcefv(sid, AL_SEC_OFFSET, &secs);
SANITY_CHECK_OPENAL_CALL();
*position = (unsigned int) (secs * 1000.0f);
}
return FMOD_OK;
}
ALBRIDGE(Channel,getVolume,(float *volume),(volume))
FMOD_RESULT OpenALChannel::getVolume(float *_volume)
{
*_volume = volume;
return FMOD_OK;
}
ALBRIDGE(Channel,isPlaying,(bool *isplaying),(isplaying))
FMOD_RESULT OpenALChannel::isPlaying(bool *isplaying)
{
// Apple's Mac OS X has a bug; alSourceRewind() doesn't make the sources
// AL_INITIAL again, so we have to track this ourselves. :/
if (initial)
{
*isplaying = true;
return FMOD_OK;
}
ALint state = 0;
alGetSourceiv(sid, AL_SOURCE_STATE, &state);
SANITY_CHECK_OPENAL_CALL();
*isplaying = ((state == AL_PLAYING) || (state == AL_INITIAL));
if (state == AL_PAUSED)
STUBBED("Should paused channels count as playing?"); // !!! FIXME
return FMOD_OK;
}
ALBRIDGE(Channel,setChannelGroup,(ChannelGroup *channelgroup),(channelgroup))
FMOD_RESULT OpenALChannel::setChannelGroup(ChannelGroup *_channelgroup)
{
OpenALChannelGroup *channelgroup = ((OpenALChannelGroup *) _channelgroup);
assert(channelgroup);
if (!channelgroup->attachChannel(this))
return FMOD_ERR_INTERNAL;
if ((group != NULL) && (group != channelgroup))
group->detachChannel(this);
group = channelgroup;
return FMOD_OK;
}
ALBRIDGE(Channel,setFrequency,(float frequency),(frequency))
FMOD_RESULT OpenALChannel::setFrequency(const float _frequency)
{
frequency = _frequency;
STUBBED("read the docs, verify this");
alSourcef(sid, AL_PITCH, _frequency);
SANITY_CHECK_OPENAL_CALL();
return FMOD_OK;
}
ALBRIDGE(Channel,setPaused,(bool paused),(paused))
FMOD_RESULT OpenALChannel::setPaused(const bool _paused, const bool setstate)
{
ALint state = 0;
alGetSourceiv(sid, AL_SOURCE_STATE, &state);
SANITY_CHECK_OPENAL_CALL();
if ((_paused) && (state == AL_PLAYING))
{
alSourcePause(sid);
initial = false;
SANITY_CHECK_OPENAL_CALL();
}
else if ((!_paused) && (initial || ((state == AL_INITIAL) || (state == AL_PAUSED))))
{
alSourcePlay(sid);
initial = false;
SANITY_CHECK_OPENAL_CALL();
}
if (setstate)
paused = _paused;
return FMOD_OK;
}
ALBRIDGE(Channel,setPriority,(int priority),(priority))
FMOD_RESULT OpenALChannel::setPriority(int _priority)
{
priority = _priority;
return FMOD_OK;
}
ALBRIDGE(Channel,setPan,(float volume),(volume))
FMOD_RESULT OpenALChannel::setPan(const float pan)
{
alSource3f(sid, AL_POSITION, pan, 0, 0);
SANITY_CHECK_OPENAL_CALL();
return FMOD_OK;
}
// FMOD::ChannelGroup implementation...
OpenALChannelGroup::OpenALChannelGroup(const char *_name)
: name(NULL)
, paused(false)
, channel_count(0)
, channels(NULL)
, volume(1.0f)
{
if (_name)
{
char *buf = new char[strlen(_name) + 1];
strcpy(buf, _name);
name = buf;
}
}
OpenALChannelGroup::~OpenALChannelGroup()
{
delete[] name;
}
bool OpenALChannelGroup::attachChannel(OpenALChannel *channel)
{
channel->setGroupVolume(volume);
for (int i = 0; i < channel_count; i++)
{
if (channels[i] == channel)
return true;
}
void *ptr = realloc(channels, sizeof (OpenALChannel *) * (channel_count + 1));
if (ptr == NULL)
return false;
channels = (OpenALChannel **) ptr;
channels[channel_count++] = channel;
return true;
}
void OpenALChannelGroup::detachChannel(OpenALChannel *channel)
{
for (int i = 0; i < channel_count; i++)
{
if (channels[i] == channel)
{
if (i < (channel_count-1))
memmove(&channels[i], &channels[i+1], sizeof (OpenALChannel *) * ((channel_count - i) - 1));
channel_count--;
return;
}
}
assert(false && "Detached a channel that isn't part of the group!");
}
ALBRIDGE(ChannelGroup,addDSP,(DSP *dsp, DSPConnection **connection),(dsp,connection))
FMOD_RESULT OpenALChannelGroup::addDSP(DSP *dsp, DSPConnection **connection)
{
STUBBED("write me");
return FMOD_ERR_INTERNAL;
}
ALBRIDGE(ChannelGroup,getPaused,(bool *paused),(paused))
FMOD_RESULT OpenALChannelGroup::getPaused(bool *_paused)
{
*_paused = paused;
return FMOD_OK;
}
ALBRIDGE(ChannelGroup,getVolume,(float *volume),(volume))
FMOD_RESULT OpenALChannelGroup::getVolume(float *_volume)
{
*_volume = volume;
return FMOD_OK;
}
ALBRIDGE(ChannelGroup,setPaused,(bool paused),(paused))
FMOD_RESULT OpenALChannelGroup::setPaused(const bool _paused)
{
for (int i = 0; i < channel_count; i++)
channels[i]->setPaused(_paused, false);
paused = _paused;
return FMOD_OK;
}
ALBRIDGE(ChannelGroup,setVolume,(float volume),(volume))
FMOD_RESULT OpenALChannelGroup::setVolume(const float _volume)
{
volume = _volume;
for (int i = 0; i < channel_count; i++)
channels[i]->setGroupVolume(_volume);
return FMOD_OK;
}
ALBRIDGE(ChannelGroup,stop,(),())
FMOD_RESULT OpenALChannelGroup::stop()
{
for (int i = 0; i < channel_count; i++)
channels[i]->stop();
return FMOD_OK;
}
// FMOD::DSP implementation...
FMOD_RESULT DSP::getActive(bool *active)
{
STUBBED("write me");
*active = false;
return FMOD_ERR_INTERNAL;
}
FMOD_RESULT DSP::remove()
{
STUBBED("write me");
return FMOD_ERR_INTERNAL;
}
FMOD_RESULT DSP::setParameter(int index, float value)
{
STUBBED("write me");
return FMOD_ERR_INTERNAL;
}
void OpenALChannel::setSound(OpenALSound *_sound)
{
if(sound == _sound)
return;
if (sound)
sound->release();
sound = _sound;
if (sound)
sound->reference();
}
ALBRIDGE(Channel,stop,(),())
FMOD_RESULT OpenALChannel::stop()
{
if (decoder)
{
decoder->stop();
decoder = NULL;
}
alSourceStop(sid);
SANITY_CHECK_OPENAL_CALL();
alSourcei(sid, AL_BUFFER, 0);
SANITY_CHECK_OPENAL_CALL();
if (sound)
{
sound->release();
sound = NULL;
}
paused = false;
inuse = false;
initial = false;
return FMOD_OK;
}
// FMOD::System implementation ...
class OpenALSystem
{
public:
OpenALSystem();
~OpenALSystem();
FMOD_RESULT init(int maxchannels, const FMOD_INITFLAGS flags, const void *extradriverdata);
FMOD_RESULT update();
FMOD_RESULT release();
FMOD_RESULT getVersion(unsigned int *version);
FMOD_RESULT setSpeakerMode(const FMOD_SPEAKERMODE speakermode);
FMOD_RESULT setFileSystem(FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, const int blockalign);
FMOD_RESULT setDSPBufferSize(const unsigned int bufferlength, const int numbuffers);
FMOD_RESULT createChannelGroup(const char *name, ChannelGroup **channelgroup);
FMOD_RESULT createDSPByType(const FMOD_DSP_TYPE type, DSP **dsp);
FMOD_RESULT createSound(const char *name_or_data, const FMOD_MODE mode, const FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound);
FMOD_RESULT createStream(const char *name_or_data, const FMOD_MODE mode, const FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound);
FMOD_RESULT getDriverCaps(const int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency, FMOD_SPEAKERMODE *controlpanelspeakermode);
FMOD_RESULT getMasterChannelGroup(ChannelGroup **channelgroup);
FMOD_RESULT playSound(FMOD_CHANNELINDEX channelid, Sound *sound, bool paused, Channel **channel);
FMOD_RESULT getNumChannels(int *maxchannels_ret);
private:
OpenALChannelGroup *master_channel_group;
int num_channels;
OpenALChannel *channels;
};
OpenALSystem::OpenALSystem()
: master_channel_group(NULL)
, num_channels(0)
, channels(NULL)
{
}
OpenALSystem::~OpenALSystem()
{
delete master_channel_group;
delete[] channels;
}
FMOD_RESULT System_Create(FMOD_SYSTEM **system)
{
*system = (FMOD_SYSTEM *) new OpenALSystem;
return FMOD_OK;
}
ALBRIDGE(System,createChannelGroup,(const char *name, ChannelGroup **channelgroup),(name,channelgroup))
FMOD_RESULT OpenALSystem::createChannelGroup(const char *name, ChannelGroup **channelgroup)
{
*channelgroup = (ChannelGroup *) new OpenALChannelGroup(name);
return FMOD_OK;
}
ALBRIDGE(System,createDSPByType,(FMOD_DSP_TYPE type, DSP **dsp),(type,dsp))
FMOD_RESULT OpenALSystem::createDSPByType(const FMOD_DSP_TYPE type, DSP **dsp)
{
*dsp = NULL;
STUBBED("write me");
return FMOD_ERR_INTERNAL;
}
static void *decode_to_pcm(VFILE *io, ALenum &format, ALsizei &size, ALuint &freq)
{
ALubyte *retval = NULL;
// Uncompress and feed to the AL.
OggVorbis_File vf;
memset(&vf, '\0', sizeof (vf));
if (ov_open_callbacks(io, &vf, NULL, 0, local_OV_CALLBACKS_NOCLOSE) == 0)
{
int bitstream = 0;
vorbis_info *info = ov_info(&vf, -1);
size = 0;
format = (info->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
freq = info->rate;
if ((info->channels != 1) && (info->channels != 2))
{
ov_clear(&vf);
return NULL;
}
char buf[1024 * 16];
long rc = 0;
size_t allocated = 64 * 1024;
retval = (ALubyte *) malloc(allocated);
while ( (rc = ov_read(&vf, buf, sizeof (buf), BBGE_BIGENDIAN, 2, 1, &bitstream)) != 0 )
{
if (rc > 0)
{
size += rc;
if (size >= allocated)
{
allocated *= 2;
ALubyte *tmp = (ALubyte *) realloc(retval, allocated);
if (tmp == NULL)
{
free(retval);
retval = NULL;
break;
}
retval = tmp;
}
memcpy(retval + (size - rc), buf, rc);
}
}
ov_clear(&vf);
return retval;
}
return NULL;
}
ALBRIDGE(System,createSound,(const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound),(name_or_data,mode,exinfo,sound))
FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE mode, const FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound)
{
assert(!exinfo);
FMOD_RESULT retval = FMOD_ERR_INTERNAL;
// !!! FIXME: if it's not Ogg, we don't have a decoder. I'm lazy. :/
char *fname = (char *) alloca(strlen(name_or_data) + 16);
strcpy(fname, name_or_data);
char *ptr = strrchr(fname, '.');
if (ptr) *ptr = '\0';
strcat(fname, ".ogg");
// just in case...
VFILE *io = vfopen(core->adjustFilenameCase(fname).c_str(), "rb");
if (io == NULL)
return FMOD_ERR_INTERNAL;
if (mode & FMOD_CREATESTREAM)
{
// Create streaming file handle decoder
*sound = (Sound *) new OpenALSound(io, (((mode & FMOD_LOOP_OFF) == 0) && (mode & FMOD_LOOP_NORMAL)));
retval = FMOD_OK;
}
else if(core->settings.prebufferSounds)
{
// Pre-decode the sound file and store the raw PCM buffer
ALenum format = AL_NONE;
ALsizei size = 0;
ALuint freq = 0;
void *data = decode_to_pcm(io, format, size, freq);
vfclose(io);
ALuint bid = 0;
alGenBuffers(1, &bid);
if (bid != 0)
{
alBufferData(bid, format, data, size, freq);
*sound = (Sound *) new OpenALSound(bid, (((mode & FMOD_LOOP_OFF) == 0) && (mode & FMOD_LOOP_NORMAL)));
retval = FMOD_OK;
}
free(data);
}
else
{
// Create streaming memory decoder
vfseek(io, 0, SEEK_END);
long size = vftell(io);
if (vfseek(io, 0, SEEK_SET) != 0)
{
debugLog("Seek error on " + std::string(fname));
vfclose(io);
return FMOD_ERR_INTERNAL;
}
void *data = malloc(size);
if (data == NULL)
{
debugLog("Out of memory for " + std::string(fname));
vfclose(io);
return FMOD_ERR_INTERNAL;
}
long nread = vfread(data, 1, size, io);
vfclose(io);
vfclear(io);
if (nread != size)
{
debugLog("Failed to read data from " + std::string(fname));
free(data);
return FMOD_ERR_INTERNAL;
}
*sound = (Sound *) new OpenALSound(data, size, (((mode & FMOD_LOOP_OFF) == 0) && (mode & FMOD_LOOP_NORMAL)));
retval = FMOD_OK;
}
return retval;
}
ALBRIDGE(System,createStream,(const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound),(name_or_data,mode,exinfo,sound))
FMOD_RESULT OpenALSystem::createStream(const char *name_or_data, const FMOD_MODE mode, const FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound)
{
return createSound(name_or_data, mode | FMOD_CREATESTREAM, exinfo, sound);
}
ALBRIDGE(System,getDriverCaps,(int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency, FMOD_SPEAKERMODE *controlpanelspeakermode),(id,caps,minfrequency,maxfrequency,controlpanelspeakermode))
FMOD_RESULT OpenALSystem::getDriverCaps(const int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency, FMOD_SPEAKERMODE *controlpanelspeakermode)
{
assert(!id);
assert(!minfrequency);
assert(!maxfrequency);
*controlpanelspeakermode = FMOD_SPEAKERMODE_STEREO; // not strictly true, but works for aquaria's usage.
*caps = 0; // aquaria only checks FMOD_CAPS_HARDWARE_EMULATED.
return FMOD_OK;
}
ALBRIDGE(System,getMasterChannelGroup,(ChannelGroup **channelgroup),(channelgroup))
FMOD_RESULT OpenALSystem::getMasterChannelGroup(ChannelGroup **channelgroup)
{
*channelgroup = (ChannelGroup *) master_channel_group;
return FMOD_OK;
}
ALBRIDGE(System,getVersion,(unsigned int *version),(version))
FMOD_RESULT OpenALSystem::getVersion(unsigned int *version)
{
*version = FMOD_VERSION;
return FMOD_OK;
}
ALBRIDGE(System,init,(int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata),(maxchannels,flags,extradriverdata))
FMOD_RESULT OpenALSystem::init(int maxchannels, const FMOD_INITFLAGS flags, const void *extradriverdata)
{
ALCdevice *dev = alcOpenDevice(NULL);
if (!dev)
return FMOD_ERR_INTERNAL;
// OpenAL doesn't provide a way to request sources that can be either
// mono or stereo, so we need to request both separately (thus allocating
// twice the theoretical requirement -- oh well). --achurch
ALCint requested_attributes[5];
requested_attributes[0] = ALC_MONO_SOURCES;
requested_attributes[1] = maxchannels;
requested_attributes[2] = ALC_STEREO_SOURCES;
requested_attributes[3] = maxchannels;
requested_attributes[4] = 0;
ALCcontext *ctx = alcCreateContext(dev, requested_attributes);
if (!ctx)
{
alcCloseDevice(dev);
return FMOD_ERR_INTERNAL;
}
ALCint num_attributes = 0;
alcGetIntegerv(dev, ALC_ATTRIBUTES_SIZE, 1, &num_attributes);
if (num_attributes > 0) {
ALCint *attributes = new ALCint[num_attributes];
alcGetIntegerv(dev, ALC_ALL_ATTRIBUTES, num_attributes, attributes);
int i;
for (i = 0; i < num_attributes; i += 2) {
if (attributes[i] == ALC_MONO_SOURCES) {
if (attributes[i+1] <= 0) {
debugLog("Couldn't get any mono sources, aborting");
alcDestroyContext(ctx);
alcCloseDevice(dev);
return FMOD_ERR_INTERNAL;
} else if (attributes[i+1] < num_channels) {
std::ostringstream os;
os << "Only got " << attributes[i+1] << " of "
<< maxchannels << " mono sources";
debugLog(os.str());
maxchannels = attributes[i+1];
}
} else if (attributes[i] == ALC_STEREO_SOURCES) {
if (attributes[i+1] <= 0) {
debugLog("Couldn't get any stereo sources, aborting");
alcDestroyContext(ctx);
alcCloseDevice(dev);
return FMOD_ERR_INTERNAL;
} else if (attributes[i+1] < num_channels) {
std::ostringstream os;
os << "Only got " << attributes[i+1] << " of "
<< maxchannels << " stereo sources";
debugLog(os.str());
maxchannels = attributes[i+1];
}
}
}
delete[] attributes;
} else {
debugLog("WARNING: couldn't get device attributes!");
}
alcMakeContextCurrent(ctx);
alcProcessContext(ctx);
#ifdef _DEBUG
printf("AL_VENDOR: %s\n", (char *) alGetString(AL_VENDOR));
printf("AL_RENDERER: %s\n", (char *) alGetString(AL_RENDERER));
printf("AL_VERSION: %s\n", (char *) alGetString(AL_VERSION));
printf("AL_EXTENSIONS: %s\n", (char *) alGetString(AL_EXTENSIONS));
#endif
SANITY_CHECK_OPENAL_CALL();
master_channel_group = new OpenALChannelGroup("master");
num_channels = maxchannels;
channels = new OpenALChannel[maxchannels];
ALenum err = alGetError(); // clear any existing error state.
for (int i = 0; i < num_channels; i++)
{
ALuint sid = 0;
alGenSources(1, &sid);
err = alGetError();
if (err != AL_NONE)
{
char errmsg[512];
sprintf(errmsg, "WARNING: OpenAL error %s:%d: 0x%X\n", __FILE__, __LINE__, (int) err);
debugLog(errmsg);
num_channels = i - 1; // last channel that worked
break;
}
alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE);
SANITY_CHECK_OPENAL_CALL();
alSource3f(sid, AL_POSITION, 0.0f, 0.0f, 0.0f); // no panning or spatialization in Aquaria.
SANITY_CHECK_OPENAL_CALL();
channels[i].setSourceName(sid);
channels[i].setChannelGroup((ChannelGroup *) master_channel_group);
}
std::stringstream ss;
ss << "Using " << num_channels << " sound channels.";
debugLog(ss.str());
OggDecoder::startDecoderThread();
return FMOD_OK;
}
ALBRIDGE(System,getNumChannels,(int *maxchannels_ret),(maxchannels_ret))
FMOD_RESULT OpenALSystem::getNumChannels(int *maxchannels_ret)
{
*maxchannels_ret = num_channels;
return FMOD_OK;
}
ALBRIDGE(System,playSound,(FMOD_CHANNELINDEX channelid, Sound *sound, bool paused, Channel **channel),(channelid,sound,paused,channel))
FMOD_RESULT OpenALSystem::playSound(FMOD_CHANNELINDEX channelid, Sound *_sound, bool paused, Channel **channel)
{
*channel = NULL;
if (channelid == FMOD_CHANNEL_FREE)
{
for (int i = 0; i < num_channels; i++)
{
if (!channels[i].isInUse())
{
channelid = (FMOD_CHANNELINDEX) i;
break;
}
}
}
if ((channelid < 0) || (channelid >= num_channels))
return FMOD_ERR_INTERNAL;
OpenALSound *sound = (OpenALSound *) _sound;
const ALuint sid = channels[channelid].getSourceName();
// alSourceRewind doesn't work right on some versions of Mac OS X.
alSourceStop(sid); // stop any playback, set to AL_INITIAL.
alSourceRewind(sid); // stop any playback, set to AL_INITIAL.
SANITY_CHECK_OPENAL_CALL();
alSourcei(sid, AL_BUFFER, 0); // Reset state to AL_UNDETERMINED.
SANITY_CHECK_OPENAL_CALL();
if (!channels[channelid].start(sound))
return FMOD_ERR_INTERNAL;
channels[channelid].reacquire();
channels[channelid].setPaused(paused);
channels[channelid].setSound(sound);
*channel = (Channel *) &channels[channelid];
return FMOD_OK;
}
ALBRIDGE(System,release,(),())
FMOD_RESULT OpenALSystem::release()
{
OggDecoder::stopDecoderThread();
ALCcontext *ctx = alcGetCurrentContext();
if (ctx)
{
for (int i = 0; i < num_channels; i++)
{
const ALuint sid = channels[i].getSourceName();
channels[i].setSourceName(0);
channels[i].setSound(NULL);
alSourceStop(sid);
alSourcei(sid, AL_BUFFER, 0);
alDeleteSources(1, &sid);
}
ALCdevice *dev = alcGetContextsDevice(ctx);
alcMakeContextCurrent(NULL);
alcSuspendContext(ctx);
alcDestroyContext(ctx);
alcCloseDevice(dev);
}
delete this;
return FMOD_OK;
}
ALBRIDGE(System,setDSPBufferSize,(unsigned int bufferlength, int numbuffers),(bufferlength,numbuffers))
FMOD_RESULT OpenALSystem::setDSPBufferSize(const unsigned int bufferlength, const int numbuffers)
{
// aquaria only uses this for FMOD_CAPS_HARDWARE_EMULATED, so I skipped it.
return FMOD_ERR_INTERNAL;
}
ALBRIDGE(System,setFileSystem,(FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, int blockalign),(useropen,userclose,userread,userseek,blockalign))
FMOD_RESULT OpenALSystem::setFileSystem(FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, const int blockalign)
{
// Aquaria sets these, but they don't do anything fancy, so we ignore them for now.
return FMOD_OK;
}
ALBRIDGE(System,setSpeakerMode,(FMOD_SPEAKERMODE speakermode),(speakermode))
FMOD_RESULT OpenALSystem::setSpeakerMode(const FMOD_SPEAKERMODE speakermode)
{
return FMOD_OK; // we ignore this for Aquaria.
}
ALBRIDGE(System,update,(),())
FMOD_RESULT OpenALSystem::update()
{
alcProcessContext(alcGetCurrentContext());
for (int i = 0; i < num_channels; i++)
channels[i].update();
#if _DEBUG
const ALenum err = alGetError();
if (err != AL_NONE)
fprintf(stderr, "WARNING: OpenAL error this frame: 0x%X\n", (int) err);
#endif
return FMOD_OK;
}
// misc FMOD bits...
FMOD_RESULT Memory_GetStats(int *currentalloced, int *maxalloced, FMOD_BOOL blocking)
{
// not ever used by Aquaria.
*currentalloced = *maxalloced = 42;
return FMOD_ERR_INTERNAL;
}
} // namespace FMOD
// end of FmodOpenALBridge.cpp ...