- 250
- Posts
- 7
- Years
- Seen Apr 23, 2025
There are many pieces of music within video games, movies, and many other forms of media that are dynamic. When I write "dynamic", I mean that the music changes slightly based on the events that are occurring, whether that means when a character dies in a film, the music suddenly becomes more somber, or when a boss becomes vulnerable in a boss fight, the music grows more energetic. I always found that to be a very interesting way of designing music, since it can make the audience or the player grow more invested with what is occurring. I recently wanted to design a way to allow for this dynamism to exist within the music of pokeemerald. I have come up with a process where this can be accomplished!
Necessary Changes
How To Use
Random Tips
Necessary Changes
Spoiler:
include/constants/flags.h
include/gba/m4a_internal.h
This is just our declaration of the function that will allow us to do conditional statements in the .s files of our songs.
sound/MPlayDef.s
This is a definition of the keyword "xCOND", which we will use in the .s files when we want to use the conditional command, as well as some constants to make life a bit easier later on.
src/m4a.c
These changes include putting in our conditional function ply_xcond, as well as giving us access to the flags as well as the FlagGet function, so we can check to see if FLAG_DYNAMIC_CHANGE is set or not. Once I get to the point where I explain how to use this stuff in the .s files, I'll explain what's going on here.
src/m4a_tables.c
We place the name of our conditional function into gXcmdTable so that, when we use the xCOND command in the .s file, the ply_xcond function will be run.
Spoiler:
Pick one of the unused flags beyond the temp flags and rename it to "FLAG_DYNAMIC_CHANGE". If you don't like that name, make it whatever you want. You'll just need to remember to use that name for any references I make to FLAG_DYNAMIC_CHANGE.
include/gba/m4a_internal.h
Spoiler:
Code:
void ply_xswee(struct MusicPlayerInfo *, struct MusicPlayerTrack *);
void ply_xcmd_0C(struct MusicPlayerInfo *, struct MusicPlayerTrack *);
void ply_xcmd_0D(struct MusicPlayerInfo *, struct MusicPlayerTrack *);
void ply_xcond(struct MusicPlayerInfo *, struct MusicPlayerTrack *); ← ← ← ← ← Add this line
#endif // GUARD_GBA_M4A_INTERNAL_H
sound/MPlayDef.s
Spoiler:
Code:
.equ XCMD, 0xcd @ extend command ***lib
.equ xIECV, 0x08 @ imi.echo vol ***lib
.equ xIECL, 0x09 @ imi.echo len ***lib
.equ xCOND, 0x0e @ Used for conditional GOTOs ← ← ← ← ← ← Add this line
.equ SET_GOTO, 0 ← ← ← ← ← ← Add this line
.equ CLEAR_GOTO, 1 ← ← ← ← ← ← Add this line
.equ SET_VOL, 2 ← ← ← ← ← ← Add this line
.equ CLEAR_VOL, 3 ← ← ← ← ← ← Add this line
src/m4a.c
Spoiler:
Code:
#include <string.h>
#include "gba/m4a_internal.h"
#include "constants/flags.h" ← ← ← ← ← ← Add this line
extern const u8 gCgb3Vol[];
extern bool8 FlagGet(u16); ← ← ← ← ← ← Add this line
......................................
void ply_xcmd_0D(struct MusicPlayerInfo *mplayInfo, struct MusicPlayerTrack *track)
{
u32 unk;
READ_XCMD_BYTE(unk, 0) // UB: uninitialized variable
READ_XCMD_BYTE(unk, 1)
READ_XCMD_BYTE(unk, 2)
READ_XCMD_BYTE(unk, 3)
track->unk_3C = unk;
track->cmdPtr += 4;
}
void ply_xcond(struct MusicPlayerInfo *mplayInfo, struct MusicPlayerTrack *track) ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← Add this function
{
void (*gotoCmd)(struct MusicPlayerInfo *, struct MusicPlayerTrack *) = *(&gMPlayJumpTable[1]);
u8 direction = *track->cmdPtr;
track->cmdPtr++;
switch(direction)
{
case 0:
if(FlagGet(FLAG_DYNAMIC_CHANGE))
gotoCmd(mplayInfo, track);
else
track->cmdPtr += 3;
break;
case 1:
if(!FlagGet(FLAG_DYNAMIC_CHANGE))
gotoCmd(mplayInfo, track);
else
track->cmdPtr += 3;
break;
case 2:
if(!FlagGet(FLAG_DYNAMIC_CHANGE))
track->cmdPtr += 2;
break;
case 3:
if(FlagGet(FLAG_DYNAMIC_CHANGE))
track->cmdPtr += 2;
break;
}
}
src/m4a_tables.c
Spoiler:
Code:
const XcmdFunc gXcmdTable[] =
{
ply_xxx,
ply_xwave,
ply_xtype,
ply_xxx,
ply_xatta,
ply_xdeca,
ply_xsust,
ply_xrele,
ply_xiecv,
ply_xiecl,
ply_xleng,
ply_xswee,
ply_xcmd_0C,
ply_xcmd_0D,
ply_xcond, ← ← ← ← ← ← ← Add this line
};
How To Use
Spoiler:
I will be using my transcription of the King Bowser battle theme from Super Mario Galaxy to demonstrate, which can be found here.
There are a few ways you may decide to go about creating dynamism in your songs. One such method would be to, before every single measure of your song, check to see if the conditions have been met to change the song, and if they have, make the change. This would result in the change happening as soon as possible after the conditions are met. However, if you aren't working with a short song with only a few tracks, this can quickly become tedious. There's also a chance that some notes in your song will carry over between measures, so you'll need to account for that. It's the price you'll have to pay for a quick reaction to the conditions being met.
Another method, which is the method that I will be using, is to have a handful of transition points where the song will change if the conditions are met. Instead of checking every single measure, you choose a few particular measures where you will check to see if the song needs to change. Depending on how far apart your chosen measures are, there will be a bit of a delay between the conditions being met and the song changing, but this will be notably easier to setup. It's your choice whether you want a more immediate transition for a lot more work or a slightly less timely change for less work.
Whichever method you choose, you will need to write a marker at each point you may need to jump to, which in this song, looks something like:
Also, at each transition point, you will need to write this:
Let's see this actually play out.
Looking at Track 1, measure 009 of King Bowser, we see this:
We can see that the XCMD command is being used here, and we are passing xCOND and SET_GOTO as arguments. XCMD is used when using any extended command. I go a bit more in-depth explaining what those are in my music tutorial. We pass in xCOND to say we are wanting to use our conditional function, which is ply_xcond. If we remember, in sound/MPlayDef.s, we defined xCOND to be equal to 0x0E. This is hex for 14. We may also remember that, in src/m4a_tables.c, we added ply_xcond to the end of gXcmdTable. That just so happens to place ply_xcond at index 14 of the table. With this information, we can now see how the XCMD command plays out: the GBA sees there's an XCMD command. It reads the passed argument, which is xCOND, which is equal to 14. The GBA then looks into gXcmdTable and picks out the 14th function in the list and runs it, which turns out to be ply_xcond.
Now lets see what happens when ply_xcond actually runs. Looking back at ply_xcond in src/m4a.c, we read the next passed argument into our "direction" variable, which, in this instance, is SET_GOTO, which we made equivalent to 0 in sound/MPlayDef.s. Since it is a 0, we check to see if FLAG_DYNAMIC_CHANGE is set. If it is, we will run a GOTO command, which will read the marker that we wrote in immediately below the XCMD in the .s file and jump to wherever that marker is in the file. This represents the music changing due to the condition (FLAG_DYNAMIC_CHANGE being set) being met. If the flag is not set, however, we will skip over the marker we wrote, as well as that W01 command that we wrote afterwards. Due to how .word's work, the game will crash if we try to just skip over the marker. Thus, we need to add one extra dummy .byte command to skip over along with it. I arbitrarily chose to use W01, but you can use any .byte command you want, as long as it has no extra parameters passed along with it.
Let's imagine that FLAG_DYNAMIC_CHANGE was set when we got to this point. That means we will jump down to where mus_king_bowser_dynamic_1_choir_010 is. We will find this marker here:
You will notice that, right at the top of this measure, I wrote in a VOICE and VOL command. I did this because the voice and volume of this version of the track may not have been the same as the voice and volume of the prior version. If you were to actually analyze this .s file, you may come to the conclusion that the voice would have actually been the same. You would be correct. However, since I had to do this multiple times for 10 tracks, I found it to be a bit safer to just restate the voice and volume to be the correct values at every transition destination. That way, I can protect myself from having a brain fart, and the voice and volume will always be correct. I could be slightly more efficient by only making the necessary changes for each transition destination, but......I'm lazy. If your song uses BEND, MOD, etc., or if the transition destination has an incomplete note command, you will need to be careful to make sure those commands are also accounted for when you transition. This song only uses BEND in one section, which is a section where the song won't transition, so I don't need to worry about it.
If we go a little ways down from this point, we will eventually come across this measure:
Here we see another instance of the use of our xCOND command, this time with CLEAR_GOTO passed in. Let's walk through the process again, this time assuming that FLAG_DYNAMIC_CHANGE is not set. The GBA reads XCMD, as well as xCOND. This leads the GBA to our ply_xcond function again. This time, since we've passed in CLEAR_GOTO, which is equivalent to 1, instead of SET_GOTO, we check to see if the flag is clear. Since it is, indeed, not set, we do a goto command, using the mus_king_bowser_dynamic_1_026 marker as our destination.
If you look through each track in this song, you will notice that each track is 138 measures long, but has a normal section and a choral section, with a dividing line in-between each section. To use this method of dynamism, each track will need to be designed roughly like this, having two sections that you jump between. I wrote "choir" within each marker that relates to the choral section. That way, I can easily differentiate between the two sections of the track.
Now, you may notice that I haven't established any way of setting or clearing FLAG_DYNAMIC_CHANGE. That's because how that happens is entirely up to you. Do you want your music to change when the player starts walking, like the route music in Pokémon Black/White? Find the section of code that deals with making the player's avatar walk and put in a FlagSet call there, as well as a FlagClear call when the player stops walking. I assumed that a lot of people would want the music to change when the opponent in a Pokémon battle was at low health, so if you want that to be set up, do the following changes to src/battle_main.c:
With these changes, if either of your opponent's Pokémon, whether they're wild or trainer Pokémon, have less than or equal to a third of their health left, the music will change. Then, once the battle is done, FLAG_DYNAMIC_CHANGE will be cleared again, ready for the next battle. Feel free to tweak these changes to fit you're desired conditions for having the music change in battle.
Here's a video of this song in action. I set up two triggers, one that sets FLAG_DYNAMIC_CHANGE, and one that clears it when the player's avatar steps on it. I also show what measure the song is currently on based on the .s file and indicates whenever a song gets to a transition point, so you can see what happens depending on the current state of FLAG_DYNAMIC_CHANGE.
https://youtu.be/p4E7kY-oT-4
There are a few ways you may decide to go about creating dynamism in your songs. One such method would be to, before every single measure of your song, check to see if the conditions have been met to change the song, and if they have, make the change. This would result in the change happening as soon as possible after the conditions are met. However, if you aren't working with a short song with only a few tracks, this can quickly become tedious. There's also a chance that some notes in your song will carry over between measures, so you'll need to account for that. It's the price you'll have to pay for a quick reaction to the conditions being met.
Another method, which is the method that I will be using, is to have a handful of transition points where the song will change if the conditions are met. Instead of checking every single measure, you choose a few particular measures where you will check to see if the song needs to change. Depending on how far apart your chosen measures are, there will be a bit of a delay between the conditions being met and the song changing, but this will be notably easier to setup. It's your choice whether you want a more immediate transition for a lot more work or a slightly less timely change for less work.
Whichever method you choose, you will need to write a marker at each point you may need to jump to, which in this song, looks something like:
Code:
mus_king_bowser_dynamic_TRACKNUMBER_MEASURENUMBER:
Code:
.byte XCMD , xCOND , SET_GOTO or CLEAR_GOTO
.word marker_to_goto
.byte W01
Let's see this actually play out.
Looking at Track 1, measure 009 of King Bowser, we see this:
Code:
@ 009 ----------------------------------------
.byte W72
.byte XCMD , xCOND , SET_GOTO
.word mus_king_bowser_dynamic_1_choir_010
.byte W01
Now lets see what happens when ply_xcond actually runs. Looking back at ply_xcond in src/m4a.c, we read the next passed argument into our "direction" variable, which, in this instance, is SET_GOTO, which we made equivalent to 0 in sound/MPlayDef.s. Since it is a 0, we check to see if FLAG_DYNAMIC_CHANGE is set. If it is, we will run a GOTO command, which will read the marker that we wrote in immediately below the XCMD in the .s file and jump to wherever that marker is in the file. This represents the music changing due to the condition (FLAG_DYNAMIC_CHANGE being set) being met. If the flag is not set, however, we will skip over the marker we wrote, as well as that W01 command that we wrote afterwards. Due to how .word's work, the game will crash if we try to just skip over the marker. Thus, we need to add one extra dummy .byte command to skip over along with it. I arbitrarily chose to use W01, but you can use any .byte command you want, as long as it has no extra parameters passed along with it.
Let's imagine that FLAG_DYNAMIC_CHANGE was set when we got to this point. That means we will jump down to where mus_king_bowser_dynamic_1_choir_010 is. We will find this marker here:
Code:
@ 010 ----------------------------------------
mus_king_bowser_dynamic_1_choir_010:
.byte VOICE , 3
.byte VOL , 80*mus_king_bowser_dynamic_mvl/mxv
.byte N06 , Dn4 , v091
.byte W12
.byte Dn4 , v075
.byte W12
.byte Dn4 , v070
.byte W06
.byte Dn4 , v075
.byte W06
.byte Dn4 , v091
.byte W12
.byte N06
.byte W12
.byte N06
.byte W12
.byte PEND
If we go a little ways down from this point, we will eventually come across this measure:
Code:
@ 025 ----------------------------------------
.byte An4
.byte W12
.byte Dn4
.byte W12
.byte N06
.byte W12
.byte An4
.byte W12
.byte Dn4
.byte W12
.byte An4
.byte W12
.byte XCMD , xCOND , CLEAR_GOTO
.word mus_king_bowser_dynamic_1_026
.byte W01
If you look through each track in this song, you will notice that each track is 138 measures long, but has a normal section and a choral section, with a dividing line in-between each section. To use this method of dynamism, each track will need to be designed roughly like this, having two sections that you jump between. I wrote "choir" within each marker that relates to the choral section. That way, I can easily differentiate between the two sections of the track.
Now, you may notice that I haven't established any way of setting or clearing FLAG_DYNAMIC_CHANGE. That's because how that happens is entirely up to you. Do you want your music to change when the player starts walking, like the route music in Pokémon Black/White? Find the section of code that deals with making the player's avatar walk and put in a FlagSet call there, as well as a FlagClear call when the player stops walking. I assumed that a lot of people would want the music to change when the opponent in a Pokémon battle was at low health, so if you want that to be set up, do the following changes to src/battle_main.c:
Code:
static void HandleEndTurn_ContinueBattle(void)
{
s32 i;
if (gBattleControllerExecFlags == 0)
{
..................................
if((gBattleMons[1].species != SPECIES_NONE && gBattleMons[1].hp <= (gBattleMons[1].maxHP / 3) && gBattleMons[1].hp != 0) ← ← ← ← ← ← ← Add this if-statement
|| (gBattleMons[3].species != SPECIES_NONE && gBattleMons[3].hp <= (gBattleMons[3].maxHP / 3) && gBattleMons[1].hp != 0))
{
FlagSet(FLAG_DYNAMIC_CHANGE);
}
}
}
..........................................................................
..........................................................................
static void HandleEndTurn_FinishBattle(void)
{
......................................
FlagClear(FLAG_DYNAMIC_CHANGE); ← ← ← ← ← ← ← ← Add this function call at the end
}
Here's a video of this song in action. I set up two triggers, one that sets FLAG_DYNAMIC_CHANGE, and one that clears it when the player's avatar steps on it. I also show what measure the song is currently on based on the .s file and indicates whenever a song gets to a transition point, so you can see what happens depending on the current state of FLAG_DYNAMIC_CHANGE.
https://youtu.be/p4E7kY-oT-4
Random Tips
Spoiler:
1. If you have a track that doesn't change, regardless of whether the song has gone through a dynamic change or not, then you don't need to worry about adding the xCOND commands to that track. I could've done that with my song, but when I set up the choral and non-choral versions of the song, I mismatched which instruments went to which tracks, so I had to make sure all of the tracks transitioned, just to be safe. Don't be like me. Be smart.
2. There is actually a different way to go about these dynamic changes. This other method is notably easier than either of the prior listed methods, but it only works if you are only fading instruments in and out, not changing the notes of any of the instruments, and if you can commit each instrument to its own track. We have already programmed this into the ply_xcond function, so if you want to do this, here's how you'd do it. When you use the xCOND command, write this instead:
With this design, if you pass in SET_VOL, and FLAG_DYNAMIC_CHANGE is set, then this track's volume will be brought to zero, muting it. If, later on, you want to trigger this track to be audible again, you could write this:
When the GBA reads these commands, if FLAG_DYNAMIC_CHANGE is clear, it will make this track audible again. You can, of course, have the volume drop when the flag is clear and go up when the flag is set. That is just personal preference. This is much simpler than needing to write down a bunch of markers, effectively doubling the size of your .s file, but it is much more limited with what you can do in terms of dramatic changes in dynamism.
3. If you toy around with the structure of ply_xcond, it has a ton of versatility. If you expand the switch statement, you can have it check for different flags or use an int variable instead of flags, thus giving you room for further dynamic change. You could have 5 versions of the song that keep transitioning between themselves! You can also have cases that are used for applying BEND, MOD, or other commands. When you experiment, just know that, to skip over a command, you need to increment track->cmdPtr an equal number of times to the number of elements in the command. For example, if you wrote something like this:
To skip over that note command, you would need to increment three times, since you have to skip over "N36", "An2", and "v127".
You could push your creativity even further by just making your own extended commands. You could make a random wait command, which will wait a random amount of time before proceeding. This could create a creepy or off-kilter kind of vibe. You might even be able to make a command that creates visual effects that occur at certain points of a song. Go crazy! Come up with something cool!
2. There is actually a different way to go about these dynamic changes. This other method is notably easier than either of the prior listed methods, but it only works if you are only fading instruments in and out, not changing the notes of any of the instruments, and if you can commit each instrument to its own track. We have already programmed this into the ply_xcond function, so if you want to do this, here's how you'd do it. When you use the xCOND command, write this instead:
Code:
.byte XCMD , xCOND, SET_VOL
.byte VOL , 0
Code:
.byte XCMD , xCOND, CLEAR_VOL
.byte VOL , 127 (or whatever volume you want)
3. If you toy around with the structure of ply_xcond, it has a ton of versatility. If you expand the switch statement, you can have it check for different flags or use an int variable instead of flags, thus giving you room for further dynamic change. You could have 5 versions of the song that keep transitioning between themselves! You can also have cases that are used for applying BEND, MOD, or other commands. When you experiment, just know that, to skip over a command, you need to increment track->cmdPtr an equal number of times to the number of elements in the command. For example, if you wrote something like this:
Code:
.byte XCMD, xCOND , 5
.byte N36 , An2 , v127
You could push your creativity even further by just making your own extended commands. You could make a random wait command, which will wait a random amount of time before proceeding. This could create a creepy or off-kilter kind of vibe. You might even be able to make a command that creates visual effects that occur at certain points of a song. Go crazy! Come up with something cool!
Last edited: