449 lines
10 KiB
C
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;
|
|
}
|
|
|