Development: Test Cases/IT

The autovibrato sweep is not reset when using portamento.

In Impulse Tracker’s software mixer, ping-pong loops are shortened by one sample. This does not happen with the GUS hardware driver, but I assume that the software drivers were more popular due to the limitations of the GUS, so OpenMPT emulates this behaviour.

This is a very interesting test case, because it actually sounds different when using Impulse Tracker’s WAV writer and Sound Blaster (and probably other drivers) output. The main difference is that the Sound Blaster driver will only consider the envelope carry flag if the New Note Action is not “Note Cut”. The WAV writer does not check for the NNA and will always apply the carry flag if it’s set. OpenMPT goes after the WAV writer, while e.g. XMPlay used the Sound Blaster behaviour for the volume and panning envelope, but the WAV writer’s behaviour for the filter envelope (because of a bug report some years ago) up until 2016. Obviously it is hard to consider which of the two behaviours is correct, so I would just say that both are. :) However, the WAV writer behaviour definitely is more useful (not being able to use Envelope Carry despite it being enabled is quite misleading), I stronlgy encourage everyone to implement this behaviour.

Envelopes that have the carry flag set cannot be “picked up” / continued after the note has been cut.

OpenMPT previously did not properly check for the correct sample slot or sample map entry in DNA and NNA checks if no instrument number was provided. This testcase should remain silent when played correctly, i.e. both the first and second note on channel 1 should sound until the end of the pattern.

In instrument mode, if an offset effect is found next to a note that maps to an invalid sample slot, the offset and illegal note should be ignored completely. Note that this behaviour is different from sample mode, where a previously playing sample will be stopped.

I think, Impulse Tracker treats instruments like an additional layer of abstraction and first replaces the note and instrument in the pattern by the sample and note assignments from the sample map table before further evaluating the pattern. That would explain why for example the empty sample map slots do nothing in this module.

If resonance is not applied, the filter envelope’s maximum value does not enable any filtering. However, it also does not disable any filtering when returning from a non-maximum point. This pretty much works like Zxx commands.

I created EnvOffLength.it without realizing that it is essentially the same bug as this one.

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 Impulse Tracker (and Fasttracker 2), 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.

If an envelope sustain loop happens to end on exactly the same tick as a note-off event occurs, the envelope is not yet released. It will be released whenever the loop end is being hit again.

Impulse Tracker resets envelopes under some more or less weird conditions. This tests some of them.

If resonant filters are rendered with integer arithmetic, they may produce scratching noises in some edge cases. You should not hear any scratches or other weird noises when playing this example.

Same as the above test case. xmp breaks this module’s output even more than older versions of OpenMPT.

After a note has been stopped in some way (for example through fade-out or note cut), tone portamento effects on the following note are ignored, i.e. there is no portamento from the stopped note to the new note.

A small test case that demonstrates that full cutoff should not enable the filter if no resonance is applied. Resonance is only ever applied if the cutoff is not full or the resonance is not zero.

A cutoff value of 0 should not be reset to full cutoff when triggering a note just because the filter envelope is enabled. This bug is probably very specific to OpenMPT, because it gets rid of some unneccessary code.

This test is just there to be sure that the filter-reset.it and filter-reset-carry.it test cases do not break NNA background channels.

As mentioned already, filtering is only ever done in IT if either cutoff is not full or if resonance is set. When a Z7F command is found next to a note and no portamento is applied, it disables the filter, however in other cases this should not happen.

I think this is also just an extenion to the previous test case, to make sure that it does not break anything else.

Unlike fine volume slides in the effect column, fine volume slides in the volume column are only ever executed on the first tick — not on multiples of the first tick if there is a pattern delay. Thus, the left and right channel of this example should always have the same volume.

Envelope carry on the filter envelope. I think this is just a general test on how envelope carry is applied. It is possible that Impulse Tracker’s MMX drivers will play this in a different way from the WAV writer.

This test uses a custom macro configuration that uses the instrument volume to control the filter cutoff. A correclty implemented MIDI Macro system should pass this test.

When using multisample instruments, even notes with no instrument number next to them can change the sample (based on the active instrument’s sample map). When switching between samples, you must not forget to update the C-5 frequency of the playing sample as well.

Set Global Volume (Vxx) should be applied on the first tick (and multiples in case of pattern delays). If played correctly, the first and second row of this module should sound more or less identical, i.e. each row should play 16 fading notes.

Out-of-range global volume commands (V81...VFF) should not change the current global volume. This test module should remain completely silent.

Global volume, channel volume and various other parameters are taken into account when interpreting the 'u' and 'v' letters in MIDI macros. However, this feature seems to be quirky, and I have decided not to support these quirks in OpenMPT, as I do not think that anyone used this feature in Impulse Tracker very much and I would want it to work in a sane way when using it to control instrument plugins in OpenMPT. The quirks are: There are also some problems with volume swing which might be fixed later.
 * Channel and global volume seem to be applied one row after they have been set. This does not seem to be the whole truth, though, as looking at pattern 0, row 9, you should hear that IT applies the M40 channel volume change on row 10.
 * Instrument / sample volume also seems to be applied one row late when using the 'u' macro, but not the 'v' macro (compare first and second pattern, row 15 - 22 each).

Another test case with an empty sample map slot which is simply ignored by Impulse Tracker.

Going one step further by also changing the sample next to that portamento.

And another similar test.

Instrument filter settings should not be applied if there is a portamento effect.

A lone instrument number should only reset sample properties to those of the corresponding sample in instrument mode. Example: C#5 01 ... <-- sample 1 C-5 .. g02 <-- sample 2 ... 01 ... <-- still sample 1, but with properties of sample 2 In the above example, no sample change happens on the second row. In the third row, sample 1 keeps playing but with the volume and panning properties of sample 2.

While Impulse Tracker cuts playing samples if it encounters an invalid sample number in sample mode, the same does not happen if we are in instrument mode.

Impulse Tracker internally uses actual frequency values, while Fasttracker 2 still uses exponentially scaled fine Amiga periods. When doing fine slides, errors from using periods instead of frequency can add up very quickly, and thus the two channel's frequency in this test will converge noticeably.

A pattern break (Cxx) on the same row as the end of a pattern loop (SBx) repeats the pattern loop x times and then jumps to the desired row. However, a position jump (Bxx) on the same row as the end of a pattern loop prevents the loop from being executed. In this test, the left and right channel of the module should produce the same click patterns.

Since the start row of a pattern loop is not reset on pattern transitions, it is possible to use a loop start past the end of the current pattern when using a pattern loop without explicit start command (SB0). In this case, playback should continue in the next pattern. In the example, this happens because the loop start is set to one row after the loop end after properly terminating the loop in pattern 0.

A test of the MIDI macro letter “n”. This letter will always send the MIDI note value of the last triggered note, note cuts and similar “notes” are not considered. This module should remain silent as both channels should receive exactly the same cutoff values.

A MIDI macro can contain more than one MIDI message. In this case, the Z90 macro sets both the filter cutoff frequency and resonance, so if only the first MIDI message is considered in this macro, both patterns will sound identical. If the macro is interpreted correctly, they will sound different.

Yet another sample map test case. Why are there so many of them? They are all fixed by the same code...

This seems to be just a fancy test of portamento from a stopped note, but in this case an empty sample slot assigned to the same instrument was played before.

A “note fade” note (any invalid note) should not do anything in sample mode. The sample should not be stopped.

A “note fade” note (any invalid note) should fade out the instrument using its fadeout value.

The sample changes on rows 4 and 20, but not on rows 8 and 24. However, the new sample′s default volume is loaded.

This is the same as noteoff2.it, but with old effects enabled. In this case, the sample should never fade out.

Any kind of Note Cut (SCx or ^^^) should stop the sample and not set its volume to 0. A subsequent volume command cannot continue the sample, but a note, even without an instrument number can do so. When played back correctly, the module should stay silent.

When "Compatible Gxx" is disabled, the key-off flag should only be removed when triggering new notes, but not when continuing a note using a portamento command (row 2, 4). However, you should keep in mind that the portamento flag is still set even if there is an offset command next to the portamento command (row 4), which would normally nullify the portamento effect (see porta-offset.it).

When "Compatible Gxx" is enabled, the key-off flag should also be removed when continuing a note using a portamento command (row 2, 4, 6). This test case was written to discover a code regression when fixing Off-Porta.it).

The panbrello offset should not only be added on the same row as the panbrello effect appears on, but also on all follow-up rows, until a panning command or a new note is encountered. This test case should remain mostly silent.

Contrary to XM, the default instrument and sample panning should only be reset when a note is encountered, not when an instrument number (without note) is encountered. The two channels of this module should be panned identically.

Delayed notes (using SDx) that are on the same row as a Row Delay effect are retriggered on every repetition. Notes without a note delay are not retriggered.

Tick delays (S6x) are added up, but when there are multiple row delays (SEx), only the first one is considered. The tricky part here is that Impulse Tracker even ignores all row delays next to a SE0 row delay. I suggest to do the following:
 * Initialize a row delay variable with 0.
 * Go through all channels. If there is a row delay on the current row and row delay is 0, set row delay = parameter + 1.
 * Calculate the complete row length: row length = (speed + tick delay) * max(1, row delay)

Rows on which a row delay (SEx) effect is placed have multiple “first ticks”, i.e. you should set your “first tick flag” on every tick that is a multiple of speed + tick delay. In this test module, the note pitch is changed multiple times per row, depending on the row and tick delay values. Note: This test case broke in r3520 and was fixed again in r4895.

A follow-up test to PortaSampleCompat.it and PortaInsNumCompat.it to verify that the behaviour described in these tests is not used when currently no note is playing. Both channels should sound identical.

Portamento with funny sample maps. Without compatible Gxx, portamento between different samples should play the new sample.

Portamento with funny sample maps, in compatible Gxx mode. With compatible Gxx, portamento between different samples should keep playing the old sample.

Fade-Porta.it already tests the general case of portamento picking up a stopped note (portamento should just be ignored in this case), 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.

Unlike Fasttracker 2, Impulse Tracker ignores the portamento command if there is an portamento command next to an offset command. Even ModPlug Tracker 1.16 gets this test right.

Impulse Tracker completely resets the tone portamento target on every new non-portamento note, i.e. a follow-up Gxx effect should not slide back to the previously triggered note.

After a note has stopped playing, a lone instrument number can restart the note. In this case, portamento and other properties should be reset.

Impulse Tracker executes the portamento when switching to instrument two on the second row when compatible Gxx is disabled.

Impulse Tracker executes the portamento and doesn't switch to the new sample on the second row when compatible Gxx is enabled.

Pan swing should not be overriden by effects such as instrument panning or panning envelopes. Previously, pan swing was overriden in OpenMPT if the instrument also had a panning envelope. In this file, pan swing should be applied to every note.

When selecting the random waveform for panbrello, the “speed” nibble indicates for how many ticks the random value will be held. This behaviour is not used for vibrato / tremolo.

A combination of the retrigger effect and instrument envelopes. Not sure how this works.

Impulse Tracker does not retrigger notes that are shorter than the duration of a tick. One might argue that this is a bug in Impulse Tracker, but OpenMPT emulates it anyway.

Changing the NNA action through the S7x command only affects the current note - The NNA action is reset on every note change, and not on every instrument change.

The S77 / S79 / S7B commands pause the instrument envelopes, they do not turn them off. S78 / S7A / S7C should resume the envelope at exactly the position where it was paused. In this test, it is again very important that the envelope position is incremented before the point is evaluated, not afterwards (see EnvLoops.it).

Contrary to InstrumentNumberChange.it, even ModPlug Tracker 1.16 passes it.

The SCx command cuts notes just like a normal note cut (^^^), it does not simply mute them. However, there is a difference when placing a lone instrument number after a note that was cut with SCx and one cut with ^^^, as it can be seen in this test case.

If there's a note delay, slide and vibrato commands in the volume column next to it should not start before the delay has finished. So if there's a fine volume slide next to a SD5 effect should start on the 6th (one-based) tick. If this was a normal volume or pitch slide and the speed was 6 ticks per row, it would never execute, as the first non-zero tick would be the seventh tick.

Sample and instrument panning override the channel surround status, i.e. surround is turned off by samples or instruments with panning enabled.

Pan swing, panbrello, panning envelopes, etc. should have no effect on surround channels.

An instrument number with no note or next to a ^^^ should always be remembered for the next instrument-less note, even if sample playback is stopped.

When a sample sustain loop, which is placed partly or completely behind a “normal” sample loop is exited (through a note-off event), and the current sample playback position is past the normal loop’s end, it is adjusted to current position - loop end + loop start.

A test focussing on finding the correct sample playback position when switching samples “on the fly” (using instrument numbers without notes next to them). The module should remain silent when being played.

Probably a very MPT-specific bug, as OpenMPT was adding the volume swing to the current volume, so even after setting the volume to 0, it was possible that you could hear the sample playing.

This module should remain completely silent, as the random variation is multiplied with the sample volume.

Of course you are not supposed to produce the same sequence of random number to fulfil this test case. I think this was just a test to explore which volume variables are actually affected by volume swing, and how it is applied.

Same as above.

Same as above.

This test detects whether out-of-range note delays are handled properly. If the SDx parameter is greater than the row length (including tick delay, but excluding row delay), any note and volume effects next to it are supposed to ignored (interestingly, the instrument number is saved, but not instantly used). The first two rows test tick delay behaviour (OpenMPT passes this test since revision 1306), the third row tests row delay behaviour.

A test for checking if tremolo tables are implemented correctly.

A test for checking if vibrato tables are implemented correctly when old FX are disabled.

Vibrato in the volume column should be applied separately from vibrato in the effect column. The two commands will add up when occuring at the same time. However, since there is only one vibrato effect memory for both commands, the volume column’s vibrato parameter has precedence if it is not 0.

A test for checking if vibrato tables are implemented correctly when old FX are enabled. Vibrato is played backwards in this mode!

Auto-vibrato should not be processed at all if the rate (speed) is 0. This is relevant for vibrato waveforms that do not start at zero, i.e. all except sine.

Volume column commands a, b, c and d (volume slide) share one effect memory, but it should not be shared with Dxy in the effect column. Furthermore, there is no unified effect memory across different kinds of volume column effects (that's how OpenMPT used to handle it up to revision 1544).

Similar to flt-env-carry.it. This test case temporarily broke in revision 1481 and was fixed in revision 1781.

This test demonstrates how Zxx macros are parsed in Impulse Tracker: I am not quite sure why the first row with notes on it emits a filter value of 1, and I am not even completely sure why OpenMPT gets this right. If interpreted correctly, the module should stay silent (minus slight filter artefacts, of course - but they should be nearly inaudible).
 * Macros are evaluated only on the first tick.
 * They appear to be parsed after note / instrument information has been read from the pattern, but before any other effects (excluding "Set Volume").
 * 'u' and 'v' macros seem to emit at least a value of 1, not 0 - v00 corresponds to Z01.

A nice broken pattern loop. The voice should say “1 4 2 1 4 2” and then repeat forever “3 4 2”. Theoretically, this would even work in ModPlug Tracker 1.16, if it did not reset the Cxx target on song loop.

Note Off + instrument / portamento combinations.

Impulse Tracker supports three MIDI macro characters which are not documented in MIDI.TXT:
 * h: Host channel, i.e. the pattern channel in which the Zxx command is encountered (0-based).
 * o</tt>: The last used sample offset value. The high offset (SAx) is not taken into account. Note that offsets above 80h are not clamped, i.e. they generate MIDI command bytes (e.g. O90 would cause a note-on command to be emitted).
 * m</tt>: This command was most likely supposed to send the loop mode (off, on, ping-pong), but it actually sends the current loop direction (forward = 0, backward = 1).

In addition, the MIDI messages FA (start song), FC (stop song) and FF (reset) reset the resonant filter parameters (cutoff = 127, resonance = 0), but do not immediately update the filter coefficients.