CSNG (File Format)

The CSNG format contains MIDI data. It appears in Metroid Prime 1 and 2. It is essentially MusyX's SON music format, with a custom header.

Format
All offsets are relative to the start of the main header (after the custom header).

Timings are represented in ticks, like MIDI. Unlike MIDI, the tick-resolution is fixed at 384 ticks per beat (e.g. 120 beats-per-minute works out to  ticks-per-second).

Custom Header
This 0x14-byte header isn't part of the MusyX format; it appears at the start of the file. After parsing this the rest of the file is copied into a buffer and then passed to the MusyX functions.

Track Header
This is a variable-length table of headers for each track

Track Data
Here begins a free-form blob of indexed track data. It starts with a variable-length u32 array of SON offsets for each track, then the track data itself.

Track Commands
After the track data header, the actual playback commands begin. There are only 2 types of commands in SON: note and control change.

Delta Time RLE
Just like MIDI, each command starts with a delta time value telling the sequencer how many ticks to wait after the previous command. Unlike MIDI, Factor5 uses a custom RLE scheme to adaptively scale the value's precision to reduce the value's size.

The RLE operates on 16-bit words, with the value 0xffff triggering a continuation, then a 'dead' 16-bit word skipped over, then the 0xffff is summed with the following RLE value, looping the decode algorithm.

In Python, decoding works like so:

Note Command
When the two bytes following the delta-time != 0xffff, and the high-bit of the first byte is unset, this is a note command.

Unlike MIDI, which has separate commands for note-on/note-off, SON attaches a note length value to a note-on command, which is then able to track its own lifetime.

Control Change Command
When the two bytes following the delta-time != 0xffff, and the high-bit of the first byte is set, this is a control change command.

See the standard MIDI control numbers for details.

End Of Track
When the two bytes following the delta-time == 0xffff, this track has no more commands.

Continuous Pitch / Modulation Data
If the pitch or mod offsets in a track are non-zero, they point to a buffer of RLE-compressed (delta-tick, delta-value) pairs, decoding to signed 16-bit precision. The decoder must track the absolute time and value, summing each consecutive update for the current time/values.

The algorithm for this RLE is different than the delta-time one for commands. It may scale down to a single byte if able.

Channel Map
This is a simple u8 table mapping 64 SON tracks to 16 MIDI channels for instrument selection via the SongGroup MIDI-Setup.

Tempo Table
When the SON has a non-zero tempo table offset, this song features tempo changes. The change events are simple absolute-tick / BPM pairs.