Open Be-ing opened 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.
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.
Closing this until somebody has access to hardware, or can provide me with hardware access.
@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.
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
@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?
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
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)
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.
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.
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
@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.
@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
@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
@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?
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
.
@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
@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
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
@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)?
@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!
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.