From cb9d77a5dff49f004420e37e1efe237307f310cc Mon Sep 17 00:00:00 2001 From: Akkari <103267904+RPGoogles@users.noreply.github.com> Date: Sun, 20 Sep 2026 15:06:38 +0100 Subject: [PATCH 1/2] Add tracker module file types to filefinder Adds functionality so the player can actually find tracker module files(.mod, .xm, .s3m and .it) for Music and Sound. --- src/filefinder.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filefinder.h b/src/filefinder.h index a3f6aca025..bebbeb1c03 100644 --- a/src/filefinder.h +++ b/src/filefinder.h @@ -41,9 +41,9 @@ namespace FileFinder { constexpr const auto IMG_TYPES = Utils::MakeSvArray(".bmp", ".png", ".xyz"); constexpr const auto MUSIC_TYPES = Utils::MakeSvArray( - ".opus", ".oga", ".ogg", ".wav", ".mid", ".midi", ".mp3", ".wma"); + ".opus", ".oga", ".ogg", ".wav", ".mid", ".midi", ".mp3", ".mod", ".xm", ".s3m", ".it", ".wma"); constexpr const auto SOUND_TYPES = Utils::MakeSvArray( - ".opus", ".oga", ".ogg", ".wav", ".mp3", ".wma"); + ".opus", ".oga", ".ogg", ".wav", ".mp3", ".mod", ".xm", ".s3m", ".it", ".wma"); constexpr const auto FONTS_TYPES = Utils::MakeSvArray(".fon", ".fnt", ".bdf", ".ttf", ".ttc", ".otf", ".woff2", ".woff"); constexpr const auto TEXT_TYPES = Utils::MakeSvArray(".txt", ".csv", ""); // "" = Complete Filename (incl. extension) provided by the user From 04ec2883e0ed3531d4133d3c6cd9f08342679773 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Sat, 3 Oct 2026 15:21:17 +0200 Subject: [PATCH 2/2] XMP: Proper support for loop handling (Jump commands in tracker modules) This feature is now enabled for BGM and disabled for SE Migrated from xmp_play_buffer to the low level API xmp_play_frame for more control about the loop count --- src/decoder_xmp.cpp | 69 +++++++++++++++++++++++++++++++++++++-------- src/decoder_xmp.h | 4 +++ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/decoder_xmp.cpp b/src/decoder_xmp.cpp index 390a524d42..e11d7e2be0 100644 --- a/src/decoder_xmp.cpp +++ b/src/decoder_xmp.cpp @@ -73,6 +73,7 @@ XMPDecoder::~XMPDecoder() { bool XMPDecoder::Open(Filesystem_Stream::InputStream stream) { finished = false; + frame_bytes_left = 0; if (!ctx) return false; @@ -106,6 +107,7 @@ bool XMPDecoder::Seek(std::streamoff offset, std::ios_base::seekdir origin) { if (offset == 0 && origin == std::ios_base::beg) { xmp_restart_module(ctx); + frame_bytes_left = 0; finished = false; return true; } @@ -138,6 +140,8 @@ bool XMPDecoder::SetFormat(int freq, AudioDecoder::Format frmt, int chans) { // restart the player, apply new format flags xmp_end_player(ctx); + frame_bytes_left = 0; + finished = false; channels = chans; format = frmt; @@ -176,21 +180,62 @@ int XMPDecoder::FillBuffer(uint8_t* buffer, int length) { if (!ctx) return -1; - /* FIXME: `xmp_play_buffer()` is a loop around `xmp_play_frame()` that may add - * silence at the end of the buffer, when there is not enough audio data left. - * We may need to use the latter directly, to have no gap between two loops. - */ - int ret = xmp_play_buffer(ctx, buffer, length, 1); + if (finished) + return 0; - // end of file - if (ret == -XMP_END) - finished = true; + int filled = 0; + + // xmp_play_frame renders a single frame of the module + // xmp_play_buffer is not used here as this inserts silence at the end + // Also gives more control about the loop handling + while (filled < length) { + if (frame_bytes_left == 0) { + int ret = xmp_play_frame(ctx); + + // error + if (ret == -XMP_ERROR_STATE) + return -1; + + // end of module + if (ret < 0) { + finished = true; + break; + } + + xmp_frame_info info; + xmp_get_frame_info(ctx, &info); + + // libxmp handles jump commands (built-in loops) automatically + // When looping is enabled for the AudioDecoder manually increment + // our own loop counter + if (info.loop_count != GetLoopCount()) { + if (!GetLooping()) { + // Play the module once, then report it as finished + finished = true; + break; + } + + // Manually set the loop count as IsFinished which handles this + // normally will never return true + loop_count = info.loop_count; + } + + frame_buffer = reinterpret_cast(info.buffer); + frame_bytes_left = info.buffer_size; + } - // error - if (ret == -XMP_ERROR_STATE) - return -1; + int copy_size = length - filled; + if (copy_size > frame_bytes_left) + copy_size = frame_bytes_left; + + memcpy(buffer + filled, frame_buffer, copy_size); + + frame_buffer += copy_size; + frame_bytes_left -= copy_size; + filled += copy_size; + } - return length; + return filled; } #endif diff --git a/src/decoder_xmp.h b/src/decoder_xmp.h index 22f54f372f..7cb8af26e6 100644 --- a/src/decoder_xmp.h +++ b/src/decoder_xmp.h @@ -56,6 +56,10 @@ class XMPDecoder : public AudioDecoder { #ifdef HAVE_LIBXMP xmp_context ctx = nullptr; #endif + /** Buffer filled by xmp_play_frame */ + const uint8_t* frame_buffer = nullptr; + /** Remaining bytes in that buffer */ + int frame_bytes_left = 0; bool finished = false; // defaults