-
Notifications
You must be signed in to change notification settings - Fork 1
MIDI Protocol
App is a Unity engine program using standard OS MIDI interface. No data can be obtained from iOS App binary due to FairPlay DRM. However, Teenage Engineering's GitHub repo reflects a fork of the library keijiro/MidiJack
which is likely used by the app.
The Mac app binary's decompiled C# code does not contain MIDI communication, but the inner file libSnakeLib.dylib
can be decompiled with a disassembler, or C or Objective-C decompiler. (Big thanks to @Lrk for finding this.) The app appears to use the open-source-parsers/jsoncpp
library at version 1.7.5.
App communicates with OP-Z via SysEx messages with common format:
F0 00 20 76 01 00 ....... F7
F0
is standard sysEx start; 00 20 76
is Teenage Engineering manufacturer code; 01
is presumably device ID. Following 00
is command/message number. F7
marks end of message; values between 00
and F7
vary based on message number.
While on "Device" screen, App repeatedly sends SysEx ID request messages F0 7E 7F 06 01 F7
. If OP-Z replies, begins to send custom messages requesting updates. ID request/response handshake is not mandatory.
OP-Z's ID reply is extended: F0 7E 7F 06 02 00 20 76 01 58 33 43 4A 44 31 59 38 31 2E 31 2E 39 28 30 00 00 00 00 00 00 00 05 F7
. Bytes 16-18 are ASCII of "serial number" displayed on app screen. Bytes 19-24 are ASCII of "version number".
To avoid clashes with the MIDI SysEx format, all values sent in SysEx messages are 7-bit. When messages requiring all 8 bits are sent, they are packed into chunks of 8 bytes or less. The first byte of each chunk holds the 8th bits of the following 7 bytes, with the LSB of the first byte being the MSB of the first data byte, the 2nd bit being the MSB of the second data byte, and so on. If there are less than 8 bytes to encode, the chunk may be shorter than 8 bytes but the first byte will still contain the MSBs of all following bytes in the same format.
The following Python code will undo the 7-bit packing:
def decode7bit(barray):
chunks = [barray[x:x+8] for x in range(0, len(barray), 8)]
out = bytearray()
for chunk in chunks:
extraBits = chunk[0]
mask = 1
for chunkIndex in range(1,len(chunk)):
value = chunk[chunkIndex]
if ((extraBits & mask) > 0):
value += 128
out.append(value)
mask = mask << 1
return out
Certain messages sent by the OP-Z are compressed using zlib. These can be identified by the Zlib signature 78 9c
at the beginning. However because of the 7-bit encoding above, the OP-Z transforms this to 78 1c
with an appropriate MSB bit in the chunk.
F0 00 20 76 01 00 03 2D 0E 05 F7
(prior to version 1.2.5)
F0 00 20 76 01 00 01 4E 2E 06 F7
(version 1.2.5)
This message must be sent regularly for any of the other SysEx messages to appear.
Sent roughly every ~1 second by app. Triggers a status response. SysEx status updates continue to be sent for ~1 second whenever data in that update changes, then stop until request refreshed. Parameters are unknown.
2D 0E
can be different. Same bytes are sent back in the response 01
message (see below), although they are capped at 7F
(7 bit max?). 03
can also be changed to 00-02. Any change to 05
makes message invalid.
On first 00
send in a session, a number of messages are sent in rapid succession, including current settings for all voices in 0E
messages (see below).
F0 00 20 76 01 01 0C 15 55 2D 0E F7
Sent by OP-Z every time a 00
message is received. Significance currently unknown. Bytes 10/11 match bytes 8/9 of 00
message, but with 8th bit cleared if it was set.
F0 00 20 76 01 02 00 0C 00 30 00 00 04 00 01 01 00 00 00 F7
8th byte appears to be a sequence number. 10th byte indicates which value was changed. 19th byte indicates what it was changed to.
F0 00 20 76 01 03 00 02 05 F7
8th byte is selected octave, with octaves 2-7 represented by signed 7-byte values 7D
to 02
.
9th byte is selected track number.
Sending this message updates appropriate values.
F0 00 20 76 01 04 02 64 7F 05 00 F7
7th byte: 3rd bit set = microphone mode.
F0 00 20 76 01 06 06 01 40 1F 1F 00 F7
8th byte 1st bit (hi) and 9th byte 7th bit (lo) indicate selected encoder mode: 00
= white, 01
= green, 10
= purple/blue, 11
= orange. 8th byte 2nd bit set = mixer button down. 8th byte 3rd bit set = record button held
10th byte: 5th bit unset = track/step button held, bits 1-4 give number of held track/step. 6th bit set = shift button held. 7th bit set = tempo button held
11th byte: 2nd bit set = screen button held. 3rd bit set = sequencer playing
12th byte: 1st bit set = track select held. 2nd bit set = program button held.
Sending these messages can update encoder mode and system modes. Sending messages indicating a button is held can "lock" OPZ into having this button held until another message is sent (pressing and releasing button does not release)
F0 00 20 76 01 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 F7
27th bit is number of selected program.
Not sendable. If sent with different settings, OP-Z resends 07
message with current settings.
Never seems to be sent. Sending F0 00 20 76 01 08 F7
provokes an 09
response and also seems to retrigger held notes.
F0 00 20 76 01 09 00 0A 00 36 00 00 00 78 57 1C 6D 5D 6D 0C 54 57 6E 7D 47 71 7F 3E 41 30 1E 04 42 2E 40 29 61 31 40 6B 58 2F 06 1C 0C 54 59 6B 05 32 5D 5D 09 46 1E 36 06 60 5A 48 4E 62 4A 3B 72 26 45 31 2F 12 79 62 35 09 42 6A 16 3B 12 60 5C 64 5D 12 0A 6C 05 2B 45 16 25 02 4A 79 1E 53 29 35 09 04 10 2F 3F 4C 0B 36 72 0B 64 4D 1C 55 59 15 7C 22 72 54 75 54 46 15 25 10 52 0A 3D 26 35 27 77 5C 27 39 73 73 30 3B 77 1E 76 1E 37 3B 73 3D 12 7A 1D 3D 5D 77 7E 66 1E 39 67 6F 2B 5C 19 1D 5D 3F 27 45 38 64 59 57 1B 48 3D 59 75 79 49 58 1E 7B 0A 1C 53 0A 5A 59 79 43 68 2F 1A 74 5B 5F 68 6A 5C 2B 3B 5F 58 20 5F 09 7A 73 76 5E 3F 5B 56 5A 2E 09 03 7A 07 F7
Code indicates that this may be the data for a full pattern, compressed using zlib and sent in packets.
F0 00 20 76 01 0B 00 09 00 00 00 36 00 00 00 00 F7
Sent in response to an 09
message sent by the app.
F0 00 20 76 01 0C 02 78 1C 63 61 60 60 64 00 64 62 62 06 02 16 20 24 60 65 7D 3F 0A 06 13 31 60 22 20 3F 71 54 6F 21 3F 74 06 28 58 40 40 10 18 34 30 3B 7D 07 52 1E 00 05 7A 71 4B F7
Sent in response to an 09
message sent by the app. Code indicates this may be global data compressed using zlib.
F0 00 20 76 01 0E 20 05 02 00 75 53 61 0D 10 2A 2B 31 39 25 73 00 13 6B 20 1B 68 00 7F
Byte | Function |
---|---|
8 | Selected track number |
9 | Engine parameter 1 |
10 | Engine parameter 2 |
11 | Attack |
12 | Decay |
13 | Sustain |
14 | Release |
15 | FX Send level 1 |
16 | FX Send level 2 |
18 | Filter |
19 | Resonance |
20 | Pan |
21 | Level |
22 | Portamendo |
24 | LFO Depth / Arp Speed |
25 | LFO Speed / Arp Pattern |
26 | LFO Value / Arp Style |
27 | LFO Shape / Arp Range |
F0 00 20 76 01 0F F7
Sent during configurator process. Provokes a 10
response.
F0 00 20 76 10 02 78 1C 63 64 44 05 0C 55 0C 4C 4C 2C 2C 6C 6C 2A 1C 1C 5C 5C 3C 3C 7C 79 7C 68 5C 01 11 46 1F 27 4E 40 40 00 00 63 0C 02 09 20 F7
Sent in response to 0F
message. This appears to be a zlib compressed and 7-bit packed message. Decoded, the message sent here is 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 97 00 00 00
. The repeating 16 sequences likely correspond to 16 tracks but the value of the sequential numbers is not clear.
F0 00 20 76 01 11 00 58 33 43 4A 44 31 59 00 38 00 31 2E 31 2E 31 00 37 2B 00 00 00 00 00 00 00 00 F7
Appears to contain ASCII serial number and firmware version number.
F0 00 20 76 01 12 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 03 7F 7F 10 20 F7
Seems to sometimes be send when notes are played, and then to be sent again with 00/00 in the later positions when notes go silent (not necessarily when keys are released). But is not always sent?
F0 00 20 76 01 33 35 53 21 7F 68 69 0A 4F F7
Sent repeatedly by OP-Z after 52
command, once a second, six times before giving up. Function unknown.
F0 00 20 76 01 34 1A 7F 68 69 0A 4F f7
OP-Z's response to 51
message below. Unknown.
F0 00 20 76 01 35 03 1E 69 00 00 00 00 00 00 00 F7
Seems to request a file to be sent in $53 packets. The bytes after the 35
seem to indicate the file. File is only sent if a file client heartbeat has also recently been sent, and the heartbeat must be maintained during file transfer or file sending will stop. 03 1E 69
may need to match bytes in the heartbeat. The large number seems to indicate the file to send.
-
00 00 00 00 00 00 00
sendssettings/slotConfiguration.json
-
01 00 00 00 00 00 00
sendssettings/plugs.json
-
02 00 00 00 00 00 00
sends nothing, but code suggests it looks for a file calledsyncjob.json
. -
03 00 00 00 00 00 00
sendsproject01.opz
and similar up to $0c.
It is not clear if any other files can be sent by including different codes here. An invalid code will give no response.
F0 00 20 76 01 51 03 1E 69 F7
Sent by app when configurator is starting up, at regular intervals.
F0 00 20 76 01 52 03 1E 69 00 F7
Sent by app during configurator operation.
F0 00 20 76 01 53 ....
Sends a packet of data. Everything after 53
and before the final F7
is file data in 7-bit encoded format. Once 7-bit decoding is applied, this will contain a 9 byte header, followed by zlib compressed file data. The 9 byte header is:
af 70 01 00 00 00 23 00 00
The seventh and eighth bytes are the sequence number of the packet (little endian).
The nineth byte is 01 if this is the last packet of the file.
F0 00 20 76 01 61 ....
Unknown functionality, but snakelib indicates a method called "addGateSequenceStep" is called.
F0 00 20 76 01 62 ....
Is followed by a command in UTF-8 text, ending with F7
(with no carriage return, zero, or any other characters at the end). Known commands are:
-
enable-debug
anddisable-debug
: enable and disable debug mode functionality based on pressing and holding SCREEN during usage. -
enable-verbose-log
anddisable-verbose-log
: may modify logging. -
enable-controller-mode
anddisable-controller-mode
: unknown. -
enable-battery-log
anddisable-battery-log
: unknown. -
enable-uart-log
anddisable-uart-log
: unknown. -
debug-crash
: sends the OP-Z directly to update mode with a "crash report" dropped in the root of the filesystem. -
debug-save
: unknown function.
Handler added in 1.2.5, but functionality not clear.
F0 00 20 76 03 xx yy F7
Documented in videolab MidiJack source code as indicating Videolab messages.