#include #include #include #include #include "lephisto.h" #include "log.h" #include "pack.h" #include "music.h" #define MUSIC_STOPPED (1<<1) #define MUSIC_PLAYING (1<<2) #define MUSIC_KILL (1<<9) #define music_is(f) (music_state & f) #define music_set(f) (music_state |= f) #define music_rm(f) (music_state ^= f) #define MUSIC_PREFIX "../snd/music/" #define MUSIC_SUFFIX ".ogg" #define BUFFER_SIZE (4096 * 8) // Gobal sound mutex. extern SDL_mutex* sound_lock; // Saves the music to ram in this structure. typedef struct alMusic_ { char name[32]; // Name. Packfile file; OggVorbis_File stream; vorbis_info* info; ALenum format; } alMusic; // Song currently playing. static SDL_mutex* music_vorbis_lock; static alMusic music_vorbis; static ALuint music_buffer[2]; // Front and back buffer. static ALuint music_source = 0; // What is available? static char** music_selection = NULL; static int nmusic_selection = 0; // Volume static ALfloat mvolume = 1.; // Vorbis suff. static size_t ovpack_read(void* ptr, size_t size, size_t nmemb, void* datasource) { return (ssize_t) pack_read(datasource, ptr, size*nmemb); } static int ovpack_retneg(void) { return -1; } // Must return -1. static int ovpack_retzero(void) { return 0; } // Must return 0. ov_callbacks ovcall = { .read_func = ovpack_read, .seek_func = (int(*)(void*, ogg_int64_t, int)) ovpack_retneg, .close_func = (int(*)(void*))ovpack_retzero, .tell_func = (long(*)(void*))ovpack_retneg }; static int stream_loadBuffer(ALuint buffer); static int music_find(void); static int music_loadOGG(const char* filename); static void music_free(void); // The thread. static unsigned int music_state = 0; int music_thread(void* unused) { (void)unused; int active; // Active buffer. ALint stat; // Main loop. while(!music_is(MUSIC_KILL)) { if(music_is(MUSIC_PLAYING)) { if(music_vorbis.file.end == 0) music_rm(MUSIC_PLAYING); else { music_rm(MUSIC_STOPPED); SDL_mutexP(music_vorbis_lock); // Lock the mutex. SDL_mutexP(sound_lock); // Start playing current song. active = 0; // Load first buffer. if(stream_loadBuffer(music_buffer[active])) music_rm(MUSIC_PLAYING); alSourceQueueBuffers(music_source, 1, &music_buffer[active]); // Start playing with buffer laoaded. alSourcePlay(music_source); active = 1; // Load the second buffer. if(stream_loadBuffer(music_buffer[active])) music_rm(MUSIC_PLAYING); alSourceQueueBuffers(music_source, 1, &music_buffer[active]); SDL_mutexV(sound_lock); active = 0; } while(music_is(MUSIC_PLAYING)) { SDL_mutexP(sound_lock); alGetSourcei(music_source, AL_BUFFERS_PROCESSED, &stat); if(stat > 0) { // Refill active buffer. alSourceUnqueueBuffers(music_source, 1, &music_buffer[active]); if(stream_loadBuffer(music_buffer[active])) music_rm(MUSIC_PLAYING); alSourceQueueBuffers(music_source, 1, &music_buffer[active]); active = 1 - active; } SDL_mutexV(sound_lock); SDL_Delay(0); } SDL_mutexP(sound_lock); alSourceStop(music_source); alSourceUnqueueBuffers(music_source, 2, music_buffer); SDL_mutexV(sound_lock); SDL_mutexV(music_vorbis_lock); } music_set(MUSIC_STOPPED); SDL_Delay(0); // We must not kill resources. } return 0; } static int stream_loadBuffer(ALuint buffer) { int size, section, result; char data[BUFFER_SIZE]; // Buffer to hold the data. size = 0; while(size < BUFFER_SIZE) { // Fill up the entire data buffer. result = ov_read( &music_vorbis.stream, // Stream. data + size, // Data. BUFFER_SIZE - size, // Amount to read. 0, // Big endian?. 2, // 16 bit. 1, // Signed. §ion); // Current bitstream. if(result == 0) return 1; else if(result == OV_HOLE) { WARN("OGG: Vorbis hole detected in music!"); return 0; } else if(result == OV_EBADLINK) { WARN("OGG: Invalid stream section or corrupt link in music!"); return -1; } size += result; if(size == BUFFER_SIZE) break; // Buffer is full. } // Load the buffer. alBufferData(buffer, music_vorbis.format, data, BUFFER_SIZE, music_vorbis.info->rate); return 0; } // Init/Exit. int music_init(void) { music_vorbis_lock = SDL_CreateMutex(); music_find(); music_vorbis.file.end = 0; // Indication that it's not loaded.. SDL_mutexP(sound_lock); alGenBuffers(2, music_buffer); alGenSources(1, &music_source); alSourcef(music_source, AL_GAIN, mvolume); alSourcef(music_source, AL_ROLLOFF_FACTOR, 0.); alSourcei(music_source, AL_SOURCE_RELATIVE, AL_TRUE); SDL_mutexV(sound_lock); return 0; } void music_exit(void) { int i; // Free the music. alDeleteBuffers(2, music_buffer); alDeleteSources(1, &music_source); music_free(); // Free selection for(i = 0; i < nmusic_selection; i++) free(music_selection[i]); free(music_selection); SDL_DestroyMutex(music_vorbis_lock); } // Internal music loading ruitines. static int music_loadOGG(const char* filename) { // Free currently loaded ogg. music_free(); SDL_mutexP(music_vorbis_lock); // Load the new ogg. pack_open(&music_vorbis.file, DATA, filename); ov_open_callbacks(&music_vorbis.file, &music_vorbis.stream, NULL, 0, ovcall); music_vorbis.info = ov_info(&music_vorbis.stream, -1); if(music_vorbis.info->channels == 1) music_vorbis.format = AL_FORMAT_MONO16; else music_vorbis.format = AL_FORMAT_STEREO16; SDL_mutexV(music_vorbis_lock); return 0; } static int music_find(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], MUSIC_PREFIX, strlen(MUSIC_PREFIX))==0) && (strncmp(files[i] + strlen(files[i]) - strlen(MUSIC_SUFFIX), MUSIC_SUFFIX, strlen(MUSIC_SUFFIX))==0)) { // Grow the selection size. music_selection = realloc(music_selection,++nmusic_selection*sizeof(char*)); // Remove the prefix and suffix. len = strlen(files[i]) - strlen(MUSIC_SUFFIX MUSIC_PREFIX); strncpy(tmp, files[i] + strlen(MUSIC_PREFIX), len); tmp[len] = '\0'; music_selection[nmusic_selection-1] = strdup(tmp); } // Free the char* allocated by pack. for(i = 0; i < nfiles; i++) free(files[i]); free(files); DEBUG("Loaded %d song%c", nmusic_selection, (nmusic_selection==1)?' ':'s'); return 0; } static void music_free(void) { SDL_mutexP(music_vorbis_lock); if(music_vorbis.file.end != 0) { ov_clear(&music_vorbis.stream); pack_close(&music_vorbis.file); music_vorbis.file.end = 0; // Somewhat ended. } SDL_mutexV(music_vorbis_lock); } void music_volume(const double vol) { if(sound_lock == NULL) return; // Sanity check! ALfloat fvol = ABS(vol); if(fvol > 1.) fvol = 1.; mvolume = fvol; // Only needed if playing! if(music_set(MUSIC_PLAYING)) { SDL_mutexP(sound_lock); alSourcef(music_source, AL_GAIN, fvol); SDL_mutexV(sound_lock); } } // Music control functions. void music_load(const char* name) { if(sound_lock == NULL) return; int i; char tmp[64]; music_stop(); while(!music_is(MUSIC_STOPPED)) SDL_Delay(0); for(i = 0; i < nmusic_selection; i++) if(strcmp(music_selection[i], name)==0) { snprintf(tmp, 64, MUSIC_PREFIX"%s"MUSIC_SUFFIX, name); music_loadOGG(tmp); return; } WARN("Requested load song '%s' but it can't be found in the music stack",name); } void music_play(void) { if(!music_is(MUSIC_PLAYING)) music_set(MUSIC_PLAYING); } void music_stop(void) { if(music_is(MUSIC_PLAYING)) music_rm(MUSIC_PLAYING); } void music_kill(void) { if(!music_is(MUSIC_KILL)) music_set(MUSIC_KILL); }