TBSA Music Format
The TBSA Music Format is the song format produced by The Bone Shaker Architect written by Mario Knezovic.
|UINT16LE||offOrderPtrs||Offset of order pointer list|
|UINT16LE||offUnknown2||Offset of unknown list, only has one element which is 0xFFFF list terminator|
|UINT16LE||offUnknown3||Offset of unknown list, only has one element which is 0xFFFF list terminator|
|UINT16LE||offUnknown4||Offset of unknown list, only has one element which is 0xFFFF list terminator|
|UINT16LE||offInstPtrs||Offset of instrument pointer list|
|UINT16LE||offPattPtrs||Offset of pattern-segment pointer list|
At the offset given by offOrderPtrs in the header, this structure is present:
|UINT16LE||offOrderList||Offset of order list(s)|
In other words, this is simply a list of UINT16LE values, terminated by the value 0xFFFF. Each value (offOrderList) is the offset (again relative to the start of the file) where that order list is located.
There is typically only one order list in a song.
At each offOrderList offset, the data for that order list is arranged in the following structure:
|UINT8||count||Number of entries in the list|
|UINT16LE[count]||orderPointers||List of offsets where each order list starts|
A "pattern segment" is the event list for one track within one pattern.
At each offset given by orderPointers, there is a UINT8 array which serves as a list of pattern segment indices, e.g. 0x01 for the second pattern segment in the file. 0xFE terminates this list. The first list (located at offset orderPointers) is the pattern segments to play for the first track, the second list (at orderPointers) is the segments to play for the second track, and so on.
This means to read the first pattern, the first byte from each list will need to be read. This will provide a list of pattern segments, each representing one track for the pattern. If one of these pattern segment indices is 0x00, then it refers to the first pattern segment in the whole file. See #Pattern segments below for how to find a pattern segment.
At the offset given by offInstPtrs in the header, there is simply a list of UINT16LE values, each of which is the offset (relative to the start of the file) where the information for that instrument can be found. The list is terminated with a single 0xFFFF value.
At each of these offsets, the data for that instrument is arranged in the following structure:
|Data type||Name||Description||OPL base register||Value range|
|UINT8||attackdecay||Attack + decay rate||0x60|
|UINT8||sustrel||Sustain level + release rate||0x80|
|UINT8||sustain||Sustain on/off||0x20 (bit 5)||bit 0 controls on/off, bits 1-7 are ignored|
|UINT8||ksr||KSR on/off||0x20 (bit 4)||bit 0 sets value, bits 1-3 must be 0 as bits 0-3 are actually written into register bits 4-7|
|UINT8||freqmul||Frequency multiplication factor||0x20 (bits 0-3)||bits 0-3 go here, bits 4-7 must be 0 as the whole byte is written directly to the register|
|UINT8||feedback||Feedback modulation factor||0xC0 (bits 1-3)||bits 0-2 go here, bits 4-7 todo|
|UINT8||vibrato||Vibrato on/off||0x20 (bit 6)||bit 0 controls on/off, bits 1-7 are ignored|
|UINT8||defVolume||Default volume||0x40 (bits 0-5)||Add 2 to this value before writing to OPL register|
|UINT8||ksl||Key scale level||0x40 (bits 6-7)||bits 0-1 go here, bits 2-7 are ignored|
|UINT8||tremolo||Tremolo on/off||0x20 (bit 7)||bit 0 controls on/off, bits 1-7 are ignored|
|UINT8||con||Connection||0xC0 (bit 0)||bit 0 controls on/off, bits 1-7 must be 0 as the whole byte is written directly to the register|
|UINT8||wavesel||Waveform select||0xE0 (bits 0-2)||bits 0-2 control value, bits 3-7 must be 0 as the whole byte is written directly to the register|
|UINT8||attackdecay||Attack + decay rate||0x63|
|UINT8||sustrel||Sustain level + release rate||0x83|
|UINT8||sustain||Sustain on/off||0x23 (bit 5)||bit 0 controls on/off, bits 1-7 are ignored|
|UINT8||ksr||KSR on/off||0x23 (bit 4)||bit 0 controls on/off, but actually bits 0-3 are written into register bits 4-7, so this can be used to control OP1 vibrato etc. for which there are no dedicated fields|
|UINT8||freqmul||Frequency multiplication factor||0x23 (bits 0-3)||bits 0-3 go here, bits 4-7 must be 0 as the whole byte is written directly to the register (providing another way of setting OP1 vibrato etc.)|
|UINT8||defVolume||Default volume||0x43 (bits 0-5)||Add 2 to this value before writing to OPL register|
|UINT8||ksl||Key scale level||0x43 (bits 6-7)||bits 0-1 go here, bits 2-7 are ignored|
|UINT8||wavesel||Waveform select||0xE3 (bits 0-2)||bits 0-2 control value, bits 3-7 must be 0 as the whole byte is written directly to the register|
Note that the instrument settings include both OPL operators (the carrier and the modulator), however if an instrument is played on an OPL rhythm-mode channel, care must be taken for the carrier-only instruments as these have their settings loaded from the modulator fields in the file. In other words:
|Instrument type|| Destination registers for
| Destination registers for |
|Normal OPL instrument||modulator||carrier|
|Rhythm bass drum||modulator||carrier|
|Rhythm tom tom||modulator||N/A|
|Rhythm top cymbal||carrier||N/A|
In the above table, the registers ending in 0 (e.g. 0x20) are the fields for the modulator, and the registers ending in 3 (e.g. 0x23) are the fields for the carrier.
A pattern segment is the musical notation for one track/channel in a pattern. Multiple pattern segments will need to be combined (one for each track/channel) to produce a full pattern. The way in which the segments combine is given in the order lists, described above.
The first six tracks in a pattern (numbered 0-5 here) are played on normal OPL channels (e.g. channels 0-5), with the remaining five tracks (here numbered 6-10) being played as OPL percussive instruments. All songs are played this way in OPL percussive mode. Track 6 is the rhythm-mode bass drum, track 7 is the snare drum, 8 is the tom tom, 9 is the top cymbal and 10 is the hi-hat. Remember that instruments played on tracks 7 and 9 (snare + cymbal) only use the OPL carrier operator, but these registers need to be populated from the modulator fields in the file. Those same instruments can be used on other channels where the modulator and carrier are loaded normally, however.
At the offset given by offPattPtrs in the header, there is simply a list of UINT16LE values, each of which is the offset (relative to the start of the file) where the data for that pattern segment can be found. The list is terminated with a single 0xFFFF value.
The pattern segments themselves are a series of UINT8 bytes, with each byte having the following split:
Here fullbyte means the raw byte value from the file, while command and value refer to bits within the same byte. This is required as some events refer to just the bits while others refer to the whole byte. Although the table above has two rows, there is only one byte and both rows refer to the same byte.
Commands are as follows:
|0-2||Note on, fullbyte is the note number. Middle C is 0x30. Add 12 to this value to get standard MIDI note numbers.|
|4||Set instrument for future notes, value is instrument number (0=first instrument)|
|5||Set small row increment for future notes, value+1 is increment (0=one row, 1=two rows, ..., 31=32 rows)|
|6||Set large row increment for future notes, value+33 is increment (0=33 rows, 1=34 rows, ..., 31=64 rows) - 31 is used for a blank track in a pattern|
|7||Extended command, see below|
Extended commands: (value in table is of fullbyte)
|E0-F4||No effect? Seems to count as a note for delay/row-advancement purposes|
|F4-FC||Shift pitch down for future events . Seems NOT to count as a note for delay/row-advancement purposes. By how much? 0xFC is a little, 0xF4 is more. Seems the 9 values might each be 1/10th of a quarter of a semitone Confirm|
|FD xx||Set volume for future notes to xx (the byte following this event). Byte range is 0 (silent) to 127 (loudest). The scale is not linear, it is exponential/logarithmic. . Does NOT counts as a note for delay/row-advancement purposes. Which one? Is there a formula?|
|FE||Note off, causes a delay of the last row-increment number of rows (same type of delay as a note-on event)|
|FF||End of track. Appears as a terminating byte at the end of each pattern segment. Each pattern segment that makes up a pattern should be the same number of rows (however, possibly due to a shortcut in the player code, placing this byte in the middle of a pattern segment will cause that track to jump to the next pattern while the other tracks are still playing the current pattern. This behaviour is probably unintentional and should not be relied upon.) All pattern segments in a given pattern should be the same number of rows in length, but they may be less than the usual 64 rows (for example the Doofus song for episode 2 only has 63 rows per pattern). Camoto handles this by adding a pattern-break event if the largest pattern segment in a given pattern is less than 64 rows. This event does NOT count as a row for delay/row-advancement purposes.|
Note on events occur at regular intervals, similar to rows in a .mod file. The increment is adjusted (with command 5) so that instead of having delays between each note, adjacent notes can simply be a set number of rows apart.
The following tools are able to work with files in this format.
|Name||Platform||Play?||Create new?||Modify?||Convert/export to other?||Import from other?||Access hidden data?||Edit metadata?||Notes|
This file format was reverse engineered by Malvineous. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)