MFB Tanzmaus Sysex Parser

MFB Tanzmaus Sysex Parser

The MFB Tanzmaus is a nice drum computer. Unfortunately, it plays not very nicely with my MIDI setup since it seems to be designed to be the MIDI clock master, and to be programmed using the buttons on the device.

While you can play the instrument using MIDI messages, you cannot simply download the loops you've programmed on the device and export them as MIDI file. But you can download whole banks in an unknown SysEx dump format. Here I am trying to reverse-engineer this format in order to obtain decoded MIDI that sounds like what you've programmed before. While I can already decode most of the step and knob data, there are yet some unknowns.

Parse the Tanzmaus’ MIDI transport encoding

The actual payload data has been encoded for MIDI transfer first, so we have to undo this.

After receiving a Tanzmaus bank dump as MIDI Sysex messages, we first need to split these messages at the F0 and F7 boundaries indicating the start and end of a single SysEx message. We now see a repeating pattern of 13 messages with a length of 317 bytes, followed by one message that is 84 bytes long. This pattern repeats 16 times (which happens to be the total number of patterns per bank in the Tanzmaus). See split_sysexes() in util.py.

Looking into these single messages, we see another pattern: They all start with:

This is followed by one byte that counts up from 0 to 13, then restarts from 0, and another byte that is increased 16 times whenever the former byte is reset. The latter byte is only present when the first is zero. This smells like the pattern number (second byte) and the message’s sequence number (first byte). parse_tanzmaus_sysex in util.py verifies that this is true for all messages.

The rest (except for the terminating F7 byte) is the 7-bit-encoded payload data. (In the MIDI protocol, the most significant bit must be one to indicate a status byte and zero for a data byte. This leaves us with only 7 bit per byte for our payload data). Thus, the data must be converted to actual 8 bit bytes (convert_7to8 in util.py, which is called by parse_tanzmaus_sysex). This is done by taking 8 7bit-bytes, glueing their 56 bits together, and splitting them into 7 8bit-bytes again. (For details and special cases, read the code.)

Now we bit-mirror every individual byte in the resulting data, and we’ve got a nice data soup to stir.

Parse the actual payload data

The above yielded 16 dumps, one for every pattern in the bank.

By programming various "obvious patterns" and trying to find them in the data, I was able to recover the following data layout:

    0000 - 01bf: step data (16 x 2 bytes A pattern + 16 x 2 bytes B pattern,
                 for BD, SD, RS, CP, TT, SP1, SP2 in that order)
    01c0 - 01c7: bitmirrored last step values. (what's the 8th value?!)
    01c8 - 01d0: mute state for bd, sd, rs, cp, tt, sp1, sp1alt, sp2, sp2alt (0x80 vs 00. no lfos)
    01d1 - 01d4: mute state for bdlfo, cplfo, ttlfo, sp1lfo (continued at 0d98)
    01d5 - 02b4: flam data
    02b5 - 02b5: 00
    02b6 - 0a75: knob data: (BD, SD, SP1, SP2, CP, TT in that order)
    0a76 - 0d95: LFO data (BD, CP, TT, SP1, SP2 in that order)
                 32x 2 byte little endian: data=amount.
                 32x 1 byte lfo speed (1..12 = 00 30 20 18 10 0c 08 06 05 04 03 01)
                 31x 1 byte lfo waveform (0,1,2,3)
    0d96:        ?? TODO
    0d97:        tempo multiplier / scale. 0x60=16ths, 0x30=8ths
    0d98:        sp2 lfo mute (80 vs 00)
    0d99:        00
    0d9a:        bitmirrored shuffle value
    0d9b - 0d9d: 00 00 00
    0d9e - 0d9f: checksum?
    0da0 - 0da1: 00 00

Further details are in the decoding routine at parse_pattern.py.

Source code

The source code and building instructions are freely available on GitHub.