warzone2100/lib/sound/openal_track.cpp

1506 lines
39 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2012 Warzone 2100 Project
Warzone 2100 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.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/** \file
* Sound library-specific functions
*/
// this has to be first
#include "lib/framework/frame.h"
#include "lib/framework/math_ext.h"
#include "lib/framework/frameresource.h"
#include "lib/exceptionhandler/dumpinfo.h"
#ifndef WZ_NOSOUND
# ifdef WZ_OS_MAC
# include <OpenAL/al.h>
# include <OpenAL/alc.h>
# else
# include <AL/al.h>
# include <AL/alc.h>
# endif
#endif
#include <physfs.h>
#include <string.h>
#include <math.h>
#include "tracklib.h"
#include "audio.h"
#include "cdaudio.h"
#include "oggvorbis.h"
#include "openal_error.h"
#include "mixer.h"
#ifndef WZ_NOSOUND
ALuint current_queue_sample = -1;
#endif
static bool openal_initialized = false;
struct AUDIO_STREAM
{
#ifndef WZ_NOSOUND
ALuint source; // OpenAL name of the sound source
#endif
struct OggVorbisDecoderState* decoder;
PHYSFS_file* fileHandle;
float volume;
// Callbacks
void (*onFinished)(void*);
void *user_data;
size_t bufferSize;
// Linked list pointer
AUDIO_STREAM * next;
};
struct SAMPLE_LIST
{
AUDIO_SAMPLE * curr;
SAMPLE_LIST * next;
};
static SAMPLE_LIST *active_samples = NULL;
#if !defined(WZ_NOSOUND)
static AUDIO_STREAM* active_streams = NULL;
static ALfloat sfx_volume = 1.0;
static ALfloat sfx3d_volume = 1.0;
static ALCdevice* device = NULL;
static ALCcontext* context = NULL;
#endif
/** Removes the given sample from the "active_samples" linked list
* \param previous either NULL (if \c to_remove is the first item in the
* list) or the item occurring just before \c to_remove in
* the list
* \param to_remove the item to actually remove from the list
*/
static void sound_RemoveSample(SAMPLE_LIST* previous, SAMPLE_LIST* to_remove)
{
if (previous != NULL && previous != to_remove)
{
// Verify that the given two samples actually follow eachother in the list
ASSERT(previous->next == to_remove, "Sound samples don't follow eachother in the list, we're probably removing the wrong item.");
// Remove the item to remove from the linked list by skipping
// it in the pointer sequence.
previous->next = to_remove->next;
}
else
{
// Apparently we're removing the first item from the list. So
// make the next one the list's head.
active_samples = to_remove->next;
}
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_InitLibrary( void )
{
#ifndef WZ_NOSOUND
int err;
const ALfloat listenerVel[3] = { 0.0, 0.0, 0.0 };
const ALfloat listenerOri[6] = { 0.0, 0.0, 1.0, 0.0, 1.0, 0.0 };
char buf[512];
const ALCchar *deviceName;
#if 0
// This code is disabled because enumerating devices apparently crashes PulseAudio on Fedora12
/* Get the available devices and print them.
* Devices are separated by NUL chars ('\0') and the list of devices is
* terminated by two NUL chars.
*/
deviceName = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
while (deviceName != NULL && *deviceName != '\0')
{
debug(LOG_SOUND, "available OpenAL device(s) are: %s", deviceName);
deviceName += strlen(deviceName) + 1;
}
#endif
#ifdef WZ_OS_WIN
/* HACK: Select the "software" OpenAL device on Windows because it
* provides 256 sound sources (unlike most Creative's default
* which provides only 16), causing our lack of source-management
* to be significantly less noticeable.
*/
device = alcOpenDevice("Generic Software");
// If the software device isn't available, fall back to default
if (!device)
#endif
{
// Open default device
device = alcOpenDevice(NULL);
}
if (!device)
{
debug(LOG_ERROR, "Couldn't open audio device.");
return false;
}
// Print current device name and add it to dump info
deviceName = alcGetString(device, ALC_DEVICE_SPECIFIER);
debug(LOG_SOUND, "Current audio device: %s", deviceName);
ssprintf(buf, "OpenAL Device Name: %s", deviceName);
addDumpInfo(buf);
context = alcCreateContext(device, NULL); //NULL was contextAttributes
if (!context)
{
debug(LOG_ERROR, "Couldn't open audio context.");
return false;
}
alcMakeContextCurrent(context);
err = sound_GetContextError(device);
if (err != ALC_NO_ERROR)
{
debug(LOG_ERROR, "Couldn't initialize audio context: %s", alcGetString(device, err));
return false;
}
// Dump Open AL device info (depends on context)
// to the crash handler for the dump file and debug log
ssprintf(buf, "OpenAL Vendor: %s", alGetString(AL_VENDOR));
addDumpInfo(buf);
debug(LOG_SOUND, "%s", buf);
ssprintf(buf, "OpenAL Version: %s", alGetString(AL_VERSION));
addDumpInfo(buf);
debug(LOG_SOUND, "%s", buf);
ssprintf(buf, "OpenAL Renderer: %s", alGetString(AL_RENDERER));
addDumpInfo(buf);
debug(LOG_SOUND, "%s", buf);
ssprintf(buf, "OpenAL Extensions: %s", alGetString(AL_EXTENSIONS));
addDumpInfo(buf);
debug(LOG_SOUND, "%s", buf);
#endif
openal_initialized = true;
#ifndef WZ_NOSOUND
// Clear Error Codes
alGetError();
alcGetError(device);
alListener3f(AL_POSITION, 0.f, 0.f, 0.f);
alListenerfv(AL_VELOCITY, listenerVel);
alListenerfv(AL_ORIENTATION, listenerOri);
alDistanceModel(AL_NONE);
sound_GetError();
#endif
return true;
}
#if !defined(WZ_NOSOUND)
static void sound_UpdateStreams(void);
#endif
void sound_ShutdownLibrary( void )
{
#if !defined(WZ_NOSOUND)
AUDIO_STREAM* stream;
#endif
SAMPLE_LIST * aSample = active_samples, * tmpSample = NULL;
if ( !openal_initialized )
{
return;
}
debug(LOG_SOUND, "starting shutdown");
#if !defined(WZ_NOSOUND)
// Stop all streams, sound_UpdateStreams() will deallocate all stopped streams
for (stream = active_streams; stream != NULL; stream = stream->next)
{
sound_StopStream(stream);
}
sound_UpdateStreams();
alcGetError(device); // clear error codes
/* On Linux since this caused some versions of OpenAL to hang on exit. - Per */
debug(LOG_SOUND, "make default context NULL");
alcMakeContextCurrent(NULL);
sound_GetContextError(device);
debug(LOG_SOUND, "destroy previous context");
alcDestroyContext(context); // this gives a long delay on some impl.
sound_GetContextError(device);
debug(LOG_SOUND, "close device");
if (alcCloseDevice(device) == ALC_FALSE)
{
debug(LOG_SOUND, "OpenAl could not close the audio device." );
}
#endif
while( aSample )
{
tmpSample = aSample->next;
free( aSample );
aSample = tmpSample;
}
active_samples = NULL;
}
/** Deletes the given sample and updates the \c previous and \c current iterators
* \param previous iterator to the previous sample in the list
* \param sample iterator to the current sample in the list which you want to delete
*/
static void sound_DestroyIteratedSample(SAMPLE_LIST** previous, SAMPLE_LIST** sample)
{
#ifndef WZ_NOSOUND
// If an OpenAL source is associated with this sample, release it
if ((*sample)->curr->iSample != (ALuint)AL_INVALID)
{
alDeleteSources(1, &(*sample)->curr->iSample);
sound_GetError();
}
#endif
// Do the cleanup of this sample
sound_FinishedCallback((*sample)->curr);
// Remove the sample from the list
sound_RemoveSample(*previous, *sample);
// Free it
free(*sample);
// Get a pointer to the next node, the previous pointer doesn't change
*sample = (*previous != NULL) ? (*previous)->next : active_samples;
}
/** Counts the number of samples in active_samples
* \return the number of actively playing sound samples
*/
unsigned int sound_GetActiveSamplesCount()
{
unsigned int num = 0;
SAMPLE_LIST* node = active_samples;
while(node)
{
num++;
node = node->next;
}
return num;
}
void sound_Update()
{
#ifndef WZ_NOSOUND
SAMPLE_LIST* node = active_samples;
SAMPLE_LIST* previous = NULL;
ALCenum err;
ALfloat gain;
if ( !openal_initialized )
{
return;
}
// Update all streaming audio
sound_UpdateStreams();
while (node != NULL)
{
ALenum state, err;
// query what the gain is for this sample
alGetSourcef(node->curr->iSample, AL_GAIN, &gain);
err = sound_GetError();
// if gain is 0, then we can't hear it, so we kill it.
if (gain == 0.0f)
{
sound_DestroyIteratedSample(&previous, &node);
continue;
}
//ASSERT(alIsSource(node->curr->iSample), "Not a valid source!");
alGetSourcei(node->curr->iSample, AL_SOURCE_STATE, &state);
// Check whether an error occurred while retrieving the state.
// If one did, the state returned is useless. So instead of
// using it continue with the next sample.
err = sound_GetError();
if (err != AL_NO_ERROR)
{
// Make sure to invoke the "finished" callback
sound_FinishedCallback(node->curr);
// Destroy this object and move to the next object
sound_DestroyIteratedSample(&previous, &node);
continue;
}
switch (state)
{
case AL_PLAYING:
case AL_PAUSED:
// If we haven't finished playing yet, just
// continue with the next item in the list.
// sound_SetObjectPosition(i->curr->iSample, i->curr->x, i->curr->y, i->curr->z);
// Move to the next object
previous = node;
node = node->next;
break;
// NOTE: if it isn't playing | paused, then it is most likely either done
// or a error. In either case, we want to kill the sample in question.
default:
sound_DestroyIteratedSample(&previous, &node);
break;
}
}
// Reset the current error state
alcGetError(device);
alcProcessContext(context);
err = sound_GetContextError(device);
if (err != ALC_NO_ERROR)
{
debug(LOG_ERROR, "Error while processing audio context: %s", alGetString(err));
}
#endif
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_QueueSamplePlaying( void )
{
#ifndef WZ_NOSOUND
ALenum state;
if ( !openal_initialized )
{
return false;
}
if ( current_queue_sample == (ALuint)AL_INVALID )
{
return false;
}
alGetSourcei(current_queue_sample, AL_SOURCE_STATE, &state);
// Check whether an error occurred while retrieving the state.
// If one did, the state returned is useless. So instead of
// using it return false.
if (sound_GetError() != AL_NO_ERROR)
return false;
if (state == AL_PLAYING)
{
return true;
}
if (current_queue_sample != (ALuint)AL_INVALID)
{
SAMPLE_LIST* node = active_samples;
SAMPLE_LIST* previous = NULL;
// We need to remove it from the queue of actively played samples
while (node != NULL)
{
if (node->curr->iSample == current_queue_sample)
{
sound_DestroyIteratedSample(&previous, &node);
current_queue_sample = AL_INVALID;
return false;
}
previous = node;
if (node)
{
node = node->next;
}
}
debug(LOG_ERROR, "Sample %u not deleted because it wasn't in the active queue!", current_queue_sample);
current_queue_sample = AL_INVALID;
}
#endif
return false;
}
/** Decodes an opened OggVorbis file into an OpenAL buffer
* \param psTrack pointer to object which will contain the final buffer
* \param PHYSFS_fileHandle file handle given by PhysicsFS to the opened file
* \return on success the psTrack pointer, otherwise it will be free'd and a NULL pointer is returned instead
*/
static inline TRACK* sound_DecodeOggVorbisTrack(TRACK *psTrack, PHYSFS_file* PHYSFS_fileHandle)
{
#ifndef WZ_NOSOUND
ALenum format;
ALuint buffer;
struct OggVorbisDecoderState *decoder;
soundDataBuffer *soundBuffer;
if ( !openal_initialized )
{
return NULL;
}
decoder = sound_CreateOggVorbisDecoder(PHYSFS_fileHandle, true);
if (decoder == NULL)
{
debug(LOG_WARNING, "Failed to open audio file for decoding");
free(psTrack);
return NULL;
}
soundBuffer = sound_DecodeOggVorbis(decoder, 0);
sound_DestroyOggVorbisDecoder(decoder);
if (soundBuffer == NULL)
{
free(psTrack);
return NULL;
}
if (soundBuffer->size == 0)
{
debug(LOG_WARNING, "sound_DecodeOggVorbisTrack: OggVorbis track is entirely empty after decoding");
// NOTE: I'm not entirely sure if a track that's empty after decoding should be
// considered an error condition. Therefore I'll only error out on DEBUG
// builds. (Returning NULL here __will__ result in a program termination.)
#ifdef DEBUG
free(soundBuffer);
free(psTrack);
return NULL;
#endif
}
// Determine PCM data format
format = (soundBuffer->channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
// Create an OpenAL buffer and fill it with the decoded data
alGenBuffers(1, &buffer);
sound_GetError();
alBufferData(buffer, format, soundBuffer->data, soundBuffer->size, soundBuffer->frequency);
sound_GetError();
free(soundBuffer);
// save buffer name in track
psTrack->iBufferName = buffer;
#endif
return psTrack;
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
TRACK* sound_LoadTrackFromFile(const char *fileName)
{
TRACK* pTrack;
PHYSFS_file* fileHandle;
size_t filename_size;
char* track_name;
// Use PhysicsFS to open the file
fileHandle = PHYSFS_openRead(fileName);
debug(LOG_NEVER, "Reading...[directory: %s] %s", PHYSFS_getRealDir(fileName), fileName);
if (fileHandle == NULL)
{
debug(LOG_ERROR, "sound_LoadTrackFromFile: PHYSFS_openRead(\"%s\") failed with error: %s\n", fileName, PHYSFS_getLastError());
return NULL;
}
if (GetLastResourceFilename() == NULL)
{
// This is a non fatal error. We just can't find filename for some reason.
debug(LOG_WARNING, "sound_LoadTrackFromFile: missing resource filename?");
filename_size = 0;
}
else
{
filename_size = strlen(GetLastResourceFilename()) + 1;
}
// allocate track, plus the memory required to contain the filename
// one malloc call ensures only one free call is required
pTrack = (TRACK*)malloc(sizeof(TRACK) + filename_size);
if (pTrack == NULL)
{
debug( LOG_FATAL, "sound_ConstructTrack: couldn't allocate memory\n" );
abort();
return NULL;
}
// Initialize everyting (except for the filename) to zero
memset(pTrack, 0, sizeof(TRACK));
// Set filename pointer; if the filename (as returned by
// GetLastResourceFilename()) is a NULL pointer, then this will be a
// NULL pointer as well.
track_name = filename_size ? (char*)(pTrack + 1) : NULL;
// Copy the filename into the struct, if we don't have a NULL pointer
if (filename_size != 0)
{
strcpy(track_name, GetLastResourceFilename());
}
pTrack->fileName = track_name;
// Now use sound_ReadTrackFromBuffer to decode the file's contents
pTrack = sound_DecodeOggVorbisTrack(pTrack, fileHandle);
PHYSFS_close(fileHandle);
return pTrack;
}
void sound_FreeTrack( TRACK *psTrack )
{
#ifndef WZ_NOSOUND
alDeleteBuffers(1, &psTrack->iBufferName);
sound_GetError();
#endif
}
#ifndef WZ_NOSOUND
static void sound_AddActiveSample( AUDIO_SAMPLE *psSample )
{
SAMPLE_LIST *tmp = (SAMPLE_LIST *) malloc( sizeof(SAMPLE_LIST) );
// Prepend the given sample to our list of active samples
tmp->curr = psSample;
tmp->next = active_samples;
active_samples = tmp;
}
#endif
/** Routine gets rid of the psObj's sound sample and reference in active_samples.
*/
void sound_RemoveActiveSample( AUDIO_SAMPLE *psSample )
{
SAMPLE_LIST* node = active_samples;
SAMPLE_LIST* previous = NULL;
while (node != NULL)
{
if (node->curr->psObj == psSample->psObj)
{
debug(LOG_MEMORY, "Removing object 0x%p from active_samples list 0x%p\n", psSample->psObj, node);
// Buginator: should we wait for it to finish, or just stop it?
sound_StopSample(node->curr);
sound_FinishedCallback(node->curr); //tell the callback it is finished.
sound_DestroyIteratedSample(&previous, &node);
}
else
{
// Move to the next sample object
previous = node;
node = node->next;
}
}
}
#ifndef WZ_NOSOUND
static bool sound_SetupChannel( AUDIO_SAMPLE *psSample )
{
sound_AddActiveSample( psSample );
return sound_TrackLooped(psSample->iTrack);
}
#endif
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_Play2DSample( TRACK *psTrack, AUDIO_SAMPLE *psSample, bool bQueued )
{
#ifndef WZ_NOSOUND
ALfloat zero[3] = { 0.0, 0.0, 0.0 };
ALfloat volume;
ALint error;
if (sfx_volume == 0.0)
{
return false;
}
volume = ((float)psTrack->iVol / 100.0f); // each object can have OWN volume!
psSample->fVol = volume; // save computed volume
volume *= sfx_volume; // and now take into account the Users sound Prefs.
// We can't hear it, so don't bother creating it.
if (volume == 0.0f)
{
return false;
}
// Clear error codes
alGetError();
alGenSources( 1, &(psSample->iSample) );
error = sound_GetError();
if (error != AL_NO_ERROR)
{
/* FIXME: We run out of OpenAL sources very quickly, so we
* should handle the case where we've ran out of them.
* Currently we don't do this, causing some unpleasant side
* effects, e.g. crashing...
*/
}
alSourcef( psSample->iSample, AL_PITCH, 1.0f );
alSourcef( psSample->iSample, AL_GAIN,volume );
alSourcefv( psSample->iSample, AL_POSITION, zero );
alSourcefv( psSample->iSample, AL_VELOCITY, zero );
alSourcei( psSample->iSample, AL_BUFFER, psTrack->iBufferName );
alSourcei( psSample->iSample, AL_SOURCE_RELATIVE, AL_TRUE );
alSourcei( psSample->iSample, AL_LOOPING, (sound_SetupChannel(psSample)) ? AL_TRUE : AL_FALSE );
// NOTE: this is only useful for debugging.
#ifdef DEBUG
psSample->is3d = false;
psSample->isLooping = sound_TrackLooped(psSample->iTrack)? AL_TRUE : AL_FALSE;
memcpy(psSample->filename,psTrack->fileName, strlen(psTrack->fileName));
psSample->filename[strlen(psTrack->fileName)]='\0';
#endif
// Clear error codes
alGetError();
alSourcePlay( psSample->iSample );
sound_GetError();
if ( bQueued )
{
current_queue_sample = psSample->iSample;
}
else if ( current_queue_sample == psSample->iSample )
{
current_queue_sample = -1;
}
#endif
return true;
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_Play3DSample( TRACK *psTrack, AUDIO_SAMPLE *psSample )
{
#ifndef WZ_NOSOUND
ALfloat zero[3] = { 0.0, 0.0, 0.0 };
ALfloat volume;
ALint error;
if (sfx3d_volume == 0.0)
{
return false;
}
volume = ((float)psTrack->iVol / 100.f); // max range is 0-100
psSample->fVol = volume; // store results for later
// If we can't hear it, then don't bother playing it.
if (volume == 0.0f)
{
return false;
}
// Clear error codes
alGetError();
alGenSources( 1, &(psSample->iSample) );
error = sound_GetError();
if (error != AL_NO_ERROR)
{
/* FIXME: We run out of OpenAL sources very quickly, so we
* should handle the case where we've ran out of them.
* Currently we don't do this, causing some unpleasant side
* effects, e.g. crashing...
*/
}
// HACK: this is a workaround for a bug in the 64bit implementation of OpenAL on GNU/Linux
// The AL_PITCH value really should be 1.0.
alSourcef(psSample->iSample, AL_PITCH, 1.001f);
sound_SetObjectPosition( psSample );
alSourcefv( psSample->iSample, AL_VELOCITY, zero );
alSourcei( psSample->iSample, AL_BUFFER, psTrack->iBufferName );
alSourcei( psSample->iSample, AL_LOOPING, (sound_SetupChannel(psSample)) ? AL_TRUE : AL_FALSE );
// NOTE: this is only useful for debugging.
#ifdef DEBUG
psSample->is3d = true;
psSample->isLooping = sound_TrackLooped(psSample->iTrack)? AL_TRUE : AL_FALSE;
memcpy(psSample->filename,psTrack->fileName, strlen(psTrack->fileName));
psSample->filename[strlen(psTrack->fileName)]='\0';
#endif
// Clear error codes
alGetError();
alSourcePlay( psSample->iSample );
sound_GetError();
#endif
return true;
}
/** Plays the audio data from the given file
* \param fileHandle PhysicsFS file handle to stream the audio from
* \param volume the volume to play the audio at (in a range of 0.0 to 1.0)
* \param onFinished callback to invoke when we're finished playing
* \param user_data user-data pointer to pass to the \c onFinished callback
* \return a pointer to the currently playing stream when playing started
* successfully, NULL otherwise.
* \post When a non-NULL pointer is returned the audio stream system will
* close the PhysicsFS file handle. Otherwise (when false is returned)
* this is left to the user.
* \note The returned pointer will become invalid/dangling immediately after
* the \c onFinished callback is invoked.
* \note You must _never_ manually free() the memory used by the returned
* pointer.
*/
AUDIO_STREAM* sound_PlayStream(PHYSFS_file* fileHandle, float volume, void (*onFinished)(void*), void* user_data)
{
// Default buffer size
static const size_t streamBufferSize = 16 * 1024;
// Default buffer count
static const unsigned int buffer_count = 2;
return sound_PlayStreamWithBuf(fileHandle, volume, onFinished, user_data, streamBufferSize, buffer_count);
}
/** Plays the audio data from the given file
* \param fileHandle,volume,onFinished,user_data see sound_PlayStream()
* \param streamBufferSize the size to use for the decoded audio buffers
* \param buffer_count the amount of audio buffers to use
* \see sound_PlayStream() for details about the rest of the function
* parameters and other details.
*/
AUDIO_STREAM* sound_PlayStreamWithBuf(PHYSFS_file* fileHandle, float volume, void (*onFinished)(void*), void* user_data, size_t streamBufferSize, unsigned int buffer_count)
{
#if !defined(WZ_NOSOUND)
AUDIO_STREAM* stream;
ALuint * buffers = (ALuint *)alloca(sizeof(ALuint) * buffer_count);
ALint error;
unsigned int i;
if ( !openal_initialized )
{
debug(LOG_WARNING, "OpenAL isn't initialized, not creating an audio stream");
return NULL;
}
stream = (AUDIO_STREAM *)malloc(sizeof(AUDIO_STREAM));
if (stream == NULL)
{
debug(LOG_FATAL, "sound_PlayStream: Out of memory");
abort();
return NULL;
}
// Clear error codes
alGetError();
// Retrieve an OpenAL sound source
alGenSources(1, &(stream->source));
error = sound_GetError();
if (error != AL_NO_ERROR)
{
// Failed to create OpenAL sound source, so bail out...
debug(LOG_SOUND, "alGenSources failed, most likely out of sound sources");
free(stream);
return NULL;
}
stream->fileHandle = fileHandle;
stream->decoder = sound_CreateOggVorbisDecoder(stream->fileHandle, false);
if (stream->decoder == NULL)
{
debug(LOG_ERROR, "sound_PlayStream: Failed to open audio file for decoding");
free(stream);
return NULL;
}
stream->volume = volume;
stream->bufferSize = streamBufferSize;
alSourcef(stream->source, AL_GAIN, stream->volume);
// HACK: this is a workaround for a bug in the 64bit implementation of OpenAL on GNU/Linux
// The AL_PITCH value really should be 1.0.
alSourcef(stream->source, AL_PITCH, 1.001f);
// Create some OpenAL buffers to store the decoded data in
alGenBuffers(buffer_count, buffers);
sound_GetError();
// Fill some buffers with audio data
for (i = 0; i < buffer_count; ++i)
{
// Decode some audio data
soundDataBuffer* soundBuffer = sound_DecodeOggVorbis(stream->decoder, stream->bufferSize);
// If we actually decoded some data
if (soundBuffer && soundBuffer->size > 0)
{
// Determine PCM data format
ALenum format = (soundBuffer->channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
// Copy the audio data into one of OpenAL's own buffers
alBufferData(buffers[i], format, soundBuffer->data, soundBuffer->size, soundBuffer->frequency);
sound_GetError();
// Clean up our memory
free(soundBuffer);
}
else
{
// If no data has been decoded we're probably at the end of our
// stream. So cleanup the excess stuff here.
// First remove the data buffer itself
free(soundBuffer);
// Then remove OpenAL's buffers
alDeleteBuffers(buffer_count - i, &buffers[i]);
sound_GetError();
break;
}
}
// Bail out if we didn't fill any buffers
if (i == 0)
{
debug(LOG_ERROR, "Failed to fill buffers with decoded audio data!");
// Destroy the decoder
sound_DestroyOggVorbisDecoder(stream->decoder);
// Destroy the OpenAL source
alDeleteSources(1, &stream->source);
// Free allocated memory
free(stream);
return NULL;
}
// Attach the OpenAL buffers to our OpenAL source
// (i = the amount of buffers we worked on in the above for-loop)
alSourceQueueBuffers(stream->source, i, buffers);
sound_GetError();
// Start playing the source
alSourcePlay(stream->source);
sound_GetError();
// Set callback info
stream->onFinished = onFinished;
stream->user_data = user_data;
// Prepend this stream to the linked list
stream->next = active_streams;
active_streams = stream;
return stream;
#else
return NULL;
#endif
}
/** Checks if the stream is playing.
* \param stream the stream to check
* \post true if playing, false otherwise.
*
*/
bool sound_isStreamPlaying(AUDIO_STREAM *stream)
{
#if !defined(WZ_NOSOUND)
ALint state;
if (stream)
{
alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
sound_GetError();
if (state == AL_PLAYING)
{
return true;
}
}
return false;
#endif
}
/** Stops the current stream from playing.
* \param stream the stream to stop
* \post The stopped stream will be destroyed on the next invocation of
* sound_UpdateStreams(). So calling this function will result in the
* \c onFinished callback being called and the \c stream pointer becoming
* invalid.
*/
void sound_StopStream(AUDIO_STREAM* stream)
{
assert(stream != NULL);
#if !defined(WZ_NOSOUND)
alGetError(); // clear error codes
// Tell OpenAL to stop playing on the given source
alSourceStop(stream->source);
sound_GetError();
#endif
}
/** Pauses playing of this stream until playing is resumed with
* sound_ResumeStream() or completely stopped with sound_StopStream().
* \param stream the stream to pause playing for
*/
void sound_PauseStream(AUDIO_STREAM* stream)
{
#if !defined(WZ_NOSOUND)
ALint state;
// To be sure we won't go mutilating this OpenAL source, check wether
// it's playing first.
alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
sound_GetError();
if (state != AL_PLAYING)
{
return;
}
// Pause playing of this OpenAL source
alSourcePause(stream->source);
sound_GetError();
#endif
}
/** Resumes playing of a stream that's paused by means of sound_PauseStream().
* \param stream the stream to resume playing for
*/
void sound_ResumeStream(AUDIO_STREAM* stream)
{
#if !defined(WZ_NOSOUND)
ALint state;
// To be sure we won't go mutilating this OpenAL source, check wether
// it's paused first.
alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
sound_GetError();
if (state != AL_PAUSED)
{
return;
}
// Resume playing of this OpenAL source
alSourcePlay(stream->source);
sound_GetError();
#endif
}
/** Retrieve the playing volume of the given stream.
*
* @param stream the stream to retrieve the volume for.
*
* @return a floating point value between 0.f and 1.f, representing this
* stream's volume.
*/
float sound_GetStreamVolume(const AUDIO_STREAM* stream)
{
#if !defined(WZ_NOSOUND)
ALfloat volume;
alGetSourcef(stream->source, AL_GAIN, &volume);
sound_GetError();
return volume;
#else
return 1.f;
#endif
}
/** Set the playing volume of the given stream.
*
* @param stream the stream to change the volume for.
* @param volume a floating point value between 0.f and 1.f, to use as this
* @c stream's volume.
*/
void sound_SetStreamVolume(AUDIO_STREAM* stream, float volume)
{
stream->volume = volume;
#if !defined(WZ_NOSOUND)
alSourcef(stream->source, AL_GAIN, stream->volume);
sound_GetError();
#endif
}
#if !defined(WZ_NOSOUND)
/** Update the given stream by making sure its buffers remain full
* \param stream the stream to update
* \return true when the stream is still playing, false when it has stopped
*/
static bool sound_UpdateStream(AUDIO_STREAM* stream)
{
ALint state, buffer_count;
alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
sound_GetError();
if (state != AL_PLAYING && state != AL_PAUSED)
{
return false;
}
// Retrieve the amount of buffers which were processed and need refilling
alGetSourcei(stream->source, AL_BUFFERS_PROCESSED, &buffer_count);
sound_GetError();
// Refill and reattach all buffers
for (; buffer_count != 0; --buffer_count)
{
soundDataBuffer* soundBuffer;
ALuint buffer;
// Retrieve the buffer to work on
alSourceUnqueueBuffers(stream->source, 1, &buffer);
sound_GetError();
// Decode some data to stuff in our buffer
soundBuffer = sound_DecodeOggVorbis(stream->decoder, stream->bufferSize);
// If we actually decoded some data
if (soundBuffer && soundBuffer->size > 0)
{
// Determine PCM data format
ALenum format = (soundBuffer->channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
// Insert the data into the buffer
alBufferData(buffer, format, soundBuffer->data, soundBuffer->size, soundBuffer->frequency);
sound_GetError();
// Reattach the buffer to the source
alSourceQueueBuffers(stream->source, 1, &buffer);
sound_GetError();
}
else
{
// If no data has been decoded we're probably at the end of our
// stream. So cleanup this buffer.
// Then remove OpenAL's buffer
alDeleteBuffers(1, &buffer);
sound_GetError();
}
// Now remove the data buffer itself
free(soundBuffer);
}
return true;
}
/** Destroy the given stream and release its associated resources. This function
* also handles calling of the \c onFinished callback function and closing of
* the PhysicsFS file handle.
* \param stream the stream to destroy
*/
static void sound_DestroyStream(AUDIO_STREAM* stream)
{
ALint buffer_count;
ALuint* buffers;
ALint error;
// Stop the OpenAL source from playing
alSourceStop(stream->source);
error = sound_GetError();
if (error != AL_NO_ERROR)
{
// FIXME: We should really handle these errors.
}
// Retrieve the amount of buffers which were processed
alGetSourcei(stream->source, AL_BUFFERS_PROCESSED, &buffer_count);
error = sound_GetError();
if (error != AL_NO_ERROR)
{
/* FIXME: We're leaking memory and resources here when bailing
* out. But not doing so could cause stack overflows as a
* result of the below alloca() call (due to buffer_count not
* being properly initialised.
*/
debug(LOG_SOUND, "alGetSourcei(AL_BUFFERS_PROCESSED) failed; bailing out...");
return;
}
// Detach all buffers and retrieve their ID numbers
buffers = (ALuint *)alloca(buffer_count * sizeof(ALuint));
alSourceUnqueueBuffers(stream->source, buffer_count, buffers);
sound_GetError();
// Destroy all of these buffers
alDeleteBuffers(buffer_count, buffers);
sound_GetError();
// Destroy the OpenAL source
alDeleteSources(1, &stream->source);
sound_GetError();
// Destroy the sound decoder
sound_DestroyOggVorbisDecoder(stream->decoder);
// Now close the file
PHYSFS_close(stream->fileHandle);
// Now call the finished callback
if (stream->onFinished)
{
stream->onFinished(stream->user_data);
}
// Free the memory used by this stream
free(stream);
}
/** Update all currently running streams and destroy them when they're finished.
*/
static void sound_UpdateStreams()
{
AUDIO_STREAM *stream = active_streams, *previous = NULL, *next = NULL;
while (stream != NULL)
{
next = stream->next;
// Attempt to update the current stream, if we find that impossible,
// destroy it instead.
if (!sound_UpdateStream(stream))
{
// First remove our current stream from the linked list
if (previous)
{
// Make the previous item skip over the current to the next item
previous->next = next;
}
else
{
// Apparently this is the first item in the list, so make the
// next item the list-head.
active_streams = next;
}
// Now actually destroy the current stream
sound_DestroyStream(stream);
// Make sure the current stream pointer is intact again
stream = next;
// Skip regular style iterator incrementing
continue;
}
// Increment our iterator pair
previous = stream;
stream = stream->next;
}
}
#endif
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_StopSample(AUDIO_SAMPLE* psSample)
{
#ifndef WZ_NOSOUND
if (psSample->iSample == (ALuint)SAMPLE_NOT_ALLOCATED)
{
debug(LOG_SOUND, "sound_StopSample: sample number (%u) out of range, we probably have run out of available OpenAL sources", psSample->iSample);
return;
}
alGetError(); // clear error codes
// Tell OpenAL to stop playing the given sample
alSourceStop(psSample->iSample);
sound_GetError();
#endif
}
void sound_SetPlayerPos(Vector3f pos)
{
#ifndef WZ_NOSOUND
alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
sound_GetError();
#endif
}
/**
* Sets the player's orientation to use for sound
* \param angle the angle in radians
@NOTE the up vector is swapped because of qsound idiosyncrasies
@FIXME we don't use qsound, but it still is in qsound 'format'...
*/
void sound_SetPlayerOrientation(float angle)
{
#ifndef WZ_NOSOUND
const ALfloat ori[6] =
{
-sinf(angle), cosf(angle), 0.0f, // forward (at) vector
0.0f, 0.0f, 1.0f, // up vector
};
alListenerfv(AL_ORIENTATION, ori);
sound_GetError();
#endif
}
/**
* Sets the player's orientation to use for sound
* \param forward forward pointing vector
* \param up upward pointing vector
*/
void sound_SetPlayerOrientationVector(Vector3f forward, Vector3f up)
{
#ifndef WZ_NOSOUND
const ALfloat ori[6] =
{
forward.x, forward.y, forward.z,
up.x, up.y, up.z,
};
alListenerfv(AL_ORIENTATION, ori);
sound_GetError();
#endif
}
//*
// =======================================================================================================================
// Compute the sample's volume relative to AL_POSITION.
// =======================================================================================================================
//
void sound_SetObjectPosition(AUDIO_SAMPLE *psSample)
{
#ifndef WZ_NOSOUND
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// coordinates
float listenerX, listenerY, listenerZ, dX, dY, dZ;
// calculation results
float distance, gain;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// only set it when we have a valid sample
if (!psSample)
{
return;
}
// compute distance
alGetListener3f( AL_POSITION, &listenerX, &listenerY, &listenerZ );
sound_GetError();
dX = psSample->x - listenerX; // distances on all axis
dY = psSample->y - listenerY;
dZ = psSample->z - listenerZ;
distance = sqrtf(dX * dX + dY * dY + dZ * dZ); // Pythagorean theorem
// compute gain
gain = (1.0f - (distance * ATTENUATION_FACTOR)) * psSample->fVol * sfx3d_volume;
// max volume
if (gain > 1.0f)
{
gain = 1.0f;
}
if (gain < 0.0f)
{
// this sample can't be heard right now
gain = 0.0f;
}
alSourcef( psSample->iSample, AL_GAIN, gain );
// the alSource3i variant would be better, if it wouldn't provide linker errors however
alSource3f( psSample->iSample, AL_POSITION, (float)psSample->x,(float)psSample->y,(float)psSample->z );
sound_GetError();
#endif
return;
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_PauseSample( AUDIO_SAMPLE *psSample )
{
#ifndef WZ_NOSOUND
alSourcePause( psSample->iSample );
sound_GetError();
#endif
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_ResumeSample( AUDIO_SAMPLE *psSample )
{
#ifndef WZ_NOSOUND
alSourcePlay( psSample->iSample );
sound_GetError();
#endif
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_PauseAll( void )
{
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_ResumeAll( void )
{
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_StopAll( void )
{
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_SampleIsFinished( AUDIO_SAMPLE *psSample )
{
#ifndef WZ_NOSOUND
//~~~~~~~~~~
ALenum state;
//~~~~~~~~~~
alGetSourcei( psSample->iSample, AL_SOURCE_STATE, &state );
sound_GetError(); // check for an error and clear the error state for later on in this function
if (state == AL_PLAYING || state == AL_PAUSED)
{
return false;
}
if (psSample->iSample != (ALuint)AL_INVALID)
{
alDeleteSources(1, &(psSample->iSample));
sound_GetError();
psSample->iSample = AL_INVALID;
}
#endif
return true;
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
float sound_GetUIVolume()
{
#ifndef WZ_NOSOUND
return sfx_volume;
#else
return 0;
#endif
}
void sound_SetUIVolume(float volume)
{
#ifndef WZ_NOSOUND
sfx_volume = volume;
// Keep volume in the range of 0.0 - 1.0
if (sfx_volume < 0.0)
{
sfx_volume = 0.0;
}
else if (sfx_volume > 1.0)
{
sfx_volume = 1.0;
}
#endif
}
float sound_GetEffectsVolume()
{
#ifndef WZ_NOSOUND
return sfx3d_volume;
#else
return 0;
#endif
}
void sound_SetEffectsVolume(float volume)
{
#ifndef WZ_NOSOUND
sfx3d_volume = volume;
// Keep volume in the range of 0.0 - 1.0
if (sfx3d_volume < 0.0)
{
sfx3d_volume = 0.0;
}
else if (sfx3d_volume > 1.0)
{
sfx3d_volume = 1.0;
}
#endif
}