-
Notifications
You must be signed in to change notification settings - Fork 23
Description
The .MOD (and .XM, .MED, and whatever else uses it...) retrigger effect currently does not initialize the retrigger timer, meaning it will always retrigger on the second tick of a given row.
Test file: in Protracker 3.15, libxmp, OpenMPT, this plays one snare hit, then three evenly spaced snare hits. In MikMod, this plays one snare hit, then four unevenly spaced snare hits. Speed=24, uses E98.
The only relevant changelog entry I could find re: this was for libmikmod-3.1.10:
- ProTracker effect E9 (Retrig) was not played correctly.
But weirdly, the only change to this effect between libmikmod-3.1.9 and libmikmod-3.1.10 is to prevent it from triggering on tick 0:
libmikmod-3.1.9:
case 0x9: /* retrig note */
/* only retrigger if data nibble > 0 */
if (nib) {
if (!a->retrig) {
/* when retrig counter reaches 0, reset counter and restart
the sample */
if (a->period) a->kick=KICK_NOTE;
a->retrig=nib;
}
a->retrig--; /* countdown */
}
break;libmikmod-3.1.10:
case 0x9: /* retrig note */
/* do not retrigger on tick 0, until we are emulating FT2 and effect
data is zero */
if (!tick && !((flags & UF_FT2QUIRKS) && (!nib)))
break;
/* only retrigger if data nibble > 0, or if tick 0 (FT2 compat) */
if (nib || !tick) {
if (!a->retrig) {
/* when retrig counter reaches 0, reset counter and restart
the sample */
if (a->main.period) a->main.kick=KICK_NOTE;
a->retrig=nib;
}
a->retrig--; /* countdown */
}
break;The worst part of this is that I loaded a test .MOD into Protracker 3.15 and it does retrigger on tick 0.
Test file: this plays one snare, then plays a single snare note with an E9A effect on the note line and an E9A effect on the following line. In Protracker, the two lines with E9A both play on ticks 0, 10, and 20. libxmp, MikMod, OpenMPT, and MPT 1.16 all do something else(!).
The first .MOD is trivial to fix (and I have a working patch) but I suspect finding the "correct" behavior for the second .MOD could be a mess, which is why this is an issue and not a PR yet.
diff --git a/libmikmod/playercode/mplayer.c b/libmikmod/playercode/mplayer.c
index e346ceb..4029eca 100644
--- a/libmikmod/playercode/mplayer.c
+++ b/libmikmod/playercode/mplayer.c
@@ -987,10 +987,13 @@ static void DoEEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod,
}
break;
case 0x9: /* retrig note */
- /* do not retrigger on tick 0, until we are emulating FT2 and effect
+ /* do not retrigger on tick 0, unless we are emulating FT2 and effect
data is zero */
- if (!tick && !((flags & UF_FT2QUIRKS) && (!nib)))
- break;
+ if (!tick) {
+ if (!(flags & UF_FT2QUIRKS) && (!nib))
+ break;
+ a->retrig = 0;
+ }
/* only retrigger if data nibble > 0, or if tick 0 (FT2 compat) */
if (nib || !tick) {
if (!a->retrig) {edit: I should note that the 3.1.9 behavior is also wrong because it would result in the retrigger timer carrying over between lines, which it shouldn't. To get the behavior of this patch in line with the Protracker 3.15 behavior, the change would probably be to initialize a->retrig to 0 instead of nib. The FT2 quirks check is correct; E90 does retrigger exactly once in Fasttracker 2 but does not retrigger at all in Protracker.
edit 2: initializing a->retrig to 0 was actually required anyway so I updated the patch. It does make the playback for the second test identical to Protracker.
edit 3: in FT2 the E9As don't play the first note unless there's a note on the line. :(
Updated patch to take that into account:
diff --git a/libmikmod/playercode/mplayer.c b/libmikmod/playercode/mplayer.c
index e346ceb..6f4e812 100644
--- a/libmikmod/playercode/mplayer.c
+++ b/libmikmod/playercode/mplayer.c
@@ -987,10 +987,16 @@ static void DoEEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod,
}
break;
case 0x9: /* retrig note */
- /* do not retrigger on tick 0, until we are emulating FT2 and effect
- data is zero */
- if (!tick && !((flags & UF_FT2QUIRKS) && (!nib)))
- break;
+ /* Protracker: retriggers on tick 0 first; does nothing when nib=0.
+ Fasttracker 2: retriggers on tick nib first, including nib=0. */
+ if (!tick) {
+ if (flags & UF_FT2QUIRKS)
+ a->retrig=nib;
+ else if (nib)
+ a->retrig=0;
+ else
+ break;
+ }
/* only retrigger if data nibble > 0, or if tick 0 (FT2 compat) */
if (nib || !tick) {
if (!a->retrig) {