#include #include #include #include #include "llua.h" #include "lluadef.h" #include "misn_lua.h" #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) #define soundLock() SDL_mutexP(sound_lock) #define soundUnlock() SDL_mutexV(sound_lock) #define musicLock() SDL_mutexP(music_vorbis_lock) #define musicUnlock() SDL_mutexV(music_vorbis_lock) #define MUSIC_LUA_PATH "../snd/music.lua" /* Gobal sound mutex. */ extern SDL_mutex* sound_lock; /* Global music lua. */ static lua_State* music_lua = NULL; /* Functions. */ static int musicL_load(lua_State* L); static int musicL_play(lua_State* L); static int musicL_stop(lua_State* L); static int musicL_get(lua_State* L); static const luaL_reg music_methods[] = { { "load", musicL_load }, { "play", musicL_play }, { "stop", musicL_stop }, { "get", musicL_get }, {0, 0} }; /* Saves the music to ram in this structure. */ typedef struct alMusic_ { char name[64]; /* 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 }; /* Music stuff. */ static int stream_loadBuffer(ALuint buffer); static int music_find(void); static int music_loadOGG(const char* filename); static void music_free(void); /* Lua stuff. */ static int music_luaInit(void); static void music_luaQuit(void); /* The thread. */ static unsigned int music_state = 0; int music_thread(void* unused) { (void)unused; int active; /* Active buffer. */ ALint state; /* 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); musicLock(); /* Lock the mutex. */ soundLock(); /* 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]); soundUnlock(); active = 0; } while(music_is(MUSIC_PLAYING)) { soundLock(); alGetSourcei(music_source, AL_BUFFERS_PROCESSED, &state); if(state > 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; } soundUnlock(); SDL_Delay(0); } soundLock(); alSourceStop(music_source); alSourceUnqueueBuffers(music_source, 2, music_buffer); soundUnlock(); musicUnlock(); } 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 dat[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. */ dat + 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, dat, BUFFER_SIZE, music_vorbis.info->rate); return 0; } /* Init/Exit. */ int music_init(void) { music_vorbis_lock = SDL_CreateMutex(); music_vorbis.file.end = 0; /* Indication that it's not loaded.. */ soundLock(); 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_FALSE); /* Start the lua music stuff. */ music_luaInit(); soundUnlock(); return 0; } int music_makeList(void) { return music_find(); } 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); /* Bye bye Lua. */ music_luaQuit(); SDL_DestroyMutex(music_vorbis_lock); } /* Internal music loading ruitines. */ static int music_loadOGG(const char* filename) { /* Free currently loaded ogg. */ music_free(); musicLock(); /* Set the new name. */ strncpy(music_vorbis.name, filename, 64); music_vorbis.name[64-1] = '\0'; /* 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; musicUnlock(); 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[MIN(len, 64-1)] = '\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) { musicLock(); if(music_vorbis.file.end != 0) { ov_clear(&music_vorbis.stream); pack_close(&music_vorbis.file); music_vorbis.file.end = 0; /* Somewhat ended. */ } musicUnlock(); } 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)) { soundLock(); alSourcef(music_source, AL_GAIN, fvol); soundUnlock(); } } /* 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); } /* Music lua stuff. */ /* Initialize. */ static int music_luaInit(void) { char* buf; uint32_t bufsize; if(music_lua != NULL) music_luaQuit(); music_lua = llua_newState(); /*luaL_openlibs(music_lua); */ lua_loadSpace(music_lua, 1); /* Space and time are readonly. */ lua_loadTime(music_lua, 1); lua_loadRnd(music_lua); lua_loadVar(music_lua, 1); /* Also read only. */ lua_loadMusic(music_lua, 0); /* Write it. */ /* Load the actual lua music code. */ buf = pack_readfile(DATA, MUSIC_LUA_PATH, &bufsize); if(luaL_dobuffer(music_lua, buf, bufsize, MUSIC_LUA_PATH) != 0) { ERR("Error loading music file: %s" MUSIC_LUA_PATH); ERR("%s", lua_tostring(music_lua, -1)); WARN("Most likely lua file has improper syntax, please check"); return -1; } free(buf); return 0; } /* Destroy. */ static void music_luaQuit(void) { if(music_lua == NULL) return; lua_close(music_lua); music_lua = NULL; } int lua_loadMusic(lua_State* L, int read_only) { (void)read_only; /* Future proof. */ luaL_register(L, "music", music_methods); return 0; } int music_choose(char* situation) { if(sound_lock == NULL) return 0; lua_getglobal(music_lua, "choose"); lua_pushstring(music_lua, situation); if(lua_pcall(music_lua, 1, 0, 0)) { /* Error occured. */ WARN("Error while choosing music: %s", (char*)lua_tostring(music_lua, -1)); return -1; } return 0; } /* The music lua functions. */ static int musicL_load(lua_State* L) { char* str; /* Check parameters. */ LLUA_MIN_ARGS(1); if(lua_isstring(L, 1)) str = (char*)lua_tostring(L, 1); else LLUA_INVALID_PARAMETER(); music_load(str); return 0; } static int musicL_play(lua_State* L) { (void)L; music_play(); return 0; } static int musicL_stop(lua_State* L) { (void)L; music_stop(); return 0; } static int musicL_get(lua_State* L) { musicLock(); lua_pushstring(L, music_vorbis.name); musicUnlock(); return 1; }