mirror of
https://github.com/AquariaOSE/Aquaria.git
synced 2024-11-15 22:19:07 +00:00
e6680da428
With this patch, entities can serve as a sound source whose position updates relative to the listener. This implements the last missing feature of my previous positional audio patch, which is moving sound sources. Previously, all sounds were relative to the listener with the listener centered at (0, 0, 0), and once a sound started playing, the position could not be changed. Volume was set as an estimation of the distance to the listener. These restrictions are now gone; OpenAL will handle the volume & panning based on the distance. The remaining problem is that stereo sounds are not attenuated, at all. Lua additions: - playSfx() takes an additional parameter now. - entity_setStopSoundsOnDeath()
1679 lines
47 KiB
C++
1679 lines
47 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;
|
|
#ifdef BBGE_BUILD_SDL2
|
|
decoderThread = SDL_CreateThread((int (*)(void *))decode_loop, "OggDecoder", NULL);
|
|
#else
|
|
decoderThread = SDL_CreateThread((int (*)(void *))decode_loop, NULL);
|
|
#endif
|
|
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);
|
|
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);
|
|
FMOD_RESULT setCallback(FMOD_CHANNEL_CALLBACK callback);
|
|
FMOD_RESULT getUserData(void **userdata);
|
|
FMOD_RESULT setUserData(void *userdata);
|
|
FMOD_RESULT set3DAttributes(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel);
|
|
FMOD_RESULT set3DMinMaxDistance(float mindistance, float maxdistance);
|
|
FMOD_RESULT setMode(FMOD_MODE mode);
|
|
FMOD_RESULT getMode(FMOD_MODE *mode);
|
|
|
|
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;
|
|
FMOD_CHANNEL_CALLBACK callback;
|
|
void *userdata;
|
|
FMOD_MODE _mode;
|
|
};
|
|
|
|
|
|
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)
|
|
, _mode(FMOD_DEFAULT)
|
|
{
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (inuse && callback)
|
|
callback(this, FMOD_CHANNEL_CALLBACKTYPE_END, NULL, NULL); // FIXME commanddata missing
|
|
paused = false;
|
|
inuse = false;
|
|
initial = false;
|
|
return FMOD_OK;
|
|
}
|
|
|
|
|
|
ALBRIDGE(Channel,setCallback,(FMOD_CHANNEL_CALLBACK callback),(callback))
|
|
FMOD_RESULT OpenALChannel::setCallback(FMOD_CHANNEL_CALLBACK callback)
|
|
{
|
|
this->callback = callback;
|
|
return FMOD_OK;
|
|
}
|
|
|
|
ALBRIDGE(Channel,getUserData,(void **userdata),(userdata))
|
|
FMOD_RESULT OpenALChannel::getUserData(void **userdata)
|
|
{
|
|
*userdata = this->userdata;
|
|
return FMOD_OK;
|
|
}
|
|
|
|
ALBRIDGE(Channel,setUserData,(void *userdata),(userdata))
|
|
FMOD_RESULT OpenALChannel::setUserData(void *userdata)
|
|
{
|
|
this->userdata = userdata;
|
|
return FMOD_OK;
|
|
}
|
|
|
|
ALBRIDGE(Channel,set3DAttributes,(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel),(pos, vel))
|
|
FMOD_RESULT OpenALChannel::set3DAttributes(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel)
|
|
{
|
|
if (pos)
|
|
alSource3f(sid, AL_POSITION, pos->x, pos->y, pos->z);
|
|
if(vel)
|
|
alSource3f(sid, AL_VELOCITY, vel->x, vel->y, vel->z);
|
|
|
|
SANITY_CHECK_OPENAL_CALL();
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
ALBRIDGE(Channel,set3DMinMaxDistance,(float mindistance, float maxdistance),(mindistance, maxdistance))
|
|
FMOD_RESULT OpenALChannel::set3DMinMaxDistance(float mindistance, float maxdistance)
|
|
{
|
|
alSourcef(sid, AL_REFERENCE_DISTANCE, mindistance);
|
|
alSourcef(sid, AL_MAX_DISTANCE, maxdistance);
|
|
SANITY_CHECK_OPENAL_CALL();
|
|
return FMOD_OK;
|
|
}
|
|
|
|
ALBRIDGE(Channel,setMode,(FMOD_MODE mode),(mode))
|
|
FMOD_RESULT OpenALChannel::setMode(FMOD_MODE mode)
|
|
{
|
|
_mode = mode;
|
|
|
|
if(mode & FMOD_3D_HEADRELATIVE)
|
|
alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
else // FMOD_3D_WORLDRELATIVE is the default according to FMOD docs
|
|
alSourcei(sid, AL_SOURCE_RELATIVE, AL_FALSE);
|
|
|
|
SANITY_CHECK_OPENAL_CALL();
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
ALBRIDGE(Channel,getMode,(FMOD_MODE *mode),(mode))
|
|
FMOD_RESULT OpenALChannel::getMode(FMOD_MODE *mode)
|
|
{
|
|
*mode = _mode;
|
|
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();
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 set3DListenerAttributes(int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up);
|
|
|
|
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_FALSE);
|
|
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());
|
|
|
|
// HACK: FMOD doesn't do this.
|
|
// For completeness, we pass FMOD_3D_LINEARROLLOFF to createSound().
|
|
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
|
|
SANITY_CHECK_OPENAL_CALL();
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
ALBRIDGE(System, set3DListenerAttributes, (int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up),
|
|
(listener, pos, vel, forward, up));
|
|
FMOD_RESULT OpenALSystem::set3DListenerAttributes(int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up)
|
|
{
|
|
// ignore listener parameter; there is only one listener in OpenAL.
|
|
|
|
if(up || forward)
|
|
{
|
|
ALfloat orientation[6];
|
|
alGetListenerfv(AL_ORIENTATION, &orientation[0]);
|
|
|
|
if(forward)
|
|
{
|
|
orientation[0] = forward->x;
|
|
orientation[1] = forward->y;
|
|
orientation[2] = forward->z;
|
|
}
|
|
|
|
if(up)
|
|
{
|
|
orientation[3] = up->x;
|
|
orientation[4] = up->y;
|
|
orientation[5] = up->z;
|
|
}
|
|
|
|
alListenerfv(AL_ORIENTATION, &orientation[0]);
|
|
}
|
|
|
|
if(pos)
|
|
alListener3f(AL_POSITION, pos->x, pos->y, pos->z);
|
|
|
|
if(vel)
|
|
alListener3f(AL_VELOCITY, vel->x, vel->y, vel->z);
|
|
|
|
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 ...
|
|
|