GEMS sound driver research

Discussion in 'Technical Discussion' started by Andlabs, Jan 1, 2010.

  1. Andlabs


    「いっきまーす」 Wiki Sysop
    Writing my own MD/Genesis sound driver :D
    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="" 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:

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pointers to songs]
    &nbsp;&nbsp;&nbsp;&nbsp;song 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[number of channels]
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pointers to channels]
    &nbsp;&nbsp;&nbsp;&nbsp;song 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[number of channels]
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pointers to channels]
    &nbsp;&nbsp;&nbsp;&nbsp;song 0 channel 0&nbsp;&nbsp;&nbsp;&nbsp;[sequencer data]
    &nbsp;&nbsp;&nbsp;&nbsp;song 0 channel 1&nbsp;&nbsp;&nbsp;&nbsp;[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

    &nbsp;&nbsp;&nbsp;&nbsp;if a note is to be played
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;play note for a given amount of time, known as "duration"
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;release note
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;perform the command
    &nbsp;&nbsp;&nbsp;&nbsp;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.

    &nbsp;&nbsp;&nbsp;&nbsp;| 7 6&nbsp;&nbsp; | 5 4 3 2 1 0 |

    Command&nbsp;&nbsp;&nbsp;&nbsp;00&nbsp;&nbsp;&nbsp;&nbsp;N - Note.
    &nbsp;&nbsp;&nbsp;&nbsp;01&nbsp;&nbsp;&nbsp;&nbsp;Bit 5 off&nbsp;&nbsp;&nbsp;&nbsp;Note
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bit 5 on&nbsp;&nbsp;&nbsp;&nbsp;Other command
    &nbsp;&nbsp;&nbsp;&nbsp;10&nbsp;&nbsp;&nbsp;&nbsp;R - Duration. Consecutive Rs are combined BEFORE processing.
    &nbsp;&nbsp;&nbsp;&nbsp;11&nbsp;&nbsp;&nbsp;&nbsp;D - Delay. Consecutive Ds are combined BEFORE processing.
    Datum&nbsp;&nbsp;&nbsp;&nbsp;If N, the whole byte is the note value:
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FM/Tone&nbsp;&nbsp;&nbsp;&nbsp;0 is C0, 1 is C#0/Db0, ..., 95 is B7
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DAC&nbsp;&nbsp;&nbsp;&nbsp;For backwards compatibility with something (the comment is cut off for some reason), take note:
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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?) (?)
    &nbsp;&nbsp;&nbsp;&nbsp;If R, is the length of the duration in subbeats.
    &nbsp;&nbsp;&nbsp;&nbsp;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:

    96&nbsp;&nbsp;&nbsp;&nbsp;End of sequence. Used for sfx and jingles to end the song.
    97&nbsp;&nbsp;&nbsp;&nbsp;Change patch - next byte in the sequence is the new patch number.
    98&nbsp;&nbsp;&nbsp;&nbsp;(?) (byte parameter)
    99&nbsp;&nbsp;&nbsp;&nbsp;No operation
    100&nbsp;&nbsp;&nbsp;&nbsp;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&nbsp;&nbsp;&nbsp;&nbsp;End loop. This completes the loop at the top of the stack.
    102&nbsp;&nbsp;&nbsp;&nbsp;Toggle retrigger mode. (?)
    103&nbsp;&nbsp;&nbsp;&nbsp;Toggle sustain mode. (?)
    104&nbsp;&nbsp;&nbsp;&nbsp;Set tempo for entire song. Next byte is tempo - 40 in beats per minute. (?)
    105&nbsp;&nbsp;&nbsp;&nbsp;Mute, though how it works is weird and involves MIDI channels (?)
    106&nbsp;&nbsp;&nbsp;&nbsp;Set channel priority. Priority goes from 0 (least) to 127 (most).
    107&nbsp;&nbsp;&nbsp;&nbsp;Start another song; next byte is the song number. Unsure if this merely saves the current song on the stack (todo)
    108&nbsp;&nbsp;&nbsp;&nbsp;Pitch bend. Next byte is the low byte of bend data (?), followed by high byte.
    109&nbsp;&nbsp;&nbsp;&nbsp;Set song to use SFX timebase.
    110&nbsp;&nbsp;&nbsp;&nbsp;Set DAC sample playback rate to value of the next byte (?).
    111&nbsp;&nbsp;&nbsp;&nbsp;Jump to position indicicated in next two bytes (lower byte first).
    112&nbsp;&nbsp;&nbsp;&nbsp;Store a byte into one of 32 byte-sized "mailboxes." Next byte is the mailbox number, followed by the value.
    113&nbsp;&nbsp;&nbsp;&nbsp;Conditional branch (if statement). The bytes thereafter take the form [mailbox #][condition][mailbox #][location].
    &nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mailbox != mailbox
    &nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mailbox > mailbox
    &nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mailbox >= mailbox
    &nbsp;&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mailbox < mailbox
    &nbsp;&nbsp;&nbsp;&nbsp;5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mailbox <= mailbox
    &nbsp;&nbsp;&nbsp;&nbsp;all other&nbsp;&nbsp;&nbsp;&nbsp;mailbox == mailbox
    &nbsp;&nbsp;&nbsp;&nbsp;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&nbsp;&nbsp;&nbsp;&nbsp;Even more functionality! Next byte is the command, followed by value
    &nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Stop sequence. (?)
    &nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Pause sequence. (?)
    &nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Resume all. Value ignred (but must still be specified).
    &nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Pause music. (?) Value ignred (but must still be specified).
    &nbsp;&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Set master volume to value. Volume goes from 0 (loudest) to 127 (softest), just like the YM2612 FM operators's TL field.
    &nbsp;&nbsp;&nbsp;&nbsp;5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Set channel volume to value. Volume goes from 0 (loudest) to 127 (softest), just like the YM2612 FM operators's TL field.
    &nbsp;&nbsp;&nbsp;&nbsp;all other&nbsp;&nbsp;&nbsp;&nbsp;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!
  2. SegaLoco


    W)(at did you say?
    Well, I might start learning audio on the Mega Drive. Might as well try this driver out. =P
  3. Smoke


    Member Member
    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
  4. Andlabs


    「いっきまーす」 Wiki Sysop
    Writing my own MD/Genesis sound driver :D
    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...
  5. Smoke


    Member Member
    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 :)
  6. saxman


    Oldbie Tech Member
    I'll contribute the voice format for GEMS:

    Code (Text):
    2. GEMS voice format:
    5. 0x00 - type
    6.     0 = FM
    7.     1 = PSG noise
    8.     2 = PSG tone
    11. TYPE2 (6 bytes):
    12. 0x01 - ?
    13. 0x02 - attack rate
    14. 0x03 - D1L (0-F) -- 0 is loud, F is soft
    15. 0x04 - AR (0-1F) -- 1F is slow, 0 is fast
    16. 0x05 - ?
    17. 0x06 - release rate (0-FF) -- 0 is slow, FF is fast
    20. TYPE1 (1 byte):
    21. 0x01 - Pitch (4-bit -- low = high pitch, high = low pitch)
    24. TYPE0 (38 bytes):
    25. 0x01 - reg022
    26. 0x02 - reg027
    27. 0x03 - reg0B0
    28. 0x04 - reg0B4
    29. 0x05 - reg030
    30. 0x06 - reg040
    31. 0x07 - reg050
    32. 0x08 - reg060
    33. 0x09 - reg070
    34. 0x0A - reg080
    35. 0x0B - reg034
    36. 0x0C - reg044
    37. 0x0D - reg054
    38. 0x0E - reg064
    39. 0x0F - reg074
    40. 0x10 - reg084
    41. 0x11 - reg038
    42. 0x12 - reg048
    43. 0x13 - reg058
    44. 0x14 - reg068
    45. 0x15 - reg078
    46. 0x16 - reg088
    47. 0x17 - reg03C
    48. 0x18 - reg04C
    49. 0x19 - reg05C
    50. 0x1A - reg06C
    51. 0x1B - reg07C
    52. 0x1C - reg08C
    53. 0x1D - reg0A2 (in special CH3 mode)
    54. 0x1E - reg0A6 (in special CH3 mode)
    55. 0x1F - reg0A8 (in special CH3 mode)
    56. 0x20 - reg0AC (in special CH3 mode)
    57. 0x21 - reg0A9 (in special CH3 mode)
    58. 0x22 - reg0AD (in special CH3 mode)
    59. 0x23 - reg0AA (in special CH3 mode)
    60. 0x24 - reg0AE (in special CH3 mode)
    61. 0x25 - reg028 (*lower* 4 bits used to enable/disable operators)
    62. 0x26 - Unused?
  7. Smoke


    Member Member
    Thank you, saxman. Are formats of PSG tones the same in GEMS and SMPS?
  8. saxman


    Oldbie Tech Member
    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.
  9. Andlabs


    「いっきまーす」 Wiki Sysop
    Writing my own MD/Genesis sound driver :D
    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.
  10. ValleyBell


    Tech Members Tech Member
    researching SMPS 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.


    EDIT: fixed links