// audio.cpp
#include "common.h"
// OpenAL includes
#include "openal/al.h"
#include "openal/alc.h"
// attenuation factor (lower means sounds fade LESS with distance, higher means sounds fade MORE with distance)
#define ATTENUATION_FACTOR 0.02f
// structures used in this module only
typedef struct openal_buffer_s
{
wchar_t pathname[MAX_PATH]; // sound pathname
ALshort *openal_samples; // OpenAL samples (16-bit mono)
ALuint openal_buffer; // associated OpenAL buffer ID
} openal_buffer_t;
typedef struct openal_source_s
{
bool is_used; // set to TRUE if this source is used
ALuint openal_source; // OpenAL sound source
} openal_source_t;
// global variables used in this module only
static ALCdevice *openal_device;
static ALCcontext *openal_context;
static openal_buffer_t *soundbuffers; // mallocated
static int soundbuffer_count;
static openal_source_t *sources; // mallocated
static int source_count;
bool Audio_Init (void)
{
// this function initializes the audio subsystem (OpenAL)
openal_device = alcOpenDevice (NULL); // open audio device
if (openal_device == NULL)
return (false);
openal_context = alcCreateContext (openal_device, NULL); // create audio context
if (openal_context == NULL)
return (false);
if (!alcMakeContextCurrent (openal_context)) // select this audio context
return (false);
soundbuffers = NULL; // we know no soundbuffer yet
soundbuffer_count = 0;
sources = NULL; // we have no playing source yet
source_count = 0;
return (true); // audio subsystem successfully initialized
}
void Audio_Shutdown (void)
{
// this function shuts down the audio subsystem
int array_index;
ALint status;
// cycle through all sound sources, stop them if needed and delete them
for (array_index = 0; array_index < source_count; array_index++)
{
if (!sources[array_index].is_used)
continue; // skip unused sources
alGetSourcei (sources[array_index].openal_source, AL_SOURCE_STATE, &status); // get this source's playing state
if (status == AL_PLAYING)
alSourceStop (sources[array_index].openal_source); // stop all playing sources
alSourcei (sources[array_index].openal_source, AL_BUFFER, NULL); // untie the buffer from this source
alDeleteSources (1, &sources[array_index].openal_source); // and tell OpenAL to dispose of it
sources[array_index].is_used = false; // mark this source as unused now
}
// cycle through all known sound buffers and delete them
for (array_index = 0; array_index < soundbuffer_count; array_index++)
{
alDeleteBuffers (1, &soundbuffers[array_index].openal_buffer); // tell OpenAL to dispose of this buffer
SAFE_free ((void **) &soundbuffers[array_index].openal_samples); // free the buffer by our side
soundbuffers[array_index].pathname[0] = 0; // remember this buffer is now empty
}
alcMakeContextCurrent (NULL); // unselect the audio context
alcDestroyContext (openal_context); // destroy it (only after it's been unselected!)
alcCloseDevice (openal_device); // and close the audio device
return; // finished, audio subsystem has been shutdown
}
void Audio_Think (void)
{
// this function disposes of sound buffers and sources that have finished playing
int source_index;
ALint status;
float angle;
float sin_pitch;
float sin_yaw;
float cos_pitch;
float cos_yaw;
ALfloat camera_position[3];
ALfloat forward_and_up[6];
// compute the sine and cosine of the pitch component
angle = current_pitch * TO_RADIANS;
sin_pitch = sinf (angle);
cos_pitch = cosf (angle);
// compute the sine and cosine of the yaw component
angle = current_yaw * TO_RADIANS;
sin_yaw = sinf (angle);
cos_yaw = cosf (angle);
// build the camera position
camera_position[0] = (ALfloat) -(cos_pitch * cos_yaw) * current_distance * ATTENUATION_FACTOR;
camera_position[1] = (ALfloat) -(cos_pitch * sin_yaw) * current_distance * ATTENUATION_FACTOR;
camera_position[2] = (ALfloat) sin_pitch * current_distance * ATTENUATION_FACTOR;
// build the camera orientation
forward_and_up[0] = -camera_position[0]; // forward direction is the opposite of camera position, since the camera is looking at the center of the scene
forward_and_up[1] = -camera_position[1];
forward_and_up[2] = -camera_position[2];
forward_and_up[3] = 0.0f;
forward_and_up[3] = 0.0f;
forward_and_up[3] = 1.0f; // FIXME: upwards direction is not quite exact. It depends on the lookdown angle.
// update the listener's position and orientation
alListener3f (AL_POSITION, camera_position[0], camera_position[1], camera_position[2]);
alListener3f (AL_VELOCITY, 0, 0, 0); // TODO: compute velocity dynamically with previous position
alListenerfv (AL_ORIENTATION, forward_and_up);
// cycle through all used sources and see if one is no longer playing
for (source_index = 0; source_index < source_count; source_index++)
{
if (!sources[source_index].is_used)
continue; // skip unused slots
alGetSourcei (sources[source_index].openal_source, AL_SOURCE_STATE, &status); // get this source's playing state
if (status == AL_PLAYING)
continue; // skip sources that are still playing
alSourcei (sources[source_index].openal_source, AL_BUFFER, NULL); // untie the buffer from this source
alDeleteSources (1, &sources[source_index].openal_source); // and tell OpenAL to dispose of it
sources[source_index].is_used = false; // mark this source as unused now
}
return; // finished, audio has been handled
}
void Audio_PlaySound (int sound_type, float pos_x, float pos_y, float pos_z)
{
// helper function to play a sound
static wchar_t soundfile_path[MAX_PATH];
int soundbuffer_index;
buffer_t soundfile;
unsigned long current_pos;
uint16_t temp16;
uint32_t temp32;
uint32_t blksiz;
uint32_t chunk_id;
int sample_count;
int sample_index;
int sample_size;
int channel_count;
int channel_index;
int channel_size;
int sample_value;
int sample_rate;
int source_index;
float pitch;
if (!options.want_sounds)
return; // if we want no sound, don't play anything
// given the type of sound we want, enqueue the right one
pitch = 1.0f; // assume fixed pitch until told otherwise
if (sound_type == SOUNDTYPE_CLICK) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/click.wav", app_path, theme->name);
else if (sound_type == SOUNDTYPE_ILLEGALMOVE) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/illegal.wav", app_path, theme->name);
else if (sound_type == SOUNDTYPE_VICTORY) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/win.wav", app_path, theme->name);
else if (sound_type == SOUNDTYPE_DEFEAT) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/lose.wav", app_path, theme->name);
else if (sound_type == SOUNDTYPE_CHECK) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/check.wav", app_path, theme->name);
else if (sound_type == SOUNDTYPE_PIECETAKEN) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/take.wav", app_path, theme->name);
else if (sound_type == SOUNDTYPE_MOVE)
{
temp32 = rand () % 6; // there are several movement sounds, pick one at random
if (temp32 == 0) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/move1.wav", app_path, theme->name);
else if (temp32 == 1) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/move2.wav", app_path, theme->name);
else if (temp32 == 2) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/move3.wav", app_path, theme->name);
else if (temp32 == 3) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/move4.wav", app_path, theme->name);
else if (temp32 == 4) swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/move5.wav", app_path, theme->name);
else swprintf_s (soundfile_path, WCHAR_SIZEOF (soundfile_path), L"%s/themes/%s/sounds/move6.wav", app_path, theme->name);
pitch = 1.0f + ((((float) rand ()) / RAND_MAX) - 0.5f) / 2.0f; // set a random pitch for these sounds between 0.75 and 1.25
}
else if (sound_type == SOUNDTYPE_SLIDE)
{
swprintf_s(soundfile_path, WCHAR_SIZEOF(soundfile_path), L"%s/themes/%s/sounds/slide.wav", app_path, theme->name);
pitch = 1.0f + ((((float)rand()) / RAND_MAX) - 0.5f) / 2.0f; // set a random pitch for these sounds between 0.75 and 1.25
}
// now cycle through our known OpenAL buffers and see if we already know this one
for (soundbuffer_index = 0; soundbuffer_index < soundbuffer_count; soundbuffer_index++)
if (wcscmp (soundbuffers[soundbuffer_index].pathname, soundfile_path) == 0)
break; // break as soon as we find it
// have we NOT found it ? if so, we must create it
if (soundbuffer_index == soundbuffer_count)
{
// load the sound file
Buffer_Initialize (&soundfile);
if (!Buffer_ReadFromFileW (&soundfile, soundfile_path))
return; // if unable to load this sound file, give up (FIXME: log something ?)
// parse the WAV file
sample_count = channel_count = sample_size = channel_size = 0;
current_pos = 0;
for (;;)
{
#define READ_DATA(type) *((type *) &soundfile.data[current_pos]); current_pos += sizeof (type); if (current_pos >= soundfile.size) break;
chunk_id = READ_DATA (uint32_t);
if (chunk_id == *((uint32_t *) "RIFF"))
{
temp32 = READ_DATA (uint32_t); // skip the "chunk size" field
temp32 = READ_DATA (uint32_t); // skip the "riff style" field (typically "WAVE")
}
else if (chunk_id == *((uint32_t *) "fmt "))
{
blksiz = READ_DATA (uint32_t);
temp16 = READ_DATA (uint16_t); if (temp16 != 1) break; // compressed WAVs are unsupported
temp32 = READ_DATA (uint16_t); channel_count = (int) temp16;
temp32 = READ_DATA (uint32_t); sample_rate = (int) temp32;
temp32 = READ_DATA (uint32_t);
temp16 = READ_DATA (uint16_t); sample_size = (int) temp16;
temp16 = READ_DATA (uint16_t); channel_size = (int) temp16 / 8;
if (blksiz > 16)
current_pos += blksiz - 16;
if (current_pos >= soundfile.size)
break; // don't go beyond the end of the file
}
else if (chunk_id == *((uint32_t *) "data"))
{
temp32 = READ_DATA (uint32_t); sample_count = (int) temp32;
break; // current_pos is now at the beginning of data, and data measures sample_count bytes long
}
else
{
blksiz = READ_DATA (uint32_t); // skip the "chunk size" field
current_pos += blksiz; // useless chunk, skip it
if (current_pos >= soundfile.size)
break; // don't go beyond the end of the file
}
#undef READ_DATA
}
if ((sample_count == 0) || (channel_count == 0) || (sample_size == 0) || (channel_size == 0))
return; // FIXME: not a wav file
// compute the total number of samples (number of channels * number of frames)
sample_count /= sample_size;
// reallocate space for one more sound buffer
soundbuffers = (openal_buffer_t *) SAFE_realloc (soundbuffers, soundbuffer_count, soundbuffer_count + 1, sizeof (openal_buffer_t), false);
wcscpy_s (soundbuffers[soundbuffer_index].pathname, sizeof (soundbuffers[soundbuffer_index].pathname), soundfile_path); // save this sound's pathname
// allocate the samples buffer and fill it, mixing all the WAV channels altogether in a 16-bit mono stream
soundbuffers[soundbuffer_index].openal_samples = (ALshort *) SAFE_malloc (sample_count, sizeof (ALshort), false);
for (sample_index = 0; sample_index < sample_count; sample_index++)
{
sample_value = 0;
if (channel_size == 1)
for (channel_index = 0; channel_index < channel_count; channel_index++)
sample_value += *((int8_t *) soundfile.data[current_pos + sample_index * sample_size + channel_index * channel_size]);
else if (channel_size == 2)
for (channel_index = 0; channel_index < channel_count; channel_index++)
sample_value += *((int16_t *) &soundfile.data[current_pos + sample_index * sample_size + channel_index * channel_size]);
sample_value /= channel_count;
soundbuffers[soundbuffer_index].openal_samples[sample_index] = (ALshort) sample_value;
}
Buffer_Forget (&soundfile); // we can now forget this sound file
alGenBuffers (1, &soundbuffers[soundbuffer_index].openal_buffer); // create an OpenAL sound buffer and fill it with our samples
alBufferData (soundbuffers[soundbuffer_index].openal_buffer, AL_FORMAT_MONO16, soundbuffers[soundbuffer_index].openal_samples, sample_count * sizeof (ALushort), (ALsizei) sample_rate);
if (alGetError () != AL_NO_ERROR)
return; // FIXME: couldn't fill OpenAL buffer
soundbuffer_count++; // one more sound buffer has been created
}
// now we have a buffer to play
// cycle through our known OpenAL sources and find a free one
for (source_index = 0; source_index < source_count; source_index++)
if (!sources[source_index].is_used)
break; // break as soon as we find it
// have we NOT found any ? if so, reallocate so as to have one more
if (source_index == source_count)
{
sources = (openal_source_t *) SAFE_realloc (sources, source_count, source_count + 1, sizeof (openal_source_t), false);
source_count++; // one more source has been created
}
// now we have a source to play our buffer
sources[source_index].is_used = true; // immediately mark it as used
alGenSources (1, &sources[source_index].openal_source); // (re)create an OpenAL source
alSourcef (sources[source_index].openal_source, AL_PITCH, (ALfloat) pitch); // set the source's pitch
alSourcef (sources[source_index].openal_source, AL_GAIN, 1.0f); // set the source's volume (full)
alSource3f (sources[source_index].openal_source, AL_POSITION, (ALfloat) pos_x * ATTENUATION_FACTOR, (ALfloat) pos_y * ATTENUATION_FACTOR, (ALfloat) pos_z * ATTENUATION_FACTOR);
alSource3f (sources[source_index].openal_source, AL_VELOCITY, 0, 0, 0); // set the source's velocity (static)
alSourcei (sources[source_index].openal_source, AL_LOOPING, AL_FALSE); // set it as non-looping
alSourcei (sources[source_index].openal_source, AL_BUFFER, soundbuffers[soundbuffer_index].openal_buffer); // attach our bufferized data to it
// play the source! Audio_Think() will dispose of it when it's finished
alSourcePlay (sources[source_index].openal_source);
return; // finished
}