630 lines
15 KiB
C
630 lines
15 KiB
C
#include <sys/stat.h>
|
|
#include <AL/alc.h>
|
|
#include <AL/alut.h>
|
|
#include <SDL.h>
|
|
#include <SDL_thread.h>
|
|
|
|
#include "lephisto.h"
|
|
#include "log.h"
|
|
#include "pack.h"
|
|
#include "music.h"
|
|
#include "sound.h"
|
|
|
|
/* ============================================== */
|
|
/* sound.c controls the routines for using a */
|
|
/* virtual voice wrapper system around the openal */
|
|
/* library to get 3D sound. */
|
|
/* */
|
|
/* We only use position sound and no doppler effect */
|
|
/* right now. */
|
|
/* ============================================== */
|
|
|
|
/* ============================================== */
|
|
/* Sound Overview: */
|
|
/* --------------- */
|
|
/* */
|
|
/* We use a priority virtual voice system with */
|
|
/* pre-allocated buffers. */
|
|
/* */
|
|
/* Nameing: */
|
|
/* -- buffer - Sound sample. */
|
|
/* -- source - openal object that plays sound. */
|
|
/* -- voice - Virtual object that wants to play sound. */
|
|
/* */
|
|
/* First we allocate all the buffers based on what */
|
|
/* we find inside the datafile. */
|
|
/* Then we allocate all the possible sources (giving */
|
|
/* the music system what it needs). */
|
|
/* Now we allow the user to dynamically create */
|
|
/* voices, these voices will always try to grab */
|
|
/* a source from the source pool. If they can't, */
|
|
/* they will pretend to play the buffer. */
|
|
/* Every so often we'll check to see if the important */
|
|
/* voices are being played and take away the sources */
|
|
/* from the lesser ones. */
|
|
/* ============================================== */
|
|
|
|
/* Sound parameters - TODO: make it variable per source. */
|
|
#define SOUND_ROLLOFF_FACTOR 1.
|
|
#define SOUND_REFERENCE_DIST 500.
|
|
#define SOUND_MAX_DIST 1000.
|
|
|
|
|
|
#define SOUND_PREFIX "../snd/sounds/"
|
|
#define SOUND_SUFFIX ".wav"
|
|
|
|
#define soundLock() SDL_mutexP(sound_lock)
|
|
#define soundUnlock() SDL_mutexV(sound_lock)
|
|
|
|
/* Give the buffers a name. */
|
|
typedef struct alSound_ {
|
|
char* name; /* Buffers name. */
|
|
ALuint buffer; /* Associated OpenAL buffer. */
|
|
} alSound;
|
|
|
|
/* Voice private flags (public in sound.h). */
|
|
#define VOICE_PLAYING (1<<0) /* Voice is playing. */
|
|
#define VOICE_DONE (1<<1) /* Voice is done - must remove. */
|
|
#define voice_set(v,f) ((v)->flags |= f)
|
|
#define voice_is(v,f) ((v)->flags & f)
|
|
|
|
/* Global sound lock. */
|
|
SDL_mutex* sound_lock = NULL;
|
|
|
|
/* Gobal device and context. */
|
|
static ALCcontext* al_context = NULL;
|
|
static ALCdevice* al_device = NULL;
|
|
|
|
/* Threads. */
|
|
static SDL_Thread* music_player = NULL;
|
|
|
|
/* List of sounds available (All preloaded into a buffer). */
|
|
static alSound* sound_list = NULL;
|
|
static int nsound_list = 0;
|
|
|
|
/* Struct to hold all the sources and currently attached voice. */
|
|
static ALuint* source_stack = NULL; /* And it's stack. */
|
|
static int source_nstack = 0;
|
|
|
|
/* Virtual voice. */
|
|
struct alVoice {
|
|
alVoice* next; /* Yes it's a linked list. */
|
|
|
|
/*ALuint id; // Unique id for the voice. */
|
|
|
|
ALuint source; /* Source itself, 0 if not set. */
|
|
ALuint buffer; /* Buffer. */
|
|
|
|
int priority; /* Base priority. */
|
|
|
|
double px, py; /* Position. */
|
|
/*double vx, vy; // Velocity. */
|
|
|
|
unsigned int start; /* time started in ms. */
|
|
unsigned int flags; /* Flags to set properties. */
|
|
};
|
|
static alVoice* voice_start = NULL;
|
|
static alVoice* voice_end = NULL;
|
|
|
|
/* Volume. */
|
|
static ALfloat svolume = 0.3;
|
|
|
|
static int sound_makeList(void);
|
|
static int sound_load(ALuint* buffer, char* filename);
|
|
static void sound_free(alSound* snd);
|
|
static int voice_getSource(alVoice* voc);
|
|
static void voice_init(alVoice* voice);
|
|
static int voice_play(alVoice* voice);
|
|
static void voice_rm(alVoice* prev, alVoice* voice);
|
|
static void voice_parseFlags(alVoice* voice, const unsigned int flags);
|
|
|
|
int sound_init(void) {
|
|
int mem, ret = 0;
|
|
ALenum err;
|
|
|
|
ret = 0;
|
|
|
|
/* We'll need a mutex. */
|
|
sound_lock = SDL_CreateMutex();
|
|
soundLock();
|
|
|
|
/* Initialize alut - I think it's worth it. */
|
|
alutInitWithoutContext(NULL, NULL);
|
|
|
|
const ALchar* device = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
|
|
/* Open the default device. */
|
|
al_device = alcOpenDevice(NULL);
|
|
if(al_device == NULL) {
|
|
WARN("Unable to open default sound device");
|
|
ret = -1;
|
|
goto snderr_dev;
|
|
}
|
|
|
|
/* Create the OpenAL context. */
|
|
al_context = alcCreateContext(al_device, NULL);
|
|
if(sound_lock == NULL) {
|
|
WARN("Unable to create OpenAL context");
|
|
ret = -2;
|
|
goto snderr_ctx;
|
|
}
|
|
|
|
/* Clear the errors. */
|
|
alGetError();
|
|
|
|
/* Set active context. */
|
|
if(alcMakeContextCurrent(al_context)==AL_FALSE) {
|
|
WARN("Failure to set default context");
|
|
ret = -4;
|
|
goto snderr_act;
|
|
}
|
|
|
|
/* Set the master gain. */
|
|
alListenerf(AL_GAIN, .1);
|
|
|
|
/* Set the distance model. */
|
|
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
|
|
|
|
/* We can unlock now. */
|
|
soundUnlock();
|
|
|
|
/* Start the music server. */
|
|
music_init();
|
|
|
|
/* Start allocating the sources - music has already taken this. */
|
|
alGetError(); /* Another error clear. */
|
|
mem = 0;
|
|
while(((err = alGetError()) == AL_NO_ERROR) && (source_nstack < 128)) {
|
|
if(mem < source_nstack+1) {
|
|
/* Allocate more memory. */
|
|
mem += 32;
|
|
source_stack = realloc(source_stack, sizeof(ALuint) * mem);
|
|
}
|
|
alGenSources(1, &source_stack[source_nstack]);
|
|
source_nstack++;
|
|
}
|
|
|
|
/* Use minimal ram. */
|
|
source_stack = realloc(source_stack, sizeof(ALuint) * source_nstack);
|
|
|
|
/* Debug magic. */
|
|
DEBUG("OpenAL: %s", device);
|
|
DEBUG("Sources: %d", source_nstack);
|
|
DEBUG("Renderer: %s", alGetString(AL_RENDERER));
|
|
DEBUG("Version: %s", alGetString(AL_VERSION));
|
|
|
|
/* Load up all the sounds. */
|
|
sound_makeList();
|
|
music_makeList(); /* And music. */
|
|
|
|
/* Now start the music thread. */
|
|
music_player = SDL_CreateThread(music_thread, NULL);
|
|
|
|
return 0;
|
|
|
|
snderr_act:
|
|
alcDestroyContext(al_context);
|
|
snderr_ctx:
|
|
al_context = NULL;
|
|
alcCloseDevice(al_device);
|
|
snderr_dev:
|
|
al_device = NULL;
|
|
soundUnlock();
|
|
SDL_DestroyMutex(sound_lock);
|
|
sound_lock = NULL;
|
|
ERR("Sound failed to initialize.");
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Clean up after the sound system. */
|
|
void sound_exit(void) {
|
|
int i;
|
|
/* Free the sounds. */
|
|
for(i = 0; i < nsound_list; i++)
|
|
sound_free(&sound_list[i]);
|
|
free(sound_list);
|
|
sound_list = NULL;
|
|
nsound_list = 0;
|
|
|
|
/* Must stop the music before killing it, */
|
|
/* then thread should commit suicide. */
|
|
if(music_player) {
|
|
music_stop();
|
|
music_kill();
|
|
SDL_WaitThread(music_player, NULL);
|
|
music_exit();
|
|
}
|
|
|
|
/* Clean up the voices. */
|
|
while(voice_start != NULL)
|
|
voice_rm(NULL, voice_start);
|
|
|
|
/* Clean up the sources. */
|
|
if(source_stack)
|
|
alDeleteSources(source_nstack, source_stack);
|
|
|
|
if(sound_lock) {
|
|
soundLock();
|
|
|
|
if(al_context) {
|
|
alcMakeContextCurrent(NULL);
|
|
alcDestroyContext(al_context);
|
|
}
|
|
if(al_device) alcCloseDevice(al_device);
|
|
|
|
soundUnlock();
|
|
SDL_DestroyMutex(sound_lock);
|
|
}
|
|
/* Cya alut! */
|
|
alutExit();
|
|
}
|
|
|
|
/* Get the buffer to sound of [name]. */
|
|
ALuint sound_get(char* name) {
|
|
if(sound_lock == NULL) return 0;
|
|
|
|
int i;
|
|
for(i = 0; i < nsound_list; i++)
|
|
if(strcmp(name, sound_list[i].name)==0)
|
|
return sound_list[i].buffer;
|
|
WARN("Sound '%s' not found in sound list", name);
|
|
return 0;
|
|
}
|
|
|
|
/* Make list of available sounds. */
|
|
static int sound_makeList(void) {
|
|
if(sound_lock == NULL) return 0;
|
|
|
|
char** files;
|
|
uint32_t nfiles, i;
|
|
char tmp[64];
|
|
int len;
|
|
int mem;
|
|
|
|
/* Get the file list. */
|
|
files = pack_listfiles(data, &nfiles);
|
|
|
|
/* Load the profiles. */
|
|
mem = 0;
|
|
for(i = 0; i < nfiles; i++)
|
|
if((strncmp(files[i], SOUND_PREFIX, strlen(SOUND_PREFIX))==0) &&
|
|
(strncmp(files[i] + strlen(files[i]) - strlen(SOUND_SUFFIX),
|
|
SOUND_SUFFIX, strlen(SOUND_SUFFIX))==0)) {
|
|
|
|
/* Expand the selection size. */
|
|
nsound_list++;
|
|
if(nsound_list > mem) {
|
|
/* We must grow. */
|
|
mem += 32; /* We'll overallocate most likely. */
|
|
sound_list = realloc(sound_list, mem*sizeof(alSound));
|
|
}
|
|
|
|
/* Remove the prefix and suffix. */
|
|
len = strlen(files[i]) - strlen(SOUND_SUFFIX SOUND_PREFIX);
|
|
strncpy(tmp, files[i] + strlen(SOUND_PREFIX), len);
|
|
tmp[len] = '\0';
|
|
|
|
/* give it the new name. */
|
|
sound_list[nsound_list-1].name = strdup(tmp);
|
|
sound_load(&sound_list[nsound_list-1].buffer, files[i]);
|
|
}
|
|
|
|
/* Shrink to minimum ram usage. */
|
|
sound_list = realloc(sound_list, nsound_list*sizeof(alSound));
|
|
|
|
/* Free the char* allocated by pack. */
|
|
for(i = 0; i < nfiles; i++)
|
|
free(files[i]);
|
|
free(files);
|
|
|
|
DEBUG("Loaded %d sound%s", nsound_list, (nsound_list==1)?"":"s");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Loads a sound into the sound_list. */
|
|
static int sound_load(ALuint* buffer, char* filename) {
|
|
if(sound_lock == NULL) return 0;
|
|
|
|
void* wavdata;
|
|
unsigned int size;
|
|
ALenum err;
|
|
|
|
/* Get the file data buffer from the packfile. */
|
|
wavdata = pack_readfile(DATA, filename, &size);
|
|
|
|
soundLock();
|
|
|
|
/* Bind to OpenAL buffer. */
|
|
(*buffer) = alutCreateBufferFromFileImage(wavdata, size);
|
|
if((*buffer) == AL_NONE) WARN("FAILURE: %s", alutGetErrorString(alutGetError()));
|
|
/*alGenBuffers(1, buffer); */
|
|
/*alBufferData(*buffer, AL_FORMAT_MONO16, wavdata, size, 22050); */
|
|
|
|
/* Errors? */
|
|
if((err = alGetError()) != AL_NO_ERROR) {
|
|
WARN("OpenAL erro '%d' loading sound '%s'.", err, filename);
|
|
return 0;
|
|
}
|
|
|
|
soundUnlock();
|
|
|
|
/* Finish up. */
|
|
free(wavdata);
|
|
return 0;
|
|
}
|
|
|
|
static void sound_free(alSound* snd) {
|
|
if(sound_lock) return;
|
|
|
|
soundLock();
|
|
|
|
/* Free the stuff. */
|
|
if(snd->name) free(snd->name);
|
|
alDeleteBuffers(1, &snd->buffer);
|
|
|
|
soundUnlock();
|
|
}
|
|
|
|
/* Update the sounds and prioritize them. */
|
|
void sound_update(void) {
|
|
ALint stat;
|
|
alVoice* voice, *prev, *next;
|
|
|
|
if(sound_lock == NULL) return; /* Sound system is off. */
|
|
if(voice_start == NULL) return; /* No voices. */
|
|
|
|
soundLock();
|
|
|
|
/* Update sound. */
|
|
prev = NULL;
|
|
voice = voice_start;
|
|
do {
|
|
next = voice->next;
|
|
|
|
/* Get status. */
|
|
stat = -1;
|
|
if(voice->source != 0)
|
|
alGetSourcei(voice->source, AL_SOURCE_STATE, &stat);
|
|
|
|
if(!voice_is(voice, VOICE_DONE)) { /* Still working. */
|
|
/* Voice has a source. */
|
|
if(voice->source != 0) {
|
|
|
|
/* Update position. */
|
|
alSource3f(voice->source, AL_POSITION,
|
|
voice->px, voice->py, 0.);
|
|
/*alSource3f(voice->source, AL_VELOCITY,
|
|
voice->vx, voice->vy, 0.);*/
|
|
}
|
|
|
|
prev = voice;
|
|
}else {
|
|
/* Delete them. */
|
|
if(stat != AL_PLAYING)
|
|
voice_rm(prev, voice); /* Do not set prev to voice. */
|
|
else
|
|
prev = voice;
|
|
}
|
|
voice = next;
|
|
} while(voice != NULL);
|
|
|
|
soundUnlock();
|
|
}
|
|
|
|
/* Remove a voice. */
|
|
static void voice_rm(alVoice* prev, alVoice* voice) {
|
|
ALint stat;
|
|
|
|
if(voice->source != 0) { /* Source must exist. */
|
|
/* Stop it if playing. */
|
|
alGetSourcei(voice->source, AL_SOURCE_STATE, &stat);
|
|
if(stat == AL_PLAYING) alSourceStop(voice->source);
|
|
|
|
/* Clear it and get rid of it. */
|
|
source_stack[source_nstack++] = voice->source; /* Throw it back. */
|
|
}
|
|
|
|
/* Delete from linked list. */
|
|
if(prev == NULL) /* Was the first member. */
|
|
voice_start = voice->next;
|
|
else /* Not first memmber. */
|
|
prev->next = voice->next;
|
|
if(voice_end == voice) /* Last voice in linked list. */
|
|
voice_end = prev;
|
|
free(voice);
|
|
}
|
|
|
|
/* Set all the sounds volume to vol. */
|
|
void sound_volume(const double vol) {
|
|
if(sound_lock == NULL) return;
|
|
|
|
svolume = (ALfloat) vol;
|
|
}
|
|
|
|
/* Attempt to alloc a source for a voice. */
|
|
static int voice_getSource(alVoice* voc) {
|
|
int ret;
|
|
|
|
/* Sound system isn't on. */
|
|
if(sound_lock == NULL) return -1;
|
|
|
|
ret = 0; /* Default return. */
|
|
|
|
soundLock();
|
|
|
|
/* Try and grab a source. */
|
|
if(source_nstack > 0) { /* We have the source. */
|
|
/* We must pull it from the free source vector. */
|
|
voc->source = source_stack[--source_nstack];
|
|
|
|
/* Initialize and play. */
|
|
voice_init(voc);
|
|
ret = voice_play(voc);
|
|
} else
|
|
voc->source = 0;
|
|
|
|
soundUnlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Must lock becore calling. */
|
|
static void voice_init(alVoice* voice) {
|
|
/* Distance model. */
|
|
alSourcef(voice->source, AL_ROLLOFF_FACTOR, SOUND_ROLLOFF_FACTOR);
|
|
alSourcef(voice->source, AL_MAX_DISTANCE, SOUND_MAX_DIST);
|
|
alSourcef(voice->source, AL_REFERENCE_DISTANCE, SOUND_REFERENCE_DIST);
|
|
|
|
alSourcef(voice->source, AL_GAIN, svolume);
|
|
alSource3f(voice->source, AL_POSITION, voice->px, voice->py, 0.);
|
|
/*alSource3f(voice->source, AL_VELOCITY, voice->vx, voice->vy, 0.); */
|
|
if(voice_is(voice, VOICE_LOOPING))
|
|
alSourcei(voice->source, AL_LOOPING, AL_TRUE);
|
|
else
|
|
alSourcei(voice->source, AL_LOOPING, AL_FALSE);
|
|
}
|
|
|
|
/* Create a dynamic moving piece. */
|
|
alVoice* sound_addVoice(int priority, double px, double py,
|
|
double vx, double vy, const ALuint buffer, const unsigned int flags) {
|
|
|
|
(void)vx;
|
|
(void)vy;
|
|
alVoice* voc;
|
|
|
|
if(sound_lock == NULL) return NULL;
|
|
|
|
/* Allocate the voice. */
|
|
voc = malloc(sizeof(alVoice));
|
|
|
|
/* Set the data. */
|
|
voc->next = NULL;
|
|
voc->priority = priority;
|
|
voc->start = SDL_GetTicks();
|
|
voc->buffer = buffer;
|
|
|
|
/* Handle positions. */
|
|
voc->px = px;
|
|
voc->py = py;
|
|
/*voc->vx = vx; */
|
|
/*voc->vy = vy; */
|
|
|
|
/* Handle the flags. */
|
|
voice_parseFlags(voc, flags);
|
|
|
|
/* Get the source. */
|
|
voice_getSource(voc);
|
|
|
|
if(voice_start == NULL) {
|
|
voice_start = voc;
|
|
voice_end = voc;
|
|
} else {
|
|
if(voice_end != NULL)
|
|
voice_end->next = voc;
|
|
voice_end = voc;
|
|
}
|
|
|
|
return voc;
|
|
}
|
|
|
|
/* Delete the voice. */
|
|
void sound_delVoice(alVoice* voice) {
|
|
if(sound_lock == NULL) return;
|
|
|
|
voice_set(voice, VOICE_DONE);
|
|
}
|
|
|
|
/* Update voice position, should be run once per frame. */
|
|
void voice_update(alVoice* voice, double px, double py, double vx, double vy) {
|
|
(void) vx;
|
|
(void) vy;
|
|
|
|
if(sound_lock == NULL) return;
|
|
|
|
voice->px = px;
|
|
voice->py = py;
|
|
/*voice->vx = vx; */
|
|
/*voice->vy = vy; */
|
|
}
|
|
|
|
/* Changes the voice's buffer. */
|
|
void voice_buffer(alVoice* voice, const ALuint buffer, const unsigned int flags) {
|
|
if(voice == NULL) return;
|
|
|
|
voice->buffer = buffer;
|
|
voice_parseFlags(voice, flags);
|
|
|
|
/* Start playing. */
|
|
soundLock();
|
|
voice_play(voice);
|
|
soundUnlock();
|
|
}
|
|
|
|
/* Stop playing sound. */
|
|
void voice_stop(alVoice* voice) {
|
|
if(voice == NULL) return;
|
|
|
|
soundLock();
|
|
if(voice->source != 0)
|
|
alSourceStop(voice->source);
|
|
soundUnlock();
|
|
}
|
|
|
|
/* Handle flags. */
|
|
static void voice_parseFlags(alVoice* voice, const unsigned int flags) {
|
|
voice->flags = 0; /* Defaults. */
|
|
|
|
/* Looping. */
|
|
if(flags & VOICE_LOOPING)
|
|
voice_set(voice, VOICE_LOOPING);
|
|
|
|
if(flags & VOICE_STATIC)
|
|
alSourcei(voice->source, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
else
|
|
alSourcei(voice->source, AL_SOURCE_RELATIVE, AL_FALSE);
|
|
}
|
|
|
|
/* Make a voice play. Must lock before calling. */
|
|
static int voice_play(alVoice* voice) {
|
|
ALenum err;
|
|
ALint stat;
|
|
|
|
/* Must have buffer. */
|
|
if(voice->buffer != 0) {
|
|
alGetSourcei(voice->source, AL_SOURCE_STATE, &stat);
|
|
if(stat == AL_PLAYING)
|
|
alSourceStop(voice->source);
|
|
/* Set buffer. */
|
|
alSourcei(voice->source, AL_BUFFER, voice->buffer);
|
|
|
|
/* Try to play the source. */
|
|
alSourcePlay(voice->source);
|
|
err = alGetError();
|
|
if(err == AL_NO_ERROR) voice_set(voice, VOICE_PLAYING);
|
|
else return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sound_listener(double dir, double px, double py, double vx, double vy) {
|
|
(void)vx;
|
|
(void)vy;
|
|
|
|
if(sound_lock == NULL) return;
|
|
|
|
soundLock();
|
|
|
|
/* Set orientation. */
|
|
ALfloat ori[] = { 0., 0., 0., 0., 0., 1. };
|
|
ori[0] = cos(dir);
|
|
ori[1] = sin(dir);
|
|
alListenerfv(AL_ORIENTATION, ori);
|
|
alListener3f(AL_POSITION, px, py, 1.);
|
|
/*alListener3f(AL_VELOCITY, vx, vy, 0.); */
|
|
|
|
soundUnlock();
|
|
}
|
|
|