Introduction
AKAO sequence is similar to MIDI sequence - it’s custom tracker format for playing sequence sound, well tuned specially for PSX.
File Structure
Header (size: 64 bytes)
struct AkaoSeqHeader
{
static const uint8_t magic[4]; // "AKAO" C-string
uint16_t id; // song ID, used for playing sequence
uint16_t length; // data length (including this header)
uint16_t reverb_type; // reverb type (range from 0 to 9)
uint8_t field_0A[6];
uint32_t field_10;
uint16_t sample_set_id; // associated sample set ID
uint16_t field_12;
uint32_t field_14;
uint32_t field_18;
uint32_t field_1C;
uint32_t mask; // represents bitmask of used channels in this song
uint32_t field_24;
uint32_t field_28;
uint32_t field_2C;
uint32_t instrument_map_offset; // relative offset to custom instrument map (0 if unused)
uint32_t drum_map_offset; // relative offset to custom drum map (0 if unused)
uint32_t field_38;
uint32_t field_3C;
};
Channel Offsets (size: 2 bytes * )
struct AkaoChannelInfo
{
uint32_t start_offsets[num_channels]; // offsets to channel opcode data
};
AkaoSeqHeader *header;
int num_channels = 0;
while (int bit = 0; bit < 32; bit++)
if (info.mask & (1 << bit)) != 0)
num_channels++;
There is
Channel Commands [AKAO Opcodes]
For every channels in an AKAO sequence, there is a set of commands to perform. This is similar to Field opcodes. Here I will call this sound commands “opcodes”. Every opcode has its own number of arguments.
Custom Instrument Map Table
When a song uses custom instruments with opcode 0xFE 0x14, a custom instrument map table will be placed after the end of AKAO opcode sequence.
This table consists of a collection of zones representing performance settings for a particular key range. A zone is an 8-byte item, and an instrument can have one or more regions.
struct AkaoInstrumentZoneAttr
{
uint8_t instrument; // corresponding to opcode 0xA1
uint8_t low_key; // lowest key (in note number)
uint8_t high_key; // highest key (in note number)
uint8_t ar; // ADSR: attack rate (0-127)
uint8_t sr; // ADSR: sustain rate (0-127)
uint8_t s_mode; // ADSR: sustain mode (1: linear increase, 3: linear decrease, 5: exponential increase, 7: exponential decrease)
uint8_t rr; // ADSR: release rate (0-31)
uint8_t volume; // adjust the note volume to n/128 of the original volume (0 will keep the original volume)
}
- Zones must be ordered from low key to high key - The final zone of the instrument must be a terminator filled with 0s (more precisely, a zone with ADSR sustain mode of 0 is considered a terminator)
Drum Instrument Map Table
When a song uses a drum kit with opcode 0xFE 0x04, a drum instrument map table will be placed at the end of the sequence. The table determines the instrument, channel volume and pan for each keys.
The table consists of a repetition of 8-byte items.
struct AkaoDrumKeyAttr
{
uint8_t instrument; // corresponding to opcode 0xA1
uint8_t key; // note number when playing the drum note
uint8_t ar; // ADSR: attack rate (0-127)
uint8_t sr; // ADSR: sustain rate (0-127)
uint8_t s_mode; // ADSR: sustain mode (1: linear increase, 3: linear decrease, 5: exponential increase, 7: exponential decrease)
uint8_t rr; // ADSR: release rate (0-31)
uint8_t volume; // adjust the percussion volume to n/128 of the original volume (0 will keep the original volume)
uint8_t pan : 7; // corresponding to opcode 0xAA
uint8_t reverb : 1; // reverb on/off (0: off, 1: on)
}
Sound Opcode List
Opcode | Summary | Length | Operands | Note |
---|---|---|---|---|
Note, Tie, Rest | 1 | The opcode indicates the note key and length. | ||
0x9A-0x9F | Unimplemented | 1 | Should not be used. | |
Finish Channel | 1 | |||
Load Instrument | 2 | instrument: byte (0-127) | ||
Overwrite Next Note Length | 2 | length: byte | Ignores the regular length (delta-time) of the next note and overwrites it with the specified length. | |
Channel Master Volume | 2 | volume: byte (0-127) | ||
Pitch Bend Slide | 3 | length: byte, semitones: signed byte | When | |
Set Octave | 2 | octave: byte (0-15) | ||
Increase Octave | 1 | |||
Decrease Octave | 1 | |||
Channel Volume | 2 | volume: byte (0-127) | ||
Channel Volume Slide | 3 | length: byte, volume: byte (0-127) | When | |
Channel Pan | 2 | pan: byte (0-127) | 64 is the center. | |
Channel Pan Slide | 3 | length: byte, pan: byte (0-127) | When | |
Noise Clock Frequency | 2 | clock: byte (0x00-0x3f) | ||
ADSR: Attack Rate | 2 | attack_rate: byte (0x00-0x7f) | ||
ADSR: Decay Rate | 2 | decay_rate: byte (0x00-0x0f) | ||
ADSR: Sustain Level | 2 | sustain_level: byte (0x00-0x0f) | ||
ADSR: Decay Rate & Sustain Level | 3 | decay_rate: byte (0x00-0x0f), sustain_level: byte (0x00-0x0f) | ||
ADSR: Sustain Rate | 2 | sustain_rate: byte (0x00-0x7f) | ||
ADSR: Release Rate | 2 | release_rate: byte (0x00-0x1f) | ||
ADSR: Reset ADSR | 1 | |||
Vibrato (Channel Pitch LFO) | 4 | delay: byte, rate: byte, type: byte (0-15) | When | |
Vibrato Depth | 2 | depth: byte | The most significant bit of the When it is 0, the range is up to about ± 50 cents (amplitude is 15/256 compared with range type 1). When it is 1, the range is up to about ± 700 cents. | |
Turn Off Vibrato | 1 | |||
ADSR: Attack Mode | 2 | attack_mode: byte (1 or 5) | ||
Tremolo (Channel Volume LFO) | 4 | delay: byte, rate: byte, type: byte (0-15) | When | |
Tremolo Depth | 2 | depth: byte | ||
Turn Off Tremolo | 1 | |||
ADSR: Sustain Mode | 2 | sustain_mode: byte (1, 3, 5 or 7) | ||
Channel Pan LFO | 3 | rate: byte, type: byte (0-15) | When | |
Channel Pan LFO Depth | 2 | depth: byte | ||
Turn Off Channel Pan LFO | 1 | |||
ADSR: Release Mode | 2 | release_mode: byte (3 or 7) | ||
Channel Transpose (Absolute) | 2 | semitones: signed byte | ||
Channel Transpose (Relative) | 2 | semitones: signed byte | ||
Turn On Reverb | 1 | |||
Turn Off Reverb | 1 | |||
Turn On Noise | 1 | |||
Turn Off Noise | 1 | |||
Turn On Frequency Modulation | 1 | |||
Turn Off Frequency Modulation | 1 | |||
Loop Point | 1 | Remember the current offset as a loop point and increase the nesting level of the loop. | ||
Return to Loop Point Up to N Times | 2 | times: byte | On the Nth repeat, this instruction will end the current loop by decrementing the nesting level of the loop. Otherwise, it will increment the loop counter and return to the loop point. When | |
Return to Loop Point | 1 | This instruction will increment the loop counter. | ||
Reset Sound Effects | 1 | Reset sound effects such as noise, frequency modulation, reverb, overlay voice and alternate voice. | ||
Turn On Legato | 1 | This instruction will stop the regular key on and key off performance of the subsequent notes and update the pitch only. | ||
Turn Off Legato | 1 | |||
Turn On Noise and Toggle Noise On/Off after a Period of Time | 2 | delay: byte | When | |
Toggle Noise On/Off after a Period of Time | 2 | delay: byte | When | |
Turn On Full-Length Note Mode | 1 | This instruction will stop the regular key off performance of the subsequent notes. | ||
Turn Off Full-Length Note Mode | 1 | |||
Turn On Frequency Modulation and Toggle Frequency Modulation On/Off after a Period of Time | 2 | delay: byte | When | |
Toggle Frequency Modulation On/Off after a Period of Time | 2 | delay: byte | When | |
Turn On Playback Rate Side Chain | 1 | Duplicate and use the playback frequency of the previous voice channel. | ||
Turn Off Playback Rate Side Chain | 1 | |||
Turn On Pitch-Volume Side Chain | 1 | Multiply the playback frequency of the previous voice channel to the output volume. Lower pitch will make the volume smaller. | ||
Turn Off Pitch-Volume Side Chain | 1 | |||
Channel Fine Tuning (Absolute) | 2 | amount: signed byte | The pitch can be changed in the range of one octave above and below. The | |
Channel Fine Tuning (Relative) | 2 | amount: signed byte | The | |
Turn On Portamento | 2 | speed: byte | When | |
Turn Off Portamento | 1 | |||
Fix Note Length | 2 | length_to_add: signed byte | Ignore the regular length (delta-time) of subsequent notes and set to the fixed length. The | |
Vibrato Depth Slide | 3 | length: byte, depth: byte | When | |
Tremolo Depth Slide | 3 | length: byte, depth: byte | When | |
Channel Pan LFO Depth Slide | 3 | length: byte, depth: byte | When | |
Unknown | 1 | |||
Unknown | 2 | value: byte | ||
Unknown | 1 | Clears the effect of opcode 0xE1 | ||
Unimplemented | 1 | Code-referenced to 0xA0. Should not be used. | ||
Vibrato Rate Slide | 3 | length: byte, rate: byte | When | |
Tremolo Rate Slide | 3 | length: byte, rate: byte | When | |
Channel Pan LFO Rate Slide | 3 | length: byte, rate: byte | When | |
Unimplemented | 1 | Code-referenced to 0xA0. Should not be used. | ||
Note, Tie, Rest with Length | 2 | length: byte | ||
Tempo | 4 | tempo: uint16 | bpm = tempo / 218.453333 (approximate) More strictly, | |
Tempo Slide | 5 | length: byte, tempo: uint16 | When | |
Reverb Depth | 4 | depth: uint16 | ||
Reverb Depth Slide | 5 | length: byte, depth: uint16 | When | |
Turn On Drum Mode | 2 | The drum map offset is recorded in the file header. | ||
Turn Off Drum Mode | 2 | |||
Unconditional Jump | 4 | destination_offset: signed int16 | This instruction can be used to make an infinite loop. | |
CPU-Conditional Jump | 5 | condition: byte, destination_offset: signed int16 | Jump if the condition variable matches | |
Jump on the Nth Repeat | 5 | times: byte, destination_offset: signed int16 | When | |
Break the Loop on the Nth Repeat | 5 | times: byte, destination_offset: signed int16 | Unlike 0xF0, this instruction will end the current loop by decrementing the nesting level of the loop. When | |
Load Instrument (No Attack Sample) | 3 | instrument: byte | Unlike 0xA1, the sample before the loop point is replaced by a short, silence sample. | |
Unknown | 6 | offset: signed int16, offset2: signed int16 | ||
Unimplemented | 2 | Code-referenced to 0xA0. Should not be used. | ||
Pattern | 4 | destination_offset: signed int16 | Remember the return address and jump to the specified location. It cannot be nested. | |
End Pattern | 2 | Return from opcode 0xFE 0x0E. | ||
Allocate Reserved Voices | 3 | count: byte | Reserve the specified | |
Free Reserved Voices | 2 | Set the count of reserved voices to 0. | ||
Channel Master Volume Slide | 3 | length: byte, volume: byte (0-127) | When | |
Unimplemented | 2 | Code-referenced to 0xA0. Should not be used. | ||
Load Custom Instrument (Key-Split Instrument) | 3 | instrument: byte | The custom instrument number corresponds to the custom instrument map pointed from the file header. Note that the number is different from the regular sample number used in opcode 0xA1. | |
Time Signature | 4 | ticks_per_beat: byte, beats_per_measure: byte | Note that two parameters can be 0. This pattern is used for initialization. | |
Measure Number | 3 | measure: byte | ||
Unimplemented | 2 | Code-referenced to 0xA0. Should not be used. | ||
Channel Volume Slide Per Note On | 4 | length: byte, volume: byte (0-127) | Applies volume slide (opcode 0xA9) for each notes. When | |
Unknown | 2 | Turn something on. | ||
Unknown | 2 | Clears the effect of opcode 0xFE 0x1A. | ||
Unknown | 3 | value: byte | Obsoleted opcode? As far as I learned from the assembly code, it probably does nothing. | |
Use Reserved Voices | 2 | Allow using the reserved voices to play the sound of the current track. If they are all used, the remaining voices will instead be used normally. Use opcode 0xFE 0x10 to allocate reserved voices. | ||
Use No Reserved Voices | 2 | Disable the effect of opcode 0xFE 0x1D. | ||
Unimplemented | 2 | Code-referenced to 0xA0. Should not be used. | ||
0xFE 0x20-0xFF | Unimplemented (Out of Range) | n/a | Do not use. | |
Unimplemented | 1 | Code-referenced to 0xA0. Should not be used. |