1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-12-25 22:25:46 +00:00
Aquaria/BBGE/FmodOpenALBridge.cpp
fgenesis 6dc1c1e8d1 [vfs, #3] All file reading code goes through the VFS now, new mod downloader & mod selector in place. Also a bunch of other stuff. (...)
- HTTP networking support, mods can be downloaded via the builtin downloader.
  All network activity runs in a seperate thread, which is started
  as soon as any network activity is requested.
- The master server is hard-coded to fg.wzff.de/aqmods/ if not specified otherwise;
  this setting can be overridden in the config file.
- The mod selector screen is now a grid-view for much better navigation;
  also works with joystick.
- VFS code is functionally similar to the old molebox-packed release
  for win32. The game could also have its data shipped in a Zip file
  or any other kind of archive.
- It is still possible to build without VFS support, but then the mod
  downloader and soft-patching will not be available.

The full commit history can be found here:
https://github.com/fgenesis/Aquaria_clean/compare/master...vfs

The most important commit messages follow:
[...]
    This replaces all std::ifstream with InStream, and fopen(), ... with vfopen(), ...
    Some code is #ifdef'd for better performance and less memory-copying.
    VFILE is defined to whatever type of file is in use:
    - FILE if BBGE_BUILD_VFS is not defined
    - tttvfs::VFSFile if it is.

    Other changes:
    - [un]packFile() is now unused and obsolete. That code has not been adjusted to use VFILE.
    - glpng can now load from a memory buffer.
    - TinyXML uses the VFS for reading operations now.
    - The rather clunky binary stream loading of glfont2 got replaced with ByteBuffer,
      which gets its data in one block (necessary to use the VFS without implementing
      a somewhat STL-compliant std::ifstream replacement.)
-------------
Implement loading mods from zip files.
-------------
Implement soft-patching game data files. (Replacing textures/audio/... on the fly)
-------------
Misc bits:
- Extended GUI focus handling a bit
- Fixed weirdness in texture loading... not sure but this seems more correct to me.
  Actually, considering that the texture will have its native size after restarting the game,
  the lines removed with this commit seem pretty useless.
2012-06-01 17:52:19 +02:00

1548 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
// 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 ...