Patterns
These are pretty tricky, but they're completely uncompressed here, which makes them far easier to understand than most formats. The S3M/XM/IT formats all encode their patterns in different ways to save space, and in fact, we will probably be encoding the MOD patterns ourselves later on, but for now, we'll keep them uncompressed. Even so, we have to make some modifications to the data for our player to use.
Pattern data it stored somewhat like a bitmap. Since we have 4 channels, it will be 4 columns wide. Since all MOD patterns have 64 rows, it is 64 rows tall, so sort of a 4x64 array. Each cell is 4 bytes, so the size of the whole pattern is 4*64*4, or 1024, which is 1KB.
Now here is why we want to reformat the data for our player. Each cell constains 4 fields, a 12-bit period value, an 8-bit sample number, a 4-bit effect type, and an 8-bit effect parameter. Now, you could organize these in a fairly simple way by storing the byte values on byte boundaries, and just tacking the 4-bit one onto the top of the 12-bit one. But no, it's a hideous mess, and we have to unscramble it. Here is how they are layed out:
byte0 byte1 byte2 byte3
---------- -------- -------- ----------
| PPPPSSSS pppppppp EEEEssss FFFFFFFF |
---------- -------- -------- ----------
bit0.....7,0......7,0......7,0......7
Sample: ssssSSSS
Period: ppppppppPPPP
Effect: EEEE
Parameter: FFFFFFFF
Taken from fmoddoc.txt, and rewritten in a way that I think makes it easier to understand (going from lowest bit to highest bit as you read left to right). If this looks backward to you, read the fmoddoc.txt one instead.
Now all we have to do now is shift those around to put them together like that. It should look something like this:
u8 cell[4];
u8 sample;
u16 period;
u8 effect;
u8 param;
fread(cell, 4, 1, modFile);
sample = (cell[2] >> 4) | (cell[0] & 0xF0);
period = cell[1] | ((cell[0] & 0xF) << 8);
effect = cell[2] & 0xF;
param = cell[3];
Now we have some data that we can actually use. But first, you must be wondering what that period value is. That's what specifies what frequency the sample will be played at. It's related to the Amiga's VBlank rate, and so makes little sense to us GBA people. Here is the formula to convert from amiga period to Hz:
frequency = 7159090.5 / (period * 2);
We are not going to use periods at all, because they are a waste of space. Instead, we will convert them to note numbers, and use a lookup table of frequencies for each note. To do this, we need a table of period values for each note. Fortunately, fmoddoc.txt supplies one:
dc.w 856,808,762,720,678,640,604,570,538,508,480,453 ; C-1 to B-1
dc.w 428,404,381,360,339,320,302,285,269,254,240,226 ; C-2 to B-2
dc.w 214,202,190,180,170,160,151,143,135,127,120,113 ; C-3 to B-3
So, right there you can see that the period for C-2, middle C, is 428. Let's run this through our formula and see what we get:
frequency = 7159090.5 / (428 * 2);
frequency = 8363.42;
Remember earlier I said 8363 is the default frequency for all samples? This right here proves it.
Traditionally, MOD only has 3 octaves. However, because the period field is 12 bits, there's no reason not to allow more. As you can see from the table, the period of octave 1 is twice that of octave 2. C-2 is 428, and C-1 is 856. If you go down to C-0, you multiply it by 2 again and get 1712. This still fits in 4 bits, so it's ok. ModPlug actually lets you do this, so we'll include support for it. Theoretically there's not much limit to how high pitched you can go, but as you go higher, the periods get smaller, meaning you have less and less accuracy. ModPlug lets you go up to B-4, so that's what we'll do too.
Next, we have to decide how to go about doing this. If you look closely at the table, you'll see that some values in fact DON'T match up to exactly 2x eachother. For example, B-1 and B-2. B-2 is 226, and B-1 is 453. But 226*2 is 452. We will take octave 1 and calculate everything else based on it, because it has the highest accuracy to begin with.
Once we have a full 5-octave table, we need to compare the periods of the notes in the pattern to it to decide what the note number should be. We'll declare note 0 to be C-0, note 1 to be C#0, note 2 D-0, etc., then note 12 is C-1, note 24 is C-2, all the way up to 59 being B-4. As you can see, our new note number only requires 6 bits to store, instead of the 12 in the original format.
So we'll start off by creating the full period table:
const u16 octave1Period[12] = {
// this is the first row of the table from before
856,808,762,720,678,640,604,570,538,508,480,453 // C-1 to B-1
};
// 5 octaves' worth of space
u16 periodTable[12*5];
u8 octave, note;
for(octave = 0; octave < 5; octave++)
{
for(note = 0; note < 12; note++)
{
// *2 to get us into octave 0, then divide by 2 for each octave up from there
periodTable[octave*12 + note] = (octave1Period[note]*2) >> octave;
}
}
Now we have a table of period values to compare our notes to.
Before we start converting our pattern data, we need to know how we plan to store it. Our note values will take 6 bits as we saw before.
The sample number, while stored as a byte originally, only needs 5 bits. MOD files only support 31 samples, so no need to waste those extra 3 bits storing it as a byte.
The effect and parameter use all their bits, so we'll keep them like they are (4 bits and 8 bits respectively). So, this comes out to 6+5+4+8 = 23 bits. We just chopped a byte off each cell in our pattern, so the total size of each pattern is now only 768 bytes, rather than 1024. The question though, is wether this savings is worth a little extra complexity. When you look at how the information will have to be stored, it will be something like this:
byte0 byte1 byte2
---------- -------- ----------
| NNNNNNss SSSxEEEE FFFFFFFF |
---------- -------- ----------
bit0.....7,0......7,0......7
Note: NNNNNN
Sample: ssSSS
Effect: EEEE
Param: FFFFFFFF
x: unused
As you can see, the note, effect and parameter are easy enough to extract, but the sample number is spread over 2 bytes. This would not be a problem if we could just load those 2 bytes, shift the sample field down, and mask off the data above it. However, because the whole field is 3 bytes, we will no longer have any guaranteed power-of-two alignment, so we can only read single bytes at a time. We can still extract all the necessary information, it would just make our code a little ugly. For now, we will go the easy route and store each field as a whole byte, like this:
byte0 byte1 byte2 byte3
---------- -------- -------- ----------
| NNNNNNxx SSSSSxxx EEEExxxx FFFFFFFF |
---------- -------- -------- ----------
bit0.....7,0......7,0......7,0......7
Much nicer. However, now that we have word alignment back, it would be easy to extract the packed values, but it would be a waste of time when we can load them seperately to begin with. Sad.
Now we will add another variable to MOD_HEADER, and convert all the patterns to our new format:
typedef struct _MOD_HEADER
{
char name[20];
SAMPLE_HEADER sample[31];
u8 order[128];
u8 **pattern;
u8 orderCount;
u8 patternCount;
} MOD_HEADER;
//////////// Down in main:
modHeader.pattern = new u8*[modHeader.patternCount];
ASSERT( modHeader.pattern ); // handle running out of memory
// Initialize the memory to 0
memset( modHeader.pattern, 0, modHeader.patternCount*sizeof(u8*) );
u8 curPattern, row, column;
for(curPattern = 0; curPattern < modHeader.patternCount; curPattern++);
{
// Allocate the memory for our new pattern (they are always 1K)
modHeader.pattern[curPattern] = new u8[1024];
ASSERT( modHeader.pattern[curPattern] ); // handle running out of memory
memset( modHeader.pattern[curPattern], 0, 1024); // initialize to 0
for(row = 0; row < 64; row++)
{
for(column = 0; column < 4; column++)
{
///////////////////////////// Copy our bit of code from earlier
u8 cell[4];
u8 sample;
u16 period;
u8 effect;
u8 param;
fread(cell, 4, 1, modFile);
sample = (cell[0] & 0xF0) | (cell[2] >> 4);
period = cell[1] | ((cell[0] & 0xF) << 8);
effect = cell[2] & 0xF;
param = cell[3];
//////////////////////////// end copied code
// Now we'll loop through our note period table and find the closest match
u8 closestNote = 0;
u16 closestDist = 0xffff; // make sure the first comparison sets the closest note
for(i = 0; i < 12*5; i++) // 5 octaves, 12 notes each
{
u16 newDist = abs( period - periodTable[i] );
if(newDist < closestDist)
{
closestNote = i;
closestDist = newDist;
}
}
// Now that we have our note, we can store the data in our new pattern
// Calculate the address of the cell to output to
// rowOffset = row * 4 columns per row * 4 bytes per cell
// columnOffset = column * 4 bytes per cell
u8 *outCell = &modHeader.pattern[curPattern][row*4*4 + column*4];
outCell[0] = closestNote;
outCell[1] = sample;
outCell[2] = effect;
outCell[3] = param;
}
}
}