Lephisto/src/music.c
2014-06-05 18:04:57 +01:00

449 lines
10 KiB
C

/**
* @file music.c
* @brief Control all the music playing.
*/
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_mutex.h>
#include <SDL.h>
#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;
}