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:
F0
(SysEx begin)00 21 0b
(MFB’s Manufacturer SysEx ID Number as assigned by the MIDI association00 03 00 00
(Probably the Tanzmaus ID)
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.