#include #include #include #include #include #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(); }