diff --git a/src/audio/audio.c b/src/audio/audio.c index 403d8029b..7f4c6c583 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -1,94 +1,324 @@ +#include +#include + +#include +#include +#include + #include "audio/audio.h" -#include "audio/sink.h" -#include "audio/sinks/openal_sink.h" +#include "resources/pathmanager.h" +#include "resources/sounds_loader.h" #include "utils/allocator.h" #include "utils/log.h" -#include -#include +#include "utils/miscmath.h" -typedef struct { - int (*sink_init_fn)(audio_sink *sink); - const char *name; -} sink_info; +#define CHANNEL_MAX 8 -static audio_sink *_global_sink = NULL; +const audio_freq output_freqs[] = { + {11025, 0, "11025Hz"}, + {22050, 0, "22050Hz"}, + {44100, 0, "44100Hz"}, + {48000, 1, "48000Hz"}, + {0, 0, 0 } // Guard +}; -const sink_info sinks[] = { -#ifdef USE_OPENAL - {openal_sink_init, "openal"}, -#endif // USE_OPENAL +const audio_mod_resampler music_resamplers[] = { + {XMP_INTERP_NEAREST, 0, "Nearest"}, + {XMP_INTERP_LINEAR, 1, "Linear" }, + {XMP_INTERP_SPLINE, 0, "Cubic" }, + {0, 0, 0 } // Guard }; -#define SINK_COUNT (sizeof(sinks) / sizeof(sink_info)) -const char *audio_get_first_sink_name() { - if(SINK_COUNT > 0) { - return sinks[0].name; +typedef struct audio_system { + int freq; + Uint16 format; + int channels; + int resampler; + float music_volume; + resource_id music_id; + xmp_context xmp_context; + Mix_Chunk *channel_chunks[CHANNEL_MAX]; +} audio_system; + +static audio_system *audio = NULL; + +static const char *get_sdl_audio_format_string(SDL_AudioFormat format) { + switch(format) { + case AUDIO_U8: + return "AUDIO_U8"; + case AUDIO_S8: + return "AUDIO_S8"; + case AUDIO_U16LSB: + return "AUDIO_U16LSB"; + case AUDIO_S16LSB: + return "AUDIO_S16LSB"; + case AUDIO_U16MSB: + return "AUDIO_U16MSB"; + case AUDIO_S16MSB: + return "AUDIO_S16MSB"; + case AUDIO_S32LSB: + return "AUDIO_S32LSB"; + case AUDIO_S32MSB: + return "AUDIO_S32MSB"; + case AUDIO_F32LSB: + return "AUDIO_F32LSB"; + case AUDIO_F32MSB: + return "AUDIO_F32MSB"; } - return NULL; + return "UNKNOWN"; } -int audio_is_sink_available(const char *sink_name) { - for(unsigned i = 0; i < SINK_COUNT; i++) { - if(strcmp(sink_name, sinks[i].name) == 0) { - return 1; - } +static Mix_Chunk *audio_get_chunk(int id, float volume, float pitch) { + char *src_buf; + int src_len; + Uint8 *dst_buf; + SDL_AudioCVT cvt; + + // Load sample (8000Hz, mono, 8bit) + if(sounds_loader_get(id, &src_buf, &src_len) != 0) { + PERROR("Requested sound sample %d not found", id); + return NULL; + } + if(src_len == 0) { + DEBUG("Requested sound sample %d has nothing to play", id); + return NULL; + } + + // Converter for sound samples. + int src_freq = 8000 * pitch; + if(SDL_BuildAudioCVT(&cvt, AUDIO_U8, 1, src_freq, audio->format, audio->channels, audio->freq) < 0) { + PERROR("Unable to build audio converter: %s", SDL_GetError()); + return NULL; + } + + // Create a buffer that can hold the source data and final converted data. + if((dst_buf = SDL_malloc(src_len * cvt.len_mult + 1)) == NULL) { + PERROR("Unable to allocate memory for sound buffer"); + return NULL; } - return 0; + SDL_memcpy((void *)dst_buf, (void *)src_buf, src_len); + + // Convert! + cvt.buf = dst_buf; + cvt.len = src_len; + if(SDL_ConvertAudio(&cvt) != 0) { + PERROR("Unable to convert audio sample: %s", SDL_GetError()); + return NULL; + } + + Mix_Chunk *chunk = SDL_malloc(sizeof(Mix_Chunk)); + chunk->volume = volume * MIX_MAX_VOLUME; + chunk->abuf = dst_buf; + chunk->alen = cvt.len_cvt; + chunk->allocated = 1; + return chunk; } -void audio_render() { - if(_global_sink != NULL) { - sink_render(_global_sink); +static void audio_sound_finished(int channel) { + assert(audio); + if(audio->channel_chunks[channel] != NULL) { + Mix_FreeChunk(audio->channel_chunks[channel]); + audio->channel_chunks[channel] = NULL; } } -int audio_init(const char *sink_name) { - sink_info si; - memset(&si, 0, sizeof(sink_info)); +static bool audio_load_module(const char *file) { + assert(audio); - // If null sink given, disable audio - if(sink_name == NULL || strlen(sink_name) <= 0) { - INFO("Audio sink NOT initialized; audio not available."); - return 0; + // Load the module file + if(xmp_load_module(audio->xmp_context, (char *)file) < 0) { + PERROR("Unable to open module file"); + goto exit_0; } - // Find requested sink - for(unsigned c = 0; c < SINK_COUNT; ++c) { - if(strcmp(sink_name, sinks[c].name) == 0) { - si = sinks[c]; - break; - } + // Show some information + struct xmp_module_info mi; + xmp_get_module_info(audio->xmp_context, &mi); + DEBUG("Loaded music track %s (%s)", mi.mod->name, mi.mod->type); + + // Start the player + int flags = 0; + if(audio->channels == 1) + flags |= XMP_FORMAT_MONO; + if(xmp_start_player(audio->xmp_context, audio->freq, flags) != 0) { + PERROR("Unable to start module playback"); + goto exit_1; + } + if(xmp_set_player(audio->xmp_context, XMP_PLAYER_INTERP, audio->resampler) != 0) { + PERROR("Unable to set music resampler"); + goto exit_2; + } + if(xmp_set_player(audio->xmp_context, XMP_PLAYER_VOLUME, audio->music_volume * 100) != 0) { + PERROR("Unable to set music volume"); + goto exit_2; + } + return true; + +exit_2: + xmp_end_player(audio->xmp_context); +exit_1: + xmp_release_module(audio->xmp_context); +exit_0: + return false; +} + +// Callback function for SDL_Mixer +void audio_xmp_render(void *userdata, Uint8 *stream, int len) { + assert(audio); + xmp_play_buffer(audio->xmp_context, stream, len, INT_MAX); +} + +static void audio_close_module() { + if(is_music(audio->music_id)) { + xmp_end_player(audio->xmp_context); + xmp_release_module(audio->xmp_context); + audio->music_id = NUMBER_OF_RESOURCES; } - if(si.name == NULL) { - PERROR("Requested audio sink was not found!"); - return 1; +} + +bool audio_init(int freq, bool mono, int resampler, float music_volume, float sound_volume) { + if(!(audio = omf_calloc(1, sizeof(audio_system)))) { + PERROR("Unable to allocate audio subsystem"); + goto error_0; + } + if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { + PERROR("Unable to initialize audio subsystem: %s", SDL_GetError()); + goto error_1; + } + if(Mix_Init(0) != 0) { + PERROR("Unable to initialize mixer subsystem: %s", Mix_GetError()); + goto error_2; + } + if((audio->xmp_context = xmp_create_context()) == NULL) { + PERROR("Unable to initialize XMP context."); + goto error_3; } - // Inform user - INFO("Using audio sink '%s'.", si.name); + INFO("Requested audio device with options:"); + INFO(" * Rate: %dHz", freq); + INFO(" * Channels: %d", mono ? 1 : 2); + INFO(" * Format: %s", get_sdl_audio_format_string(AUDIO_S16SYS)); - // Init sink - _global_sink = omf_calloc(1, sizeof(audio_sink)); - sink_init(_global_sink); - if(si.sink_init_fn(_global_sink) != 0) { - omf_free(_global_sink); - return 1; + // Setup audio. We request for configuration, but we're not sure what we get. + if(Mix_OpenAudio(freq, AUDIO_S16SYS, mono ? 1 : 2, 2048) != 0) { + PERROR("Unable to initialize audio device: %s", SDL_GetError()); + goto error_4; } - // Success - INFO("Audio system initialized."); - return 0; + // Initialize playback parameters. + audio_set_sound_volume(sound_volume); + audio_set_music_volume(music_volume); + audio->resampler = resampler; + audio->music_id = NUMBER_OF_RESOURCES; + + Mix_ChannelFinished(audio_sound_finished); + + // Get the actual device configuration we got. + Mix_QuerySpec(&audio->freq, &audio->format, &audio->channels); + INFO("Opened audio device:"); + INFO(" * Rate: %dHz", audio->freq); + INFO(" * Channels: %d", audio->channels); + INFO(" * Format: %s", get_sdl_audio_format_string(audio->format)); + return true; + +error_4: + xmp_free_context(audio->xmp_context); +error_3: + Mix_Quit(); +error_2: + SDL_QuitSubSystem(SDL_INIT_AUDIO); +error_1: + omf_free(audio); +error_0: + audio = NULL; + return false; } void audio_close() { - if(_global_sink != NULL) { - sink_free(_global_sink); - omf_free(_global_sink); - _global_sink = NULL; - INFO("Audio system closed."); + if(audio != NULL) { + audio_close_module(); + if(audio->xmp_context) { + xmp_free_context(audio->xmp_context); + audio->xmp_context = NULL; + } + Mix_ChannelFinished(NULL); + Mix_CloseAudio(); + for(int i = 0; i < CHANNEL_MAX; i++) { + if(audio->channel_chunks[i] != NULL) { + Mix_FreeChunk(audio->channel_chunks[i]); + audio->channel_chunks[i] = NULL; + } + } + omf_free(audio); + audio = NULL; } + Mix_Quit(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); } -audio_sink *audio_get_sink() { - return _global_sink; +void audio_play_sound(int id, float volume, float panning, float pitch) { + assert(audio); + int channel; + float pan_left, pan_right; + + // Anything beyond these are invalid + if(id < 0 || id > 299) + return; + + volume = clampf(volume, VOLUME_MIN, VOLUME_MAX); + panning = clampf(panning, PANNING_MIN, PANNING_MAX); + pitch = clampf(pitch, PITCH_MIN, PITCH_MAX); + pan_left = (panning > 0) ? 1.0f - panning : 1.0f; + pan_right = (panning < 0) ? 1.0f + panning : 1.0f; + + Mix_Chunk *chunk; + if(!(chunk = audio_get_chunk(id, volume, pitch))) + return; + channel = Mix_GroupAvailable(-1); + audio->channel_chunks[channel] = chunk; + Mix_SetPanning(channel, clamp(pan_left * 255, 0, 255), clamp(pan_right * 255, 0, 255)); + if(Mix_PlayChannel(channel, chunk, 0) == -1) { + PERROR("Unable to play sound: %s", Mix_GetError()); + } } + +void audio_play_music(resource_id id) { + assert(audio); + assert(is_music(id)); + if(audio->music_id != id) { + audio_close_module(); + const char *music_file = pm_get_resource_path(id); + if(!audio_load_module(music_file)) { + PERROR("Unable to load music track: %s", music_file); + return; + } + audio->music_id = id; + Mix_HookMusic(audio_xmp_render, NULL); + } +} + +void audio_stop_music() { + assert(audio); + Mix_HaltMusic(); + Mix_HookMusic(NULL, NULL); +} + +void audio_set_music_volume(float volume) { + assert(audio); + audio->music_volume = clampf(volume, VOLUME_MIN, VOLUME_MAX); + xmp_set_player(audio->xmp_context, XMP_PLAYER_VOLUME, audio->music_volume * 100); +} + +void audio_set_sound_volume(float volume) { + assert(audio); + volume = clampf(volume, VOLUME_MIN, VOLUME_MAX); + Mix_MasterVolume(volume * MIX_MAX_VOLUME); +} + +const audio_freq *audio_get_freqs() { + return output_freqs; +} + +const audio_mod_resampler *audio_get_resamplers() { + return music_resamplers; +} \ No newline at end of file