taisei/src/audio_common.c

320 lines
7.4 KiB
C

/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "taisei.h"
#include <string.h>
#include <stdio.h>
#include "audio.h"
#include "resource/resource.h"
#include "global.h"
CurrentBGM current_bgm = { .name = NULL };
static char *saved_bgm;
static ht_str2int_t sfx_volumes;
static struct enqueued_sound {
LIST_INTERFACE(struct enqueued_sound);
char *name;
int time;
int cooldown;
bool replace;
} *sound_queue;
static void play_sound_internal(const char *name, bool is_ui, int cooldown, bool replace, int delay) {
if(delay > 0) {
struct enqueued_sound *s = malloc(sizeof(struct enqueued_sound));
s->time = global.frames + delay;
s->name = strdup(name);
s->cooldown = cooldown;
s->replace = replace;
list_push(&sound_queue, s);
return;
}
if(!audio_backend_initialized() || global.frameskip) {
return;
}
Sound *snd = get_sound(name);
if(!snd || (!is_ui && snd->lastplayframe + 3 + cooldown >= global.frames)) {
return;
}
snd->lastplayframe = global.frames;
(replace ? audio_backend_sound_play_or_restart : audio_backend_sound_play)
(snd->impl, is_ui ? SNDGROUP_UI : SNDGROUP_MAIN);
}
static void* discard_enqueued_sound(List **queue, List *vsnd, void *arg) {
struct enqueued_sound *snd = (struct enqueued_sound*)vsnd;
free(snd->name);
free(list_unlink(queue, vsnd));
return NULL;
}
static void* play_enqueued_sound(struct enqueued_sound **queue, struct enqueued_sound *snd, void *arg) {
play_sound_internal(snd->name, false, snd->cooldown, snd->replace, 0);
free(snd->name);
free(list_unlink(queue, snd));
return NULL;
}
void play_sound(const char *name) {
play_sound_internal(name, false, 0, false, 0);
}
void play_sound_ex(const char *name, int cooldown, bool replace) {
play_sound_internal(name, false, cooldown, replace, 0);
}
void play_sound_delayed(const char *name, int cooldown, bool replace, int delay) {
play_sound_internal(name, false, cooldown, replace, delay);
}
void play_ui_sound(const char *name) {
play_sound_internal(name, true, 0, true, 0);
}
void play_loop(const char *name) {
if(!audio_backend_initialized() || global.frameskip) {
return;
}
Sound *snd = get_sound(name);
if(!snd) {
return;
}
if(!snd->islooping) {
audio_backend_sound_loop(snd->impl, SNDGROUP_MAIN);
snd->islooping = true;
}
if(snd->islooping == LS_LOOPING) {
snd->lastplayframe = global.frames;
}
}
static void* reset_sounds_callback(const char *name, Resource *res, void *arg) {
bool reset = (intptr_t)arg;
Sound *snd = res->data;
if(snd) {
if(reset) {
snd->lastplayframe = 0;
}
if(snd->islooping && (global.frames > snd->lastplayframe + LOOPTIMEOUTFRAMES || reset)) {
audio_backend_sound_stop_loop(snd->impl);
snd->islooping = LS_FADEOUT;
}
if(snd->islooping && (global.frames > snd->lastplayframe + LOOPTIMEOUTFRAMES + LOOPFADEOUT*60/1000. || reset)) {
snd->islooping = LS_OFF;
}
}
return NULL;
}
void reset_sounds(void) {
resource_for_each(RES_SFX, reset_sounds_callback, (void*)true);
list_foreach(&sound_queue, discard_enqueued_sound, NULL);
}
void update_sounds(void) {
resource_for_each(RES_SFX, reset_sounds_callback, (void*)false);
for(struct enqueued_sound *s = sound_queue, *next; s; s = next) {
next = (struct enqueued_sound*)s->next;
if(s->time <= global.frames) {
play_enqueued_sound(&sound_queue, s, NULL);
}
}
}
void pause_sounds(void) {
audio_backend_sound_pause_all(SNDGROUP_MAIN);
}
void resume_sounds(void) {
audio_backend_sound_resume_all(SNDGROUP_MAIN);
}
void stop_sounds(void) {
audio_backend_sound_stop_all(SNDGROUP_MAIN);
}
Sound* get_sound(const char *name) {
return get_resource_data(RES_SFX, name, RESF_OPTIONAL);
}
Music* get_music(const char *name) {
return get_resource_data(RES_BGM, name, RESF_OPTIONAL);
}
static bool store_sfx_volume(const char *key, const char *val, void *data) {
int vol = atoi(val);
if(vol < 0 || vol > 128) {
log_warn("Volume %i for sfx %s is out of range; must be within [0, 128]", vol, key);
vol = vol < 0 ? 0 : 128;
}
log_debug("Default volume for %s is now %i", key, vol);
if(vol != DEFAULT_SFX_VOLUME) {
ht_set(&sfx_volumes, key, vol);
}
return true;
}
static void load_config_files(void) {
ht_create(&sfx_volumes);
parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, NULL);
}
static inline char* get_bgm_desc(char *name) {
Music *music = get_music(name);
assert(music != NULL);
return music->title;
}
int get_default_sfx_volume(const char *sfx) {
return ht_get(&sfx_volumes, sfx, DEFAULT_SFX_VOLUME);
}
void resume_bgm(void) {
start_bgm(current_bgm.name); // In most cases it just unpauses existing music.
}
static void stop_bgm_internal(bool pause, double fadetime) {
if(!current_bgm.name) {
return;
}
current_bgm.started_at = -1;
if(!pause) {
stralloc(&current_bgm.name, NULL);
}
if(audio_backend_music_is_playing() && !audio_backend_music_is_paused()) {
if(pause) {
audio_backend_music_pause();
log_debug("BGM paused");
} else if(fadetime > 0) {
audio_backend_music_fade(fadetime);
log_debug("BGM fading out");
} else {
audio_backend_music_stop();
log_debug("BGM stopped");
}
} else {
log_debug("No BGM was playing");
}
}
void stop_bgm(bool force) {
stop_bgm_internal(!force, false);
}
void fade_bgm(double fadetime) {
stop_bgm_internal(false, fadetime);
}
void save_bgm(void) {
// XXX: this is broken
stralloc(&saved_bgm, current_bgm.name);
}
void restore_bgm(void) {
// XXX: this is broken
start_bgm(saved_bgm);
free(saved_bgm);
saved_bgm = NULL;
}
void start_bgm(const char *name) {
if(!name || !*name) {
stop_bgm(false);
return;
}
// if BGM has changed, change it and start from beginning
if(!current_bgm.name || strcmp(name, current_bgm.name)) {
audio_backend_music_stop();
stralloc(&current_bgm.name, name);
if((current_bgm.music = get_music(name)) == NULL) {
log_warn("BGM '%s' does not exist", current_bgm.name);
stop_bgm(true);
free(current_bgm.name);
current_bgm.name = NULL;
return;
}
}
if(audio_backend_music_is_paused()) {
audio_backend_music_resume();
}
if(audio_backend_music_is_playing()) {
return;
}
if(!audio_backend_music_play(current_bgm.music->impl)) {
return;
}
// Support drawing BGM title in game loop (only when music changed!)
if((current_bgm.title = get_bgm_desc(current_bgm.name)) != NULL) {
current_bgm.started_at = global.frames;
// Boss BGM title color may differ from the one at beginning of stage
current_bgm.isboss = strendswith(current_bgm.name, "boss");
} else {
current_bgm.started_at = -1;
}
log_info("Started %s", (current_bgm.title ? current_bgm.title : current_bgm.name));
}
static bool audio_config_updated(SDL_Event *evt, void *arg) {
if (config_get_int(CONFIG_MUTE_AUDIO) == 1) {
audio_backend_set_sfx_volume(0.0);
audio_backend_set_bgm_volume(0.0);
} else {
audio_backend_set_sfx_volume(config_get_float(CONFIG_SFX_VOLUME));
audio_backend_set_bgm_volume(config_get_float(CONFIG_BGM_VOLUME));
}
return false;
}
void audio_init(void) {
load_config_files();
audio_backend_init();
events_register_handler(&(EventHandler) {
audio_config_updated, NULL, EPRIO_SYSTEM, MAKE_TAISEI_EVENT(TE_CONFIG_UPDATED)
});
audio_config_updated(NULL, NULL);
}
void audio_shutdown(void) {
events_unregister_handler(audio_config_updated);
audio_backend_shutdown();
ht_destroy(&sfx_volumes);
}