Lephisto/src/sound.c

626 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) {
voice->buffer = buffer;
voice_parseFlags(voice, flags);
// Start playing.
soundLock();
voice_play(voice);
soundUnlock();
}
// Stop playing sound.
void voice_stop(alVoice* voice) {
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();
}