Lephisto/src/sound.c
2013-02-22 05:34:24 +00:00

285 lines
6.6 KiB
C

#include <sys/stat.h>
#include <AL/alc.h>
#include "SDL.h"
#include "SDL_thread.h"
#include "main.h"
#include "log.h"
#include "pack.h"
#include "music.h"
#include "sound.h"
#define SOUND_PREFIX "snd/sounds/"
#define SOUND_SUFFIX ".wav"
// Give the buffers a name.
typedef struct {
char* name; // Buffers name.
ALuint buffer; // Associated OpenAL buffer.
} alSound;
#define VOICE_PLAYING (1<<0) // Voice is playing.
#define VOICE_LOOPING (1<<1) // Voice is looping.
#define voice_set(v,f) ((v)->flags |= f)
#define voice_is(v,f) ((v)->flags & f)
// Gobal device and context.
static ALCcontext* al_context = NULL;
static ALCdevice* al_device = NULL;
// Music player thread to assure streaming is perfect.
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;
// Current sources playing.
static alVoice** voice_stack = NULL;
static int nvoice_stack = 0;
static int mvoice_stack = 0;
static int sound_makeList(void);
static int sound_load(ALuint* buffer, char* filename);
static void sound_free(alSound* snd);
int sound_init(void) {
const ALchar* device = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
DEBUG("OpenAL using device '%s'", device);
// Open the default device.
al_device = alcOpenDevice(NULL);
if(al_device == NULL) {
WARN("Unable to open default sound device");
return -1;
}
// Create the OpenAL context.
al_context = alcCreateContext(al_device, NULL);
if(al_context == NULL) {
WARN("Unable to create OpenAL context");
return -2;
}
// Clear the errors.
alGetError();
// Set active context.
if(alcMakeContextCurrent(al_context)==AL_FALSE) {
WARN("Failure to set default context");
return -4;
}
// Load up all the sounds.
sound_makeList();
// Start the music server.
music_init();
music_player = SDL_CreateThread(music_thread, NULL);
return 0;
}
// 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.
music_stop();
music_kill();
SDL_WaitThread(music_player, NULL);
music_exit();
if(al_context) {
alcMakeContextCurrent(NULL);
alcDestroyContext(al_context);
}
if(al_device) alcCloseDevice(al_device);
}
// Get the buffer to sound of [name].
ALuint sound_get(char* name) {
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) {
char** files;
uint32_t nfiles, i;
char tmp[64];
int len;
// Get the file list.
files = pack_listfiles(data, &nfiles);
// Load the profiles.
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.
sound_list = realloc(sound_list, ++nsound_list * sizeof(alSound));
// Remove the prefix and suffix.
len = strlen(files[i]) - strlen(SOUND_SUFFIX SOUND_SUFFIX);
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]);
}
// Free the char* allocated by pack.
for(i = 0; i < nfiles; i++)
free(files[i]);
free(files);
DEBUG("Loaded %d sound%c", nsound_list, (nsound_list==1)?' ':'s');
return 0;
}
// Loads a sound into the sound_list.
static int sound_load(ALuint* buffer, char* filename) {
void* wavdata;
unsigned int size;
ALenum err;
// Get the file data buffer from the packfile.
wavdata = pack_readfile(DATA, filename, &size);
// Bind to OpenAL buffer.
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;
}
// Finish up.
free(wavdata);
return 0;
}
static void sound_free(alSound* snd) {
if(snd->name) free(snd->name);
alDeleteBuffers(1, &snd->buffer);
}
// Update the sounds and prioritize them.
void sound_update(void) {
int i;
// TODO: Prioritize the things.
for(i = 0; i < nvoice_stack; i++) {
if(voice_is(voice_stack[i], VOICE_PLAYING)) {
// Update position.
alSource3f(voice_stack[i]->source, AL_POSITION,
voice_stack[i]->px, voice_stack[i]->py, 0.);
alSource3f(voice_stack[i]->source, AL_VELOCITY,
voice_stack[i]->vx, voice_stack[i]->vy, 0.);
}
}
}
// Create a dynamic moving voice.
alVoice* sound_addVoice(int priority, double px, double py, double vx, double vy,
const ALuint buffer, const int looping) {
alVoice* voc;
ALenum err;
nvoice_stack++;
if(nvoice_stack > mvoice_stack)
voice_stack = realloc(voice_stack, ++mvoice_stack*sizeof(alVoice*));
voc = malloc(sizeof(alVoice));
voice_stack[nvoice_stack-1] = voc;
// Try and grab a source.
voc->source = 0;
alGenSources(1, &voc->source);
err = alGetError();
if(err != AL_NO_ERROR) voc->source = 0;
// Set the data.
voc->priority = priority;
voc->start = SDL_GetTicks();
voc->buffer = buffer;
if(looping) voice_set(voc, VOICE_LOOPING);
voc->px = px;
voc->py = py;
voc->vx = vx;
voc->vy = vy;
// Set the source.
if(voc->source) {
alSourcei(voc->source, AL_SOURCE_RELATIVE, AL_TRUE);
alSourcef(voc->source, AL_GAIN, 1.);
alSourcei(voc->source, AL_BUFFER, buffer);
alSource3f(voc->source, AL_POSITION, voc->px, voc->py, 0.);
alSource3f(voc->source, AL_VELOCITY, voc->vx, voc->vy, 0.);
if(voice_is(voc, VOICE_LOOPING))
alSourcei(voc->source, AL_LOOPING, AL_TRUE);
// Try to play the source.
alSourcePlay(voc->source);
err = alGetError();
if(err == AL_NO_ERROR) voice_set(voc, VOICE_PLAYING);
else DEBUG("Source player failure");
}
return voc;
}
void sound_delVoice(alVoice* voice) {
ALint stat;
int i;
for(i = 0; i < nvoice_stack; i++)
if(voice == voice_stack[i])
break;
// No match found.
if(i >= nvoice_stack) {
WARN("Unable to find voice to free from stack");
return;
}
if(voice->source) {
alGetSourcei(voice->source, AL_SOURCE_STATE, &stat);
if(stat == AL_PLAYING) alSourceStop(voice->source);
alDeleteSources(1, &voice->source);
voice->source = 0;
}
nvoice_stack--;
for(; i < nvoice_stack; i++)
voice_stack[i] = voice_stack[i+1];
}
void voice_update(alVoice* voice, double px, double py, double vx, double vy) {
voice->px = px;
voice->py = py;
voice->vx = vx;
voice->vy = vy;
}