The research is based on GAX Engine V3. Information about V2 can be found at the end of the document.
FYI: GaXM is a tool that can analyze GAX data. You may find more information there.
FYI: You can find my IDA FLIRT signature for GAX V3 here. https://github.com/loveemu/ida-sig
FYI: Gaxtapper: Diagnostic tool / Automated GSF ripper for GAX Sound Engine.
The data is normally stored somewhere on a GBA ROM in this order:
- Instrument definitions
- Data for each instruments
- Part A
- Part B
- Instrument header
- Instrument entries (address table)
- Data for each instruments
- Sample definitions
- Sample pool
- Sample entries (address/size table)
- First entry indicates the start address of sample pool?
- Second entry indicates the end address of sample pool?
- Music data for each songs
- Sequence data for each patterns
- Title / Copyright string
- Pattern table for each tracks
- Song header
- SFX definitions
- SFX instruments
- SFX sample pool & sample entries
- SFX top-level header (same structure as the song header)
- Parameters / read-only data for sound driver code
TBA
An entry is a pointer to an instrument header (uint32).
Samples are signed 8-bit PCM.
An entry is structured as follows.
Name | Type |
---|---|
Address | uint32 |
Size | uint32 |
Whole header size is about 200 bytes long (depends on the version).
Name | Type | Count | Comment |
---|---|---|---|
Number of Channels | uint16 | 1 | |
Number of Rows per Pattern | uint16 | 1 | |
Number of Patterns per Channel | uint16 | 1 | |
Loop Point | uint16 | 1 | Pattern index starting from 0 |
Master Volume | uint16 | 1 | Standard volume is 0x100 |
Reserved | uint16 | 1 | 0 |
Pointer to Sequence Data | uint32 | 1 | Must be accessed via Pattern Table |
Pointer to Instrument Set | uint32 | 1 | Link to instrument entries |
Pointer to Sample Set | uint32 | 1 | Link to sample entries |
Mixing Rate (Hz) | uint16 | 1 | Can be programmatically overridden |
FX Mixing Rate (Hz) | uint16 | 1 | 0 for the same rate as music. Can be programmatically overridden |
Number of FX Slots | uint8 | 1 | Up to 4? Can be programmatically overridden |
Reserved | uint8 | 1 | 0 |
Reserved | uint16 | 1 | 0 |
Pointer to Pattern Table | uint32[] | N | Playlist for each channels |
Padding (filled by zero) | uint32[] | N |
Available mixing rates: 5735, 9079, 10513, 11469, 13380, 15769, 18158, 21025, 26760, 31537, 36316, 40138, 42049
If a mixing rate different from the above is specified, the lowest rate that exceeds the given rate will be selected.
Collections that contain shared samples, such as FX, are represented by song headers with a channel count of zero.
TBA
Meta information for the song. For instance:
"Title Theme" © Martin Schioeler
Note that the text is NOT null-terminated. The end of string must be dword-aligned (padded with zero).
You may like to scan these strings by regular expression as follows. (Note: This regular expression pattern is not perfect because this text sometimes contains iso-8859-1 characters that are not in ASCII.)
"[ -~]+?" © [ -~]+
As far as I know, the string isn't referenced from anywhere. It's usually located just before the first pattern table.
The pattern table is a list of pattern entries. An entry is a 32-bit long integer as follows.
Name | Type | Comment |
---|---|---|
Pattern Offset | uint16 | Offset from the beginning of Sequence Data |
Transpose | int8 | In semitones |
Reserved | uint8 | 0 |
Note that the symbol names are for convenience only and are not the actual names.
// The details of the structure vary from version to version.
// The following is for Maya The Bee: Sweet Gold.
struct Gax2Params {
void *wram;
uint32_t wram_size;
int16_t mixing_rate;
int16_t fx_mixing_rate;
int16_t field_C;
int16_t flags;
int16_t num_fx_channels;
int16_t volume;
uint8_t unknown_14[0x18];
const void *global_samples;
const void *music;
int32_t field_34;
bool8_t debug_assert;
uint8_t field_39;
uint8_t field_3A;
uint8_t field_3B;
};
void gax2_estimate(Gax2Params *params);
void gax2_new(Gax2Params *params);
bool gax2_init(Gax2Params *params);
bool gax2_jingle(const Gax2SongHeader *);
void gax_irq();
void gax_play();
int gax_fx(uint8_t fxid);
void gax2_fx(Gax2FxParams *fxparams);
void gax2_new_fx(Gax2FxParams *fxparams);
size_t gax_save_fx(int fxchannel, void *buf);
void gax_restore_fx(int fxchannel, const void *buf);
void AgbMain() {
// Initialization omitted
REG_IME = 0;
Gax2Param params;
gax2_new(¶ms);
// params.mixing_rate = -1;
// params.fx_mixing_rate = -1;
// params.flags = 0;
// params.num_fx_channels = -1;
// params.volume = -1;
params.song = song_address;
params.sfx = sfx_address;
params.debug_assert = true;
gax2_estimate(¶ms);
params.wram = malloc(params.wram_size);
gax2_init(¶ms);
REG_DISPSTAT = DSTAT_VBL_IRQ | DSTAT_VCT_IRQ | DSTAT_VCT(10);
REG_IE = IRQ_VBLANK | IRQ_VCOUNT;
REG_IME = 1;
while (1) {
Halt();
}
}
void VBlankIntr() {
gax_irq();
}
void VCountIntr() {
// It is not mandatory to use the VCOUNT interrupt.
// gax_play can be called synchronously in the main function.
gax_play();
}
While GAX V2 and GAX V3 share most of the same APIs, their data structures are different. Apparently, V2 has a more programmable and complex structure.
The research was done primarily for Bruce Lee: Return of the Legend and the details may be different for other games.
The top-level structure of song has the following layout.
struct Gax2SongEntry {
int num_handers;
const Gax2SoundHandler* handler_0;
const Gax2SoundHandler* handler_1;
const Gax2SongEntry* handler_2;
const Gax2SoundHandler* handler_3;
const Gax2SoundHandler* more_handlers[];
};
The number of handlers must be at least 4, or 0 if you want to represent empty data.
The handler defines how the sound should be processed, and holds several function pointers and parameters.
struct Gax2SoundHandler {
Gax2InitHandlerFunc init_handler;
Gax2UnknownHandlerFunc unknown_handler;
Gax2PlayHandlerFunc play_handler;
int num_related_handlers;
const Gax2SoundHandler* related_handlers; // details of the relationship and usage are still unknown
int field_14;
const void* data; // can be song header, pattern table, etc.
};
In the actual example, it looks like the first three handlers come first, followed by the handlers for each channel.
struct Gax2ExampleSongEntry {
int num_handers = 9;
const Gax2SoundHandler* patterns_handler;
const Gax2SoundHandler* song_header_handler;
const Gax2SongEntry* unknown_handler;
const Gax2SoundHandler* channel_1_handler;
const Gax2SoundHandler* channel_2_handler;
const Gax2SoundHandler* channel_3_handler;
const Gax2SoundHandler* channel_4_handler;
const Gax2SoundHandler* channel_5_handler;
const Gax2SoundHandler* channel_6_handler;
};
The structure of the song header is almost identical to V3, but V2 does not have references to each channel.
Name | Type | Count | Comment |
---|---|---|---|
Number of Channels | uint16 | 1 | |
Number of Rows per Pattern | uint16 | 1 | |
Number of Patterns per Channel | uint16 | 1 | |
Loop Point | uint16 | 1 | Pattern index starting from 0 |
Master Volume | uint16 | 1 | Standard volume is 0x100 |
Reserved | uint16 | 1 | 0 |
Pointer to Sequence Data | uint32 | 1 | Must be accessed via Pattern Table |
Pointer to Instrument Set | uint32 | 1 | Link to instrument entries |
Pointer to Sample Set | uint32 | 1 | Link to sample entries |
Mixing Rate (Hz) | uint16 | 1 | Can be programmatically overridden |
Number of FX Slots | uint8 | 1 | Up to 4? Can be programmatically overridden |
Reserved | uint8 | 1 | 0 |
Unknown Pointer | uint32 | 1 | Not available before GAX 2.3 |
As in GAX V3, a song header with zero channels is used.
This entry is not only necessary for playing sound effects, but apparently it also affects the result of music playback. If you omit the pointer to this entry, some instruments may be missing.
@ The root of the whole structure is placed last
word_8263F3C:
.2byte 0 @ number of channels is 0
.2byte 0
.2byte 0
.2byte 0
.2byte 0xD0
.2byte 0
.4byte 0
.4byte off_820D820 @ instrument pointers
.4byte stru_8263AD4 @ sample entries
.2byte 0
.1byte 0
.1byte 0
.4byte 0
stru_8263F5C:
.4byte 0
stru_8263F60:
.4byte sub_8137AB0+1
.4byte nullsub_1+1
.4byte sub_8137B38+1
.4byte 1
.4byte stru_8263F5C
.4byte 0x4C
.4byte stru_8263F3C
gaxSampleSet: @ The length of the array is variable (or may vary from version to version)
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
.4byte stru_8263F60
The format of the 4-byte pattern is the same as V3. The pointer to the pattern table is recorded in the handler of each channel.
As in V3, the title text is embedded in the song. In the case of V2, the text will be placed just before the pattern table of the earliest appearing channel (i.e. the channel with the smallest address).
Sample data in GAX v1 is indeed signed 8-bit PCM, but from GAX 2 and onwards the sample data is unsigned 8-bit PCM.