taisei/src/audio_mixer.c
Andrei Alexeyev 513d613387
Consistent indentation: indent with tabs, align with spaces (#104)
I would've preferred to just go with 4-spaces for indent and no tabs,
but lao is a bit conservative about it. :^)

Still, this is a ton better than mixing different styles all over the
place, especially within the same file.
2018-01-12 20:26:07 +02:00

446 lines
9.9 KiB
C

/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "taisei.h"
#include <SDL_mixer.h>
#include "audio.h"
#include "audio_mixer.h"
#include "global.h"
#include "list.h"
#define AUDIO_FREQ 44100
#define AUDIO_FORMAT MIX_DEFAULT_FORMAT
#define AUDIO_CHANNELS 100
#define UI_CHANNELS 4
#define UI_CHANNEL_GROUP 0
#define MAIN_CHANNEL_GROUP 1
static bool mixer_loaded = false;
static struct {
unsigned char first;
unsigned char num;
} groups[2];
const char *mixer_audio_exts[] = { ".ogg", ".wav", NULL };
void audio_backend_init(void) {
if(mixer_loaded) {
return;
}
if(SDL_InitSubSystem(SDL_INIT_AUDIO)) {
log_warn("SDL_InitSubSystem() failed: %s", SDL_GetError());
return;
}
if(Mix_OpenAudio(AUDIO_FREQ, AUDIO_FORMAT, 2, config_get_int(CONFIG_MIXER_CHUNKSIZE)) == -1) {
log_warn("Mix_OpenAudio() failed: %s", Mix_GetError());
Mix_Quit();
return;
}
if(!(Mix_Init(MIX_INIT_OGG) & MIX_INIT_OGG)) {
log_warn("Mix_Init() failed: %s", Mix_GetError());
Mix_Quit(); // Try to shutdown mixer if it was partly initialized
return;
}
int channels = Mix_AllocateChannels(AUDIO_CHANNELS);
if(!channels) {
log_warn("Unable to allocate any channels");
Mix_CloseAudio();
Mix_Quit();
return;
}
if(channels < AUDIO_CHANNELS) {
log_warn("Allocated only %d out of %d channels", channels, AUDIO_CHANNELS);
}
int wanted_ui_channels = UI_CHANNELS;
if(wanted_ui_channels > channels / 2) {
wanted_ui_channels = channels / 2;
log_warn("Will not reserve more than %i channels for UI sounds (wanted %i channels)", wanted_ui_channels, UI_CHANNELS);
}
int ui_channels;
if((ui_channels = Mix_GroupChannels(0, wanted_ui_channels - 1, UI_CHANNEL_GROUP)) != wanted_ui_channels) {
log_warn("Assigned only %d out of %d channels to the UI group", ui_channels, wanted_ui_channels);
}
int wanted_main_channels = channels - ui_channels, main_channels;
if((main_channels = Mix_GroupChannels(ui_channels, ui_channels + wanted_main_channels - 1, MAIN_CHANNEL_GROUP)) != wanted_main_channels) {
log_warn("Assigned only %d out of %d channels to the main group", main_channels, wanted_main_channels);
}
int unused_channels = channels - ui_channels - main_channels;
if(unused_channels) {
log_warn("%i channels not used", unused_channels);
}
groups[UI_CHANNEL_GROUP].first = 0;
groups[UI_CHANNEL_GROUP].num = ui_channels;
groups[MAIN_CHANNEL_GROUP].first = ui_channels;
groups[MAIN_CHANNEL_GROUP].num = main_channels;
mixer_loaded = true;
audio_backend_set_sfx_volume(config_get_float(CONFIG_SFX_VOLUME));
audio_backend_set_bgm_volume(config_get_float(CONFIG_BGM_VOLUME));
int frequency = 0;
uint16_t format = 0;
Mix_QuerySpec(&frequency, &format, &channels);
if(frequency != AUDIO_FREQ || format != AUDIO_FORMAT) {
log_warn(
"Mixer spec doesn't match our request, "
"requested (freq=%i, fmt=%u), got (freq=%i, fmt=%u). "
"Sound may be distorted.",
AUDIO_FREQ, AUDIO_FORMAT, frequency, format
);
}
log_info("Audio subsystem initialized (SDL2_Mixer)");
SDL_version v;
const SDL_version *lv;
SDL_MIXER_VERSION(&v);
log_info("Compiled against SDL_mixer %u.%u.%u", v.major, v.minor, v.patch);
lv = Mix_Linked_Version();
log_info("Using SDL_mixer %u.%u.%u", lv->major, lv->minor, lv->patch);
}
void audio_backend_shutdown(void) {
mixer_loaded = false;
Mix_CloseAudio();
Mix_Quit();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
log_info("Audio subsystem uninitialized (SDL2_Mixer)");
}
bool audio_backend_initialized(void) {
return mixer_loaded;
}
void audio_backend_set_sfx_volume(float gain) {
if(mixer_loaded)
Mix_Volume(-1, gain * MIX_MAX_VOLUME);
}
void audio_backend_set_bgm_volume(float gain) {
if(mixer_loaded)
Mix_VolumeMusic(gain * MIX_MAX_VOLUME);
}
static char* try_path(const char *prefix, const char *name, const char *ext) {
char *p = strjoin(prefix, name, ext, NULL);
if(vfs_query(p).exists) {
log_debug("Returning %s", p);
return p;
}
free(p);
return NULL;
}
char* audio_mixer_sound_path(const char *prefix, const char *name, bool isbgm) {
char *p = NULL;
if(isbgm && (p = try_path(prefix, name, ".bgm"))) {
return p;
}
for(const char **ext = mixer_audio_exts; *ext; ++ext) {
if((p = try_path(prefix, name, *ext))) {
return p;
}
}
return NULL;
}
bool audio_mixer_check_sound_path(const char *path, bool isbgm) {
if(isbgm && strendswith(path, ".bgm")) {
return true;
}
return strendswith_any(path, mixer_audio_exts);
}
static Mix_Music *next_loop;
static double next_loop_point;
static void mixer_music_finished(void) {
// XXX: there may be a race condition in here
// probably should protect next_loop with a mutex
log_debug("%s stopped playing", next_loop_point ? "Loop" : "Intro");
if(next_loop) {
if(Mix_PlayMusic(next_loop, next_loop_point ? 0 : -1) == -1) {
log_warn("Mix_PlayMusic() failed: %s", Mix_GetError());
} else if(next_loop_point >= 0) {
Mix_SetMusicPosition(next_loop_point);
} else {
next_loop = NULL;
}
}
}
void audio_backend_music_stop(void) {
if(mixer_loaded) {
Mix_HookMusicFinished(NULL);
Mix_HaltMusic();
}
}
void audio_backend_music_fade(double fadetime) {
if(mixer_loaded) {
Mix_HookMusicFinished(NULL);
Mix_FadeOutMusic(floor(1000 * fadetime));
}
}
bool audio_backend_music_is_paused(void) {
return mixer_loaded && Mix_PausedMusic();
}
bool audio_backend_music_is_playing(void) {
return mixer_loaded && Mix_PlayingMusic() && Mix_FadingMusic() != MIX_FADING_OUT;
}
void audio_backend_music_resume(void) {
if(mixer_loaded) {
Mix_HookMusicFinished(mixer_music_finished);
Mix_ResumeMusic();
}
}
void audio_backend_music_pause(void) {
if(mixer_loaded) {
Mix_HookMusicFinished(NULL);
Mix_PauseMusic();
}
}
bool audio_backend_music_play(void *impl) {
if(!mixer_loaded)
return false;
MixerInternalMusic *imus = impl;
Mix_Music *mmus;
int loops;
Mix_HookMusicFinished(NULL);
Mix_HaltMusic();
if(imus->intro) {
next_loop = imus->loop;
next_loop_point = next_loop ? imus->loop_point : 0;
mmus = imus->intro;
loops = 0;
Mix_HookMusicFinished(mixer_music_finished);
} else {
mmus = imus->loop;
next_loop_point = imus->loop_point;
if(next_loop_point >= 0) {
loops = 0;
next_loop = imus->loop;
Mix_HookMusicFinished(mixer_music_finished);
} else {
loops = -1;
Mix_HookMusicFinished(NULL);
}
}
bool result = (Mix_PlayMusic(mmus, loops) != -1);
if(!result) {
log_warn("Mix_PlayMusic() failed: %s", Mix_GetError());
}
return result;
}
bool audio_backend_music_set_position(double pos) {
if(!mixer_loaded) {
return false;
}
// FIXME: BGMs that have intros are not handled correctly!
Mix_RewindMusic();
if(Mix_SetMusicPosition(pos)) {
log_warn("Mix_SetMusicPosition() failed: %s", Mix_GetError());
return false;
}
return true;
}
static int translate_group(AudioBackendSoundGroup group, int defmixgroup) {
switch(group) {
case SNDGROUP_MAIN: return MAIN_CHANNEL_GROUP;
case SNDGROUP_UI: return UI_CHANNEL_GROUP;
default: return defmixgroup;
}
}
static int pick_channel(AudioBackendSoundGroup group, int defmixgroup) {
int mixgroup = translate_group(group, MAIN_CHANNEL_GROUP);
int channel = -1;
if((channel = Mix_GroupAvailable(mixgroup)) < 0) {
// all channels busy? try to override the oldest playing sound
if((channel = Mix_GroupOldest(mixgroup)) < 0) {
log_warn("No suitable channel available in group %i to play the sample on", mixgroup);
}
}
return channel;
}
static int audio_backend_sound_play_on_channel(int chan, MixerInternalSound *isnd) {
chan = Mix_PlayChannel(chan, isnd->ch, 0);
if(chan < 0) {
log_warn("Mix_PlayChannel() failed: %s", Mix_GetError());
return false;
}
isnd->playchan = chan;
return true;
}
bool audio_backend_sound_play(void *impl, AudioBackendSoundGroup group) {
if(!mixer_loaded)
return false;
MixerInternalSound *isnd = impl;
return audio_backend_sound_play_on_channel(pick_channel(group, MAIN_CHANNEL_GROUP), isnd);
}
bool audio_backend_sound_play_or_restart(void *impl, AudioBackendSoundGroup group) {
if(!mixer_loaded) {
return false;
}
MixerInternalSound *isnd = impl;
int chan = isnd->playchan;
if(chan < 0 || Mix_GetChunk(chan) != isnd->ch) {
chan = pick_channel(group, MAIN_CHANNEL_GROUP);
}
return audio_backend_sound_play_on_channel(chan, isnd);
}
bool audio_backend_sound_loop(void *impl, AudioBackendSoundGroup group) {
if(!mixer_loaded)
return false;
MixerInternalSound *snd = (MixerInternalSound *)impl;
snd->loopchan = Mix_PlayChannel(pick_channel(group, MAIN_CHANNEL_GROUP), snd->ch, -1);
if(snd->loopchan == -1) {
log_warn("Mix_PlayChannel() failed: %s", Mix_GetError());
return false;
}
return true;
}
bool audio_backend_sound_stop_loop(void *impl) {
if(!mixer_loaded)
return false;
MixerInternalSound *snd = (MixerInternalSound *)impl;
if(snd->loopchan == -1) {
return false;
}
Mix_HaltChannel(snd->loopchan);
return true;
}
bool audio_backend_sound_pause_all(AudioBackendSoundGroup group) {
if(!mixer_loaded) {
return false;
}
int mixgroup = translate_group(group, -1);
if(mixgroup == -1) {
Mix_Pause(-1);
} else {
// why is there no Mix_PauseGroup?
for(int i = groups[mixgroup].first; i < groups[mixgroup].first + groups[mixgroup].num; ++i) {
Mix_Pause(i);
}
}
return true;
}
bool audio_backend_sound_resume_all(AudioBackendSoundGroup group) {
if(!mixer_loaded) {
return false;
}
int mixgroup = translate_group(group, -1);
if(mixgroup == -1) {
Mix_Resume(-1);
} else {
// why is there no Mix_ResumeGroup?
for(int i = groups[mixgroup].first; i < groups[mixgroup].first + groups[mixgroup].num; ++i) {
Mix_Resume(i);
}
}
return true;
}
bool audio_backend_sound_stop_all(AudioBackendSoundGroup group) {
if(!mixer_loaded) {
return false;
}
int mixgroup = translate_group(group, -1);
if(mixgroup == -1) {
Mix_HaltChannel(-1);
} else {
Mix_HaltGroup(mixgroup);
}
return true;
}