openAVproductions / openAV-Ctlra

A plain C library to program with hardware controllers.
BSD 3-Clause "New" or "Revised" License
78 stars 16 forks source link

Ableton Push 2 support #12

Open Be-ing opened 7 years ago

Be-ing commented 7 years ago

Ableton has publicly documented the interface for their Push 2 controller. It is a standard USB MIDI device with a separate USB Bulk endpoint for sending pixmaps to the controller's screen. They have published a small demo application that uses JUCE to draw graphics for the screen.

Supporting devices that use multiple communication protocols presents an interesting challenge for the idea I mentioned in #1 to use cross platform libraries that abstract away the communication protocol. Ableton's demo application uses libusb like Ctlra does currently. It may be a good idea to support using MIDI controllers via libusb or via a library like PortMIDI or RtMIDI that abstracts the wire protocol.

harryhaaren commented 7 years ago

Yes I'm happy that vendors (Ableton in this case) are becoming more open with protocols / device access details. I feel it is a logical next step to enable next-gen software / hardware integration.

Indeed the Push has USB and MIDI interfaces. Given the MIDI interface is a standard compliant USB MIDI implementation, it means that ALSA MIDI exposes the device's MIDI endpoint already. Re-using this is a no brainer, the question becomes what abstraction/library to use. Although abstraction libraries like PortMIDI and RtMIDI can be very powerful for certain uses, I am wary of adding lots of dependencies to Ctlra too. On Linux, ALSA has to be linked against when using RtMidi or PortMIDI - so ALSA is a required dependency. I have coded a MIDI backend for Ctlra using raw ALSA Seq API. This removes the need for C++ code, and external dependencies.

In future, I hope to enable "direct" access to MIDI devices (without explicit connection in the eg; QJackCtl Connections dialog). This would improve the user-experience quite a lot, as currently all USB HID devices "just work" when plugged in, and there is no "routing" of MIDI events required.

As I don't currently have access to a Push2 device, I don't currently have plans to support it. If a developer with the device is interested in working on a driver, I will try to be of assistance, and if "somebody" wants to provide Push2 device to me, I will create a Ctlra driver for it.

Be-ing commented 7 years ago

If Ctlra does not end up supporting MIDI devices, perhaps it could be used just for the Push 2's screen and leave the MIDI part to the OS's normal USB MIDI drivers. A similar approach may work for the Numark NS7III as well.

harryhaaren commented 6 years ago

Closing this until somebody has access to hardware, or can provide me with hardware access.

simonvanderveldt commented 6 years ago

@harryhaaren I've got a push2 here and would like to add support for it to Ctlra. Any pointers how/where to start? I couldn't find any docs regarding how to add a new device.

harryhaaren commented 6 years ago

Hi @simonvanderveldt! Cool, Push2 support would be awesome.

The smallest device that was added was probably the SpaceMouse device[1], which might be a good start to check what adding a driver involves. Its a single commit - so gives a good overview.

The downside is that it is not a USB device, so refer to one of the many USB devices connect() function to see how that is implemented. Similarly for the MIDI I/O, refer to the MidiGeneric driver.

Hope that helps, and any questions please post here :) I'll re-open this issue! Cheers, -Harry

[1] https://github.com/openAVproductions/openAV-Ctlra/pull/44

simonvanderveldt commented 6 years ago

@harryhaaren Cool, will do so!

I was mainly looking at if there's a way to use Ctlra to identify what every control on the controller sends for signal. Is that possible? And if so which kind of minimum setup is needed to get to that point?

harryhaaren commented 6 years ago

Lucky for you, the Push2 is documented by Ableton, the 2nd link below pretty much tells you what you'll need to know. The MIDI part is a bit more complex than I'd expected - you'll have to figure the details between the 3 midi modes and that. I'd leave the screen until the end - get some basic in/out events first.

Given its a MIDI device, a copy/paste of the midi_generic.c driver, and modifications might be a good start? A printf() on line 68 should detail the midi message coming in - then that needs to be mapped to a ctlra_event_t struct, and dispatched. That's basically it for input.

https://github.com/Ableton/push-interface https://github.com/Ableton/push-interface/blob/master/doc/AbletonPush2MIDIDisplayInterface.asc

simonvanderveldt commented 6 years ago

OK, well I managed to get it registered but apart from that didn't get that far. Don't seem to be able to get any input. I guess I need to define somehow which interface/endpoint to use for the USB device? Couldn't really find how to do so in combination with ctlra_midi_open. lsusb output for reference

lsusb -v -s 002:013

Bus 002 Device 013: ID 2982:1967  
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x2982 
  idProduct          0x1967 
  bcdDevice            1.00
  iManufacturer           1 Ableton AG
  iProduct                2 Ableton Push 2
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength          126
    bNumInterfaces          3
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              500mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass    255 Vendor Specific Subclass
      bInterfaceProtocol    255 Vendor Specific Protocol
      iInterface              3 Push 2 Display
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      1 Control Device
      bInterfaceProtocol      0 
      iInterface              0 
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      1 (HEADER)
        bcdADC               1.00
        wTotalLength            9
        bInCollection           1
        baInterfaceNr( 0)       2
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         1 Audio
      bInterfaceSubClass      3 MIDI Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      MIDIStreaming Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      1 (HEADER)
        bcdADC               1.00
        wTotalLength           37
      MIDIStreaming Interface Descriptor:
        bLength                 6
        bDescriptorType        36
        bDescriptorSubtype      2 (MIDI_IN_JACK)
        bJackType               1 Embedded
        bJackID                 1
        iJack                   4 Live Port
      MIDIStreaming Interface Descriptor:
        bLength                 6
        bDescriptorType        36
        bDescriptorSubtype      2 (MIDI_IN_JACK)
        bJackType               1 Embedded
        bJackID                 2
        iJack                   5 User Port
      MIDIStreaming Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      3 (MIDI_OUT_JACK)
        bJackType               1 Embedded
        bJackID                 3
        bNrInputPins            1
        baSourceID( 0)          1
        BaSourcePin( 0)         1
        iJack                   4 Live Port
      MIDIStreaming Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      3 (MIDI_OUT_JACK)
        bJackType               1 Embedded
        bJackID                 4
        bNrInputPins            1
        baSourceID( 0)          2
        BaSourcePin( 0)         1
        iJack                   5 User Port
      Endpoint Descriptor:
        bLength                 9
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
        bRefresh                0
        bSynchAddress           0
        MIDIStreaming Endpoint Descriptor:
          bLength                 6
          bDescriptorType        37
          bDescriptorSubtype      1 (GENERAL)
          bNumEmbMIDIJack         2
          baAssocJackID( 0)       1
          baAssocJackID( 1)       2
      Endpoint Descriptor:
        bLength                 9
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
        bRefresh                0
        bSynchAddress           0
        MIDIStreaming Endpoint Descriptor:
          bLength                 6
          bDescriptorType        37
          bDescriptorSubtype      1 (GENERAL)
          bNumEmbMIDIJack         2
          baAssocJackID( 0)       3
          baAssocJackID( 1)       4
Device Qualifier (for other device speed):
  bLength                10
  bDescriptorType         6
  bcdUSB               2.00
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  bNumConfigurations      1
can't get debug descriptor: Resource temporarily unavailable
Device Status:     0x0000
  (Bus Powered)
harryhaaren commented 6 years ago

Overview

I think there's a bit of mixup in how the Push2 works, and Ctlra backends. The Push2 (from my understanding, without owning the device..) exposes two interfaces.

Midi I/O

The MIDI I/O is handled by the OS. It is a standard compliant USB device with its USB MIDI "endpoints". This means that any OS supporting USB MIDI I/O, will automatically add the device to its MIDI capabilities. On linux, this means that it will show up in ALSA, or an ALSA frontend like the ALSA MIDI tab of QJackCtl.

To send/recieve MIDI from the device, Ctlra creates a pair of virtual ALSA MIDI ports, which must be connected to the devices MIDI ports. The OS handles retrieving the MIDI bytes from the USB endpoint, and the Ctlra library is forwarded the MIDI event. The driver for a device is expected to take this event and map it to a ctlra_event_t structure, making it available to the application in a generic way.

USB Device Specific Screen

The Push2 screen is not a standard compliant endpoint. I don't know if there even are standardized descriptors for USB screens. Ableton have very helpful, and documented the protocol and details of how to push pixels here: https://github.com/Ableton/push-interface/blob/master/doc/AbletonPush2MIDIDisplayInterface.asc#display-interface

In this case Ctlra must open the USB endpoint directly, and communicate with the Push2 using whatever protocol the hardware uses. To access the correct device and endpoint, some sample code is provided in the link above. Summarized here:

#define ABLETON_VENDOR_ID 0x2982
#define PUSH2_PRODUCT_ID  0x1967
/* interface define derived from lsusb output and this: libusb_claim_interface(device_handle, 0) */
#define PUSH2_SCREEN_INTERFACE 0

/* next section from 2nd code sample */
#define PUSH2_BULK_EP_OUT 0x01

Ctlra is quite "helpful" in allowing you to access USB: you don't write code to libUSB directly, but use the Ctlra USB API instead (which does use libUSB, but is tuned and implemented for the real-time async sensitive use case Ctlra is built for).

/* open a handle to the device */
ctlra_dev_impl_usb_open(&dev->base, ABLETON_VENDOR_ID, PUSH2_PRODUCT_ID);

/* open the endpoint. The handle_idx allows for multiple connections to one device.
   The Push2 only needs the screen endpoint, so we only need handle_idx 0 */
int handle_idx = 0; 
ctlra_dev_impl_usb_open_interface(ctlra_dev, handle_idx, PUSH2_SCREEN_INTERFACE);

Finally, to actually push some bytes down the cable, we use the Ctlra provided bulk_write() function:

ctlra_dev_impl_usb_bulk_write(ctlra_dev->base, handle_idx,
                        PUSH2_BULK_EP_OUT,
                        (uint8_t *)data,
                        data_size);

Hope this essay helps a bit, both in support Push2, and perhaps in future it can be re-worked into Ctlra documentation :D -H

simonvanderveldt commented 6 years ago

@harryhaaren OK, so I couldn't just leave it and tried again :P I've got a single (MIDI CC) button working, how should I progress to map all the buttons? How should I setup the structs that contain the buttons/knobs/sliders? Since they are MIDI they are different than the NI ones.

harryhaaren commented 6 years ago

@simonvanderveldt ha nice! I think the following is probably the easier solution that comes to mind. This allows the Ctlra driver to "reindex" the buttons. Since both CC and Note-On events from the Push2 are buttons in the Ctlra world, we cannot blindly translate them. As a result, this re-indexing gives the flexibility to tidy up button numbers etc.

This array works as follows: the MIDI CC value is used as an index into this array. Eg: Tap Tempo button is CC3, so

struct push2_t {
   // base dev
   // midi
   /* translates CC messages into Ctlra Button IDs. Values set in connect() function */
   uint16_t cc_to_btn_id[127];
};

push2_connect()
{
   // this is the long boring part, of remapping. The "counter" just saves some typing :)
   // note that Ctlra events start from id 0, and count upwards monotonically
   int counter = 0;
   dev->cc_to_btn_id[3] = counter++; // tap tempo
   dev->cc_to_btn_id[9] = counter++; // metronome
}

With the above, when an event arrives, its easy to get the correct button ID with this in the MIDI handling function:

case 0xB0:
         int id = dev->cc_to_btn_id[   buf[1]   ];  // spaces here for readability, not required in real code
         event->button.id = id;
         event->button.pressed = buf[2] > 0;  // assuming 0 is release, and anything else is pressed?
         dev->event_func(event);

This approach allows us to later add another such "map" from note-on events, to map the remaining events. Its a bit clunky, but unfortunately "mushing" from MIDI is just kinda messy... this is why I like the Ctlra event abstraction much more than MIDI messages :D

simonvanderveldt commented 6 years ago

@harryhaaren If you have some time, I'm a bit stuck on using device_test, it segfaults once I press a button :(

Thread 1 (Thread 0x7ffff7fb68c0 (LWP 11349)):
#0  0x00007ffff7bc2ac8 in avtka_mirror_hw_cb (base=0x0, num_events=1, events=0x7fffffffdeb0, userdata=0x0) at ../ctlra/devices/avtka.c:175
#1  0x0000000000400e55 in simple_event_func (dev=0x60e290, num_events=1, events=0x7fffffffdeb0, userdata=0x6088a0) at ../examples/device_test/device_test.c:86
#2  0x00007ffff7bbaf48 in ableton_push2_midi_input_cb (nbytes=3 '\003', buf=0x7fffffffdf20 "\260U\177", ud=0x60e290) at ../ctlra/devices/ableton_push2.c:258
#3  0x00007ffff7bba4fa in ctlra_midi_input_poll (s=0x61acf0) at ../ctlra/midi.c:145
#4  0x00007ffff7bbaba1 in ableton_push2_poll (base=0x60e290) at ../ctlra/devices/ableton_push2.c:188
#5  0x00007ffff7bb7b68 in ctlra_dev_poll (dev=0x60e290) at ../ctlra/ctlra.c:197
#6  0x00007ffff7bb832b in ctlra_idle_iter (ctlra=0x6088a0) at ../ctlra/ctlra.c:408
#7  0x00000000004014ac in main (argc=1, argv=0x7fffffffe0d8) at ../examples/device_test/device_test.c:217
#8  0x00007ffff781c4f0 in __libc_start_main () from /lib64/libc.so.6
#9  0x0000000000400b3a in _start ()

I'm guessing, but it seems to be caused by dev being NULL here: https://github.com/openAVproductions/openAV-Ctlra/blob/cfa723e0ab3cf527b98e85a4c99c0c1aaf48ae3a/ctlra/devices/avtka.c#L174

simonvanderveldt commented 6 years ago

@harryhaaren Just checking in, no problem if you don't have time now, but I expect I'll have to give the push2 back either end of this week or start of next week. Ideally I'd like to finish input and output without the screen before that. You have some time to help out this week?

harryhaaren commented 6 years ago

Hey @simonvanderveldt, sorry for delay - little time for OpenAV recently. The device_test expects that the number of controls exposed are "filled-in" in the backend.

In short - currently device_test isn't doing any form of error-checking on the values it gets. I can look into the exact cause (and add error checking there), but for now an alternative approach is to only set the count of controls to those that are exposed. (The "count" is set in the last part of the code, just above REGISTER() macro).

I'll link to this issue if I find some time (later today / this week) to add debug checks to device_test.

simonvanderveldt commented 6 years ago

@harryhaaren No problem :) And thanks for the pointers, I'll give it a try and see if I can figure it out. Will update here if I do/don't

harryhaaren commented 6 years ago

@simonvanderveldt, try applying the above commit and then re-running. It looks like it was crashing in AVTKA mirror hw(), because the UI wasn't instantiated. This adds a few checks to see if the UI is initialized, and if not just bypasses the functionality.. should enable your testing to continue. Hopefully! -H

simonvanderveldt commented 6 years ago

Forgot to update here, sorry about that.

Unfortunately I've not been able to fix the segfault and I had to return the device. So I wasn't able to finish this. What I have so far can be found here https://github.com/simonvanderveldt/openAV-Ctlra/tree/add-ableton-push2

simonvanderveldt commented 6 years ago

@harryhaaren I might be able to borrow a Push 2 for LAC. Do you think it would be worthwhile (i.e. would you have a enough time to create something that works in between the sessions)?

harryhaaren commented 6 years ago

@simonvanderveldt thanks for thinking of it - honestly I'd prefer spend the LAC talking to folks - also the LED colouring is pretty darn complex, so its not something to casually code inbetween sessions... I'll try get access to a Push2 in Ireland somewhere (surely somebody in the city I live has one : ) Thanks again, and see you at LAC!