So in the spirit of the new year, I decided to give you all what I know about GEMS's sequence format. I don't know everything yet, but I will soon enough :v: First, read <a href="http://forums.sonicretro.org/index.php?s=&showtopic=12224&view=findpost&p=312909" target="_blank">this post from a while ago</a>. That post describes GEMS's general layout; this post describes the sequence format. From my notes: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->The demonstration code for the Ship game included in the GEMS devkit shows that the song data format is very simple: [pointers to songs] song 0 [number of channels] [pointers to channels] song 1 [number of channels] [pointers to channels] ... song 0 channel 0 [sequencer data] song 0 channel 1 [sequencer data] ... However, we don't know the format of the sequencer data, which is stored as raw data in the code. Fortunately, the GEMS devkit also contains the source code to the Z80 driver, as comments in the file GEMS\Z80.ASM. I went through the sound driver and documented the sequencer data format. Here is what I found by observing both the driver and how Sonic Spinball's sequencer data works. The driver is basically an infinite loop of the form if a note is to be played play note for a given amount of time, known as "duration" release note else perform the command wait a specific amount of time, known as "delay" (except for some commands) Each channel has their own delay and duration value, so you can set the duration and delay for a group of notes first, then issue the notes sequentially. This implies that unlike most sound drivers (and unlike most people's expectation of how music would be stored), GEMS expects the duration BEFORE the note. Sequencer commands always start with a byte denoting what the command does. | 7 6 | 5 4 3 2 1 0 | |-------|-------------| |Command|Datum | Command 00 N - Note. 01 Bit 5 off Note Bit 5 on Other command 10 R - Duration. Consecutive Rs are combined BEFORE processing. 11 D - Delay. Consecutive Ds are combined BEFORE processing. Datum If N, the whole byte is the note value: FM/Tone 0 is C0, 1 is C#0/Db0, ..., 95 is B7 Noise (?) DAC For backwards compatibility with something (the comment is cut off for some reason), take note: 48 (C4) is sample 0, 49 (C#4) is 1, ..., 95 (B7) is 47, 0 (C0) is 48, 1 (C#0) is 49, ..., 47 (B4) is 95 (96?) (?) If R, is the length of the duration in subbeats. If D, is the length of the delay in subbeats. In the case that the Command bits are 01, a new table of commands is used in which the entire byte is the command: Byte Command 96 End of sequence. Used for sfx and jingles to end the song. 97 Change patch - next byte in the sequence is the new patch number. 98 (?) (byte parameter) 99 No operation 100 Start loop. Next byte in the sequence is the number of iterations, or 127 for an infinte loop. Loops are stored in a stack. 101 End loop. This completes the loop at the top of the stack. 102 Toggle retrigger mode. (?) 103 Toggle sustain mode. (?) 104 Set tempo for entire song. Next byte is tempo - 40 in beats per minute. (?) 105 Mute, though how it works is weird and involves MIDI channels (?) 106 Set channel priority. Priority goes from 0 (least) to 127 (most). 107 Start another song; next byte is the song number. Unsure if this merely saves the current song on the stack (todo) 108 Pitch bend. Next byte is the low byte of bend data (?), followed by high byte. 109 Set song to use SFX timebase. 110 Set DAC sample playback rate to value of the next byte (?). 111 Jump to position indicicated in next two bytes (lower byte first). 112 Store a byte into one of 32 byte-sized "mailboxes." Next byte is the mailbox number, followed by the value. 113 Conditional branch (if statement). The bytes thereafter take the form [mailbox #][condition][mailbox #][location]. Condition Test 1 mailbox != mailbox 2 mailbox > mailbox 3 mailbox >= mailbox 4 mailbox < mailbox 5 mailbox <= mailbox all other mailbox == mailbox If the expression yields true, the address given is jumped to; if false, do nothing. BEWARE: the address to jump to is a byte that is always offset from 0 — the if statement can only jump to addresses 0 through 255! 114 Even more functionality! Next byte is the command, followed by value Command Action 0 Stop sequence. (?) 1 Pause sequence. (?) 2 Resume all. Value ignred (but must still be specified). 3 Pause music. (?) Value ignred (but must still be specified). 4 Set master volume to value. Volume goes from 0 (loudest) to 127 (softest), just like the YM2612 FM operators's TL field. 5 Set channel volume to value. Volume goes from 0 (loudest) to 127 (softest), just like the YM2612 FM operators's TL field. all other Do nothing. All other command values are ignored (" *** THIS COULD USE SOME FANCY ERROR DETECTION RIGHT ABOUT NOW"). After a command is executed (except 96, 100, 101, 111, and the R and D commands), delays are processed.<!--c2--></div><!--ec2--> There is one important thing to note: sequences are not assigned output audio channels. The current output channel of the sequence depends on the current voice, so a sequence can change output channels on the fly. Except for voices that take advantage of FM channel 3 multifrequency mode, FM channels are specified at runtime by an allocator method (think malloc()), so there is no way of knowing what FM channel a sequence would output to until it's time to actually play the note! This is a problem, and I've already seen it explode into a mess of hackery when I wrote my GEMS to SMPS converter. I think I'm going to have to start it over again. I also don't know the locations of the sequence data in GEMS ROMs. If people want to help by finding out, then great! I'll provide instructions for people with disassemblies later. Until then, I hope this information proves useful for some people!