The world map is based on NASA's blue marble images.
The picture of the Arts Building is from the University of Saskatchewan Archives.
back |
The MOD format was introduced with the first tracker program, the Ultimate Soundtracker, released in 1987. The Ultimate Soundtracker itself did not enjoy a big commercial success. Its lasting impact comes from the fact that its code formed the foundation on which most of the successful tackers on the Amiga were built.
One of the stated objectives of Karsten Obarski, the creator of the Soundtracker, was to help improve the quality of the music in Amiga games by providing developers with a tool to compose scores that require only minimal disk and memory space, do not tax the CPU too heavily during playback and are easy to use in projects. The Soundtracker, as well as most other Amiga trackers that appeared later, came with the assembly source code of a playback routine that could be integrated into programs.
Likely because of the usefulness of the module concept for making demos, various groups soon “developed” their own trackers and released them for free. It is no coincidence that most of the trackers on the Amiga look virtually identical. They can be considered forks of the same codebase, as much of their development took the form of disassembling other people’s programs. One consequence of this is that, even though some of tracker properties and behaviours are rather odd and unexpected, they can still be considered “standard”, as they are consistent across most of the Amiga trackers simply because the implementations were so similar.
NoiseTracker from 1989 was the first widely used tracker, but it was ProTacker, initially released in the following year, that added lots of improvements and innovations to the concept of the tracker and turned the MOD format into what it is today.
Many of the features and characteristics of MOD files were originally tied to properties of the underlying hardware. Early module players used video timing and processed exactly one tick per frame, which is the reason why the default tempo of modules is still 50 ticks per second. The format was limited to four channels that correspond to the four hardware audio channels on the Amiga, and even the volume and period values used in the MOD format directly match the values that needed to be written to hardware registers that control audio output. Most of the constraints have been relaxed in one way or another, but lots of traces of the original system the format was developed for remain.
In a way, Obarski's original objective has been achieved thanks to the popularity of the trackers that ripped off the original tracker. Lots of games on the Amiga and PC used module music until at least the mid-1990s. One well-known example is Star Control II, whose developers famously found a very cost-effective way of sourcing high-quality music for their game. They ran a “contest” among module composers, in which the winners would get a little bit of prize money and the honour of having their music included in a video game, [GDC].
MOD File | ||
size (bytes) | content | description |
20 | title | song title, padded with zeros |
15⋅30 or 31⋅30 | sampleHeader[] | 15 or 31 Sample Header fields |
1 | numSongPos | number of entries in the pattern table |
1 | restartPos | may contain the restart position of the song; see remarks below |
128 | patternTable[] | list of patterns to be played |
0 or 4 | signature | an identifiable 4-character ASCII signature (31 sample file) or nothing (15 sample file); this field also indicates the number of channels and other aspect of the file format version |
n⋅c⋅256 | patternData[] | 256⋅c bytes of each of the n patterns stored; the number of patterns is derived as the highest pattern index stored in the pattern table plus one; c is the number of channels, which is 4 unless otherwise indicated in the signature field |
? | sampleData[] | or each sample (15 or 31 of them) 2⋅length bytes as given in the corresponding sample header |
Sample Header | ||
size (bytes) | content | description |
20 | sampleTitle | sample title, padded with zeros; typically these strings are not actually sample descriptions, but form a message when read in order |
2 (BE) | length | sample length, in words |
1 | finetune | bits 0-3 are a signed finetune value |
1 | volume | 0..64 |
2 (BE) | repeatStart | sample loop start, in words |
2 (BE) | repeatLen | sample loop length, in words |
standard 31-sample file: | “M.K.”, “M&K!” |
more than 64 patterns: | “M!K!” |
giving the number of channels: | “xCHN”, “xxCH”, “xxCN”, “TDZx”, “FLTx”; x or xx is an ASCII decimal number giving the number of channels, e.g. “12CN” or “FLT8”; “CD81”, “OCTA” and “OKTA” indicate 8-channel mods. “FLT8” is a bit of a special case that needs to be handled differently from the other file versions as explained below. |
If no signature field is present, try reading the file as a 15-instrument mod.
Any number of channels that's not \(c=4\) is a post-ProTracker format extension.
patternTable
is the order list of the MOD file. It lists all patterns indices in the order in which they are to be played. Legal values are 0 to 63, unless the signature
“M!K!” indicates that there may be more than 64 patterns. Scan all 128 entries to find the highest referenced pattern. This is the last pattern saved in the file, i.e. in a file with \(n\) patterns, this index would be \(n-1\).
Only the first numSongPos
entries of the patternTable
are actually played. Song positions can range from 1 to 128.
restartPos
may indicate the position in the pattern table to jump to after going though all song positions once. [MMW] provides some discussion, but the gist of it is that values of $78 and $7F should be ignored, while other values below numSongPos
are likely valid restart positions. If no restart position is provided, restart from the beginning. This is largely a NoiseTracker feature that’s not supported by ProTracker.
byte | 0 | 1 | 2 | 3 | ||||
nibble | H | L | H | L | H | L | H | L |
data | period | eff | effParam | |||||
sample |
Sample numbers run from 1 to 15 or 31. A sample number of 0 in a cell means no new sample is provided, so the old sample information remains unchanged. Similarly, a period value of zero indicates that there is no new note to be played. There is no corresponding null-value for the effect. The convention is to use effect 0 (arpeggio) with a zero parameter, which happens to do nothing of consequence anyway, to indicate “no effect”.
Within each pattern, the cell data are organized by row and channel:
[row 0, ch 1],[row 0, ch 2] ... [row 0, ch c],[row 1, ch 1], ...
pat
in the pattern table means the 8 channels in patterns pat
and pat+1
are to be played. The best approach to dealing with this format version may be to divide all pattern table entries by two and to merge pairs of adjacent 4-channel patterns into a single 8-channel pattern upon loading.
The sample data for each sample are stored consecutively after the pattern data as signed bytes. Upon loading, the first two bytes of each sample are overwritten with zeros (ProTracker 1.0 would zero out the first 4 bytes, starting with version 1.1 only 2 bytes).
The sample header also includes information on the default volume of the sample and a finetune value.
A player processes patterns in the order in which they are listed in the first numSongPos
entries of the pattern table. After that, it starts over either at restartPos if provided or at the beginning. Each pattern is processed row by row. The initial speed settings are 6 ticks per row and a tempo of 125, meaning one tick takes \(\frac{2.5\text{ s}}{125}=0.02\text{ s}\).
On the first tick of a row, for each channel, the corresponding cell is evaluated. If a sample is provided, it is set up for the channel; if a period is included, a new note gets triggered; if there is a valid effect in the cell, it is processed as well. For the remaining ticks of the row, ongoing effects continue to be processed.
Most of these behaviours can be modified by effects, as discussed below.
While rows and ticks are processed on a fixed timing schedule, the sample that’s active on each channel effectively plays in the background at the current volume and the rate that corresponds to the current period. Non-looping samples just play through once, whereas looping samples continue playing until they are replaced by a different sample.
If a non-standard number of channels are present in the MOD file, their mapping to the stereo output channels should follow the same pattern as for 4 channel MODs, e.g. LRRL-LRRL for 8 channels.
When starting a new row, on the first tick, the new instrument is latched first, then the note is triggered, and then most effects are processed. Some effects are dealt with earlier, which will be discussed below.
I will therefore discuss in some detail how the NoiseTracker/ProTracker family of trackers deals with sample and period information separately instead of just explaining what happens when a note gets triggered. OpenMPT recreates these behaviours faithfully for MOD, thus acknowledging them as the proper standard.
To understand how instruments are handled, it is important to remember that from the perspective of the player, they are not much more than a pointer to the sample data, combined with some length and loop information.
Whenever a new sample is provided in a cell, it is “latched” (see [MMW]), meaning all the relevant information for playing it is copied to a channel-specific data structure for future use. This information includes a pointer to the start of the sample, the default starting index of zero, and the loop start and length, as well as the other two variables defined in the sample header, the finetune value and the default volume. The new volume is applied to the channel immediately, but the other changes may not have an instantaneous effect on what’s playing in the channel unless the sample is retriggered as well.
On the Amiga, the audio hardware (the Paula chip) used DMA to independently play a whole sample without any further CPU support. When a new sample is latched, the old one keeps on playing until it the new sample is triggered or the old sample reaches the end of its current run. If the previous sample reaches the its end (or the end of its loop), the channel uses the information in the latched sample to decide what to do: either start playing the loop of the newly latched sample if it has one or play nothing. If the previous non-looping sample had already reached its end point and stopped playing, the loop of the newly latched sample will start playing immediately.
In ProTracker, the latched instrument values are initialized such that triggering a note before specifying an instrument in a channel does not play anything. However, there are some modules that do trigger notes before setting a sample, suggesting that there are trackers out there that handle this situation differently.
Samples should be scaled by the current volume and played at a rate \(\varphi\) that’s inversely related to the current period \(p\), \(\varphi=\frac{\Phi}{p}\), where \(\Phi\) is the relevant system clock (this happened to be the chroma clock, timing colour output on the screen), \(\Phi\)=3,546,895 Hz on PAL systems and \(\Phi\)=3,579,545 Hz on NTSC, see [AHR], pages 140-141. The period found in MOD files is really just a counter value used by the Amiga audio hardware to divide the clock signal. Depending on which clock constant is used, the pitch of samples differs by just under 1% or 0.15 of a semitone. Originally, all trackers were written for PAL systems and the MOD scene was very much concentrated in PAL territory. However, with the move from the Amiga to the PC, new trackers generally adopted the NTSC parameters to determine playback rates.
The second part is straightforward, the only slight complication to consider is that an effect 9xx in the same row may require an update to the starting index before triggering the sample, as explained below.
The first part is not difficult either. In contrast to other module formats that require the calculation of a period value based on a note index and instrument properties, MOD files directly provide the period corresponding the note that is supposed to be played. This period value may have to be adjusted if the currently latched instrument has a non-zero finetune value.
Finetune values range from -8 to 7 and are supposed adjust the pitch of a sample by 8ths of a semitone. For example, a note of C2 would normally, for a finetune value of 0, be played at a period of 428. For a finetune value of -5 ($B), the period should be adjusted by a factor of \(2^{\frac{1}{12}\frac{5}{8}}\approx 1.03676\) to 444, resulting in a pitch that’s about 3.7% or 5/8 of a semitone lower. (Actual finetune period values were tabulated and not always exact, as explained below.)
An effect E5x on the same row may require updating the finetune value before it is used to calculate the period.
On the Amiga trackers, playable notes were limited to a range of three octaves, C1 to B3, corresponding to default period values of 856 to 113. The upper limit of B3 was a result of hardware limitations. In fact, the period 113 was already pushing the limits, being below the minimum of 123 to 124 mandated by the Amiga Hardware Reference Manual to ensure a reliable DMA transfer of audio data (see [AHR], p. 141). For modules made on or for the Amiga, the period values should always be clamped to fall into the allowable range. This is not so much an issue for triggering notes as it is for effects involving frequency slides such as 1xx and 2xx. These constraints can be relaxed for modules that were created post-ProTracker, for example to include octaves 1 and 4 as suggested in [MOD]. Modules that have notes outside of the Amiga period limits are not uncommon. However, some constraints still have to be applied, as periods approaching zero are always problematic.
In ProTracker and other trackers at the time, the periods associated with notes were tabulated (see [MOD] for such a table). In fact, there were tables for all possible finetune values. When writing just a player rather than a tracker, tabulation of notes is not helpful per se, as period values are supplied in the MOD file anyway (finetune-deviations from standard note periods are not, though).
The following table summarizes the available effects. In some cases, a more detailed description or some background information is provided below (column "D" indicates additional general information, column "T" means that there are ProTracker-specific details). The table also indicates whether an effect has “memory” in the sense that an effect parameter of zero means “use the last non-zero parameter for this effect” (an entry "E" in column "M" signifies effect memory). A similar table can be found in [MPT].
The two important note-specific variables that are affected by many effects are the period and the volume. It is useful to think of their current values as having two components, a persistent one that is initially set when a note is triggered or an instrument is latched and that retains its value until it is deliberately changed, and a temporary one that is reset to zero at the beginning of each tick. The effective current volume is then v_eff=v+v_temp
and the effective current period is p_eff=p+p_temp
. These effective values determined for each tick are then used for the playback of the sample during the duration of the tick, with the understanding that both the permanent and effective values are always clamped to stay within their allowable bounds. I will use the symbol T
for the total number of ticks in a row and tick=0...T-1
for the current tick. Normally, notes are triggered on the first tick of the row, tick=0
.
As mentioned, some effects have a “parameter memory”. If used with a non-zero parameter, they copy that parameter value to their memory and then only used the memorized parameter to execute the effect. I will use notation like eff4_x
to refer to the memory value of an effect parameter. The memory value should be updated when starting to work on a cell before any actual effects are processed. The parameter memory as well as other effect-specific states are separate for each channel unless explicitly noted otherwise.
effect | name | description | D | T | M |
0xy | Arpeggio | on every tick, cycle between note, note+x semitones, note+y semitonesif (tick%3==1) p_temp=p*(2^(x/12)-1) | D | T | - |
1xx | Portamento Up | increase pitch by xx on every tick except the firstif (tick>0) p=p-xx | - | - | - |
2xx | Portamento Down | decrease pitch by xx on every tick except the firstif (tick>0) p=p+xx | - | - | - |
3xx | Tone Portamento | set portamento target if note is provided and slide towards the target by xx on every tick except the first; glissando (effect E3x) affects the type of the slide | D | T | E |
4xy | Vibrato | perform vibrato with speed x and depth y using the waveform set by effect E4x on every tick except the first | D | T | E |
5xy | Volume Slide with Tone Portamento | This executes two effects (Axy and 3xx) at the same time: equivalent to Axy with 300 | - | - | - |
6xy | Volume Slide with Vibrato | equivalent to Axy with 400 | - | - | - |
7xx | Tremolo | perform tremolo with speed x and depth y using the waveform set by effect E7x on every tick except the first | D | T | E |
8xy | Set Panning | set panning value for the channel to xx (0=left, $FF=right) this is not a ProTracker effect | D | - | - |
9xx | Set Sample Offset | set the starting offset of the current sample to 256⋅xx | D | T | E |
Axy | Volume Slide | increase the volume by x or decrease it by y on every tick except the firstif (tick>0) && (y==0) v=v+x | - | T | - |
Bxx | Position Jump | after processing this row, set the song position to xx and continue with row 0 of the corresponding pattern | D | T | - |
Cxx | Set Volume | set the current volume to xxv=min{xx,64} | - | - | - |
Dxy | Pattern Break | after processing this row, continue playing with row 10⋅x+y of the next song position; note that xy is decimal | D | T | - |
Fxx | Set Speed | set ticks per row (for xx<$20) or tempo (for xx>=$20) to the indicated parameter value; xx=0 is invalid | D | T | - |
NoiseTracker supported effect Exx, where xx=0 would turn on an audio filter, xx=1 would turn it off. With only one bit of the parameter value being used, ProTracker repurposed this this effect by letting the upper nibble of the effect parameter select a sub-effect, while making the lower nibble its effect parameter. This added 15 effects with 4-bit parameters to the MOD format. | |||||
effect | name | description | D | T | M |
E0x | Set Filter | x=0 turns on a low-pass filter, x=1 turns it off | D | T | - |
E1x | Fine Portamento Up | increase pitch by x on the first tickif (tick==0) p=p-x | - | - | - |
E2x | Fine Portamento Down | decrease pitch by x on the first tickif (tick==0) p=p+x | - | - | - |
E3x | Glissando Control | enable (x=1) or disable (x=0) glissando for effect 3xx | D | T | - |
E4x | Set Vibrato Waveform | choose the waveform to use with effect 4xy | D | T | - |
E5x | Set Finetune Value | set the finetune value for the current instrument to x | D | T | - |
E6x | Loop Pattern | set the current row as the loop target (x=0) or jump back to the loop target x times from the current row (x>0) | D | T | - |
E7x | Set Tremolo Waveform | choose the waveform to use with effect 7xy | D | T | - |
E8x | Set Panning | set panning value for the channel to x (0=left, $F=right) this is not a ProTracker effect | - | - | - |
E9x | Retrigger | retrigger the current instrument every x ticks if x>0; for x=0 do nothing | D | - | - |
EAx | Fine Volume Slide Up | increase the volume by x on the first tickif (tick==0) v=v+x | - | - | - |
EBx | Fine Volume Slide Down | decrease the volume by x on the first tickif (tick==0) v=v-x | - | - | - |
ECx | Note Cut | set the volume to zero after x ticks | - | - | - |
EDx | Note Delay | delay triggering the note by x ticks | D | T | - |
EEx | Pattern Delay | increase the number of ticks in this row from T to (1+x)⋅T | D | - | - |
EFx | Funk Repeat/Invert Loop | think of the most absurd effect you could apply to a sample loop; then apply it | D | T | - |
If xy=0, this effect does nothing, so 000 can legitimately be considered to mean “no effect”.
eff3_xx
and a target period p_target
.
If the effect is in the same cell as a note, the finetune-adjusted period for the current instrument is calculated as usual, but the note is not triggered. Instead, this period becomes the target period p_target
. As long as the effect is active, the period p
is adjusted towards p_target
on every non-zero tick:
if (tick>0)
if (p<p_target) p=min{p+eff3_xx,p_target}
if (p>p_target) p=max{p-eff3_xx,p_target}
It’s common to have a 3xx with a note followed by 300 without notes to continue the slide.This effect (as well as the related 5xy) must be identified by the player before triggering a note.
The effect is modified if glissando is turned on (see effect E3x below).
The state of the vibrato effect has 3 values: current speed eff4_x
and depth eff4_y
are part of the effect parameter memory and updated separately (i.e. a command 4xy sets both, 4x0 and 40y only one and 400 sets neither); and the wave table index eff4_wti
may be initialized to zero if a note is triggered (see E4x for details). Vibrato uses eff4_wti
on a table set by effect E4x to look up the value of a wave with period 64. Wavetable values should be interpreted as fixed point values in the range between -1 and 1. The default wave is a sine.
The effect adjusts the effective period according to the current wavetable value scaled by twice the depth eff4_y
, then increases the wave table index by speed eff4_x
:
if (tick>0)
p_temp=eff4_y*2*eff4_wave[eff4_wti]
eff4_wti=(eff4_wti+eff4_x)&63
Vibrato depth was originally different in ProTracker 1.0.
if (tick>0)
v_temp=eff7_y*4*eff7_wave[eff7_wti]
eff7_wti=(eff7_wti+eff7_x)&63;
wave1=(((eff4_wti<32)?eff7_wti:-eff7_wti)&31)*((eff7_wti<32)?1:-1)/32
Here, eff7_wti
and eff4_wti
are the wave table indices for tremolo and vibrato, respectively, with values ranging from
0 to 63. Everything would be good if eff7_wti
had been used consistently in the formula instead of eff4_wti
.
First we calculate the absolute value of the waveform function:
wave1_abs=((eff7_wti<32)?eff7_wti:-eff7_wti)&31
This is useful, because now we can multiply it by the effect parameter eff7_y
and do some scaling using unsigned multiplication and shifts.
v_temp=(wave1_abs*eff7_y)>>3
Now we just flip the sign.
if (eff7_wti>=32) v_temp=-v_temp
All this would be largely copy and paste from the corresponding vibrato processing, which is why in the first step, eff4_wti
was incorrectly used.
Despite being an extension to the ProTracker canon of effects, it is an important command. First, quite a few modules use it. Second, panning is a feature that became relevant mostly on the PC, where the MOD format continued to be popular after ProTracker ceased to be the reference implementation for MOD effects.
eff9_xx
rather than 0. [MOD] states that the offset xx is in words, but ProTracker 1.0, ProTracker 2.1, FastTracker and OpenMPT agree that it’s in bytes (or samples). It is possible that some other early tracker used a word offset. This effect is entirely straightforward if a new sample is triggered in the same cell.
If a note is triggered on the current row (non-zero period in the cell), ProTracker checks for a 9xx effect and, if it finds one, adds the proper offset of 256⋅eff9_xx
to the starting index (or pointer) of the latched sample. This is done after latching a new sample but before triggering a note, ensuring that any new note gets played from the right starting point.
After triggering, when the current effects are processed as usual, this effect again adds 256⋅eff9_xx
to the latched starting index, this time to make sure that the starting index is updated for the next use of the latched sample.
This results in the following slightly unusual behaviour: If a note is triggered with effect 9xx and an instrument present, it will play the sample from position 256⋅xx as expected. If a new note is then triggered again without an instrument, it will play from 2⋅256⋅xx.
Because the parameter gets added to the starting offset, the effect of multiple 9xx commands is cumulative. Providing an instrument always resets the starting point by latching a new sample.
There are two things to be careful about: First, the effect parameter xy is specified as a decimal number, that means that the target row is calculated as 10⋅x+y rather than 16⋅x+y. The reason is probably that row numbers were shown as decimals in trackers, so this practice avoids mistakes, confusion and mental math.
Second, it is a common idiom to combine this effect with Bxx to jump to a particular row at a particular song position. For this to work as expected, the control flow in modules must be handled in a way that consistent with how ProTracker did it. One way of doing this is to use a pattern break flag that indicates if a new song position should be loaded:
Dxy sets pattern_break=true
and row_next=x*10+y
Bxx sets pattern_break=true
and row_next=0, songPos_next=xx
The routine that updates the current row and song pos after a row is completed then does something like this:
row=row_next
if (row>64)
row=0
pattern_break=true
row_next=row+1
if (pattern_break)
songPos=songPos_next
check if songPos valid, restart song if necessary, etc.
songPos_next=songPos+1
pattern_break=false
With this, combining the Bxx with Dxx makes jumps to arbitrary point possible if the effects appear in this order on the same row, i.e. if Bxx is entered in a lower channel than Dyx. Otherwise, if the rightmost jump command on the row is a Bxx, it will fully determine the jump destination. This is exactly the behaviour observed on ProTracker and many other trackers.
Even before the effect was extended to allow setting tempo, NoiseTracker already limited the allowable values for ticks per row to be less than $20. [MMW] therefore suggests always interpreting xx as ticks per row for old (pre-NoiseTracker) 15-sample modules, which seems like reasonable advice.
There is some inconsistency with regards to how a value of xx=$20 is treated, with some trackers interpreting it as ticks per row. ProTracker and current versions of OpenMPT take xx=$20 to be a tempo setting, which is consistent with the ProTracker documentation ([PTM]).
The ProTracker docs also state that a value of xx=0 stops playback. Some trackers implement the effect this way, while others, including OpenMPT, ignore an effect F00. The MOD format does not have a direct way of indicating the end of a song, so some modules use F00 to show that playback should stop.
This effect is processed on the first tick. However, tempo changes (x>=$20) do not yet affect the length of the initial tick of the row. Presumably, the interrupt timing for handling the next tick is set up before processing effects.
This effect turns this filter on or off. Notice that counterintuitively, x=0 enables the filter. The recommendation, starting with the NoiseTracker manual, has always been to keep the filter turned off. OpenMPT suggests to leave this command alone, unless your module may be played on an Amiga, in which case you may use E01 to ensure the filter is off.
None of the major trackers on the PC in the 90s implemented this effect, as there was no hardware support. Thus, MODs created back then on a PC would likely ignore this effect.
Overall, this is really not an important effect. Most MODs do not use it, some turn the filter off, very few turn the filter on. And if 1990s PC trackers are the reference point, not implementing this effect may be the most authentic choice.
At the same time, if audio output is being filtered anyway, this effect is extremely simple to implement.
This effect turns glissando on (x=1) or off (x=0). Even if turned on, glissando only applies while effect 3xx (or 5xx) is active. By default, glissando is off.
This effect has not been supported as consistently as one might expect. ImpulseTracker does not have it, OpenMPT describes its implementation as “quirky”. It is generally considered to be an unimportant, rarely used and little liked effect.
Despite all this, it’s not a difficult effect to implement. All it takes is to “round” the current playback frequency to the nearest semitone if glissando and effect 3xx are both active. In ProTracker and other trackers of that time, this was clearly accomplished by using period tables. Today, it can be done in a much simpler way. Just round the relative deviation from any reference note appropriately when calculating the data rates used for playing samples. When tone portamento is active, such a reference semitone is always available in the form of the target period.
effPeriod=period
if (eff3xx_active && glissando_active)
rel_period=log_2(effPeriod/target_period)*12
effPeriod=target_period*2^(round(rel_period)/12)
sample_data_rate=Phi/effPeriod
The lowest two bits of the effect parameter selects one of four possible waveforms:
0: | sine wave: | wave0[i]=sin(2*pi*i/64) |
1: | ramp down: | wave1[i]=((i+32)%64)/32-1 |
2: | square wave: | wave2[i]=i<32?1:-1 |
3: | random: | wave3[i]=? |
Note that, despite being advertised as “ramp down” in the documentation, wave 1 is very much a ramp up in the numerical sense (after triggering, both wave 0 and wave 1 initially increase in value). "Down" probably refers to the direction of the pitch change when the wave is used for vibrato.
Different sources have different opinions on what “random” means. [MOD] claims it means “pick one of waveforms 0-2 randomly”. ImpulseTracker and others implement it as what appears to be a sequence of uniformly distributed, i.i.d. random numbers – basically noise. The OpenMPT docs state that ProTracker does not implement the random waveform and recommends avoiding its use in modules.
Bit 2 of the effect parameter determines whether a wave is retriggered with a new note. If this it is zero, set the wave table indices to zero whenever a new note is triggered. If it’s one, don’t do this. The default is to retrigger.
Bit 3 of the effect parameter does nothing.
How is this implemented in practice? First of all, there’s no jumping between patterns. The loop target is really just a row number. Second, there is a loop counter that ensures that ensures the loop gets processed the right number of times. This is how far a general description of this effect can go. Trackers differ widely in their implementations and how they handle unusual scenarios. I will describe the behaviour of ProTracker here.
if (x==0) loopTargetRow=row
else
if (globalLoopCounter<=0) globalLoopCounter=x
else globalLoopCounter-=1
if (globalLoopCounter>0) row_next=loopTargetRow
The loop counter and target row are initialized to zero, but then not updated further except by the E6x effect. In particular, the target row is not reset to zero when moving to a new pattern.This implementation makes it possible to do some unintended things: by setting a row in one pattern, then using a E6x jump early in a later pattern, it is possible to jump forward rather than back in a pattern. Having a E60 followed by two E6x commands in the same pattern will create an infinite loop.
Later trackers handle this command differently. Some have channel specific-loop states, allowing nested loops across channels. Most seem to reset the jump target in E6x commands to prevent infinite loops. Such implementations will be discussed later in the context of the other module formats.
For x>0, the note gets retriggered on the first tick (even if no period was supplied in the cell so that normal triggering would not happen) and then every x ticks (i.e. on every tick if x=0) on the current row.
For x=0, the effect does nothing, i.e. it does not affect the normal processing of whatever else is in the current cell.
Other module formats have a persistent counter associated with this effect which allows it to operate across rows. The MOD format does not.
In the relatively simple MOD format, there’s lots of different (and mostly equivalent) ways of making this happen. However, from looking at more complex formats such as XM or IT, it is clear that this effect actually ends up meaning “delay the processing of all new information in this cell by x ticks”. One way of thinking about this is that this effect changes the channel-specific tick index to channelTick=globalTick-x
. No current effects are processed on the “negative” channel ticks -x to -1, then the new note is triggered and things start happening at channelTick
0, when the other channels are already on tick x.
This is done by setting (rather than adding to) a global variable, meaning that in the case of multiple EEx commands on the same row, only the one in the highest channel matters.
This family of effects was introduced by ProTracker. The EFx command generally controls an effect that operates in the background. The parameter x sets the “speed” of the effect by selecting a value from a table. This value is then used on every tick to increment a counter. When this counter overflows, it is reset to zero and something bad happens. A parameter value of x=0 selects an increment of zero and thus effectively turns the effect off.
ProTracker originally implemented an effect called “Funk Repeat:”
Whenever the funk effect is triggered on a tick, the loop of the current sample is moved forward by the loop length. Once it moves past the end of the sample, it is reset to its original position. It’s not entirely clear what this is supposed to achieve. I have not dared to try out how this sounds in practice, so I will quote from the effect description in the on-disk manual that accompanied ProTracker 1.0C: “Sounds like shit, really, but who cares?”
This effect was soon replaced by a reworked version, which was called “Invert Loop”, but, confusingly, in some documents as well as ProTracker sources is still referred to as Funk Repeat. The new effect changes what happens when the counter overflows and an event is triggered. Now the effect increases an index and then inverts the sample value at this index inside the loop of the current sample: currSampleData[loopStart+invertIndex%loopLength]^=$FF
What does this achieve? Bitwise inversion of the sample’s 2’s complement value is approximately the same as multiplying it by -1. For a longer loop, this will not change the sound much, except for the introduction of clicking noises between the inverted and uninverted parts. For short loops, it will likely superimpose an audible rectangular wave that is strongly modulated at a rate related to the effect speed. Why would we want that? I have no idea, but [MMW] asserts that a few modules need this effect to enhance the epicness of their sound.
A clear issue with this effect is that it irreparably modifies actual sample data in the middle of playback. When using a tracker, do we expect it to keep an unmodified copy of the samples or is it ok that samples get mangled more and more every time a module is played? Quoting from the ProTracker 2.1A manual: “This effect will trash the sample, and will probably be removed in the next version.” This promise was not kept.
The good news is that later trackers consistently refuse to implement the EFx effect. My suggestion: follow their example.
Specific details, including the relevant increment table, are explained in [MMW]. A significantly deeper and, from the perspective of implementer, more helpful discussion can be found in [TST].
For the purpose of writing a player, this is not a big concern, as the MOD file already supplies the correct period for each note to be played. The only two situations where we would be required to look up a tabulated value is when applying finetune to a period and when using glissando. Above, I explained how the required adjustments can be calculated. The result of these calculations is going to be very close to ProTracker’s table values, but may still be off by a small fraction of a percent. It’s unlikely that this makes an audible difference.
When writing a tracker, using the “official” period values for notes is important, because these values get written to the MOD file, and non-standard periods may confuse other trackers or players. These period values are listed in [MOD], for example. Trying to calculate these values based on a reference note will not work; some periods will always be off by +/-1. Still, it’s sufficient to tabulate one octave (the lowest one to be used), and then calculate the periods for higher octaves by bit-shifting.
For ultimate playback faithfulness to ProTracker, the complete table including periods for non-zero finetune values can be found in [PTT], along with some theories and discussion of why it might be such an inconsistent mess.
When that filter was off, the Amiga 1000 and 500 (i.e. the pre-1992 models) still filtered audio output with a 1st-order low-pass filter at about 4.9 kHz. See [AMT] for the Amiga filter values allegedly used by MikyTracker and [AFS] for audio recordings on different Amiga models.
On the PC at the time, SoundBlaster cards filtered audio output quite heavily, with the default being comparable to the Amiga’s Butterworth filter (12 dB per octave, and cut-offs at about 3.8 kHz and 3.2 kHz on the SoundBlaster and SoundBlaster Pro, respectively).
In 1992, non-optional filters disappeared, both on the new Amiga models and the new SoundBlaster 16 on the PC. The optional Butterworth filter continued to exist on the next generation of Amigas.
So, what’s the right filter for MOD files?
One thing to consider is that the maximum sample rate the format supports in its original form is about 30 kHz for a theoretical maximum tone frequency of 15 kHz. Generally, the inherent restrictions on sample rates in MOD files make it difficult to generate high frequencies in the audio output with reasonable quality. This constraint was relaxed by later module formats that allow higher sampling rates and a wider range of usable octaves. Moreover, the Amiga hardware allowed for variable sample playback rates that could differ across channels, whereas on the PC, having to choose a fixed rate for the whole module was the norm. This means that aliasing was likely much more of a problem on the PC, in particular considering that earlier PCM cards did not support sample rates anywhere near the 44+ kHz that would later be considered the minimum standard. For these reasons, any frequencies above maybe 10-15 kHz could be considered unwanted and would have been largely filtered out anyway, at least on pre-1992 hardware.
For modules that respect the original Amiga period range, the Amiga filter configuration may be a pretty good choice, considering the characteristics of both the format and the hardware at the time of its development. For the ones that do not, it may be hard to determine what’s authentic, but I had the impression that applying a filter generally improves the perceived audio quality.
previous | index | next |