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 this post from a while ago. That post describes GEMS's general layout; this post describes the sequence format. From my notes: Code (Text): 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. 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!
I am working on GEMS2SMPS converter for now and achieved some decent results. Loops and modulation and many other features are not supported for now. Can't demonstrate the soft because it's too unstable right now. However, I uploaded a game rom with 3 converted gems songs from Zero Tolerance and Vectorman. Converted songs are $D, $F and $10 I think the results are very close to the originals. Your thoughts? ROM is here
Cool. How are you dealing with mailboxes and channel changes? I gave up trying to write a SMPS converter because of the mailoxe sand ability to change output channels by changing instruments...
Mailboxes are not supported now (mmm, 112 and 113 commands). Dynamic voice allocation only for FM channels is working. PSG and DAC are not supported (have to separate dac and psg patches from FM). Firstly, the program search channel and sequence with the smallest current playback time. Then it's dealing with these channel and sequence. I declared 2 variables for Sequence and Hardware Channel. Busy time (for channel) and Full time (for sequence). By comparing them the program understands if hardware channel busy or not. Next by comparing full time and busy time the rest note ($80) is put with duration equal (full time-busy time). Before write a note the program checks what is current voice in channel and which patch a sequence uses in that time. If they are not the same $EF flag is put. Same for volume. After that current duration is put and Full time variable is incremented by current delay value. Busy time is incremented by current duration value. Sorry I'm not good in explaining and English language. hope you understand me
I'll contribute the voice format for GEMS: Code (Text): GEMS voice format: 0x00 - type 0 = FM 1 = PSG noise 2 = PSG tone TYPE2 (6 bytes): 0x01 - ? 0x02 - attack rate 0x03 - D1L (0-F) -- 0 is loud, F is soft 0x04 - AR (0-1F) -- 1F is slow, 0 is fast 0x05 - ? 0x06 - release rate (0-FF) -- 0 is slow, FF is fast TYPE1 (1 byte): 0x01 - Pitch (4-bit -- low = high pitch, high = low pitch) TYPE0 (38 bytes): 0x01 - reg022 0x02 - reg027 0x03 - reg0B0 0x04 - reg0B4 0x05 - reg030 0x06 - reg040 0x07 - reg050 0x08 - reg060 0x09 - reg070 0x0A - reg080 0x0B - reg034 0x0C - reg044 0x0D - reg054 0x0E - reg064 0x0F - reg074 0x10 - reg084 0x11 - reg038 0x12 - reg048 0x13 - reg058 0x14 - reg068 0x15 - reg078 0x16 - reg088 0x17 - reg03C 0x18 - reg04C 0x19 - reg05C 0x1A - reg06C 0x1B - reg07C 0x1C - reg08C 0x1D - reg0A2 (in special CH3 mode) 0x1E - reg0A6 (in special CH3 mode) 0x1F - reg0A8 (in special CH3 mode) 0x20 - reg0AC (in special CH3 mode) 0x21 - reg0A9 (in special CH3 mode) 0x22 - reg0AD (in special CH3 mode) 0x23 - reg0AA (in special CH3 mode) 0x24 - reg0AE (in special CH3 mode) 0x25 - reg028 (*lower* 4 bits used to enable/disable operators) 0x26 - Unused?
Well GEMS creates voices out of the PSG tones in that it allows you to specify an attack rate, decay rate, etc. SMPS simply allows you to play notes through the PSG, no attack or decay information specified. So GEMS uses voice data even for the PSG, while SMPS doesn't use voice data for the PSG. Now as far as the actual sequence data, I don't know much about how that is formatted in GEMS. Looking at my DOOM 32X notes, here's what I documented: I haven't touched it in a while, so I don't know much more than that.
SMPS does use voice data for the PSG, however it's just a linear array of attenuation levels read at constant intervals and ending with a value with its high bit set, not an ADSR. The voice list is global to all songs and SFX and is specific for each instance of the sound drivers.
Another week, another bump, another release. So today we have GEMS, because I did some research on it during the last 2 years. The most interesting tool I made is GemsPlay. Like SMPSPlay, it is a program that plays back GEMS songs. It was made by porting the original GEMS ASM code to C. I actually started to work on it, because I wanted to do something easy while sitting in the bus to university. I worked on it from January to April 2013. You can download a Win32 binary or the source code. It includes a seperate .exe that emulates 2 YM2612 chips for 6 additional FM channels. (only normal ones - no second special FM3 or DAC channel) This really makes a difference for some songs. (example: Cool Spot's Rave Dancetune, about 40 seconds into the song) It can play from ROMs (you must include all 4 pointers though) or from pre-extracted GEMS files. (It works to drop all 4 extracted files on the .exe, but read the readme for that.) And as usual, it can log VGMs. (Sorry, no loop detection this time.) For people who want to look at the source, I kept all comments from the original GEMS source and included "cleaned" versions of the original source code. Then we have a few GEMS tools: gems2mid and gemsscan. gemsscan scans a ROM for the GEMS driver and extracts all data (driver, sequences, instruments, etc.) It might not find every set of data in games that contain more than one. gems2mid does what the name says - it converts GEMS sequences to .mid files. (And it is over a year old, actually.) It can also extract samples (to .raw) and convert the instruments to .gyb files. Finally, I took notes of the driver variants I found in the (comparatively small amount of) GEMS games I checked, wrote down pointers to everything and ripped all data files. You can download everything here. It also includes some notes on the GEMS format, the sample table format and a file with information on how to detect GEMS in ROMs by searching for certain ASM codes. Enjoy! EDIT: fixed links