From 0eb851e7bf38d600f294798ae72173d0b4bb544d Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Mon, 8 Jul 2024 16:58:23 +0300 Subject: [PATCH] .xm playback --- CMakeLists.txt | 3 + src/audio.c | 131 ++++++++++++++++++++++++++++++++++---------- src/private/audio.h | 5 ++ 3 files changed, 111 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 269e810..006c7ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ set(PHYSFS_BUILD_SHARED FALSE) set(PHYSFS_DISABLE_INSTALL TRUE) set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall") add_subdirectory(third-party/physfs) +add_subdirectory(third-party/libxm) set(SOURCE_FILES @@ -113,6 +114,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE third-party/physfs/src third-party/physfs/extras + third-party/libxm/include ) # header-only libraries should be marked as "system includes" @@ -136,6 +138,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC SDL2_image::SDL2_image SDL2_ttf::SDL2_ttf physfs-static + xms ) # copy dlls for baby windows diff --git a/src/audio.c b/src/audio.c index d2883fb..8868697 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1,5 +1,6 @@ #include "private/audio.h" #include "audio.h" +#include "config.h" #include "context.h" #include "util.h" @@ -22,6 +23,7 @@ static const char *audio_exts[audio_file_type_count] = { /* TODO: count frames without use, free the memory when threshold is met */ +/* TODO: count repeated usages for sound effect cases with rendering to ram? */ /* stores path to data hash, useful for sound effects */ static struct audio_file_cache { char *key; @@ -100,7 +102,7 @@ static union audio_context init_audio_context(const char *path, t_audio_file_typ stb_vorbis_info info = stb_vorbis_get_info(handle); - return (union audio_context){ + return (union audio_context) { .vorbis = { .data = data, .handle = handle, @@ -110,6 +112,29 @@ static union audio_context init_audio_context(const char *path, t_audio_file_typ }; } + case audio_file_type_xm: { + unsigned char *data; + int64_t len = get_audio_data(path, &data); + if (len == -1) { + CRY("Audio error", "Error reading file"); + break; + } + + xm_context_t *handle; + int response = xm_create_context_safe(&handle, + (const char *)data, + (size_t)len, + AUDIO_FREQUENCY); + if (response != 0) { + CRY("Audio error", "Error loading xm module"); + break; + } + + return (union audio_context) { + .xm = { .handle = handle } + }; + } + default: CRY("Audio error", "Unhandled audio format (in init)"); return (union audio_context){0}; @@ -120,9 +145,22 @@ static union audio_context init_audio_context(const char *path, t_audio_file_typ static void repeat_audio(struct audio_channel *channel) { - (void)channel; + switch (channel->file_type) { + case audio_file_type_ogg: { + stb_vorbis_seek_start(channel->context.vorbis.handle); + break; + } - /* TODO */ + case audio_file_type_xm: { + /* TODO: test */ + xm_seek(channel->context.xm.handle, 0, 0, 0); + break; + } + + default: + CRY("Audio error", "Unhandled audio format (in repeat)"); + break; + } } @@ -160,20 +198,50 @@ t_play_audio_args get_default_audio_args(void) { } -static void audio_sample_and_mixin_channel(struct audio_channel *channel, +/* this assumes int16_t based streams */ +static void audio_mixin_streams(const struct audio_channel *channel, + uint8_t *restrict a, + uint8_t *restrict b, + size_t frames) +{ + int16_t *const sa = (int16_t *)a; + int16_t *const sb = (int16_t *)b; + + const float left_panning = fminf(fabsf(channel->args.panning - 1.0f), 1.0f); + const float right_panning = fminf(fabsf(channel->args.panning + 1.0f), 1.0f); + +#if AUDIO_N_CHANNELS == 2 + + for (size_t s = 0; s < frames; s += 2) { + /* left channel */ + sa[s] += (int16_t)(sb[s] * channel->args.volume * left_panning); + + /* right channel */ + sa[s + 1] += (int16_t)(sb[s + 1] * channel->args.volume * right_panning); + } +#else +#error "Unimplemented channel count" +#endif +} + + +/* remember: sample is data for all channels where frame is a part of it */ +static void audio_sample_and_mixin_channel(const struct audio_channel *channel, uint8_t *stream, - int len) { - int16_t *const sstream = (int16_t *)stream; + int len) +{ static uint8_t buffer[16384]; - const int buffer_frames = sizeof (buffer) / sizeof (int16_t); + const int int16_buffer_frames = sizeof (buffer) / sizeof (int16_t); + const int float_buffer_frames = sizeof (buffer) / sizeof (float); + const int stream_frames = len / sizeof (int16_t); switch (channel->file_type) { case audio_file_type_ogg: { /* feed stream for needed conversions */ - for (int i = 0; i < (len / 2);) { - const int n_frames = ((len / 2) - i) > buffer_frames ? buffer_frames : (len / 2) - i; + for (int i = 0; i < stream_frames; ) { + const int n_frames = (stream_frames - i) > int16_buffer_frames ? + int16_buffer_frames : stream_frames - i; - /* TODO: handle end of file */ const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( channel->context.vorbis.handle, channel->context.vorbis.channel_count, @@ -191,24 +259,8 @@ static void audio_sample_and_mixin_channel(struct audio_channel *channel, } /* panning and mixing */ - - const float left_panning = fminf(fabsf(channel->args.panning - 1.0f), 1.0f); - const float right_panning = fminf(fabsf(channel->args.panning + 1.0f), 1.0f); - -#if AUDIO_N_CHANNELS == 2 - - for (int s = 0; s < samples_per_channel * ctx.audio_stream_channel_count; s += 2) { - /* left channel */ - sstream[i + s] += (int16_t)(((int16_t *)buffer)[s] * - channel->args.volume * left_panning); - - /* right channel */ - sstream[i + s + 1] += (int16_t)(((int16_t *)buffer)[s + 1] * - channel->args.volume * right_panning); - } -#else -#error "Unimplemented channel count" -#endif + audio_mixin_streams(channel, &stream[i * sizeof(int16_t)], buffer, + samples_per_channel * ctx.audio_stream_channel_count); i += samples_per_channel * ctx.audio_stream_channel_count; } @@ -216,6 +268,29 @@ static void audio_sample_and_mixin_channel(struct audio_channel *channel, break; } + case audio_file_type_xm: { + for (int i = 0; i < stream_frames; ) { + const int n_frames = (stream_frames - i) > float_buffer_frames ? + float_buffer_frames : stream_frames - i; + + /* TODO: make it report generated frame count and stop right before looping */ + xm_generate_samples(channel->context.xm.handle, (float *)buffer, n_frames / 2); + + /* convert floats to int16_t */ + for (int p = 0; p < n_frames; ++p) { + const int16_t value = (int16_t)(((float *)buffer)[p] * 32768.0f); + ((int16_t *)buffer)[p] = value; + } + + /* panning and mixing */ + audio_mixin_streams(channel, &stream[i * sizeof(int16_t)], buffer, n_frames); + + i += n_frames; + } + + break; + } + default: CRY("Audio error", "Unhandled audio format (in sampling)"); break; diff --git a/src/private/audio.h b/src/private/audio.h index 16a4344..a484130 100644 --- a/src/private/audio.h +++ b/src/private/audio.h @@ -7,6 +7,7 @@ #define STB_VORBIS_HEADER_ONLY #include +#include #include #include @@ -27,6 +28,10 @@ union audio_context { int frequency; uint8_t channel_count; } vorbis; + + struct { + xm_context_t *handle; + } xm; };