maschinIO is dead, long live maschinIO!

A screenshot of the Push2 Emulator

A few months after the last update (sorry about that...), here's an overview of the current status of maschinIO.
First of all: there's no maschinIO anymore. Disappointed? Well... keep on reading ;-)

 

The maschinIO project started as a quick hack, then it grew into a small library supporting a couple of Maschine controllers (thus the name), and eventually ended up being a general purpose abstraction layer supporting several different controllers on the market (see the list below).
This is the reason why I decided to change the name of the library to cabl (Controller ABstraction Layer), unless you have any better suggestion...

Here's an overview of the current status of cabl:

  • Linux(ARM, x86 and x86/64), OSX and Windows are now fully supported
  • The library currently supports:
    •   NI Maschine MK1
    •   NI Maschine MK2
    •   NI Maschine Mikro MK2
    •   NI Traktor Kontrol F1
    •   NI Komplete Kontrol S25
    •   NI Komplete Kontrol S49
    •   NI Komplete Kontrol S61
    •   NI Komplete Kontrol S88
    •   Ableton Push
  • The control surface of Ableton Push2 is supported as well, color displays support is being investigated (more details below)
  • Essential documentation and examples are being finalized
  • The python wrapper is almost done


The library API looks stable enough, here's what most likely won't be part of the first release:

  • The SAM3X8E(Arduino Due), MAX3421E(USB Host shields for Arduino) and STM32Fx drivers
  • Detailed documentation


Now: what's the meaning of the picture above? What does Push2 emulation have to do with cabl?
Adding support to cabl for the first version of Push was no big deal, the remote scripts were already decompiled years ago and most of the SysEx messages were already documented.
Then, a couple of weeks ago, Ableton introduced Push2, with an amazing color display and a redesigned interface, so I felt the need to add it to the list of devices supported by cabl.

The main problem is that at the moment I don't have a Push2, so I decided to start looking at the application itself, the remote scripts, the logs, to gather all the information I could and try to understand how Push2 differs from its first version.
The layout of the Live app bundle is quite similar to the previous versions, so I easily found the remote scripts ("Contents/App-Resources/MIDI Remote Scripts/") and, as expected, a "Push2" subfolder.


Before we can continue, please read the following disclaimer:
The purpose of this article is purely didactic, and I will not provide any support nor release any source code to start the emulator, so please don't ask :-)
If you really know what you're doing and decide to try some of the steps described below be aware that I will not be responsible for any direct or indirect damage to your software installation and hardware devices. Also, please note that the steps described below only work for the current release of Ableton Live (9.5.0), and I will not update this information for any future release.


So, if you're still interested: let's go back to the main topic!
The first thing I noticed is that most of the communication with Push2 still flows over MIDI, pretty much like Push1.
Each element of the interface sends midi CCs, the velocity sensitive pads send midi notes, sending midi notes back to Push sets the color of the respective pads, and so on.
Push1 display content can be controlled easily via SysEx messages, but that's not going to work for sure with the big and colorful (960x160 pixels) display available on Push2.

At this point, another subfolder of the app bundle caught my attention: "Contents/Push2/", which contains another app bundle called Push2DisplayProcess.
Hmm.
That's interesting, let's have a look at the binary inside the bundle.
A quick and dirty listing of all the strings contained in the binary (ever heard of the strings and c++filt commands?) shows a lot of useful information without even decompiling the file:

  • Some strings look like command line options, and the presence of some boost::program_options symbols is another hint in that direction
  • Damn, "./Push2DisplayProcess --help" does not work :-)
  • A few strings are mentioning IPC communication
  • Looking a bit closer, I noticed some more interesting strings mentioning nanomsg and boost::interprocess: there must be definitely some IPC communication here!
  • There's even some explanations: "push2-ipc-channel" -> "Set the ipc channel to connect to. Default is 'push2ipc'", "push2-log-level" -> "Set the log-level. Default is 'info'", and so on
  • The most interesting: "push2-mode" -> "Chooses the type. Choices are 'hardware', 'emulator'. 'hardware' is the default". Wow, an emulator! Might be a great tool to help me understand how to control the hardware with cabl...

So let's start the emulator then (you may have to set a few paths before):

./Push2DisplayProcess --push2-log-to-console --push2-log-level=debug --push2-mode=emulator


HOLY ***T!!! A window popped up!!!!! Wait a sec... it's a Push2 emulator!!!
I didn't even have the time to click a single button that the window disappeared again, and here's an extract of what I saw on my terminal:

debug { "message": "Opening IPC pair socket with URI 'ipc:///tmp/push2ipc'" }
debug { "message": "IPC client 'push2ipc'" }
...
debug { "message": "bark bark: shutdown!" }
warning { "message": "Shutting down because live didn't ack in time" }
debug { "message": "IPC socket 'ipc:///tmp/push2ipc' stops listening" }
info { "message": "Push2DisplayProcess - terminated with 0" }



Damn, there's still something missing, looks like the emulator is expecting some kind of ACK message.

There's an interesting information, though: "IPC socket 'ipc:///tmp/push2ipc' stops listening"
If you're familar with nanomsg, you might know about a tool called "nanocat": we will bind it to the IPC address mentioned in the log message and print whatever Push2DisplayProcess will send (something like "nanocat --pair --bind-ipc /tmp/push2ipc --hex").
Running again the binary will result in one line being printed by nanocat: "0x00-0x00-0x00-x00".
Hmm.
So the binary sends four bytes with the same value (zero) and expects an answer back.
Let's try to figure out the possible answer, these could be the most simple cases:

  • Reply with 0x00-0x00-0x00-0x01
  • Reply with 0x01-01x0-0x01-0x01
  • Reply with 0x01-0x00-0x00-0x00

Found it!
Replying with 0x01-0x00-0x00-0x00 tricks the emulator into thinking that it is connected to Ableton Live!
At this point, the emulator opens two virtual MIDI ports(an input and an output), but the display still looks black.

The good news: I can already use the emulator to map the buttons and encoders for my library.
The bad news: I still don't know how to enable the display.

Let's continue our exploration.

How can Live recognize a connected Push2? Live queries MIDI devices for identity information using a standard Universal SysEX message (Identity Request, see here and here), so if we were able to reconstruct the content of the reply sent by Push2, we could be able to go one step further.

Let's write a simple C++ application using RtMidi, which will simply create two MIDI ports (in/out) and reply to Identity Request messages.
Ok, but what would be the content of the Identity Reply?
A look at the decompiled version of Push2/sysex.pyc (under the MIDI Remote Scripts folder) will make it obvious, Live expects an Identity Reply message containing:

  • Ableton MIDI manufacturer Id (0x00, 0x21, 0x1D)
  • A product identifier (0x67, 0x32, 0x02, 0x00)
  • Major/minor version and build number
  • A serial number
  • [optionally] A board revision.
I decided to use an existing version number, which I found by looking at the file name of the Push2 firmware file (MIDI Remote Scripts/Push2/firmware/app_push2_1.0.44.upgrade).

Replying with such information should make Live think that a Push2 has just been connected (there's no trace of the TEA based challenge-response message used with Push1).
As soon as this happens, Live starts Push2DisplayProcess in "hardware" mode, passing it's pid and an IPC socket address which differs from the default one.
Unfortunately the internal watchdog kills the process after a few seconds, and Live keeps restarting it in an endless loop. We're not going anywhere :(

What's wrong? First of all, we need to convince Live to start Push2DisplayProcess in "emulator" mode, and this could be possible in (at least) two ways:

  • By manipulating the Live binary (basically by swapping the two strings "emulator" and "hardware" with an hex editor). A very ugly solution, feels a bit like cheating... I'd prefer something less intrusive.
  • By renaming Push2DisplayProcess to something else and writing a shell script named Push2DisplayProcess which will replace the "--push2-mode" parameter value with "emulator" and forward the rest of the parameters to the renamed executable. Sounds better :)

What happens at this point?

  • After starting Live, I start my executable which creates the virtual ports and replies to Live Identity Request with a Push2 Identity Response
  • Live calls Push2DisplayProcess, which now is a shell script that will force the emulator mode and forward the IPC address and the parent PID to the real Push2DisplayProcess which I renamed to something else
  • The communication with the display emulator is established, now I only have to open Live's Preferences and swap the MIDI ports created by my executable with the MIDI ports created by the emulator process


Et voilà! The emulator is now fully functional :D

This was of course quite some fun, but unfortunately the display emulation is not really helping me with my library. The reason is that Live is not sending any frame to the display process, but rather json objects containing high-level information on HOW to render the text and graphics.
I thought initially that boost::interprocess was used to send display frames to the emulator but I was wrong, as far as I can tell it's used to send waveform data and some realtime information like the level of each channel, etc.
The frames are rendered instead by Push2DisplayProcess internally using Qt (all the QML files are available in the folder "Contents/Push2/Push2DisplayProcess.app/Contents/Push2/qml") and sent to the device (in "hardware" mode) using libUSB OR directly shown in the emulator window.

What's next? Well I guess now I need to find a real Push2 and log the USB communication between the display and Push2DisplayProcess. If the frame format is not proprietary (e.g. it is using some weird compression/encryption) it will be very easy to add it to my library, otherwise... well, you'll have to live without Push2 display support in cabl ;)

Stay tuned for cabl first release, hopefully happening veeeery soon ;)

comments powered by Disqus