Now we get into the real nitty-gritty of the .s file. We'll look at how tracks are designed, as well as the many commands that are used within these tracks.
Each track is seperated with a line, as well as a denotation of which track it is. It'll look something like this:
Code:
@**************** Track 1 (Midi-Chn.1) ****************@
This line is only here for us humans that look at these files. This line is completely ignored by the GBA due to it being commented out. Any line that has a "@" at the far left is commented out.
Then we see an important label:
This tells the GBA that this is the first track of this song, and everything below it is part of this track. At the top of each track, you'll see a line similar to this, with the number relating to the track in question.
As we go through this track, you'll see dashed lines with numbers to the left of them like this:
Code:
@ 000 ----------------------------------------
Again, due to them being commented out, these are meant for us humans. Each of these sections can be considered as one measure of the song. Of course, since this is used for a program, the first measure is 000 rather than 001, since us programers love starting with 0. We then see several commands, which I will explain. You can see all of these commands in
sound/MPlayDef.s
if you so desire.
Just a side-note that I feel like I need to point out: every single command in a .s file starts with ".byte". Always. Without exception.
The first command we see is:
Code:
.byte KEYSH , mus_rg_ajito_key+0
KEYSH is the key-shift command. It will shift the key of the track it's in. Whatever value you add or subtract the key with will shift the track's pitch by that many half-steps. As you may notice, it uses the key of the entire song, "mus_rg_ajito_key", and then adds a value to it. In this case, the command does nothing, since it shifts the key by 0. Sometimes when a .mid file is turned into a .s file, some commands are made that serve no purpose. Just a minor glitch with mid2agb, I guess. Or maybe mid2agb is designed to put one of these commands at the top of each track, just in case the user wants to change the key? Who knows. The next command is this:
Code:
.byte TEMPO , 176*mus_rg_ajito_tbs/2
TEMPO is the tempo-changing command. This sets the tempo for the entire song, not just the one track that it's in. This is one of the few (if not only) commands that affects every track. This command takes a number, which is the BPM that you want the song to be, multiplies it by the song's tbs value, and then divides it by 2. Why does it need to be divided by 2, you ask? I don't know, but it does. This is always the format for TEMPO commands.
Next line:
VOICE is the voice-changing command. This sets which voice from the voicegroup of the song is being used in this track. The number is the location of the voice within the voicegroup. This particular command sets Track 1 to use the 39th instrument in the voicegroup. Not the 38th. Remember, this is a program, so the first voice in the voicegroup would be 0, the second voice would be 1, and so on. If a track is never given a VOICE command, it cannot make any sound whatsoever. This can actually be a handy trick if you want to mute a track: just comment out all of the VOICE commands in the track, and the track will be completely muted.
Next line:
Code:
.byte VOL , 106*mus_rg_ajito_mvl/mxv
VOL is the volume-changing command. The volume can go up to 127 and no higher, and the number must be multiplied by the song's mvl value and then divided by mxv, which is a constant for the loudest possible volume on the GBA. Kind of a weird format, but that's how it's done.
Next line:
PAN is the....pan...command. Pan is where the sound is being placed in stereo space. Whether it's coming more from the right speaker or the left speaker. You always need to add/subtract from c_v. Adding moves the sound to the right, subtracting to the left.
Next line:
MOD is the...mod...command. There are three types of mods: vibrate (mod_vib), tremolo (mod_tre), and auto-panpot (mod_pan). Before you would use MOD, you would use the command
MODT and set the type to be whichever one of these that you want, using the names in the parenthesis. However, when I tested using anything except mod_vib, I didn't notice any change to the music. Perhaps I was using too low a value for MOD (I tried entering 15, which is higher than I've ever seen it used), but it is something to note. MODT is set to mod_vib by default.
Next line:
Ah, here is our first actual
note! Whenever a note is played, it follows this format: note-length, pitch, velocity. This note will last for 17...time units, and it will play E1 at a velocity of 112 (max is 127). Time units is just the term I have for the numbers used with N##. If a song is in 4/4, a single measure has 96 time units, which would mean one beat lasts for 24 time units. If the song is in 3/4, a single measure will instead have 72 time units, still leaving a single beat with 24 time units. En1 means E-natural-1. Each number represents an octave. The higher the number, the higher pitched the octave. You can look through
sound/MPlayDef.s
to see all of the possible notes you can use. And for those that don't know, velocity is another term for volume that is often used in music, especially with midis. That velocity value will affect specifically this one note. The volume set by a VOL command is still in effect here, but this velocity element gives us a bit more precision when it comes to volume control.
Next line:
Pretty short, but pretty important. This is a
wait command. This forces the track to wait 24 time units before going to the next command. An important thing to point out here: even though the prior command had a note that lasted for 17 time units, that doesn't mean 17 time units pass before this wait command starts. The note starts to play, and then we immediately start the wait command. Every command will immediately play one after the other if no wait commands are between them. As such, each measure should have a number of wait commands that add up to 96 (or 72 if the song is in 3/4), so as to have the measure last it's full length.
Next line:
Mmmm, now here's where things get interesting. This looks like a note command, and yet we don't see the duration of the note! Strange, isn't it? This is actually an opitimization that the GBA's programming uses. Since we aren't given a note duration here, the GBA will just use the prior note duration, which would be N17. If we hadn't given a velocity value, it would have also used the prior one, which was 112. The GBA can look back like this to fill empty spots for every type of command. Say we set the volume to 110 like this:
Code:
.byte VOL , 110*mus_rg_ajito_mvl/mxv
But then we want to immediately change the volume to 100. We can just type this:
Code:
.byte 100*mus_rg_ajito_mvl/mxv
The GBA will remember that the prior command was a VOL command, so it knows what to do with this data. An important thing to point out: if we had typed a different command between these two volume changes, like a note command, we would have still needed to type VOL into that second command. If we had typed this:
Code:
.byte VOL , 110*mus_rg_ajito_mvl/mxv
.byte N17 , En1 , v112
.byte 100*mus_rg_ajito_mvl/mxv
The GBA would get real confused. It would try to play a note using "100*mus_rg_ajito_mvl/mxv" since the prior command was a note command, but it can't do anything with that data. The only command that won't cause this confusion is a wait command. The GBA will look further back to the prior non-wait command to get it's missing data. Back to note commands, if we want to repeat a note, we could write something like this:
Code:
.byte N17 , En1 , v112
.byte W20
.byte N17
.byte W20
.byte N17
Or this:
Code:
.byte N17 , En1 , v112
.byte W20
.byte En1
.byte W20
.byte En1
These two would both play the exact same thing. Since all of the data was given in an earlier command, we just need to give a single element of the prior note to let the GBA know that we want to play another note. I'm not sure if you can do this by just giving the velocity, though. We can also get a bit more creative in writing our notes by typing something like this:
Code:
.byte N17 , En1 , v112
.byte W20
.byte N20
.byte W20
.byte Cn2
This bit of code would first play an E-natural-1 for 17 time units at 112 velocity. Once 20 time units passed, it would then play another E-natural-1 at 112 velocity, but this time for 20 time units. After waiting another 20 time units, another note would be played for 20 time units at 112 velocity, but this time it's a C-natural-2. As you can see, we can leave out certain commands and change others to simplify the data for each line without losing any information. mid2agb does this automatically, so it's important to be aware of how these lines can be written.
Not a whole lot of other commands are used until later on in the song that I'm using as an example, so I'll just list off the remaining commands from
sound/MPlayDef.s
FINE defines the end of a track. Example:
Code:
@ 080 ----------------------------------------
.byte FINE
@**************** Track 2 (Midi-Chn.2) ****************@
BEND will bend the pitch of a track. You would use c_v, similarly to the PAN command. Example:
BENDR will affect how much the BEND value bends the pitch. From what I can tell, it looks like it is set to 2 by default in
src/m4a.c
within the
m4aMPlayImmInit
function. This BENDR value is multiplied by the BEND value to determine the amount of bend applied to the note. Example:
TIE can replace a note duration. This will make a note last until an EOT command is given. Example:
EOT will end a TIE note. Example:
Code:
.byte TIE , Gs1 , v127
.byte W32
.byte W01
.byte EOT
XCMD basically just announces that an extended command is about to be used. These extended commands take an additional argument that states what type of extended command is being used, which you will see in the examples for the next couple of commands. These commands are based on C code that can be found in
src/m4a.c
. It is possible to add your own commands via these extended commands, which I have demonstrated in
this tutorial of mine that lets you make dynamic music.
xIECV sets the volume for an echo effect. It is at 0 by default, meaning there's no echo. Example:
xIECL sets the length of the echo, or how long the echo lasts. It is at 0 by default, also meaning there's no echo. Example:
There are several other extended commands that do not have a declared constant in
sound/MPlayDef.s
, but can still be used in your songs. For example:
Code:
.byte XCMD , 4 , 250
.byte XCMD , 5 , 150
.byte XCMD , 6 , 50
.byte XCMD , 7 , 220
These commands set the attack envelope of the track to be 250, the decay envelope to 150, the sustain envelope to 50, and the release envelope to 220, respectively. These commands do not work on voices that use voice_keysplit or voice_keysplit_all, since that voice technically contains multiple sets of voice envelopes. There are a few other extended commands that don't have constants, but I don't fully understand how they work yet, so I won't bother giving you an uneducated explanation. You can see a full table of all of the extended commands at the bottom of
src/m4a_tables.c
, and you can see what each command does in
src/m4a.c
. To use any of these commands, you just need to write:
Code:
.byte XCMD , INDEX_OF_COMMAND_IN_TABLE , PARAMETERS
If you want, you can also go into
sound/MPlayDef.s
and add your own constants for these commands. For example, I've added "xDECAY" and "xSUSTAIN" as constants in that file, making them equivalent to 5 and 6, respectively.
There are some commands that need to be seen in action to be understood. Looking back at the song I was using as an example before, you'll see at Track 1 measure 14 (or 014) that there's this line right at the top:
This is a label. Labels are used for a few commands:
PEND can be used to make a measure with a label become a pattern. You place this command where you want your pattern to end, hence the name PEND. This technically doesn't only apply to a single measure. You could have an entire track be a measure, as long as there are no labels between the label of the first measure and the PEND command. This is incredibly uncommon and usually risky, so it's generally best to just have a pattern last for one measure. Looking at the bottom of measure 14, you will see this command:
Code:
@ 014 ----------------------------------------
mus_rg_ajito_1_014:
.byte N11 , Fn1 , v124
.byte W12
.byte Fn2 , v116
.byte W12
.byte Fn1 , v120
.byte W12
.byte Fn2 , v116
.byte W12
.byte Fn1 , v124
.byte W12
.byte Fn2 , v116
.byte W12
.byte Fn1 , v120
.byte W12
.byte Fn2 , v116
.byte W12
.byte PEND
PATT will play a pattern, and then proceed through the song. This can be seen in Track 1 measure 15:
Code:
.byte PATT
.word mus_rg_ajito_1_014
".word" is used with some commands almost like an argument for a method. With PATT, .word holds the pattern label that indicates which pattern shall be played.
GOTO will cause the track to jump to a label. It'll look something like this:
Code:
.byte GOTO
.word mus_rg_ajito_1_014
This is different from PATT in two ways:
1. GOTO doesn't need a pattern to be marked with PEND, whereas PATT does.
2. GOTO causes the track to play from the given label, whereas PATT will play the pattern, and then move on to the next measure. For example, this:
Code:
@ 015 ----------------------------------------
.byte PATT
.word mus_rg_ajito_1_014
Will cause measure 15 to be identical to measure 14, and then the track will move on to measure 16. This, on the other-hand:
Code:
@ 015 ----------------------------------------
.byte GOTO
.word mus_rg_ajito_1_014
Would cause the track to move back to measure 14, play measure 14, and then move forward to measure 15, where we would again go back to measure 14, and find ourselves in an infinite loop. GOTO is generally used only to loop the music.
There are other commands within
sound/MPlayDef.s
, but those that I haven't already explained rarely (if ever) show up, so I don't know much about them. I'm loosely aware of what they may do, like
LFOS and
LFODL having something to do with how the effects of MOD sound, but it's just surface knowledge.