Decoding the Digitone SysEx

True, but all the “someones” are related to this forum and employed by Elektron. Elektron can agree among themselves to do so. It’s a Public Relations issue more than anything.

I think they have too much on their plate. The original MD had the specs in the manual, DSI provides the specs in theirs. There is nothing proprietary about encoding or decoding a stream of values, it just makes it easier for the community to contribute with software. The decoding of the RYTM’s sysex allowed void to help users out with Sample Upload when C6 was giving people problems. I have received numerous emails saying how my app Collider got them to purchase / re-purchase a RYTM. It’s a small effort with potentially big results.

2 Likes

Oh wait. That’s me!

News Everybody!

Apologies on falling off on this project. Things got super hectic with school and I ended up building some other things over the break (shameless plug for ortx.org, an internet radio station that highlights underground Texas musicians, DJs and curators). I found some time to get back on this and am happy to announce that I’ve decoded the Sysex data.

What it is

This is a simple python library that can analyze patch data from the Digitone’s SysEx dumps. It can also analyze the live state from the Digitone with a simple sysex request. I’ve attempted to be as verbose in the in-line documentation as possible so people can translate to other languages as needed. The only known issue at this point is the LFO Depth output tends to be off by 0.01 when it is negative.

What it is not

This library is not yet 100% guaranteed to work. I’ve been able to successfully display the correct values for all parameters in my test code, but have not had the time to fully test the library. Isolating and testing the individual bytes is an extremely tedious process but I’m confident enough that the library is giving useful data back.

This library cannot (yet) decode Pattern data and other SysEx messages from the Digitone. It cannot (yet) encode the SysEx data to be returned to the Digitone, though that is the next thing I’ll be working on and it shouldn’t take 4 months to complete this time around. :slight_smile:

You can check it out over at the github repo.

20 Likes

Thanks, looking forward to digging into this!

  1. The current request of “patch” is the active state of the Digitone?

msg_array = [int(‘0x00’, 16), int(‘0x20’, 16), int(‘0x3c’, 16), int(‘0x0d’, 16), int(‘0x00’, 16),
int(‘0x6B’, 16), int(‘0x01’, 16), int(‘0x01’, 16), 0, int(‘0x00’, 16),
int(‘0x00’, 16), int(‘0x00’, 16), int(‘0x05’, 16)]

Is that the sysex for requesting the current state (besides adding F0 @ the start and F7 @ the end)? If not, could you list in the docs the exact Sysex message you found for requesting the current state?

  1. Could you put a usage guide? I haven’t used python in years, so a guide on how to import the library and run a request would be useful for many people

Good job, by the way, let’s keep this going. If I can help out in any way let me know.

1 Like

I can get a little more documentation up by this weekend and I’ll follow up here. As far as using the library in python, a full tutorial would be a little out of my scope. If you clone the project and place the libdigitone folder in yr project folder, you can simply add the line… import libdigitone as dt and start working with the functions.

If you look at the dev branch on github there is a test.py file that shows some usage examples

2 Likes

Awesome work and thanks for sharing. I wonder how much different the Digitakt is to the Digitone in regards to sysex.

great work!

wasn’t even aware it was possible to request the current live state - where did you find the sysex message specified?

They’re using the same basic format as they did with the machine drum and monomachine. That documentation was a gold mine once I worked it out.

2 Likes

I suspect not much. I don’t have access to one so I have any way to test. The hardest part is figuring out how they decided to encode parameters that have 14-bit information. They’re seems to be a standard method that they used but it varies a little bit for a few parameters.

1 Like

thx for the info - will check it out!

To answer the first part of your question, that is the message to request the current patch. It will retrieve whichever patch is live on the active track on the digitone. It’s formatted like that as a quick hack to use with the python mido library. I’ll work on this as I have some time and make what is going on a little more explicit and standardized with the rest of the library. That code is 4 months old and it would take me a little more than a cursory glance to figure out which byte is the actual request byte.

1 Like

the 6b is the request current state command

2 Likes

Seems to support the following dump request bytes:

96 - specified pattern dump - byte 9 = pattern
97 - specified pattern dump without sound data? - byte 9 = pattern
98 - specified pattern all sounds dump - byte 9 = pattern
99 - specified pattern track sound dump - byte 9 = pattern - how to select track??
100 - ??
104 - current pattern dump
105 - current pattern dump without sound data?
106 - current pattern all sounds dump
107 - current sound dump - byte 9 = track
111 - all patterns dump

4 Likes

I guess now need the corresponding sysex to send, instead of request

I’m not understanding how to get the value for the 3 Byte Parameters.

Let’s use Detune as an example. According to the CC’s send out by the Digitone, I determined this Parameter goes from 0 - 16,256. When setting the Detune value so it displays 10.73, the CC’s output look to be the value of 1373 or 1374 (argh). The 3 Bytes for Detune in this case are 8, 10, 58 or 8, 10, 59

  1. So, it appears there is an even finer resolution than what the DN displays on screen (a little annoying)
  2. How do you reconstruct the value 1373 / 1374 from those 3 Bytes?

This is actually what took me so long. Take a look in the constants.py file. In the PARAM_LOOK dictionary there are comments with the formulas.

3 Likes

Basically, there are three bytes that dictate the final value. A flag byte, a msb, and a lsb. The lsb maps between 0-50. If the flag byte is activated, the lsb maps from 51-99. The msb is just the number to the left of the decimal.

Where this gets very fun is that the LFO depth, harm, and b operator values abide by a different formula.

4 Likes

I did look at the PARAM_LOOK and see your comments, but I still can’t reconstruct the values using what I posted.

Can you somehow write a formula to reconstruct 1373 / 1374 from those 3 Byte Examples? I don’t see how the flag even matters :[

10 * 128 + 58 = 1338 != 1373

Apologies if I’m being dense

Edit: Ok, so the MSB is the left of the decimal…so in this case that makes sense since we are looking for the display value of 10.73 and the MSB is 10. What about the .73?

It seems weird that they represent these values with 2 CC’s (MSB and LSB), but store them in the sysex with 3 values. I just don’t understand why they did that.

Nah, yr not being dense, the way it’s programmed is a little obtuse.

Here’s the snippet of code that does the magic:

                 if flag_byte[flag_bit] == '0':
                    lsb_value = int((50 / 127) * lsb_value)
                    return round(msb_value + (lsb_value / 100), 2)
                else:
                    lsb_value = int((50 / 127) * lsb_value + 50)
                    return round(msb_value + (lsb_value / 100), 2)

This checks the flag byte (which is usually shared amongst a few parameters). If it the bit that belongs to DTUN is low, then this will map the LSB from 0-50. Otherwise, if the bit is high, it does the same mapping but also adds 50 to the lsb_value. The return statements divide the lsb_value by 100 to make it a decimal and add it to the msb_value, outputting the correct value as an integer.

It really makes no sense why the did it at all to me. Even stranger is how they modified this structure to do the LFO, when they could have just used the flag_byte as a simple negative sign. :man_shrugging:

2 Likes

Ok, so the flag is set so we get: (50 / 127) * 58 + 50 = 72.8346456693

Which I guess is the .73 in the display…so we can construct the 10.73 that we see. I guess I still can’t reconstruct the raw value of 1373 (out of 16,256). It seems so weird they are encoding the sysex with what is displayed and not to match the High Resolution CC’s they send out :thinking:. Am I wrong that the resolution is from 0 to 16, 256?