Difference between revisions of "AGSC (File Format)"

From Retro Modding Wiki
Jump to: navigation, search
(Pool)
(Normal / Drum Page Entry)
 
(80 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
'''AGSC''' is the sound effect format for Metroid Prime and Metroid Prime 2: Echoes. Each AGSC file contains a group of sound effects. The first two Metroid Prime games utilize the MusyX audio engine created by Factor5 and as a result, AGSC files are essentially just embedded MusyX files.  
 
'''AGSC''' is the sound effect format for Metroid Prime and Metroid Prime 2: Echoes. Each AGSC file contains a group of sound effects. The first two Metroid Prime games utilize the MusyX audio engine created by Factor5 and as a result, AGSC files are essentially just embedded MusyX files.  
  
The audio codec used in AGSC is the standard GameCube DSP ADPCM codec.
+
The audio codec used in AGSC is the standard GameCube DSP-ADPCM codec, but MusyX itself also offers uncompressed PCM as an option.
  
{{researchmoderate
+
{{todo|Better descriptions for how SoundMacros work and a description for what each command does.}}
|reason=The proj chunk and the sdir chunk still need some reverse-engineering.
+
{{research|minor|The proj and sdir chunks have a couple unknowns left.}}
}}
+
  
 
__TOC__
 
__TOC__
Line 24: Line 23:
 
| 0x0
 
| 0x0
 
| '''D'''
 
| '''D'''
| '''Audio Directory'''. Always "Audio/." Zero-terminated.
+
| '''Audio Directory'''. Always "Audio/". Zero-terminated.
 
|-
 
|-
 
| 0x0 + D
 
| 0x0 + D
Line 50: Line 49:
 
|-
 
|-
 
| 0x4 + D
 
| 0x4 + D
 +
| 2
 +
| '''Group ID'''; 0xFFFF if unspecified
 +
|-
 +
| 0x6 + D
 
| 4
 
| 4
 
| '''Pool size'''
 
| '''Pool size'''
 
|-
 
|-
| 0x8 + D
+
| 0xA + D
 
| 4
 
| 4
 
| '''Project size'''
 
| '''Project size'''
 
|-
 
|-
| 0xC + D
+
| 0xE + D
 
| 4
 
| 4
| '''Sample size'''
+
| '''Sample directory size'''
 
|-
 
|-
| 0x10 + D
+
| 0x12 + D
 
| 4
 
| 4
| '''Sample directory size'''
+
| '''Sample size'''
 
|-
 
|-
| 0x14 + D
+
| 0x16 + D
 
| colspan=2 {{unknown|End of header}}
 
| colspan=2 {{unknown|End of header}}
 
|}
 
|}
Line 71: Line 74:
 
=== Pool ===
 
=== Pool ===
  
The Pool chunk denotes MusyX's "SoundMacros", small scripts that apply various effects and commands on the sounds in the game.  The chunk first calls out a command ID then the parameters of that particular command, which varies.
+
The Pool chunk contains sub-chunk tables for SoundMacros, ADSR, keymaps, and layers, if applicable. It starts with a 16-byte header before the different data tables begin.
 +
 
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''SoundMacros Offset''' (always 0x10)
 +
|-
 +
| 0x4
 +
| 4
 +
| '''Tables Offset'''
 +
|-
 +
| 0x8
 +
| 4
 +
| '''Keymaps Offset'''
 +
|-
 +
| 0xC
 +
| 4
 +
| '''Layers Offset'''
 +
|-
 +
| 0x10
 +
| colspan=2 {{unknown|End of entry}}
 +
|}
 +
 
 +
==== ObjectID ====
 +
 
 +
After this are four tables of objects. Each object is identified with a 16-bit '''ObjectID'''.
 +
 
 +
Factor5 designed ObjectIDs to used in a polymorphic manner. The top 2 bits of the ID are used
 +
to differentiate between SoundMacros, Keymaps, and Layers. If the ID passes a mask of 0x4000,
 +
the object is a ''Keymap''. If the ID passes a mask of 0x8000, the object is a ''Layer''.
 +
Otherwise, the object is assumed to be a ''SoundMacro''. ''Tables'' don't require this type of
 +
polymorphism due to the context in which they are accessed.
 +
 
 +
==== SoundMacros ====
 +
 
 +
The first Pool table denotes MusyX's '''SoundMacros''', small scripts that apply various effects on the sounds in the game.  Each macro is composed of a header followed by a number of commands; each command specifies its type through a single-byte command ID, then specifies the parameters of that particular command, which vary.
 +
 
 +
The header of each SoundMacro is eight bytes, and is structured as follows:
 +
 
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''Chunk Size''' ''(note: includes the size value itself)''
 +
|-
 +
| 0x4
 +
| 2
 +
| '''SoundMacro ObjectID'''
 +
|-
 +
| 0x6
 +
| 2
 +
| '''Padding'''
 +
|-
 +
| 0x8
 +
| colspan=2 {{unknown|Commands begin}}
 +
|}
  
Every 4 bytes were originally little endian, but have been swapped to big endian in the AGSC files (despite not being longs). To read the data as originally formatted, every four bytes needs to be byte-swapped.
+
On the commands, each 4 bytes were originally little endian, but have been swapped to big endian in the AGSC files (despite not being longs). To read the data as originally formatted, every four bytes needs to be byte-swapped. Each command is 8 bytes, and is structured as follows:
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 92: Line 157:
 
|}
 
|}
  
The SoundMacro terminates when the END command is read.  The command ID for END is 0 and has null command arguments; start the next SoundMacro after reading it.  The entire chunk terminates when 0xFFFF is read.
+
The SoundMacro will continue with commands until it terminates when the END command is executed.  The command ID for END is 0 and has null command arguments; the next SoundMacro begins after reading it.  
  
 
These are the possible commands:
 
These are the possible commands:
Line 378: Line 443:
 
|-
 
|-
 
| 0x29
 
| 0x29
 +
| UNTRAP_EVENT
 +
| Event
 +
| colspan="2" {{unknown|}}
 +
| colspan="2" {{unknown|}}
 +
| colspan="2" {{unknown|}}
 +
|-
 +
| 0x2A
 
| SEND_MESSAGE
 
| SEND_MESSAGE
 
| IsVar
 
| IsVar
 
| colspan="2" | Macro
 
| colspan="2" | Macro
 
| VID
 
| VID
| Varibale
+
| Variable
 
| colspan="2" {{unknown|}}
 
| colspan="2" {{unknown|}}
 
|-
 
|-
| 0x2A
+
| 0x2B
 
| GET_MESSAGE
 
| GET_MESSAGE
| Varibale
+
| Variable
 
| colspan="6" {{unknown|}}
 
| colspan="6" {{unknown|}}
 
|-
 
|-
| 0x2B
+
| 0x2C
 
| GET_VID
 
| GET_VID
| Varibale
+
| Variable
 
| PLAY_MACRO
 
| PLAY_MACRO
 
| colspan="5" {{unknown|}}
 
| colspan="5" {{unknown|}}
Line 420: Line 492:
 
| colspan="5" {{unknown|}}
 
| colspan="5" {{unknown|}}
 
|-
 
|-
| 0x34
+
| 0x36
 
| SETPRIORITY
 
| SETPRIORITY
 
| Prio
 
| Prio
 
| colspan="6" {{unknown|}}
 
| colspan="6" {{unknown|}}
 
|-
 
|-
| 0x35
+
| 0x37
 
| ADDPRIORITY
 
| ADDPRIORITY
 
| {{unknown|}}
 
| {{unknown|}}
Line 431: Line 503:
 
| colspan="4" {{unknown|}}
 
| colspan="4" {{unknown|}}
 
|-
 
|-
| 0x36
+
| 0x38
 
| AGECNTSPEED
 
| AGECNTSPEED
 
| colspan="3" {{unknown|}}
 
| colspan="3" {{unknown|}}
 
| colspan="4" | Time
 
| colspan="4" | Time
 
|-
 
|-
| 0x37
+
| 0x39
 
| AGECNTVEL
 
| AGECNTVEL
 
| {{unknown|}}
 
| {{unknown|}}
Line 650: Line 722:
 
| colspan="2" | Immediate
 
| colspan="2" | Immediate
 
| {{unknown|}}
 
| {{unknown|}}
 +
|-
 +
| 0x65
 +
| SET_VAR
 +
| Ctrl
 +
| A =
 +
| colspan="1" {{unknown|}}
 +
| colspan="2" | Immediate
 +
| colspan="2" {{unknown|}}
 
|-
 
|-
 
| 0x70
 
| 0x70
Line 673: Line 753:
 
| colspan="7" {{unknown|}}
 
| colspan="7" {{unknown|}}
 
|}
 
|}
 +
 +
After the last soundmacro, the table terminated by a value of 0xFFFF.
 +
 +
==== Tables ====
 +
 +
'''Tables''' have two functions: for defining curves for volume scaling, or to be used as ADSR envelopes.
 +
 +
The tables continue until 0xffffffff terminator is reached.
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''Chunk Size'''
 +
|-
 +
| 0x4
 +
| 2
 +
| '''Table ObjectID'''
 +
|-
 +
| 0x6
 +
| 2
 +
| '''Padding'''
 +
|-
 +
| Chunk Size
 +
| colspan=2 {{Unknown|ADSR/Curve data}}
 +
|}
 +
 +
===== ADSR =====
 +
 +
When the size of the table data is exactly 8, it may represent ADSR envelopes with this structure:
 +
 +
'''Note:''' All fields of the envelope are ''little endian''.
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 2
 +
| '''Attack time'''; in milliseconds
 +
|-
 +
| 0x2
 +
| 2
 +
| '''Decay time'''; in milliseconds
 +
|-
 +
| 0x4
 +
| 2
 +
| '''Sustain'''; percentage mapped between [0x0,0x1000]
 +
|-
 +
| 0x6
 +
| 2
 +
| '''Release time'''; in milliseconds
 +
|-
 +
| 0x8
 +
| colspan=2 {{unknown|End of ADSR}}
 +
|}
 +
 +
===== DLS ADSR =====
 +
 +
MusyX can also express more advanced envelopes using a modified [[wikipedia:DLS format|DLS]] representation.
 +
This representation includes scaling coefficients to respond to played note and velocity
 +
(so slamming down a key harder plays longer).
 +
 +
The attack and decay members are expressed in ''time-cents''. This may be converted to seconds using:
 +
<code>2<sup>timecents / (1200.0 * 65536.0)</sup></code>
 +
 +
The attack and decay scale members are expressed as 0.1% increments in 16.16 fixed-point.
 +
This may be converted to a normalized factor using:
 +
<code>scale / (1000.0 * 65536.0)</code>
 +
 +
'''Note:''' All fields of the envelope are ''little endian''.
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''Attack time'''; in time-cents
 +
|-
 +
| 0x4
 +
| 4
 +
| '''Decay time'''; in time-cents
 +
|-
 +
| 0x8
 +
| 2
 +
| '''Sustain'''; percentage mapped between [0x0,0x1000]
 +
|-
 +
| 0xA
 +
| 2
 +
| '''Release'''; in milliseconds
 +
|-
 +
| 0xC
 +
| 4
 +
| '''Velocity to Attack Scale'''; 0.1% increments as 16.16 fixed-point
 +
|-
 +
| 0x10
 +
| 4
 +
| '''Key to Decay Scale'''; 0.1% increments as 16.16 fixed-point
 +
|-
 +
| 0x14
 +
| colspan=2 {{unknown|End of DLS ADSR}}
 +
|}
 +
 +
===== Curves =====
 +
 +
To express a volume curve, the table data is simply an arbitrarily-sized table of <code>uint8_t</code> values (although typically in MIDI range [0,127])
 +
 +
==== Keymaps ====
 +
 +
'''Keymaps''' are swappable, fixed-length tables mapping 128 MIDI keys to sound-producing objects.
 +
 +
The keymaps continue until 0xffffffff terminator is reached.
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''Chunk Size'''; (usually 0x1032)
 +
|-
 +
| 0x4
 +
| 2
 +
| '''Keymap ObjectID'''
 +
|-
 +
| 0x6
 +
| 2
 +
| '''Padding'''
 +
|-
 +
| Chunk Size
 +
| colspan=2 {{Unknown|128 Keymap entries}}
 +
|}
 +
 +
===== Keymap Entry =====
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 2
 +
| '''ObjectID'''
 +
|-
 +
| 0x2
 +
| 1
 +
| '''Transpose'''
 +
|-
 +
| 0x3
 +
| 1
 +
| '''Pan'''
 +
|-
 +
| 0x4
 +
| 1
 +
| '''Priority Offset'''
 +
|-
 +
| 0x8
 +
| colspan=2 {{Unknown|Padded to 8 bytes}}
 +
|}
 +
 +
==== Layers ====
 +
 +
'''Layers''' are one-to-many, ranged keyboard mappings to sound-producing objects.
 +
 +
The layers continue until 0xffffffff terminator is reached.
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''Chunk Size'''
 +
|-
 +
| 0x4
 +
| 2
 +
| '''Layer ObjectID'''
 +
|-
 +
| 0x6
 +
| 2
 +
| '''Padding'''
 +
|-
 +
| Chunk Size
 +
| colspan=2 {{Unknown|Layer data}}
 +
|}
 +
 +
===== Layer Data =====
 +
 +
Within the layer data, there is a '''u32 count''' of layer range structs:
 +
 +
{| class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 2
 +
| '''ObjectID'''
 +
|-
 +
| 0x2
 +
| 1
 +
| '''Key Lo'''
 +
|-
 +
| 0x3
 +
| 1
 +
| '''Key Hi'''
 +
|-
 +
| 0x4
 +
| 1
 +
| '''Transpose'''
 +
|-
 +
| 0x5
 +
| 1
 +
| '''Volume'''
 +
|-
 +
| 0x6
 +
| 1
 +
| '''Priority Offset'''
 +
|-
 +
| 0x7
 +
| 1
 +
| '''Surround Pan'''; (0: extreme forward, 64: center, 127: extreme rearward)
 +
|-
 +
| 0x8
 +
| 1
 +
| '''Pan'''; (0: extreme left, 64: center, 127: extreme right)
 +
|-
 +
| 0xC
 +
| colspan=2 {{Unknown|Padded to 12 bytes}}
 +
|}
 +
 +
The entire Pool chunk is terminated by a value of 0xFFFF.
  
 
=== Project ===
 
=== Project ===
  
 
The Project properties chunk contains values for the sounds, including priority, polyphony, volume, etc.
 
The Project properties chunk contains values for the sounds, including priority, polyphony, volume, etc.
 +
 +
Structurally, the Project is the root of the Audio Group tree, defining one or more ''Song Groups'' or ''SFX Groups''
 +
 +
{|class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 4
 +
| '''Group end offset''' (points to next group in project)
 +
|-
 +
| 0x2
 +
| 2
 +
| '''Group ID'''
 +
|-
 +
| 0x4
 +
| 2
 +
| '''Group Type'''; 0 for SongGroup (for use with [[CSNG (File Format)|CSNG]]), 1 for SFXGroup.
 +
|-
 +
| 0x8
 +
| 4
 +
| '''SoundMacro ID table offset'''
 +
|-
 +
| 0xC
 +
| 4
 +
| '''Sample ID table offset'''
 +
|-
 +
| 0x10
 +
| 4
 +
| '''Tables table offset'''
 +
|-
 +
| 0x14
 +
| 4
 +
| '''Keymaps table offset'''
 +
|-
 +
| 0x18
 +
| 4
 +
| '''Layers table offset'''
 +
|-
 +
| 0x1C
 +
| 4
 +
| '''Normal page table''' (SongGroup) / '''SFX table offset''' (SFXGroup)
 +
|-
 +
| 0x20
 +
| 4
 +
| '''Drum page table offset''' (SongGroup)
 +
|-
 +
| 0x24
 +
| 4
 +
| '''MIDI Setup table offset''' (SongGroup)
 +
|-
 +
| 0x20
 +
| colspan=2 {{unknown|End of group header}}
 +
|}
 +
 +
After the header are a number of data tables.
 +
 +
==== SoundMacro ID Table ====
 +
 +
This is a ranged-table of shorts; there's no count value, so it's terminated with a value of 0xFFFF. It's a list of SoundMacro IDs present in the file. Contiguous ranges are expressed by IDs with most-significant bit set (0x8000). The range begins on the marked ID and incrementally reaches the next ID in the list, including that ID. All other IDs are singular.
 +
 +
==== Sample ID / Table / Keymap / Layer Tables ====
 +
 +
These function the same way as the SoundMacro ID table, but indexes other types of entities instead.
 +
 +
'''Note:''' Keymap and Layer IDs in these tables have their top 2 bits (indicating their type) masked off.
 +
Keymaps must be OR'd with 0x4000 and Layers must be OR'd with 0x8000 in order to reconstruct the actual IDs.
 +
 +
==== Normal / Drum Page Entry ====
 +
 +
Used to map [https://www.midi.org/specifications/item/gm-level-1-sound-set General MIDI program numbers] (instruments)
 +
to sound entities (macros, keymaps, layers)
 +
 +
{|class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 2
 +
| '''ObjectID'''
 +
|-
 +
| 0x2
 +
| 1
 +
| '''Priority'''; voices are limited, so priority is used to play more important sounds over others
 +
|-
 +
| 0x3
 +
| 1
 +
| '''Max number of voices'''
 +
|-
 +
| 0x4
 +
| 1
 +
| '''GM Program Number'''
 +
|-
 +
| 0x5
 +
| 1
 +
| '''Padding'''
 +
|}
 +
 +
'''Note:''' The drum table is accessed when the MIDI channel is 10, otherwise the normal table is accessed.
 +
 +
==== SFX Entry ====
 +
 +
Used to map auto-generated <code>#define</code> IDs (used by game code) to sound entities (macros, keymaps, layers)
 +
 +
This table begins with a 16-bit count value, then 16 bits of padding. Each entry in the table is 10 bytes.
 +
 +
{|class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 2
 +
| '''DefineID'''; referenced by game code
 +
|-
 +
| 0x2
 +
| 2
 +
| '''ObjectID'''
 +
|-
 +
| 0x4
 +
| 1
 +
| '''Priority'''; voices are limited, so priority is used to play more important sounds over others
 +
|-
 +
| 0x5
 +
| 1
 +
| '''Max number of voices'''
 +
|-
 +
| 0x6
 +
| 1
 +
| '''Definite Velocity'''; volume (usually 7F)
 +
|-
 +
| 0x7
 +
| 1
 +
| '''Panning'''
 +
|-
 +
| 0x8
 +
| 1
 +
| '''Definite Key'''; The default pitch - usually 0x3C (MIDI C4)
 +
|-
 +
| 0x9
 +
| 1
 +
| '''Padding'''
 +
|}
 +
 +
==== MIDI Setup Entry ====
 +
 +
Table of fixed-length tables to map all 16 MIDI channels to program numbers
 +
(in-turn resolving to sound entities via the page table).
 +
 +
Multiple MIDI Setups may be created to support Song data requiring totally different
 +
banks of instruments.
 +
 +
Each MIDI Setup starts with a u16 '''MIDI-Setup-ID''', then 16-bits padding, then 16 entries of the following structure (one for each channel):
 +
 +
{|class="wikitable"
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 1
 +
| '''Program Number'''
 +
|-
 +
| 0x1
 +
| 1
 +
| '''Volume'''
 +
|-
 +
| 0x2
 +
| 1
 +
| '''Panning'''
 +
|-
 +
| 0x3
 +
| 1
 +
| '''Reverb'''
 +
|-
 +
| 0x4
 +
| 1
 +
| '''Chorus'''
 +
|}
 +
 +
MIDI setups continue until the ''group end offset'' is reached.
  
 
=== Sample ===
 
=== Sample ===
Line 697: Line 1,200:
 
| 0x0
 
| 0x0
 
| 2
 
| 2
| '''Sound ID''' (note: not 100% confirmed)
+
| '''Sound ID'''
 
|-
 
|-
 
| 0x2
 
| 0x2
 
| 2
 
| 2
| Padding.
+
| '''Padding'''; always 0
 
|-
 
|-
 
| 0x4
 
| 0x4
Line 712: Line 1,215:
 
|-
 
|-
 
| 0xC
 
| 0xC
| 2
+
| 1
| {{unknown|'''Unknown'''; appears to always be 0x3C00}}
+
| '''Base Note'''; Corresponds to the MIDI note played in the sample, at the native sample-rate (which MusyX obtains from the INST chunk of .aiff files or SMPL chunk of .wav files, along with looping info). To play at a specified pitch in [[wikipedia:Cent (music)|cents]], set the playback sample rate using this formula: <code>sampleRate * 2<sup>((pitch - baseNote * 100) / 1200.0)</sup></code>
 +
|-
 +
| 0xD
 +
| 1
 +
| '''Padding'''; always 0
 
|-
 
|-
 
| 0xE
 
| 0xE
Line 720: Line 1,227:
 
|-
 
|-
 
| 0x10
 
| 0x10
| 4
+
| 1
 +
| '''Audio format''' <ol start="0"><li>DSP-ADPCM</li><li>DSP-ADPCM (Drum Sample)</li><li>PCM</li><li>N64-VADPCM (Legacy Format)</li></ol>
 +
|-
 +
| 0x11
 +
| 3
 
| '''Number of samples'''
 
| '''Number of samples'''
 
|-
 
|-
Line 750: Line 1,261:
 
| 0x0
 
| 0x0
 
| 2
 
| 2
| {{unknown|'''Unknown'''}}
+
| {{unknown|'''Unknown'''; always 8}}
 
|-
 
|-
 
| 0x2
 
| 0x2
| 2
+
| 1
| {{unknown|'''Unknown'''}}
+
| '''Initial predictor/scale''' (matches first frame header)
 +
|-
 +
| 0x3
 +
| 1
 +
| '''Loop predictor/scale''' (matches loop start frame header)
 
|-
 
|-
 
| 0x4
 
| 0x4
| 4
+
| 2
| {{unknown|'''Unknown'''}}
+
| '''Loop context sample history 2'''
 +
|-
 +
| 0x6
 +
| 2
 +
| '''Loop context sample history 1'''
 
|-
 
|-
 
| 0x8
 
| 0x8
Line 770: Line 1,289:
 
== Tools ==
 
== Tools ==
  
* [https://drive.google.com/file/d/0B9MLV21H7SDvemgwN1daYnliSjA/view?usp=sharing Prime Audio Decoder] by Parax will dump all sound effects contained in a given AGSC file.
+
* [https://drive.google.com/file/d/0B9MLV21H7SDvemgwN1daYnliSjA/view?usp=sharing Prime Audio Decoder] by Aruki will dump all sound effects contained in a given AGSC file.
  
 
[[Category:File Formats]]
 
[[Category:File Formats]]

Latest revision as of 14:56, 16 August 2018

AGSC is the sound effect format for Metroid Prime and Metroid Prime 2: Echoes. Each AGSC file contains a group of sound effects. The first two Metroid Prime games utilize the MusyX audio engine created by Factor5 and as a result, AGSC files are essentially just embedded MusyX files.

The audio codec used in AGSC is the standard GameCube DSP-ADPCM codec, but MusyX itself also offers uncompressed PCM as an option.


GravitySuitIcon.png To do:
Better descriptions for how SoundMacros work and a description for what each command does.
Morphball render.png This file format is almost completely documented
The proj and sdir chunks have a couple unknowns left.


Format

The AGSC format is essentially four data chunks combined into one resource, each of which is a standard MusyX file. Of the four data chunks (pool, proj, samp, and sdir), there's one for sound engine scripts, one for sound properties, one for actual ADPCM sound data, and one for sound metadata. The main difference between Prime 1 and 2 is the header, and some slight changes in the way the four chunks are organized. In Metroid Prime, each chunk begins with its own size value; in Metroid Prime 2, every chunk instead has its size listed at the beginning of the file, at the end of the header. In addition, in Metroid Prime, the third chunk is samp, and the fourth is sdir; in Metroid Prime 2, it's the other way around.

Header

Metroid Prime

Offset Size Description
0x0 D Audio Directory. Always "Audio/". Zero-terminated.
0x0 + D N Audio Group Name. Zero-terminated.
0x0 + D + N End of header

Metroid Prime 2

Offset Size Description
0x0 4 Unknown; always 1
0x4 D Audio Group Name. Zero-terminated.
0x4 + D 2 Group ID; 0xFFFF if unspecified
0x6 + D 4 Pool size
0xA + D 4 Project size
0xE + D 4 Sample directory size
0x12 + D 4 Sample size
0x16 + D End of header

Pool

The Pool chunk contains sub-chunk tables for SoundMacros, ADSR, keymaps, and layers, if applicable. It starts with a 16-byte header before the different data tables begin.

Offset Size Description
0x0 4 SoundMacros Offset (always 0x10)
0x4 4 Tables Offset
0x8 4 Keymaps Offset
0xC 4 Layers Offset
0x10 End of entry

ObjectID

After this are four tables of objects. Each object is identified with a 16-bit ObjectID.

Factor5 designed ObjectIDs to used in a polymorphic manner. The top 2 bits of the ID are used to differentiate between SoundMacros, Keymaps, and Layers. If the ID passes a mask of 0x4000, the object is a Keymap. If the ID passes a mask of 0x8000, the object is a Layer. Otherwise, the object is assumed to be a SoundMacro. Tables don't require this type of polymorphism due to the context in which they are accessed.

SoundMacros

The first Pool table denotes MusyX's SoundMacros, small scripts that apply various effects on the sounds in the game. Each macro is composed of a header followed by a number of commands; each command specifies its type through a single-byte command ID, then specifies the parameters of that particular command, which vary.

The header of each SoundMacro is eight bytes, and is structured as follows:

Offset Size Description
0x0 4 Chunk Size (note: includes the size value itself)
0x4 2 SoundMacro ObjectID
0x6 2 Padding
0x8 Commands begin

On the commands, each 4 bytes were originally little endian, but have been swapped to big endian in the AGSC files (despite not being longs). To read the data as originally formatted, every four bytes needs to be byte-swapped. Each command is 8 bytes, and is structured as follows:

Offset Size Description
0x0 1 Command ID (varies; there are 79 known commands in the MusyX audio engine.)
0x1 7 Command arguments (varies between commands)
0x8 End of entry

The SoundMacro will continue with commands until it terminates when the END command is executed. The command ID for END is 0 and has null command arguments; the next SoundMacro begins after reading it.

These are the possible commands:

ID Name Arguments
0x1 STOP
0x2 SPLITKEY Keynumber SoundMacro ID SoundMacro step
0x3 SPLITVEL Velocity SoundMacro ID SoundMacro step
0x4 WAIT_TICKS Keyoff Random Sampleend Absolute ms switch Ticks/Millisec.
0x5 LOOP Keyoff Random Sampleend SoundMacro step Times
0x6 GOTO SoundMacro ID SoundMacro step
0x7 WAIT_MS Keyoff Random Sample end Absolute Millisec.
0x8 PLAYMACRO Addnote SoundMacro ID SoundMacro step Priority MaxVoices
0x9 SENDKEYOFF Variable Last started
0xA SPLITMOD Mod value SoundMacro ID SoundMacro step
0xB PIANOPAN Scale Centerkey Centerpan
0xC SETADSR Table-ID (ADSR) DLS mode
0xD SCALEVOLUME Scale Add Table-ID (Curve) Org vol
0xE PANNING Pan position Time ms Width
0xF ENVELOPE Scale Add Table-ID (Curve) Org vol
0x10 STARTSAMPLE Sample-ID Mode Offset
0x11 STOPSAMPLE
0x12 KEYOFF
0x13 SPLITRND RND SoundMacro ID SoundMacro step
0x14 FADE-IN Scale Add Table-ID (Curve) ms switch Ticks/Millisec.
0x15 SPANNING Pan position Time ms Width
0x16 SETADSRCTRL Attack Decay Sustain Relase
0x17 RNDNOTE Note Lo Detune Note Hi Fixed/Free Abs/Rel
0x18 ADDNOTE Add Detune org key ms switch Ticks/Millisec.
0x19 SETNOTE Key Detune ms switch Ticks/Millisec.
0x1A LASTNOTE Add detune ms switch Ticks/Millisec.
0x1B PORTAMENTO Port. State Port. Type ms switch Ticks/Millisec.
0x1C VIBRATO Level note Level fine Modwheel flag ms switch Ticks/Millisec.
0x1D PITCHSWEEP1 Times Add ms switch Ticks/Millisec.
0x1E PITCHSWEEP2 Times Add ms switch Ticks/Millisec.
0x1F SETPITCH Frequency (Hz) Fine
0x20 SETPITCHADSR Table-ID (ADSR) Note range Detune range
0x21 SCALEVOLUME DLS Scale Org vol
0x22 MOD2VIBRANGE Key range Cent range
0x23 SETUP TREMOLO Tremolo scale Modw. add scale
0x24 RETURN
0x25 GOSUB SoundMacro ID SoundMacro step
0x28 TRAP_EVENT Event SoundMacro ID SoundMacro step
0x29 UNTRAP_EVENT Event
0x2A SEND_MESSAGE IsVar Macro VID Variable
0x2B GET_MESSAGE Variable
0x2C GET_VID Variable PLAY_MACRO
0x30 ADDAGECOUNT Add
0x31 SETAGECOUNT Counter
0x32 SENDFLAG Flag-ID Value
0x33 PITCHWHEELR Range up Range down
0x36 SETPRIORITY Prio
0x37 ADDPRIORITY Add
0x38 AGECNTSPEED Time
0x39 AGECNTVEL AGE Base AGE Scale
0x40 VOL_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x41 PAN_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x42 PitchW_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x43 ModW_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x44 PEDAL_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x45 PORTA_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x46 REVERB_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x47 SPAN_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x48 DOPPLER_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x49 TREMOLO_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x4A PREA_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x4B PREB_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x4C POSTB_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x4D AUXAFX_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x4E AUXBFX_SELECT MIDI Contr. Scaling percentage Combine is var. Fine scaling
0x50 SETUP_LFO LFO Nr. Period in ms
0x58 MODE_SELECT DLS vol ITD
0x59 SET_KEYGROUP group kill
0x5A SRCMODE_SELECT SRC type Type 0 SRC filter
0x60 ADD_VARS Var/Ctrl A = Var/Ctrl B + Var/Ctrl C
0x61 SUB_VARS Var/Ctrl A = Var/Ctrl B - Var/Ctrl C
0x62 MUL_VARS Var/Ctrl A = Var/Ctrl B * Var/Ctrl C
0x63 DIV_VARS Var/Ctrl A = Var/Ctrl B / Var/Ctrl C
0x64 ADDI_VARS Var/Ctrl A = Var/Ctrl B + Immediate
0x65 SET_VAR Ctrl A = Immediate
0x70 IF_EQUAL Ctrl A == Ctrl B Not SoundMacro Step
0x71 IF_LESS Ctrl A < Ctrl B Not SoundMacro Step
0x0 END

After the last soundmacro, the table terminated by a value of 0xFFFF.

Tables

Tables have two functions: for defining curves for volume scaling, or to be used as ADSR envelopes.

The tables continue until 0xffffffff terminator is reached.

Offset Size Description
0x0 4 Chunk Size
0x4 2 Table ObjectID
0x6 2 Padding
Chunk Size ADSR/Curve data
ADSR

When the size of the table data is exactly 8, it may represent ADSR envelopes with this structure:

Note: All fields of the envelope are little endian.

Offset Size Description
0x0 2 Attack time; in milliseconds
0x2 2 Decay time; in milliseconds
0x4 2 Sustain; percentage mapped between [0x0,0x1000]
0x6 2 Release time; in milliseconds
0x8 End of ADSR
DLS ADSR

MusyX can also express more advanced envelopes using a modified DLS representation. This representation includes scaling coefficients to respond to played note and velocity (so slamming down a key harder plays longer).

The attack and decay members are expressed in time-cents. This may be converted to seconds using: 2timecents / (1200.0 * 65536.0)

The attack and decay scale members are expressed as 0.1% increments in 16.16 fixed-point. This may be converted to a normalized factor using: scale / (1000.0 * 65536.0)

Note: All fields of the envelope are little endian.

Offset Size Description
0x0 4 Attack time; in time-cents
0x4 4 Decay time; in time-cents
0x8 2 Sustain; percentage mapped between [0x0,0x1000]
0xA 2 Release; in milliseconds
0xC 4 Velocity to Attack Scale; 0.1% increments as 16.16 fixed-point
0x10 4 Key to Decay Scale; 0.1% increments as 16.16 fixed-point
0x14 End of DLS ADSR
Curves

To express a volume curve, the table data is simply an arbitrarily-sized table of uint8_t values (although typically in MIDI range [0,127])

Keymaps

Keymaps are swappable, fixed-length tables mapping 128 MIDI keys to sound-producing objects.

The keymaps continue until 0xffffffff terminator is reached.

Offset Size Description
0x0 4 Chunk Size; (usually 0x1032)
0x4 2 Keymap ObjectID
0x6 2 Padding
Chunk Size 128 Keymap entries
Keymap Entry
Offset Size Description
0x0 2 ObjectID
0x2 1 Transpose
0x3 1 Pan
0x4 1 Priority Offset
0x8 Padded to 8 bytes

Layers

Layers are one-to-many, ranged keyboard mappings to sound-producing objects.

The layers continue until 0xffffffff terminator is reached.

Offset Size Description
0x0 4 Chunk Size
0x4 2 Layer ObjectID
0x6 2 Padding
Chunk Size Layer data
Layer Data

Within the layer data, there is a u32 count of layer range structs:

Offset Size Description
0x0 2 ObjectID
0x2 1 Key Lo
0x3 1 Key Hi
0x4 1 Transpose
0x5 1 Volume
0x6 1 Priority Offset
0x7 1 Surround Pan; (0: extreme forward, 64: center, 127: extreme rearward)
0x8 1 Pan; (0: extreme left, 64: center, 127: extreme right)
0xC Padded to 12 bytes

The entire Pool chunk is terminated by a value of 0xFFFF.

Project

The Project properties chunk contains values for the sounds, including priority, polyphony, volume, etc.

Structurally, the Project is the root of the Audio Group tree, defining one or more Song Groups or SFX Groups

Offset Size Description
0x0 4 Group end offset (points to next group in project)
0x2 2 Group ID
0x4 2 Group Type; 0 for SongGroup (for use with CSNG), 1 for SFXGroup.
0x8 4 SoundMacro ID table offset
0xC 4 Sample ID table offset
0x10 4 Tables table offset
0x14 4 Keymaps table offset
0x18 4 Layers table offset
0x1C 4 Normal page table (SongGroup) / SFX table offset (SFXGroup)
0x20 4 Drum page table offset (SongGroup)
0x24 4 MIDI Setup table offset (SongGroup)
0x20 End of group header

After the header are a number of data tables.

SoundMacro ID Table

This is a ranged-table of shorts; there's no count value, so it's terminated with a value of 0xFFFF. It's a list of SoundMacro IDs present in the file. Contiguous ranges are expressed by IDs with most-significant bit set (0x8000). The range begins on the marked ID and incrementally reaches the next ID in the list, including that ID. All other IDs are singular.

Sample ID / Table / Keymap / Layer Tables

These function the same way as the SoundMacro ID table, but indexes other types of entities instead.

Note: Keymap and Layer IDs in these tables have their top 2 bits (indicating their type) masked off. Keymaps must be OR'd with 0x4000 and Layers must be OR'd with 0x8000 in order to reconstruct the actual IDs.

Normal / Drum Page Entry

Used to map General MIDI program numbers (instruments) to sound entities (macros, keymaps, layers)

Offset Size Description
0x0 2 ObjectID
0x2 1 Priority; voices are limited, so priority is used to play more important sounds over others
0x3 1 Max number of voices
0x4 1 GM Program Number
0x5 1 Padding

Note: The drum table is accessed when the MIDI channel is 10, otherwise the normal table is accessed.

SFX Entry

Used to map auto-generated #define IDs (used by game code) to sound entities (macros, keymaps, layers)

This table begins with a 16-bit count value, then 16 bits of padding. Each entry in the table is 10 bytes.

Offset Size Description
0x0 2 DefineID; referenced by game code
0x2 2 ObjectID
0x4 1 Priority; voices are limited, so priority is used to play more important sounds over others
0x5 1 Max number of voices
0x6 1 Definite Velocity; volume (usually 7F)
0x7 1 Panning
0x8 1 Definite Key; The default pitch - usually 0x3C (MIDI C4)
0x9 1 Padding

MIDI Setup Entry

Table of fixed-length tables to map all 16 MIDI channels to program numbers (in-turn resolving to sound entities via the page table).

Multiple MIDI Setups may be created to support Song data requiring totally different banks of instruments.

Each MIDI Setup starts with a u16 MIDI-Setup-ID, then 16-bits padding, then 16 entries of the following structure (one for each channel):

Offset Size Description
0x0 1 Program Number
0x1 1 Volume
0x2 1 Panning
0x3 1 Reverb
0x4 1 Chorus

MIDI setups continue until the group end offset is reached.

Sample

The Sample chunk is all the sound data encoded using the standard Gamecube DSP ADPCM codec. It can be decoded the same way as a DSP file. Each sound's size is padded to 32 bytes before the next sound's data begins.

Sample Directory

The Sample Directory chunk (chunk 4 in Metroid Prime, chunk 3 in Metroid Prime 2) is made up of two sets of tables. The structure of both these tables is identical between both games.

Table A

The first metadata table has one entry per sound, and is terminated with 0xFFFFFFFF; since there's no known sound count anywhere in the file, the only way to read this correctly is to read until you reach the terminator value. Each entry is 0x20 bytes long.

Offset Size Description
0x0 2 Sound ID
0x2 2 Padding; always 0
0x4 4 Sound start offset, relative to the start of the ADPCM chunk
0x8 4 Unknown
0xC 1 Base Note; Corresponds to the MIDI note played in the sample, at the native sample-rate (which MusyX obtains from the INST chunk of .aiff files or SMPL chunk of .wav files, along with looping info). To play at a specified pitch in cents, set the playback sample rate using this formula: sampleRate * 2((pitch - baseNote * 100) / 1200.0)
0xD 1 Padding; always 0
0xE 2 Sample rate
0x10 1 Audio format
  1. DSP-ADPCM
  2. DSP-ADPCM (Drum Sample)
  3. PCM
  4. N64-VADPCM (Legacy Format)
0x11 3 Number of samples
0x14 4 Loop start sample
0x18 4 Loop length, in samples. To get the loop end sample, add this to the start sample and subtract 1.
0x1C 4 Table B entry offset, relative to the start of the sound metadata chunk
0x20 End of entry

Table B

These are accessed through the offsets in table A's entries; note that it might not match the sound count, because the same entry in this table can be used with multiple sounds. Each entry is 0x28 bytes long.

Offset Size Description
0x0 2 Unknown; always 8
0x2 1 Initial predictor/scale (matches first frame header)
0x3 1 Loop predictor/scale (matches loop start frame header)
0x4 2 Loop context sample history 2
0x6 2 Loop context sample history 1
0x8 2 × 16 Decode coefficients
0x28 End of entry

Tools