Proper looping of modular tracks

Got a cool idea that should be in R'n'D? Let's hear it!

Moderators: Flumminator, Zomis

Post Reply
User avatar
TheOnyxGuy
Posts: 693
Joined: Wed Oct 30, 2013 5:32 am
Location: Russia
Contact:

Proper looping of modular tracks

Post by TheOnyxGuy »

Some of MOD/IT/XM/S3M music actually have a little bit different looping principles. To be specific, some tracks are first playing few samples once and then loops only remaining ones and never plays first ones again, and you can see that in (almost) any tracker like Fast Tracker, Impulse Tracker, Scream Tracker or Schism Tracker (the one I'm using). For example, Bidrag7 by Wasp has this sample order:

Code: Select all

002
000
004
003
001
001
005
007
006
008
First it plays the entire track and then restarts from fifth sample "001", because samples 002, 000, 004 and 003 are the intro parts, while the remaining ones are parts of the main music loop. Importing tracks with scripted loop like that one into R'n'D, however, ignores that script and just loops the entire song and proper loop breaks, which is sad.
It would be awesome if this proper looping feature was implemented! Or maybe... we could even make the game do more specific loop ourselves whatever the music format is? Like, in musicinfo.conf somehow?
Just to show what I mean, here's the Schism Tracker (if needed) - [link] and Bidrag 7 attached to the topic below, you can try it, I guess.
Attachments
bidrag.zip
Yeah, archived, sorry...
(41.58 KiB) Downloaded 668 times
Previously known as Eizzoux (boooo)
User avatar
Holger
Site Admin
Posts: 4480
Joined: Fri Jun 18, 2004 4:13 pm
Location: Germany
Contact:

Re: Proper looping of modular tracks

Post by Holger »

Yes, I remember that Alan Bond reported some similar misbehaviour when playing MOD style music files. :-/

Unfortunately, I did not write that part of the game by myseld, but instead used the SDL_mixer library, which itself uses either the "mikmod" or "modplug" library; not sure if they differ in behaviour regarding the reported issue.

So I'm afraid that I can't do much about this, apart from debugging or improving the code of the mikmod or modplug libraries.

The only thing I might be able to do is checking if I use the very latest version of the SDL_mixer library, which is maybe not the case, as there were some updates recently, I think.

BTW: It may be possible that you can try for yourself: Download the latest version of SDL_mixer.dll (with all related libraries, like mikmod.dll or modplug.dll) and maybe also SDL.dll, replace the DLL files and try if it helps. (If it does, I should replace the library files also in the main distribution package.)
User avatar
TheOnyxGuy
Posts: 693
Joined: Wed Oct 30, 2013 5:32 am
Location: Russia
Contact:

Proper looping of modular tracks (is here, and I somehow missed that until today)

Post by TheOnyxGuy »


Ya know what, that just hit me that technically the (relatively) recent inclusion of newer SDL libraries actually brought this idea to reality, since now modular tracks not only loop properly, they also don't have any kind of weird delay... Let me explain.

Before 4.4.2.0, modular tracks (MOD, IT, XM, S3M) were basically treated like they're converted into MP3/WAV. At least I see it that way because whenever you put a looping tracker music into use in an R'n'D levelset, the song does indeed loop, but not quite correctly, there's a slight pause until the song finally cuts to silence and then starts off again.

Yet again, the best example to that - Reflektor. To be more specific, in demos 4 and 5, there's the TITLE_INITIAL disclaimer screen that has it's own music with global anims pulsing to it's beat. The thing is, for the reason I just mentioned (at least I theorize that it somehow does it like that, I'm not sure), the song wasn't quite looping well and was always off in the end, there was that short delay, which frustrated me. So I did a little workaround and just... make the global anims have a short additional delay on the last beat to keep the loop synced. Open the levelset in 4.4.1.3 and you'll see what I mean.

But as I opened the levelset in 4.4.2.3, I not only noticed that the MOD music started sounding... a bit different, like, a bit closer to how it sounded back in the days of R'n'D 2.0.0, but not as noisy and crispy, a fine line between absolutely crusty sound and severe smoothing to the likes of SNES's sound chip. But also the songs now loop as they should, as they do whenever you play them on actual Amiga or with a Mod tracker. And I mean that both in the now absent delay at the end of the track way and the track having the intro part that only repeats once and then plays the looping part over and over way.

What that basically meant is that, first, the disclaimer song now loops without any delay, which means the animation is now going to be delayed (which is fine, very simple to fix for a next demo, if I ever release it, gosh darn it, let alone the full levelset), plus one of the songs that I specifically cropped for the levelset (sparkling_remix.mod, the Level X-3 song) can now be properly included as the full song.

Here's the video showing off RF disclaimer differences in 4.4.1.3 and 4.4.2.0 4.4.2.3 (oops...):
Youtube video link

Previously known as Eizzoux (boooo)
ncrecc
Posts: 349
Joined: Thu Jul 12, 2018 12:59 am

Re: Proper [stereo separation] of modular tracks

Post by ncrecc »

i also notice that, in 4.4.2.3, Amiga modules (e.g. the MOD. files the game comes with) are fully hard-panned. this sounds rather jarring, e.g. the drums & bass in "apoplexy" are entirely on the right channel and the lead instrument is entirely on the left channel, where in old versions the drums & bass (module channels 2 and 3) were panned 50% to the right and the lead (module channels 1 and 4) were panned 50% to the left. i have no idea why whatever MOD player SDL relies on (modplug?) decided to change to 100% stereo separation by default (even if it is technically accurate to the Amiga hardware) as it sounds very ugly and most popular trackers & MOD players do not do this.

does SDL expose some way to reduce the stereo separation for Amiga modules to make it sound more like old versions? :|
User avatar
Holger
Site Admin
Posts: 4480
Joined: Fri Jun 18, 2004 4:13 pm
Location: Germany
Contact:

Re: Proper looping of modular tracks (is here, and I somehow missed that until today)

Post by Holger »

TheOnyxGuy wrote: Sun Jul 19, 2026 12:55 pm
Ya know what, that just hit me that technically the (relatively) recent inclusion of newer SDL libraries actually brought this idea to reality, since now modular tracks not only loop properly, they also don't have any kind of weird delay... Let me explain.

[...]

Here's the video showing off RF disclaimer differences in 4.4.1.3 and 4.4.2.0 4.4.2.3 (oops...):
Youtube video link

Great to hear that the SDL libraries update has improved this (although having other, potentially "unwelcome" changes -- I will follow up on this one next)!

What changed about MOD playing in SDL_mixer was that it used the "modplug" library for a long time, but the SDL_mixer team has switched to the "xmp" library since recent SDL_mixer versions. Here's what Google's AI says about it:
Libxmp is the leading open-source library used to interpret and render over 90 Amiga and PC module music formats (such as MOD, XM, S3M, and IT) into raw PCM audio. It is highly portable, written in C, and serves as the audio engine for several standalone players.
So apparently this is a recent, well-maintained library for modern MOD playing (which is always good, as older libraries for such "historic" stuff like MOD playing sometimes lag behind current changes in libraries like SDL -- not sure about previous MOD playing libraries used in SDL like "mikmod" and "modplug"). :)
User avatar
Holger
Site Admin
Posts: 4480
Joined: Fri Jun 18, 2004 4:13 pm
Location: Germany
Contact:

Re: Proper [stereo separation] of modular tracks

Post by Holger »

ncrecc wrote: Sun Jul 19, 2026 2:25 pm i also notice that, in 4.4.2.3, Amiga modules (e.g. the MOD. files the game comes with) are fully hard-panned. this sounds rather jarring, e.g. the drums & bass in "apoplexy" are entirely on the right channel and the lead instrument is entirely on the left channel, where in old versions the drums & bass (module channels 2 and 3) were panned 50% to the right and the lead (module channels 1 and 4) were panned 50% to the left. i have no idea why whatever MOD player SDL relies on (modplug?) decided to change to 100% stereo separation by default (even if it is technically accurate to the Amiga hardware) as it sounds very ugly and most popular trackers & MOD players do not do this.

does SDL expose some way to reduce the stereo separation for Amiga modules to make it sound more like old versions? :|
I have just taken your post and question, and pasted it into Anthropic's AI "Claude", which often results in good answers.

Here's the result, which seemed interesting and insightful to me:
Your diagnosis is right, and I can confirm the mechanism. SDL2_mixer's built-in MODPLUG backend (`music_modplug.c`) calls `ModPlug_GetSettings()` and then only overwrites a handful of fields — `mFlags`, `mChannels`, `mBits`, `mFrequency`, `mResamplingMode`, `mReverbDepth/Delay`, `mBassAmount/Range`, `mSurroundDepth/Delay`, `mLoopCount`. It never touches `mStereoSeparation`, so that field is left at libmodplug's compiled-in default of **128** on its 1–256 scale, applied straight to a MOD's Amiga-native hard-panned channel layout. That's why you hear a fully hard-panned mix now — not a deliberate "authenticity" decision, just an unset field. There's no `Mix_SetHint`/environment variable/public `Mix_*` call that exposes `mStereoSeparation`; it's baked into that one source file.

So your realistic options:

1. Post-mix crossfeed effect (no recompiling anything) — probably your best bet

SDL_mixer's `Mix_RegisterEffect()` lets you hook the *final* mixed stereo stream via `MIX_CHANNEL_POST`, which runs after music and channels are combined, on the raw interleaved `Sint16` buffer. You can blend a fraction of each channel into the other, which is functionally identical to lowering `mStereoSeparation`:

Code: Select all

void crossfeed_effect(int chan, void *stream, int len, void *udata) {
    Sint16 *buf = (Sint16 *)stream;
    int samples = len / sizeof(Sint16);
    const float mix = 0.35f; // 0 = untouched, 1 = mono; tune to taste

    for (int i = 0; i < samples; i += 2) {
        Sint16 l = buf[i], r = buf[i + 1];
        buf[i]     = (Sint16)(l * (1.0f - mix) + r * mix);
        buf[i + 1] = (Sint16)(r * (1.0f - mix) + l * mix);
    }
}

Mix_RegisterEffect(MIX_CHANNEL_POST, crossfeed_effect, NULL, NULL);
Downside: this affects *everything* going through the mixer, including sound effects, not just the MOD music. If your SFX aren't hard-panned that's usually fine, but worth checking.

2. Bypass SDL_mixer's MOD loader, drive libmodplug yourself

Link libmodplug directly, call `ModPlug_GetSettings()` / set `mStereoSeparation` to something like 32–64 (128 = "normal"/full, lower = narrower) / `ModPlug_SetSettings()`, load the file with `ModPlug_Load()`, and pull PCM via `ModPlug_Read()` into your own `SDL_AudioStream` or queue it with `SDL_QueueAudio()`. This gives you exact control and matches what you're describing as the "old" sound, since it's the same knob OpenMPT/WinAmp's ModPlug plugin exposed as "Stereo Separation." More setup work, but it's the option purpose-built for this.

3. Patch SDL2_mixer's `music_modplug.c` and rebuild

Just add `settings.mStereoSeparation = 48;` (pick your value) next to the other overrides in `MODPLUG_Open()` and rebuild SDL2_mixer against libmodplug. Same effect as option 2 but keeps you on the stock `Mix_*` API — only downside is maintaining a patched SDL2_mixer build.

4. Try the MikMod backend instead

If SDL2_mixer is built with `libmikmod` support (`music_mod.c` instead of `music_modplug.c`), MikMod's own mixer historically did *not* fully hard-pan 4-channel Amiga MODs the same way — this may be closer to the "old versions" sound you remember, since a lot of older engines that used SDL 1.2 + MikMod behaved this way. Worth a quick test build if you have both libraries available, since it costs nothing but a rebuild flag.

Given you're already doing cross-platform C/SDL2 builds, I'd lean toward option 1 first since it's a same-day fix with no dependency changes, and fall back to option 2 if you want it scoped to music only.
I added another question ...
I should add that this is related to "SDL_mixer" version 2.x in its latest version, compared to an earlier version.
... which got me some more information about that issue:
That extra detail is the key piece, and it points to a different (and more precisely fixable) root cause than what I originally described.

What actually changed: SDL_mixer switched its default MOD/tracker backend starting with 2.8.0. Before 2.8.0, the built-in codec was **libmodplug**; from 2.8.0 SDL_mixer began using libxmp instead of modplug as the default backend for MOD music, with libmodplug only available if you explicitly configure with --enable-music-mod-modplug --disable-music-mod-xmp. So "old version" vs. "latest version" in your case most likely means "modplug backend" vs. "libxmp backend," not just a modplug setting drifting.

That fits your wording about it being "technically accurate to the Amiga hardware," because libxmp is the more purist, format-accurate tracker engine of the two (it grew out of the Extended Module Player project, which has always cared about faithfully reproducing what the original hardware/tracker did) — modplug historically took more liberties for a "nicer" default sound.

What libxmp exposes for this, if you go that route: libxmp has exactly the setting you want, called stereo mix. Its own docs state "Stereo mixing: percentual left/right channel separation. Default is 70", and it's set via `xmp_set_player(ctx, XMP_PLAYER_MIX, value)` — this is the same knob the `xmp` command-line player exposes as `-P`, and other engines (Quakespasm, DarkPlaces) explicitly call this to tune Amiga-MOD playback. A value around 40–60 is what most modern players/trackers settle on to avoid the "hard left/right" Amiga sound.

The catch: from what I could inspect of SDL_mixer's XMP integration code, it doesn't call `xmp_set_player(..., XMP_PLAYER_MIX, ...)` itself, so whatever default libxmp ships with is what you get — and there's no `Mix_*` public API in SDL_mixer 2.x to reach into that setting from outside. So the situation is structurally the same as it was for modplug: **no hint/env var, no public Mix_ call, just an unexposed backend default.**

Given that, my two earlier recommendations still stand, just retargeted at libxmp:
  1. Post-mix crossfeed via `Mix_RegisterEffect(MIX_CHANNEL_POST, ...)` — still the quickest fix, doesn't care which backend SDL_mixer picked, and doesn't require rebuilding anything.
  2. Bypass SDL_mixer's MOD loader and drive libxmp yourself, calling `xmp_set_player(ctx, XMP_PLAYER_MIX, 50)` (or whatever value sounds right) right after `xmp_create_context()`/before `xmp_start_player()`, then feed the rendered PCM into your own SDL audio stream. This is now the *cleaner* option compared to the old modplug case, since libxmp's public API was clearly designed with this exact use case in mind.
If you want, I can sketch the minimal libxmp playback loop (context creation → load → start_player with the mix setting → play_frame loop feeding an `SDL_AudioStream`) so you can drop your own Amiga-style MOD player in and skip SDL_mixer's MOD path entirely.
The first suggested solution in the first answer seems like something that could be added to R'n'D relatively easily.

Patching the SDL_mixer library would be better, of course, because it would limit the changed panning to the music (which is especially relevant as R'n'D uses stereo sound effects itself), but this would (a) be a lot of work for all platforms supported by R'n'D, and would (b) result in much more work to promptly update SDL when new releases come out (which is just now easier than before, as I just switched to using the "official", pre-compiled SDL libraries for all R'n'D platforms instead of compiling most of them by myself, which always results in little trouble here and there, as some library often refuses to compile correctly for certain platforms, plus things like support for Win32 and Win64 for Windows, or Intel and Apple Silicon for the Mac, which is always a lot of work when done manually.
Post Reply