Music Hacking/Pointer and Header Format
From Sonic Retro
Music Hacking: Pointer and Header Format | Voice and Note Editing | DAC Samples, Coordination Flags, Game Specifics, and Real-Time Music Editing | Other Games and Data Locations | Tricks of the Trade |
Contents
Pointer format
Pointers, as they pertain to SMPS, are values used to locate data at any point in the file. In the context of music editing, pointers will be used to locate the start of each channel's notation data; in addition, they are also used to branch to certain portions of data within the actual data through the use of coordination flags. This section in particular will describe the format used in the song headers and within the branching flags — F6, F7, and F8, respectively.
68k SMPS
Headers
Pointers in the headers of a song or sound effect are 16-bits (two bytes) and work relative to the beginning of the song file. For example, when working with a standalone piece of SMPS data, if the voice pointer reads 0180, you would go to offset $180 within the file to locate the voice data.
If you're working with a song inside a full compiled ROM, the process is similar — simply take the offset where the song data starts and add the voice pointer's value to calculate the offset of the data. So, for example, if your music is at offset $E8900 in the ROM, you'd take the voice pointer value — in this case, $180 — and you'd add it to the offset in order to locate the data. In this particular instance, $E8A80 would be the resulting offset, and subsequently the location of the voice data in the song.
This pointer format applies to all pointers within the header of a piece of SMPS data.
Coordination flags
In contrast to Z80 SMPS, the pointer format in branching coordination flags differs slightly. As before, each pointer is two bytes, but this time pointers are relative to the start of the pointer — not the start of the song file. To clarify, if you were using the format of the F7 flag — F7 xx yy zzzz — the pointer would be relative to the offset in the file where zzzz is stored.
Pointers in coordination flags use signed relative values to locate data both after and before the current location. To do this, there is an imposed limit of about $8000 bytes in either direction on how far one single flag can branch in the song. When dealing with these flags, a value of FFFF acts as zero; in other words, a pointer value of FFFF would branch to the current location — nowhere.
To branch ahead of the current location, one would increase the value beyond FFFF to jump ahead. So, for example, if our pointer was 0298, we would be branching ahead $299 bytes from our current location. Assuming the current location is $E8000, this branch would be locating data at offset $E8299. A general formula you could use in order to calculate the length of positive branches quickly would be to increase a pointer value by $1 — this would help you get into the mindset of locating data more adeptly.
To branch before the current location, one would decrease the value beyond FFFF to jump back. So, for example, if our pointer was FFE8, we would be branching $17 bytes before the current location. Assuming our current location is, once again, $E8000, this branch would be locating data at offset $E7FE9. The easiest way to calculate the length of a negative branch would be to take the pointer value and subtract it from FFFF. In our previous example, subtracting FFE8 from FFFF gives a result of $17.
68k SMPS is unique in that each song is literally self-contained — a song can be stored at any offset within a ROM and the relative nature of the pointers will always locate data properly. This is a significant advantage over the Z80 variant of the engine, which requires all data to be at absolute offsets within a data bank.
Points of note:
- Ristar's coordination flag pointers have $1 added to them, compared to the pointers in other 68k SMPS games. If you want to convert between the two, just add or subtract $1 thusly.
- Some 68k SMPS games use the Sonic 3-style coordination flags.
Z80 SMPS
Z80 SMPS differs from its 68k counterpart in a few different ways.
First and foremost, unlike 68k SMPS, the same pointer format is used in both the coordination flags and the headers. All pointers are stored in little-endian format due to the nature of the processor; in other words, the byte order is reversed. If a pointer reads 7880, you should mentally parse it as referring to bank offset 8078.
Secondly, pointers are absolute instead of relative, but are only absolute in terms of the current Z80 data bank. Pointer values will always start with a value of 8000, regardless of whether or not the bank itself is stored at an offset that contains 8000 as part of the address; this is so the music can be properly located in the context of the Z80 bank. So, for example, if the current Z80 bank is at offset $70000, a pointer value of 7880 (which, when swapped, becomes 8078) would be locating data at offset $70078.
When dealing with standalone Z80 SMPS files, it is required that you first make note of the starting offset of the song within its respective Z80 bank before you can properly calculate pointer values within the song. So, for example, if a song is located at offset $F0900 in the ROM, your starting offset for the song will be 8900. To calculate relative offsets within the file where data is stored, subtract the starting offset from your pointer value. So, for example, if our DAC pointer reads value 508E (which after swapping the bytes becomes 8E50), the relative offset of our DAC notation within the file will be offset $550, or $550 bytes from the start of the song file.
This process is relatively simpler for the SMPS variant used in the SMS version of Sonic 2; since all music data is contained in bank 2 ($8000-$FFFF), simply convert the pointer to big endian and use that as your absolute address in the ROM.
In addition to within Z80 SMPS data, this format is also used to locate data within a game's music pointer index.
Sonic 2 Final/Mega Man: Wily Wars
Sonic 2 is a special case amongst games that use the Z80 SMPS engine in that its music is compressed to individual files in the saxman compression, which are subsequently parsed within Z80 RAM.
In contrast to normal Z80 SMPS music, the only difference is that compressed music in Sonic 2 always has a starting offset of 1380. All operations regarding the calculation of offsets from pointers remains the same. It is, however, worth noting that not all music in Sonic 2 is compressed; exceptions are noted as such later on in the guide.
Mega Man: Wily Wars stores songs uncompressed, but also has music copied to Z80 RAM. The starting offset for this game is always $1002.
Sorcerian stores songs LZSS-compressed. The 68k decompresses them and copies the data to Z80 RAM, starting with offset $1000.
Virtua Racing stores songs uncompressed and has music copied to Z80 RAM. The starting offset for this game is always $1872.
Many games that use SMPS Z80 store SFX data in Z80 RAM.
Header formats
Mega Drive SMPS
All pointers in this section differ according to the game you are editing. Remember, relative pointers for 68k SMPS, and absolute little endian pointers for Z80 SMPS. The music header format listed is a universal format used in all incarnations of the SMPS engine except for Knuckles' Chaotix, which uses a slightly modified version to accommodate for the extra sound hardware (the PWM sound channels).
SMPS main header
Byte offset | Description |
---|---|
$00‑$01 | Voice table pointer. |
$02 | Number of FM+DAC channels. There must always be a DAC channel defined, so this is number of FM channels + 1, and the DAC channel MUST be the first one. If you don't want to use the DAC, you must put a $F2 right at the start of the track. Sonic 1 or 2 specific: There is enough track RAM for 6 FM+1 DAC. If you want to use 6 FM channels, you must put a $07 in this byte to initialize all tracks and disable the DAC as above. The DAC channel will be disabled in this case. For Sonic 2 only, DAC will be enabled during music initialization, Sonic 1 only disables it. Sonic 3 or K specific: There is enough track RAM on the z80 for 5 FM + 1 DAC. You can't use the 6th FM channel for music (you can for SFX). There is one song (the song that plays when you get a chaos emerald song) that initializes 6 FM+1 DAC as above — and it works only by luck, as the FM6 track is treated as a PSG channel and the PSG3 track is treated as an FM channel with its own voice pointer. |
$03 | Number of PSG tracks. If you want a noise channel, the 3rd PSG channel is the only one that can be safely converted to noise. |
$04 | Dividing timing: in all drivers I analyzed (Sonic 1, Sonic 2, Sonic 3, Sonic & Knuckles, Sonic 3D Blast, Ristar, Outrunners) this works by multiplying note duration by this value. This can lead to broken notes, as the final duration is stored in a single byte; thus, the maximum note duration you can use without problems is $FF/dividing timing (round down, maximum of $7F). Sonic 3 or K specific: a dividing timing of $00 multiplies the note duration by 256, making all notes have a duration of $00 and last for 256 frames. |
$05 | Main tempo modifier. This works as follows: Sonic 1 specific: if main tempo is nn, the song runs for nn-1 frames and is delayed by 1 frame. A main tempo of $01 runs for 0 frames and is delayed by 1 frame, hence is broken; a main tempo of $00 will overflow and run for $FF frames and be delayed by 1 frame. Sonic 2 specific: a main tempo of nn runs on nn out of 256 frames, as evenly spaced as possible. A main tempo of $00 does not run at all. Sonic 3 or K specific: a main tempo of nn runs on (256 - nn) out of 256 frames, as evenly spaced as possible. All tempo values are valid. |
It is easy to convert between Sonic 2 and Sonic 3+ main tempo values:
S2 main tempo = (256 - S3 main tempo) mod 256
S3 main tempo = (256 - S2 main tempo) mod 256
The conversion works for all values of main tempo other than 0 — this value cannot be converted exactly between the two drivers. The conversion of main tempos from Sonic 1 to Sonic 2 can be done (approximately) with the following formula:
S2 main tempo = floor{ [ (S1 main tempo-1) * 256 + floor(S1 main tempo/2) ] / S1 main tempo }
Here, a S1 main tempo of 0 should be plugged as 256. There is an error intrinsic to this approximation; this error is always less than half a frame out of every 256 frames, and the average absolute error for all main tempo values is about 0.239 frames out of every 256. This means that all Sonic 1 main tempos can be converted quite accurately to Sonic 2, or to Sonic 3+ by composing the formulas.
The main tempo conversion from Sonic 2 to Sonic 1 can be done with the following formula:
S1 main tempo = floor{ floor[ (768 - S2 main tempo)/2 ] / (256 - S2 main tempo) }
This formula has much worse error bounds that the other formula: wast swatches of Sonic 2 main tempos are squashed to the same value because they can't be accurately represented in the Sonic 1 main tempo. This is shown in this plot:
The "duty cycle" is how many frames the music is updated in out of every 256 frames; this illustrates how bad the formula can get for "low" S2 tempos ($C0 or less).
Then follows headers for FM and DAC channels, as many as are specified on byte $02 (offsets relative to header position in file).
SMPS FM and DAC headers
Byte offset | Description |
---|---|
$00‑$01 | Track data pointer. |
$02 | Initial channel key displacement (signed, ignored on DAC). This is added to the note before it is converted to a frequency. Sonic 3 or K specific: In the alternate SMPS parsing mode, the channel key displacement is added to the *frequency* instead. |
$03 | Initial track volume attenuation: $00 is maximum volume, $7F is total silence, above that bit 7 is ignored. This is read when DAC channel is initialized, but it is ignored afterwards. |
The first channel specified is the DAC channel, regardless of whether or not bytes $02‑$03 are zero or not. After those, there are the PSG channels:
SMPS PSG headers
Byte offset | Description |
---|---|
$00‑$01 | Track data pointer. |
$02 | Initial channel key displacement (signed, ignored on DAC). This is added to the note before it is converted to a frequency. Sonic 3 or K specific: In the alternate SMPS parsing mode, the channel key displacement is added to the *frequency* instead. |
$03 | Initial track volume attenuation: $00 is maximum volume, $0F is total silence. Sonic 2 specific: attenuation above $0F is set to $0F. Sonic 3 or K specific: attenuation above $0F is only set to $0F if bit 4 is set, otherwise it screws up volume commands to PSG. |
$04 | Sonic 2 specific: ignored. Sonic 3+ specific: modulation control byte. Do not use unless you know what you are doing. |
$05 | Default PSG tone (index in lookup table). |
Only PSG3 can be made into a noise channel without bugs. To use a noise channel, you need to use 3 PSG channels and convert the third into a noise channel.
For SFX, the header is quite different:
Main SFX header
Byte offset | Description |
---|---|
$00‑$01 | Voice table pointer. |
$02 | Dividing timing. See notes for main music header. |
$03 | Total channels (FM + PSG) used by SFX. Sonic 1 specific: SFX can use a maximum of 3 FM and 3 PSG channels. If the SFX index is from $D0 to $DF, it can have at most a single FM (which *must* be FM4) and a single PSG channel (which MUST be PSG 3). Sonic 2 specific: SFX can use up to 3 FM and 3 PSG channels. Sonic 3 or K specific: SFX can use up to 4 FM and 3 PSG channels. |
Unlike the case for music, SFX track headers are equal for all tracks; there is one header for each track specified on byte $03:
SFX track headers
Byte offset | Description |
---|---|
$00 | Initial playback control bitfield. Just set it to $80 and be done with it (unless you *really* know what you are doing). Sonic K specific: Bit 0 must be set for noise channels. |
$01 | Channel identifier. For a PSG channel: $80 = PSG1, $A0 = PSG2, $C0 = PSG3, $E0 = noise. For a FM channel: $02 = FM3, $04 = FM4, $05 = FM5. Anything else is invalid. Sonic 3+ specific: you can also specify $06 to use FM6; this overrides the DAC channel. |
$02‑$03 | Track data pointer. |
$04 | Initial channel key displacement (signed). See comments for music, as adequate to the track's type. |
$05 | Initial track volume attenuation. See comments for music, as adequate to the track's type. |
So you must specify the channel type on a track-by-track basis. If you are careful enough designing SFX, you can theoretically play (and hear) several of them at the same time as long as they do not share the same channels — each SFX channel has a fixed location in z80 RAM and a fixed song track in RAM that it overrides on initialization. In any of the games, you can't safely use PSG3 ($C0) and the noise channel ($E0) at the same time at SFX initialization, as they will want to use the same block of RAM. Turning a channel to noise with coord. flags afterwards is safe, however you will always want to convert a PSG3 channel to noise or you will face many bugs. In Sonic & Knuckles, you will want to avoid using $E0 to define a noise channel, as it will be very buggy.
32X SMPS
The format for the SMPS music header is slightly different to accommodate for the 32x's extra sound hardware.
Word offset | Description |
---|---|
$00‑$01 | Voice pointer. |
$02‑$03 | Channel setup. |
$04‑$05 | Tempo modifier. |
$06‑$07 | FM channel 1 pointer. |
$08‑$09 | FM channel 1 modifier. |
$0A‑$0B | FM channel 2 pointer. |
$0C‑$0D | FM channel 2 modifier. |
$0E‑$0F | FM channel 3 pointer. |
$10‑$11 | FM channel 3 modifier. |
$12‑$13 | FM channel 4 pointer. |
$14‑$15 | FM channel 4 modifier. |
$16‑$17 | FM channel 5 pointer. |
$18‑$19 | FM channel 5 modifier. |
$1A‑$1B | FM channel 6 pointer. |
$1C‑$1D | FM channel 6 modifier. |
$1E‑$1F | PSG channel 1 pointer. |
$20‑$21 | PSG channel 1 modifier. |
$22‑$23 | Current PSG 1 instrument. |
$24‑$25 | PSG channel 2 pointer. |
$26‑$27 | PSG channel 2 modifier. |
$28‑$29 | Current PSG 2 instrument. |
$2A‑$2B | PSG channel 3 pointer. |
$2C‑$2D | PSG channel 3 modifier. |
$2E‑$2F | Current PSG 3 instrument. |
$30‑$31 | PWM channel 1 pointer. This will point to one of the four available PWM channels used for percussion and other samples that can be played in the song. It works exactly like DAC, except there are more channels available for use. |
$32‑$33 | PWM channel 1 modifier. Unlike DAC, the pitch and volume of a PWM channel can be changed, and in the same way as any other melodic sound channel. However, the volume byte will work by output strength, and not resistance — in other words, the higher the value, the louder the sample. |
$34‑$35 | PWM channel 2 pointer. |
$36‑$37 | PWM channel 2 modifier. |
$38‑$39 | PWM channel 3 pointer. |
$3A‑$3B | PWM channel 3 modifier. |
$3C‑$3D | PWM channel 4 pointer. |
$3E‑$3F | PWM channel 4 modifier. |
Master System/Game Gear SMPS
Since the SMS only has 4 PSG channels (3 tone, 1 noise), the header format is changed to accomodate this.
Word offset | Description |
---|---|
$00‑$01 | Unknown - pointer to PSG envelope data? |
$02‑$03 | Channel setup. Only the first byte is used, and it can range from $01 to $04. |
$04‑$05 | Tempo modifier. |
$06‑$07 | PSG channel 1 pointer. |
$08‑$09 | PSG channel 1 modifier. |
$0A‑$0B | PSG channel 2 pointer. |
$0C‑$0D | PSG channel 2 modifier. |
$0E‑$0F | PSG channel 3 pointer. |
$10‑$11 | PSG channel 3 modifier. |
$12‑$13 | PSG channel 4 pointer. Channel 4 is always the noise channel. |
$14‑$15 | PSG channel 4 modifier. |
Understanding the header is essential to editing music. This is the basis of basic music editing, such as pitch and tempo editing. Once you understand the pointers and the header, then you are ready to start learning more advanced things.
Mega-CD SMPS ("SMPS-PCM")
Since SMPS-PCM runs on a CPU that can only access the Mega-CD's PCM chip which hooks into a global sample bank (for each SMPS program on disc), the header is different:
Offset | Size | Description |
---|---|---|
$00 | Word | 0 - as SMPS grabs sample data from its own bank |
$02 | Byte | Channel setup. Usually 8 or 9. |
$03 | Byte | 0 - only one sound source is used. |
$04‑$05 | Byte, byte | Tempo modifier; see above. (Sonic 1 formula) |
$06, $0A, $0E, etc. | Word | PCM channel n pointer |
$08, $0C, $10, etc. | Byte | PCM channel n modifier. The signed value is added to the note value grabbed from the song; so if this is $FD (-$3) and the note byte in the song data is $93, it is treated as if it was a byte $90 instead. Coordination flag $FB changes this later. |
$09, $0D, $11, etc. | Byte | Unknown (possibly channel pan modifier; L/R outputs have additional individual volumes) |
References