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