Development: Test Cases/XM

Another thing to keep in mind is that Fasttracker 2 does not reset various channel memory variables when restarting a module or loading a new one. Thus, when comparing against the output of Fasttracker 2, you should only ever consider the first play of the first module loaded into Fasttracker 2. Quit and restart Fasttracker 2 between tests.

If a tone portamento effect is encountered, the instrument number next to it is always interpreted as the instrument number of the currently playing instrument. This test shows how the instrument envelope is reset when such an event is encountered.

Two tests in one: An offset effect that points beyond the sample end should stop playback on this channel. The note must not be picked up by further portamento effects.

Arpeggio behavior is very weird with more than 16 ticks per row. This comes from the fact that Fasttracker 2 uses a LUT for computing the arpeggio note (instead of doing something like  or similar). The LUT only has 16 entries, so when there are more than 16 ticks, it reads beyond array boundaries. The vibrato table happens to be stored right after arpeggio table. The tables look like this in memory:

All values except for the first in the vibrato table are greater than 1, so they trigger the third arpeggio note. Keep in mind that Fasttracker 2 counts downwards, so the table has to be read from back to front, i.e. at 16 ticks per row, the 16th entry in the LUT is the first to be read. This is also the reason why Arpeggio is played “backwards” in Fasttracker 2.

Arpeggio parameters are clamped differently than the base note. The upper limit is XM note 96 with a finetune of ±0.

Like in some other trackers (e.g. Impulse Tracker), arpeggio notes are supposed to be relative to the current note frequency, i.e. the arpeggio should still sound as intended after executing a portamento. However, this is not quite as simple as in Impulse Tracker, since the base note is first rounded (up or down) to the neares semitone and then the arpeggiated note is added.

A fun test for rogue note delays that outputs a nice drum beat if played correctly. Combinations of note delays with and without instrument number are tested. The following tests are more “testable”.

Rogue note delay test. It seems that internally, Fasttracker 2 always acts like the last played note is next to a note delay (EDx with x > 0) if there is no note. Doing exactly this is probably the easiest way to pass this test. This also explains Fasttracker 2’s behaviour if there is an instrument number next to such a rogue note delay, which is shown in this test. Both channels should play exactly the same combination of snare and bass sounds.

Just when you thought that you have got it right, you step into the next pitfall. Here you can see that the rogue note delay behaviour is only found with EDx effects with x > 0. An ED0 effect should just be ignored and not retrigger any notes.

Naturally, Fasttracker 2 ignores notes next to an out-of-range note delay. However, to check whether the delay is out of range, it is simply compared against the current song speed, not taking any pattern delays into account. No notes should be triggered in this test case, even though the second row is technically longer than six ticks.

Even more idiosyncrasies with the EDx command, this time in combination with note offs.

The E90 effect does not use any effect memory. Instead, it retriggers the note on the first tick.

In this test, all possible combinations of the envelope sustain point and envelope loops are tested, and you can see their behaviour on note-off. If the sustain point is at the loop end and the sustain loop has been released, don't loop anymore. Probably the most important thing for this test is that in Fasttracker 2 (and Impulse Tracker), envelope position is incremented before the point is evaluated, not afterwards, so when no ticks have been processed yet, the envelope position should be invalid. This test was passed from revision 1213 to 1512, however in favour of fixing EnvOff.xm, the old code was restored, so now the last test case in this module no longer works correctly.

Fixing EnvLoops.xm made OpenMPT cut off envelopes one tick to early in some cases, so modules sounded kind of "dry".

Note delays retrigger envelopes.

A test for the E5x finetune command. Behaviour is very different from the MOD format - first off, E50 is the lowest finetuning and E5F is the highest. E5x is only applied if there is a real note next to the command (this simplifies a few things).

The lower three bits of the sample finetune should be ignored to achieve the same precision as Fasttracker 2.

EAx and EBx memory should not be shared. If effect memory is applied correctly, the module should stay silent.

Here is a really mind-boggling test case.
 * Fasttracker 2 limits the period when sliding up (the period limits appears to be 1...31999 - fine periods, not Amiga periods).
 * It does not limit the period when sliding down. It slides to infinity, or more precisely, happily wraps around at 65536 (it is a 16-Bit value).
 * Periods are apparently upside-down in Fasttracker 2 (so they follow the same direction as frequency). This leads to some funny wraparound behaviour once the frequency goes so low that the shift operator in Fasttracker′s period to frequency LUT conversion becomes negative.

Contrary to most other trackers, Fasttracker 2 uses the square root pan law (not to be confused with the sin/cos equal power pan law). This is how it works: Note that there is no such thing as “100% right panning”, as the LUT will never return 0 for the left channel.
 * Create a LUT with 257 entries:
 * Compute channel panning, range is 0...255
 * Left channel volume is
 * Right channel volume is

In this context, it is also worth noting that volume-column panning is simply multiplied by 16, i.e.  is equivalent to , not.

Samples 2 and 3 contain the output of FT2 (left and right channel respectively), so you can use these to compare your code against.

Enabling glissando should round (up or down) the result of a tone portamento to the next semitone. Normal portamento up and down effects are not affected by this.

Since global volume appears to be applied during pattern parsing time, global volume changes are not applied to channels that are left of the global volume command until the second tick of the row. Effectively this means that every note on a row could have a different global volume. Since global volume is applied on the complete mix buffer rather than individual notes, this behaviour cannot be fixed in OpenMPT for now.

Key off at tick 0 (K00) is very dodgy command. If there is a note next to it, the note is ignored. If there is a volume column command or instrument next to it and the current instrument has no volume envelope, the note is faded out instead of being cut.

Apparently, any note number in a pattern causes instruments to recall their original sample settings (volume, panning, ...) - no matter if there's a Note Off next to it or whatever. In the first pattern, the panning of the both channels should always be opposed.

More K00 fun! You should ignore any note or instrument data next to a K00 effect. Probably the best thing to do is to wipe them from your internal channel memory in such a situation.

Fasttracker 2 does weird things if the calculated note (pattern note + sample transpose) is lower than the lowest possible note. Note the high DC offset on the first two rows of Fasttracker 2’s output, it seems like it is playing the first sample “very” slowly.

I think one of the first things Fasttracker 2 does when parsing a pattern cell is calculating the “real” note (i.e. pattern note + sample transpose), and if this “real” note falls out of its note range, it is ignored completely (wiped from its internal channel memory). The instrument number next it, however, is not affected and remains in the memory.

This test is an addendum to the previous test (note-limit.xm) because I forgot that you first have to check if there is an instrument change happening before calculating the “real” note. I always took the previous transpose value when doing this check, so when switching from one instrument (with a high transpose value) to another one, it was possible that valid notes would get rejected.

I think the essence of this test was that a note-off note does not stop the sample, it just mutes it...

Various combinations of note-off notes and other things. One of my earliest tests...

When an instrument has no volume envelope, a note-off instantly sets the instrument volume to 0. If the note-off is not delayed (i.e. no EDx effect next to it), the fade-out flag is set in addition. The left and right channel of this module should sound identical.

A lone instrument number after a note-off should reset the note-off status of the instrument. The left and right channel of this module should sound identical.

If an instrument has no volume envelope, a note-off command should cut the sample completely - unless there is a volume command next it. This applies to both volume commands (volume and effect column).

Note Delays combined with anything else are a lot of fun! Note Off combined with a Note Delay will cause envelopes to retrigger! And that is actually all it does if there is an envelope. No fade out, no nothing.

Test to verify the exact behaviour for out-of-range offset commands. Only the first sample should be played, because it is the only sample that is longer than 256 samples.

All notes in this test should be panned hard right.

A more thorough check than PanMemory.xm. Both channels should be panned identically and the module should thus stay silent.

Another chapter of weird FT2 bugs: Note-Off + Note Delay + Volume Column Panning = Panning effect is ignored.

As the panning slide commands in the volume column don't have memory, they should also not interfere with the effect memory of the Pxy command.

The panning slide commands in the volume column are not supposed to have an effect memory. So it is all natural that the pan slide right command with parameter 0 does nothing. However, the pan slide left command resets the panning to 0 (hard left) on all ticks but the first when using 0 as parameter. The first tick of this test should be panned centre, the following ticks of the first row should be panned hard left, the complete second row should be panned centre. The first tick of the third row should be panned centre, the following ticks should be panned to 5 (of 64) because the volume column is evaluated first (so the panning is first set to 0 on every tick, then 5 is added), and on the last row the first tick should be centre and then 5 should be added on every following tick.

If the sustain point of the panning envelope is reached before key-off, it is never released. Probably the most tricky part is that this does not applied if the sustain is just about to be reached on the same tick as a key-off.

Header size fields in XM files should be respected in all cases, even if they are too small or too big. In this case, the pattern header is not the usual 9 bytes, and the pattern header size indicates this. You have to skip as many bytes as this field says before starting to read the pattern.

This is a nice test for E6x + Dxx behaviour. First make sure that E6x is played correctly by your player. A position jump should not clear the pattern loop memory (just like in Impulse Tracker).

A simple test for infinite pattern loop behaviour. Even ModPlug Tracker 1.16 passes this.

This is similar to PatLoop-Break.xm.

I am not sure if OpenMPT plays this correctly, because FT2 seems to play this differently every time. However, the core pattern loop logic in OpenMPT should be correct.

This test fails because OpenMPT will ignore the invalid E60 restart position. After having played pattern 0, it should continue on pattern 1, row 3, but such a row does not exist so it simply continues on the first row and a computer voice says “fail”. Fasttracker 2 jumps to this non-existent row and then returns to the first pattern, saying “success”.

This is similar to PatLoop-Break.xm. You should hear a bit of quiet noise and then finally a computer voice saying "Success". If it says "Fail", something is wrong with pattern loop and pattern break behaviour.

This is similar to PatLoop-Break.xm. The voice should say "1 4 2" and then repeat "3 4 2" forever.

Just like in IT and S3M, a note delay next to a row delay is retriggered on every row repetition.

If there are multiple pattern delays (EEx), only the one on the rightmost channel is considered (even if the EEx parameter is 0). Even ModPlug Tracker 1.16 passes this. The second pattern is not very important, it only tests the command X ModPlug Tracker extension, which adds fine pattern delays (like in the IT format) to XM files.

Only the very first tick of a row should be considered as the “first tick”, even if the row is repeated multiple times using the pattern delay (EEx) command (i.e. multiples of the song speed should not be considered as the first tick). This is shown in this test by using the extra-fine portamento commands, which are only executed on the first tick.

This seems to be related to EnvOff.xm. Sound output should never go completely silent between the notes.

I wish I didn't know about this test case, because it's absolutely mind-boggling.
 * If there is a portamento next to a note delay, it is executed (as per VolColDelay.xm: not on the first tick and not on the delayed tick), but its parameter is not updated (old parameter is used).
 * As the portamento is not applied on the delayed tick, the note next to the delay effect is set, but the portamento target stays the same as before.

If there is a portamento command next to a note delay, the portamento command is ignored completely. See PortaDelay.xm.

3xx-no-old-samp.xm already tests the general case of portamento picking up a stopped note (the new note should not be played), but there is an edge case when the note just stopped on the previous tick. In this case, OpenMPT did previously behave differently and still execute the portamento effect.

1xx, 2xx, E1x, E2x, X1x and X2x memory should not be shared. Both channels should sound identical if effect memory is applied correctly.

If there is a portamento command next to an offset command, the offset command is ignored completely. In particular, the offset parameter is not memorized.

An instrument number should not reset the current portamento target. The portamento target is valid until a new target is specified by combining a note and a portamento effect.

Fasttracker 2’s retrigger commands are especially dodgy. For example, the first tick is skipped if a volume command is next to the retrigger command, and the retrigger count is reset to 1 if there is an instrument or note next to the command. Not sure when the speed 6 patterns were fixed, but the speed 4 issue was resolved in revision 1923.

A note-off with an instrument number starts to fade out instruments. An instrument number next to a retrigger effect resets the fade-out. Combine the two on the same row, and the latter is no longer true: Note-off with instrument number and retrigger keeps fading out.

Another dedicated test for tick 0 retrig checks: All combinations of instrument, note, note-off, etc...

Fasttracker 2 seems to switch the sample on row 13, but the instrument settings (including the fade-out settings of the previous instrument) are kept.

When using the Lxx effect, Fasttracker 2 only sets the panning envelope position if the volume envelope’s sustain flag is set.

Tone portamento is actually the only volume column command with an effect memory (as it is shared with the other effect column). Another nice bug demonstrated in this module is the combination of both portamento commands (Mx and 3xx) in the same cell: The 3xx parameter is ignored completely, and the Mx parameter is doubled, i.e. M2 3FF is the same as M4 000.

Due to sloppy copy&paste, the tremolo "ramp down" waveform is influenced by the vibrato position: If the vibrato position has passed the first half of the LFO cycle, the ramp down waveform is inverted.

In particular the “ramp down” is difficult to get right, which is tested in detail in TremoloVibrato.xm

The tremor counter is not updated on the first tick, and the counter is only ever reset after a phase switch (from on to off or vice versa), so the best technique to implement tremor is to count down to zero and memorize the curren phase (on / off), and when you reach zero, switch to the other phase by reading the current tremor parameter. Keep in mind that T00 recalls the tremor effect memory, but the phase length is always incremented by one, i.e. T12 means “on for two ticks, off for three”.

Instrument numbers reset tremor count.

Even if a tremor effect muted the sample on a previous row, volume commands should be able to override this effect. Instrument numbers should reset the tremor effect as well. Currently, OpenMPT only supports the latter.

Volume column vibrato should not reset the note frequency, while effect column vibrato does it (like any other tracker).

If there is a vibrato depth command in the volume column and some kind of vibrato command in the effect column (4xx / 6xx), the vibrato waveform is advanced at double speed. The effect effect column’s parameter has precedence if it is not 0.

Fasttracker 2 tries to be inconsistent where possible, so you have to duplicate a lot of code or add conditions to behave exactly like Fasttracker. Yeah! Okay, seriously: Generally the vibrato and tremolo tables are identical to those that ProTracker uses, but the vibrato’s “ramp down” table is upside down. You will have to negate its sign for it to work as intended.

If there's a note delay, slide commands in the volume column are not executed on the first tick and, if there's an instrument number next to it, also not on the delayed tick. This effectively breaks fine volume slides.