/** * @file music.c * @brief Control all the music playing. */ #include #include #include #include "llua.h" #include "lluadef.h" #include "llua_misn.h" #include "lephisto.h" #include "log.h" #include "ldata.h" #include "music.h" #define MUSIC_PREFIX "../snd/music/" /**< Prefix of where to find music. */ #define MUSIC_SUFFIX ".ogg" /**< Suffix of music. */ #define MUSIC_LUA_PATH "../snd/music.lua" /**< Lua music control file. */ #define CHUNK_SIZE 32 /**< Size of chunk to allocate. */ int music_disabled = 0; /**< Whether or not music is disabled. */ double music_defVolume = 0.8l; /**< Music default volume. */ /* Handle if music should run Lua script. Must be locked to ensure same * behaviour always! */ static SDL_mutex* music_lock = NULL; /**< Lock for music_runLua so it doesn't run twise in a row with weird results. DO NOT CALL MIX* FUNCTIONS WHEN LOCKED!!!! FUCK!!!! */ static int music_runchoose = 0; /**< Whether or not music should run the choose function. */ static char music_situation[PATH_MAX]; /**< What situation music is in. */ /* Global music lua. */ static lua_State* music_lua = NULL; /**< The Lua music control state. */ /* Functions. */ static int music_runLua(char* situation); static int musicL_load(lua_State* L); static int musicL_play(lua_State* L); static int musicL_stop(lua_State* L); static int musicL_isPlaying(lua_State* L); static int musicL_current(lua_State* L); static const luaL_reg music_methods[] = { { "load", musicL_load }, { "play", musicL_play }, { "stop", musicL_stop }, { "isPlaying", musicL_isPlaying }, { "current", musicL_current }, { 0, 0 } }; /**< Music specific methods. */ /* What is available? */ static char** music_selection = NULL; /**< Available music selection. */ static int nmusic_selection = 0; /**< Size of available music selection. */ /* Current music. */ static char* music_name = NULL; /**< Current music name. */ static void* music_data = NULL; /**< Current music data. */ static SDL_RWops* music_rw = NULL; /**< Current music RWops. */ static Mix_Music* music_music = NULL; /**< Current music. */ /* Music stuff. */ static int music_find(void); static void music_free(void); /* Lua stuff. */ static void music_rechoose(void); static int music_luaInit(void); static void music_luaQuit(void); /** * @brief Update the music. */ void music_update(void) { char buf[PATH_MAX]; if(music_disabled) return; /* Lock music and see if it needs to update. */ SDL_mutexP(music_lock); if(music_runchoose == 0) { SDL_mutexV(music_lock); return; } music_runchoose = 0; strncpy(buf, music_situation, PATH_MAX); SDL_mutexV(music_lock); music_runLua(buf); /* Make sure music is playing. */ if(!music_isPlaying()) music_choose("idle"); } /** * @brief Run the Lua music choose function. * @param situation Situation in to choose music for. * @return 0 on success. */ static int music_runLua(char* situation) { if(music_disabled) return 0; /* Run the choose function in Lua. */ lua_getglobal(music_lua, "choose"); if(situation != NULL) lua_pushstring(music_lua, situation); else lua_pushnil(music_lua); if(lua_pcall(music_lua, 1, 0, 0)) /* Error has occured. */ WARN("Error while choosing music: %s", (char*)lua_tostring(music_lua, -1)); return 0; } /** * @brief Initializes the music subsystem. * @return 0 on success. */ int music_init(void) { if(music_disabled) return 0; if(music_find() < 0) return -1; if(music_luaInit() < 0) return -1; music_volume(music_defVolume); /* Create the lock. */ music_lock = SDL_CreateMutex(); return 0; } /** * @brief Exits the music subsystem. */ void music_exit(void) { music_free(); /* Destroy the lock. */ if(music_lock != NULL) { SDL_DestroyMutex(music_lock); music_lock = NULL; } } /** * @brief Free the current playing music. */ static void music_free(void) { if(music_music != NULL) { Mix_HookMusicFinished(NULL); Mix_HaltMusic(); Mix_FreeMusic(music_music); /*SDL_FreeRW(music_rw);*/ /*FreeMusic frees it itself. */ free(music_data); free(music_name); music_name = NULL; music_music = NULL; music_rw = NULL; music_data = NULL; } } /** * @brief Internal music loading routines. * @return 0 on success. */ static int music_find(void) { char** files; uint32_t nfiles, i; char tmp[64]; int len, suflen, flen; int mem; if(music_disabled) return 0; /* Get the file list. */ files = ldata_list(MUSIC_PREFIX, &nfiles); /* Load the profiles. */ mem = 0; suflen = strlen(MUSIC_SUFFIX); for(i = 0; i < nfiles; i++) { flen = strlen(files[i]); if((flen > suflen) && strncmp(&files[i][flen - suflen], MUSIC_SUFFIX, suflen)==0) { /* Grow the selection size. */ nmusic_selection++; if(nmusic_selection > mem) { mem += CHUNK_SIZE; music_selection = realloc(music_selection, sizeof(char*)*mem); } /* Remove the prefix and suffix. */ len = flen - suflen; strncpy(tmp, files[i], len); tmp[MIN(len, 64-1)] = '\0'; music_selection[nmusic_selection-1] = strdup(tmp); } /* Clean up. */ free(files[i]); } music_selection = realloc(music_selection, sizeof(char*)*nmusic_selection); DEBUG("Loaded %d song%c", nmusic_selection, (nmusic_selection==1)?' ':'s'); /* More clean up. */ free(files); return 0; } /** * @brief Set the music volume. * @param vol Volume to set to (between 0 and 1). * @return 0 on success. */ int music_volume(const double vol) { if(music_disabled) return 0; return Mix_VolumeMusic(MIX_MAX_VOLUME*vol); } /** * @brief Load the music by name. * @param name Name of the file to load. */ void music_load(const char* name) { unsigned int size; char filename[PATH_MAX]; if(music_disabled) return; music_free(); /* Load the data. */ snprintf(filename, PATH_MAX, MUSIC_PREFIX"%s"MUSIC_SUFFIX, name); music_name = strdup(name); music_data = ldata_read(filename, &size); music_rw = SDL_RWFromMem(music_data, size); music_music = Mix_LoadMUS_RW(music_rw); if(music_music == NULL) WARN("SDL_Mixer: %s", Mix_GetError()); Mix_HookMusicFinished(music_rechoose); } /** * @brief Play the loaded music. */ void music_play(void) { if(music_disabled) return; if(music_music == NULL) return; if(Mix_FadeInMusic(music_music, 0, 500) < 0) WARN("SDL_Mixer: %s", Mix_GetError()); } /** * @brief Stop the loaded music. */ void music_stop(void) { if(music_disabled) return; if(music_music == NULL) return; if(Mix_FadeOutMusic(2000) < 0) WARN("SDL_Mixer: %s", Mix_GetError()); } /** * @brief Pauses the music. */ void music_pause(void) { if(music_disabled) return; if(music_music == NULL) return; Mix_PauseMusic(); } /** * @brief Resumes the music. */ void music_resume(void) { if(music_disabled) return; if(music_music == NULL) return; Mix_ResumeMusic(); } /** * @brief Check to see if the music is playing. */ int music_isPlaying(void) { if(music_disabled) return 0; /* Always playing when music is off. */ return Mix_PlayingMusic(); } /** * @brief Sets the music to a position in seconds. * @param sec Position to go to in seconds. */ void music_setPos(double sec) { if(music_disabled) return; if(music_music == NULL) return; Mix_FadeInMusicPos(music_music, 1, 1000, sec); } /* Music lua stuff. */ /** * @brief Initializes the music Lua control system. * @return 0 on success. */ static int music_luaInit(void) { char* buf; uint32_t bufsize; if(music_disabled) return 0; 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 = ldata_read(MUSIC_LUA_PATH, &bufsize); if(luaL_dobuffer(music_lua, buf, bufsize, MUSIC_LUA_PATH) != 0) { ERR("Error loading music file: %s\n" "%s\n" "Most likely Lua file has improper syntax, please check", MUSIC_LUA_PATH, lua_tostring(music_lua, -1)); return -1; } free(buf); return 0; } /** * @brief Quit the music Lua control system. */ static void music_luaQuit(void) { if(music_disabled) return; if(music_lua == NULL) return; lua_close(music_lua); music_lua = NULL; } /** * @brief Load the music functions into a lua_State. * @param L Lua State to load the music functions into. * @param read_only Load the write functions? * @return 0 on success. */ int lua_loadMusic(lua_State* L, int read_only) { (void)read_only; /* Future proof. */ luaL_register(L, "music", music_methods); return 0; } /** * @brief Actually run the music stuff, based on situation. * @param situation choose a new music to play. * @return 0 on success. */ int music_choose(char* situation) { if(music_disabled) return 0; music_runLua(situation); return 0; } /** * @brief Attempts to rechoose the music. * * ARGH! DO NOT CALL MIX_* FUNCTIONS FROM WITHIN THE CALLBACKS! */ static void music_rechoose(void) { if(music_disabled) return; /* Lock so it doesn't run in between an update. */ SDL_mutexP(music_lock); music_runchoose = 1; strncpy(music_situation, "idle", PATH_MAX); SDL_mutexV(music_lock); } /* 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_isPlaying(lua_State* L) { lua_pushboolean(L, music_isPlaying()); return 1; } static int musicL_current(lua_State* L) { lua_pushstring(L, (music_name != NULL) ? music_name : "none"); return 1; }