The KLM Format is used by Wacky Wheels to store Adlib music tracks. It is probably named after Mark Klem, the Wacky Wheels composer.
The file is structured as follows.
|UINT16LE tempo||Playback speed in ticks per second|
|UINT16LE offNotes||Offset of song data|
|BYTE inst||Instrument data|
|BYTE notes||Song data|
The instrument block contains all the instruments used in the song. Each instrument is 11 bytes long, and is stored one after the other, in the following structure.
|Data type||OPL base register||Description|
|UINT8 iModScale||0x40||Modulator key scaling/output level|
|UINT8 iCarScale||0x43||Carrier key scaling/output level|
|UINT8 iModAttack||0x60||Modulator attack/decay level|
|UINT8 iCarAttack||0x63||Carrier attack/decay level|
|UINT8 iModSustain||0x80||Modulator sustain/release level|
|UINT8 iCarSustain||0x83||Carrier sustain/release level|
|UINT8 iModChar||0x20||Modulator characteristic (Mult, KSR, EG, VIB and AM flags)|
|UINT8 iCarChar||0x23||Carrier characteristic (Mult, KSR, EG, VIB and AM flags)|
|UINT8 iModWaveSel||0xE0||Modulator wave select|
|UINT8 iCarWaveSel||0xE3||Carrier wave select|
Song data follows the instrument data.
The song data appears to be a custom event-style structure, superficially similar to MIDI. Each command byte is followed by a variable number of data bytes whose purpose depends on the command. The lower four bits are used for the channel (so command bytes of 0x30 and 0x31 perform the same command on the first and second channels, respectively.)
Many songs are stored with 128 ticks per quarter note, however some do differ. The correct value for this does not appear to be stored in the file, however this is not required for playback, only to neatly arrange notes on a graphical musical staff.
|Command byte||# of data bytes||Description|
|0x10||0/2||Note on with frequency|
|0xFF||0||End of song|
0x00: Note off
Turn the note on the current channel off (this sets the keyon bit to 0, on OPL base reg 0xB0).
0x10: Note on with frequency
Plays a note at the given frequency.
For channels 0-5 inclusive (i.e. command bytes 0x10 to 0x15), the first data byte is written to OPL base register 0xA0, and the second data byte is written to OPL base register 0xB0. As an example, if the command byte was 0x12 (set frequency on channel 2) then the two data bytes are written to register 0xA2 and 0xB2.
For channels 6-10 inclusive, there are no data bytes. Instead, these activate the OPL rhythm-mode percussion channels without setting a frequency:
|Command byte||OPL 0xBD bit||Instrument|
This command sets the velocity of subsequent notes on the channel. As with MIDI, the single data value ranges between 0 (silence) and 127 (full volume). Wacky Wheels uses this value to write the appropriate output level to the OPL chip, using the following formula:
- value = (127 - databyte) / 2
Thus a data byte of 0x58 results in the value 0x13 being written to the OPL chip. The value is written to the carrier operator on OPL base reg 0x40 (so channel 0 will actually write to reg 0x43.)
0x30: Set instrument
This command causes the instrument settings to be copied into the corresponding OPL registers for the channel. A data byte of 0x00 selects the first instrument in the instrument block.
0x40: Note on
Turn the note on the current channel on, at the last set frequency (this sets the keyon bit to 1, on OPL base reg 0xB0).
This command causes a delay. The single data byte indicates how many ticks to wait for. Combined with the ticks-per-second tempo field in the header, the actual delay time can be calculated. With a data byte of 0x10 (delay for 16 ticks) and a tempo of 0x138 (312 ticks per second) the delay should be 16 ÷ 312 = 0.0512 seconds (51.2ms).
0xFE: Long delay
0xFF: End of song
This command has no delay bytes and signifies the end of the song data.
- Camoto can just about load KLM files (some instruments are a bit off)
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!)