Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Support for Maschine MK3 #9

Open
Drachenkaetzchen opened this issue Dec 19, 2017 · 43 comments
Open

[WIP] Support for Maschine MK3 #9

Drachenkaetzchen opened this issue Dec 19, 2017 · 43 comments

Comments

@Drachenkaetzchen
Copy link

Drachenkaetzchen commented Dec 19, 2017

This issue documents my findings about the Maschine MK3.

Basic Information from usb-devices

T:  Bus=02 Lev=02 Prnt=02 Port=05 Cnt=03 Dev#= 11 Spd=480 MxCh= 0
D:  Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs=  1
P:  Vendor=17cc ProdID=1600 Rev=01.41
S:  Manufacturer=Native Instruments
S:  Product=Maschine MK3
S:  SerialNumber=REMOVED
C:  #Ifs= 7 Cfg#= 1 Atr=80 MxPwr=480mA
I:  If#= 0 Alt= 0 #EPs= 0 Cls=01(audio) Sub=01 Prot=20 Driver=snd-usb-audio
I:  If#= 1 Alt= 1 #EPs= 1 Cls=01(audio) Sub=02 Prot=20 Driver=snd-usb-audio
I:  If#= 2 Alt= 0 #EPs= 0 Cls=01(audio) Sub=02 Prot=20 Driver=snd-usb-audio
I:  If#= 3 Alt= 0 #EPs= 2 Cls=01(audio) Sub=03 Prot=00 Driver=snd-usb-audio
I:  If#= 4 Alt= 0 #EPs= 2 Cls=03(HID  ) Sub=00 Prot=00 Driver=(none)
I:  If#= 5 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=bd Prot=00 Driver=(none)
I:  If#= 6 Alt= 0 #EPs= 0 Cls=fe(app. ) Sub=01 Prot=01 Driver=(none)

if#=5 is bulk data, maybe for the displays?
if#=6 seems to be for the firmware update.

@Drachenkaetzchen
Copy link
Author

A bit of success: I can now make device-test to turn on the Mute/Choke button as well as 4 LEDs around the encoder button. No inputs are recognized yet, but well, at least it's connecting ;)

@Drachenkaetzchen
Copy link
Author

Okay, I got the buttons working (partially). My c++ is very rusty, so I need to figure out how the mapping in cabl works. I added debug outputs to both the DeviceTest (which outputs the button) as well as the device implementation method processButtons to display the button ID of the device.

I now have a partial map of buttons, some buttons didn't output anything, so I guess the index list is wrong.

I also tried to modify the button list in MaschineMK3::Button however it's not clear how it works. I assume the position in the list defines which button is which. For example, NoteRepeat is button number 20, so I assume that NoteRepeat must be at position 20 in the list, right?

@shaduzlabs
Copy link
Owner

That's good news, I didn't really have time so far to work on the Mk3 support :)
You're right, the position in the list is the actual position of the corresponding bit in the HID report, let me know if I can help you any further!
The display is going to be a bit tricky: like Maschine Studio and Komplete Kontrol Mk2, the new Maschine controller uses a proprietary compression algorithm to send display frames and I didn't try to reverse-engineer it so far.

@Drachenkaetzchen
Copy link
Author

I'm on to the display support. I've captured frames via Wireshark and usbpcap.

What I know so far is documented in this google doc:

https://docs.google.com/document/d/1xdWTY4XAMPFj8dKzvZJnUyJy-SxdZiPVdcSUw1nVl8A/edit?usp=sharing

Feel free to read and comment if you find something interesting ;)

@Drachenkaetzchen
Copy link
Author

Okay, that was way easier than expected.
image

@shaduzlabs
Copy link
Owner

Great job @FELICITUS, that was really quick :)
Are you also able to send partial updates?

@Drachenkaetzchen
Copy link
Author

@shaduzlabs not yet. Right now I only have a PHP script to convert a 480x272 image to a data file and a python script to send the raw data to the interface. If you want both the python and the PHP script tell me :)

There's more to the protocol, but right now I just send the raw pixel data using command code 1 to the device. Command code 2 supports pixel repetition, maybe there are other command codes. I also gotta clean up the documentation

@Drachenkaetzchen
Copy link
Author

I can finally watch anime on the Maschine MK3 ;)

https://www.youtube.com/watch?v=3qieMM4V1SU

@Drachenkaetzchen
Copy link
Author

A few words on the protocol:

It's basically a bunch of commands being sent to the display. Format is RGB565. Right now I found two commands:

first 16 bytes: Header, which includes the display to address as well as X/Y start position and the width/height of the transmission.

After that commands are following. Each command header is 4 bytes long. The first of that 4 bytes is the command code and the next 3 are parameters.

Command Code 00: Sends the next two pixel values to the display. Parameters are all zero. Data is two pixels with 2 bytes each
Command Code 01: Repeats two given pixel values n times. Parameters are the number of repetitions. Data is two pixels with 2 bytes each.
Command Code 03: Unknown yet, but required to display things on the display. Parameters are all zero. No data.
Command Code 04: End of Transmission. Parameters are all zero. No data

@Drachenkaetzchen
Copy link
Author

Documentation for the display protocoll can now be found here:

https://github.com/Drachenkaetzchen/cabl/tree/develop/doc/hardware/maschine-mk3

Will send a PR as soon as I got the code working.

@Drachenkaetzchen
Copy link
Author

I just added documentation for the HID protocol, including touch strip, touch sense knobs and all LEDs.

Fun fact: The Maschine MK3 even knows the physical state of the volume knobs on the back.

@Drachenkaetzchen
Copy link
Author

I now have a preliminary version in my repository. However, currently it's very unlikely that I'll finish support, since my C++ isn't that strong anymore and my holiday is almost over.

There are a few things to consider:

  • Right now, the USB Driver claims interface #0 hardcoded, whilst the NI Maschine MK3 uses interface case-sensitive CoreMIDI error in DeviceTest #5
  • The bulk transfer, for some reason, doesn't allow me to push the display data for one display at once; instead, I have a workaround where I transmit every line individually. This is pretty slow. When I try to transmit the full display data (262208 bytes), I see in wireshark that only around 62kb are transmitted.
  • I believe the Macro M_REGISTER_DEVICE_CLASS must be rewritten to allow for the same device with the same USB vendor and product ID to exist in the registry; however, I didn't research this for too long.

@shaduzlabs
Copy link
Owner

Wow! Kudos for this seriously impressive work :)

Now I'll be able to add support for Komplete Kontrol MK2 and Maschine Studio as well since they use the same display protocol.
I'll do some refactoring taking into account your input and will investigate the display transfer issue further.
Also, in the next weeks I'm going to replace the gfx subsystem with a separate library, so there will be a few more changes to the display handling code.

@mstaack
Copy link

mstaack commented Oct 24, 2018

@shaduzlabs have there been any updates?

@mstaack
Copy link

mstaack commented Oct 25, 2018

@Drachenkaetzchen amazing work with that documentation regarding the mk3!!

@TronicLabs
Copy link

@shaduzlabs
Any new on refractoring?

@mpex2006km
Copy link

mpex2006km commented Mar 11, 2020

@Drachenkaetzchen @shaduzlabs
I don't know if the topic is still alive but my S5 for some reason now plays YouTube videos.

https://youtu.be/NPPl5OQnLEg
I am really interested to finishing this project. Full control of the controller. (for now full hid support for virtual dj all buttons/pots/encoders/leds mapped even "hardware" ones like "cue vol"). My goal is to make a full translation software. Midi in -> Display , Or if you want a full custom displays Pixel Values -> to display .

@Drachenkaetzchen : I have some different behavior of the screen selection part of the header . If interested I can post the differences. After that fix i can send a full display update at ones (Wireshark reports 261175 bytes ? ). (windows 10 Visual C++ + libusb). Plus I get some strange behavior with some part of the end packets (any help really appreciated)

@spacepluk
Copy link

Do you think this would work on a Komplete Kontrol mk2?

@mpex2006km
Copy link

@spacepluk the screens look the same so my short answer is yes.

Long Answer : Probably some code tweaks must be done. The protocol must be kinda similar but the
without hardware testing I cannot promise anything. I only own an s5 , so for any other hardware is impossible to know (and difficult to develop)

@spacepluk
Copy link

I have a s61 mk2 here. I'll give it a try this weekend.

@3phase33
Copy link

@ mpex2006km Did you alter any of the original code to have it working on the S5?

@mpex2006km
Copy link

@3phase33 What do you mean by that . Original code from native ? or from CABL.

Native : Did not use any of their code not even the driver. (it doesn't let me access the USB interface )
Cabl : I only used some part of the @Drachenkaetzchen 's documentation (huge thanks). Some stuff worked out of the box other did not work at all (for example huge discrepancy in the display selection , if i follow the documentation from incomplete frames to nothing at all).

But all code is written from scratch in visual c++ and hours in wire shark

@3phase33
Copy link

@mpex2006km Thanks for the quick reply.
Sounds like you were getting the same result i was from Cabl.
I gave up due to lack of c++ knowledge.

@asutherland
Copy link

If it's any help, I have a branch/fork of node-traktor-f1 at https://github.com/asutherland/node-traktor-f1/tree/maschine-mk3 based on Drachenkaetzchen's work here that is able to display stuff on the screens of Mk3's and Traktor Kontrol D2 using node.js. Note that if you send the Mk3 packets it doesn't like, it may need to be power-cycled before it will start responding correctly to well-formed packets.

@mpex2006km
Copy link

@asutherland My S5 did not have any problems like this . I sent a lot of "bad" packets but i was unable to make it unrecoverable. As soon as i send a good packet the screens go back to normal.

One question do you have any video of the displays while you load the software . I had a strange effect of hole display turning white for a burst of time but then goes back to normal. This only happens some times only on loading. I am wondering what is so different between the protocol of MK3 is compared to S5. The packets in wireshark look the same .

Did you use the native instruments driver and if yes in what os ?

Thank you very much in advance (again @Drachenkaetzchen documentation helped a lot)

@asutherland
Copy link

@asutherland My S5 did not have any problems like this . I sent a lot of "bad" packets but i was unable to make it unrecoverable. As soon as i send a good packet the screens go back to normal.

It may have been a situation where the Mk3 was expecting a packet size of N and while experimenting I sent it <N and so the effective phase of the packets was messed up once I corrected it to N. It may also just be that I run linux and the USB subsystem was sad.

One question do you have any video of the displays while you load the software . I had a strange effect of hole display turning white for a burst of time but then goes back to normal. This only happens some times only on loading. I am wondering what is so different between the protocol of MK3 is compared to S5. The packets in wireshark look the same .

I haven't seen anything like that across 2 Mk3's and the one D2 in general use or in a video I just took and slowmo scrubbed across. The displays start initialized to showing "Maschine" and "Production and Performance System" in white text on a black background. My software then sends images derived from HTML with a black background, and there's no blanking flicker or anything like that.

Did you use the native instruments driver and if yes in what os ?

Just Ubuntu Linux using libusb with udev permissions set like https://github.com/node-hid/node-hid#udev-device-permissions suggests.

@3phase33
Copy link

@asutherland Any hints on how i could go about testing this out?

@asutherland
Copy link

@asutherland Any hints on how i could go about testing this out?

In the interest of not getting too off-topic on this issue, I've enabled issues on https://github.com/asutherland/node-traktor-f1 and changed the default branch to be the maschine-mk3 one. Feel free to open an issue there if you want to elaborate on your use case (what NI device you want to work, what OS you're trying to interface with it on, what you're generally trying to achieve, etc.) and I'll see what advice I can give.

@3phase33
Copy link

@asutherland Thanks.

@cap4096
Copy link

cap4096 commented May 27, 2020

I'm currently working code for Maschine Studio.
The display data looks like this:

Note that the end differs.

Question: The display number does not work, i get it work on the left screen only not on the right. Any ideas?

LEFT:

   Displ                  x     y     w     h
   -----                ----- ----- ----- -----
84 00 00 60 00 00 00 00 00 00 00 00 01 e0 01 10 : Header
00 00 00 2a 10 41 10 41 10 41 10 41 10 41 10 41 : Command,  Data
----------- -----------------------------------
...
...
...

03 00 00 00 40 00 00 00  : Command (Blit), Command (End)
----------- -----------
HEADER_LENGTH = 16
COMMAND_LENGTH = 4
DATA_START = HEADER_LENGTH + COMMAND_LENGTH


RIGHT:


   Displ                  x     y     w     h
   -----                ----- ----- ----- -----
84 00 01 60 00 00 00 00 00 00 00 00 01 e0 01 10 : Header
00 00 00 0b 20 00 20 00 20 00 20 00 20 20 20 20 : Command,  Data
----------- -----------------------------------
...
...
...

03 00 00 00 40 00 01 00  : Command (Blit), Command (End)
----------- -----------
HEADER_LENGTH = 16
COMMAND_LENGTH = 4
DATA_START = HEADER_LENGTH + COMMAND_LENGTH

Any ideas welcome.

@cap4096
Copy link

cap4096 commented May 28, 2020

I wrote a litte program in C++:

#include <iostream>
#include <vector>
#include <cstdint>
#include <unistd.h>
#include <libusb-1.0/libusb.h>

std::uint16_t timage[480*272];

std::vector<std::uint8_t> blit_rectangle(std::uint8_t display, std::uint16_t x,
					 std::uint16_t y, std::uint16_t w,
					 std::uint16_t h,
					 const std::uint16_t *image)
{
	if (display > 1)
		return {};

	uint32_t sz = w*h/2;
	uint8_t sz_l = static_cast<std::uint8_t>(sz & 0xFF);
	uint8_t sz_m = static_cast<std::uint8_t>((sz >> 8) & 0xFF);
	uint8_t sz_h = static_cast<std::uint8_t>((sz >> 16) & 0xFF);

	std::vector<std::uint8_t> msg {
		0x84, 0x00, display, 0x60, 0x00, 0x00, 0x00, 0x00,
		static_cast<std::uint8_t>((x >> 8) & 0xFF),  static_cast<std::uint8_t>(x & 0xFF),
		static_cast<std::uint8_t>((y >> 8) & 0xFF),  static_cast<std::uint8_t>(y & 0xFF),
		static_cast<std::uint8_t>((w >> 8) & 0xFF),  static_cast<std::uint8_t>(w & 0xFF),
		static_cast<std::uint8_t>((h >> 8) & 0xFF),  static_cast<std::uint8_t>(h & 0xFF),
		0x00, sz_h, sz_m, sz_l };

	// RGB565

	for (const uint16_t *pixel = image; pixel < image + w * h; ++pixel) {
		msg.push_back(*pixel >> 8);
		msg.push_back(*pixel & 0xFF);
	}

	for (uint8_t b : {0x03, 0x00, 0x00, 0x00, 0x40, 0x00, (int)display, 0x00}) {
		msg.push_back(b);
	}

	return msg;
}

std::vector<std::uint8_t> clear_screen(std::uint8_t display)
{
	if (display > 1)
		return {};

	uint16_t x=0;
	uint16_t y=0;
	uint16_t w=480;
	uint16_t h=272;

	uint32_t sz = w*h/2;
	uint8_t sz_l = static_cast<std::uint8_t>(sz & 0xFF);
	uint8_t sz_m = static_cast<std::uint8_t>((sz >> 8) & 0xFF);
	uint8_t sz_h = static_cast<std::uint8_t>((sz >> 16) & 0xFF);

	std::vector<std::uint8_t> msg {
		0x84, 0x00, display, 0x60, 0x00, 0x00, 0x00, 0x00,
		static_cast<std::uint8_t>((x >> 8) & 0xFF),  static_cast<std::uint8_t>(x & 0xFF),
		static_cast<std::uint8_t>((y >> 8) & 0xFF),  static_cast<std::uint8_t>(y & 0xFF),
		static_cast<std::uint8_t>((w >> 8) & 0xFF),  static_cast<std::uint8_t>(w & 0xFF),
		static_cast<std::uint8_t>((h >> 8) & 0xFF),  static_cast<std::uint8_t>(h & 0xFF),
		0x01, sz_h, sz_m, sz_l, 0x00, 0x00, 0x00, 0x00 , 0x03, 0x00, 0x00, 0x00, 0x40, 0x00, display, 0x00};

	return msg;
}


int main()
{
	for(int i=0;i<sizeof(timage)/sizeof(timage[0]);i++){
		timage[i] = 0x5577;
	}


	libusb_device **devs;

	if(libusb_init(nullptr)<0){
		std::cout << "Init fail.\n";
	}


	auto handle = libusb_open_device_with_vid_pid(nullptr, 0x17CC,0x1300);


	auto msg_clear_left = clear_screen(0);
	auto msg_clear_right = clear_screen(1);

	int sz = 0;
	libusb_bulk_transfer(handle, 3, &msg_clear_left[0], msg_clear_left.size(), &sz, 20000 );
	libusb_bulk_transfer(handle, 3, &msg_clear_right[0], msg_clear_right.size(), &sz, 20000 );

	for(std::uint16_t i=0;i<8;i++){

		auto msg_left = blit_rectangle(0,i*16,i*16,16,16, &timage[0]);
		auto msg_right = blit_rectangle(1,480-(i+1)*16,i*16,16,16, &timage[0]);

		libusb_bulk_transfer(handle, 3, &msg_left[0], msg_left.size(), &sz, 20000 );
		libusb_bulk_transfer(handle, 3, &msg_right[0], msg_right.size(), &sz, 20000 );
	}

	libusb_close(handle);
	libusb_exit(nullptr);
}

It clear and draws on booth displays.

@cap4096
Copy link

cap4096 commented May 28, 2020

IMG_5842

@mickeyl
Copy link

mickeyl commented May 30, 2020

Amazing stuff, @cap4096. I wonder whether it would be feasible to take an free software GUI (e.g. Rasterman's EFL) toolkit and add an output renderer to it. That way we would get a whole lot of stuff for free (advanced text rendering, geometric primitives, alpha blending, etc.)�.

@cap4096
Copy link

cap4096 commented May 30, 2020

You probably could, but I think the limited display sizes requires some "minimalistic" GUI toolkit to produce a usable GUI. Also I think that if you have a "flat" theme on the GUI it is possible to compress the image transfer. I have not tried that yet but I think that is what the protocol is made for.

@Analog74
Copy link

So glad to see there is still current work going on with this!
Anyone else know of more resources like this as it relates to Maschine studio controllers?

@terminar
Copy link

Just to mention it for those who are (still) interested in using the Native Instruments devices for other stuff:

I've reverse engineered the proprietary (ipc) protocol which Native Instruments is using for talking to their daemons/services called "Native Instruments Hardware Agent" (NIHA) and "Native Instruments Host Integration Agent" (NIHIA). They are running as service on Windows and as Agent/Daemon on MacOS.
It's used with their own applications like "Maschine" and "Komplete Kontrol" (and maybe other like Traktor stuff) to manage active "Instances". Several NI applications can of course be used with the hardware in parallel and can be switched via the instance button on the devices.

Why is that maybe interesting? To use the devices (Maschine MK2, MK3, Komplete Kontrol MK2, ....) for different purposes NIHA/NIHIA have to be killed and a direct USB connection to the devices is needed - but that also kills the possibility to use "Maschine" and "Komplete Kontrol" in parallel. That's possible now via the (ipc) connection to NIHA/NIHIA.

I'm currently writing a cross platform (well, Windows and OS X) C library and golang based OSC gateway using that library which can be integrated in any other applications.

just showing some knob/button and display instance control.

My assumption is that the protocol exists at least since 2012 and is deeply integrated into the software stack of NI (and maybe not easily changeable). I know that it's working with the KK MK2 S61, Maschine MK3 and Maschine Mikro MK2 but I assume that it's also working with:

  • Maschine (MK2,MK3)/JAM/Studio/Mikro (MK1,MK2)
  • Komplete Kontrol (MK1/MK2)/A/M
  • maybe the Traktor hardware stuff

but I can't test it yet due to missing hardware. We'll try to test this in further beta stages or after public release.
A first test with other users showed that it's working as expected on different machines/installations, not only within my setup. I've constructed some reverse engineering helper integrated into the library to assist in mapping the key/pad/button IDs and such stuff.

Not sure about a timeframe but there are some additional ideas for the future:

  • OSC support (gateway between OSC server listening for commands to the hardware instances)
  • state maschine for instances with higher level control (button press path like "SHIFT/GROUP_A/KEYBOARD" which is implemented now), LVGL (https://lvgl.io/) widgets for the displays
  • opening the user defined functions in the library to extend the lib (library uses Lua 5.4)

Bugs known: horrible slow regarding the displays but that's known due to massive debug stuff and some functions which are currently implemented temporarily on the Lua script side.

Next alpha/beta will be released to interested users within June - I've implemented a JsonRPC API and some first commands (sendLedData, sendDisplayData). Need to test that on Windows now... Maybe first public release, not sure yet.

If anybody is interested drop me a message/comment here.

@mickeyl
Copy link

mickeyl commented Jun 2, 2021

I'm still interested – and although I currently don't have a free NI device for experimenting, I'll keep an eye on their product line and may buy the next MASCHINE & KK incarnation.

@maranite
Copy link

Hey Terminar (Björn) - the code which interfaces with NIHardwareAgent to drive the displays... Do you have it shared anywhere?

@maranite
Copy link

Also... Anyone here keen on collaborating on a full Bitwig controller script integration into Maschine via the NIHardwareAgent IPC protocol?

@terminar
Copy link

@maranite yes, both.
https://github.com/terminar/rebellion
if you are interested, join the discord and I can answer your questions directly. ;)

@4ndrw
Copy link

4ndrw commented Aug 5, 2021

Also... Anyone here keen on collaborating on a full Bitwig controller script integration into Maschine via the NIHardwareAgent IPC protocol?

oooo... a bitwig controller with this would be amazing

@k0k0c
Copy link

k0k0c commented Aug 5, 2021

Also... Anyone here keen on collaborating on a full Bitwig controller script integration into Maschine via the NIHardwareAgent IPC protocol?

oooo... a bitwig controller with this would be amazing

I have already success with some cases in this direction. Work in progress :)
https://www.youtube.com/watch?v=8b1mASa4V0M&ab_channel=MikhailKorovkin

@wolfbeee
Copy link

@maranite yes, both. https://github.com/terminar/rebellion if you are interested, join the discord and I can answer your questions directly. ;)

Hi. I don't have discord. Is there any update on this project or where it's headed please?
Last post was 3 years ago.. and it's not like linux users to say kit is too old, as that is one of the bens of linux, that it raises old tech from the dumps.
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests