ShuttlePro2

Has anyone successfully gotten a ShuttlePro2 working with Ardour under Linux?

I’ve got a few of the buttons doing things but it seems like it shouldn’t be to hard to get the jog and spin wheels working for ‘something’. Ideally something like the play head (red) as default and the record head (blue) when ctrl is pressed… that would be really nice! :slight_smile:

Anyway - thoughts? Comments?

Boing!

where are the specs for the protocol used by the beast? adding basic control from a new h/w device within ardour2 is very easy now.

Maybe this will help:
Here’s the source from the Kino implementation of it. Maybe that will help. Following is the .h file that seems to have the ‘rest’ of the story.

Let me know if this is what you were asking for.

I apologize for it being so long. :slight_smile:

Boing.

-------------BEGIN mediactrl.c---------------

/*

  • This program is free software; you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation; either version 2 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program; if not, write to the Free Software Foundation,
  • Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    */
    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <asm/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdint.h>
    #include <string.h>
    #include <errno.h>

#include <linux/input.h>
#include “mediactrl.h”

static char *_shuttle_name = “Shuttle”;
static char *_jog_name = “Jog”;

/*
ShuttlePro keys
*/
static struct media_ctrl_key mc_shuttle_pro_keys[] = {
{ 0x100, “Button 1”, MEDIA_CTRL_F1 },
{ 0x101, “Button 2”, MEDIA_CTRL_F2 },
{ 0x102, “Button 3”, MEDIA_CTRL_F3 },
{ 0x103, “Button 4”, MEDIA_CTRL_F4 },
{ 0x104, “Button 5”, MEDIA_CTRL_B4 },
{ 0x105, “Button 6”, MEDIA_CTRL_B2 },
{ 0x106, “Button 7”, MEDIA_CTRL_B1 },
{ 0x107, “Button 8”, MEDIA_CTRL_B3 },
{ 0x108, “Button 9”, MEDIA_CTRL_B5 },
{ 0x109, “Button 10”, MEDIA_CTRL_B6 },
{ 0x10a, “Button 11”, MEDIA_CTRL_B7 },
{ 0x10b, “Button 12”, MEDIA_CTRL_B8 },
{ 0x10c, “Button 13”, MEDIA_CTRL_B9 },
{ 0, NULL, 0 }
};

/*
ShuttleXPress keys
*/
static struct media_ctrl_key mc_shuttle_xpress_keys[] = {
{ 0x104, “Button B4”, MEDIA_CTRL_B4 },
{ 0x105, “Button B2”, MEDIA_CTRL_B2 },
{ 0x106, “Button B1”, MEDIA_CTRL_B1 },
{ 0x107, “Button B3”, MEDIA_CTRL_B3 },
{ 0x108, “Button B5”, MEDIA_CTRL_B5 },
{ 0, NULL, 0 }
};

/*
JLCooper MCS3 Keys
*/
static struct media_ctrl_key mc_jlcooper_mcs3_keys[] = {
{ 0x107, “F1”, MEDIA_CTRL_F1 },
{ 0x101, “F2”, MEDIA_CTRL_F2 },
{ 0x105, “F3”, MEDIA_CTRL_F3 },
{ 0x102, “F4”, MEDIA_CTRL_F4 },
{ 0x103, “F5”, MEDIA_CTRL_F5 },
{ 0x104, “F6”, MEDIA_CTRL_F6 },
{ 0x10d, “W1”, MEDIA_CTRL_B6 },
{ 0x10e, “W2”, MEDIA_CTRL_B4 },
{ 0x100, “W3”, MEDIA_CTRL_B2 },
{ 0x106, “W4”, MEDIA_CTRL_B1 },
{ 0x110, “W5”, MEDIA_CTRL_B3 },
{ 0x111, “W6”, MEDIA_CTRL_B5 },
{ 0x115, “W7”, MEDIA_CTRL_B7 },
{ 0x116, “STICK_LEFT”, MEDIA_CTRL_STICK_LEFT },
{ 0x113, “STICK_RIGHT”, MEDIA_CTRL_STICK_RIGHT },
{ 0x114, “STICK_UP”, MEDIA_CTRL_STICK_UP },
{ 0x112, “STICK_DOWN”, MEDIA_CTRL_STICK_DOWN },
{ 0x10f, “Rewind”, MEDIA_CTRL_REWIND },
{ 0x108, “Fast Forward”, MEDIA_CTRL_FAST_FORWARD },
{ 0x109, “Stop”, MEDIA_CTRL_STOP },
{ 0x10a, “Play”, MEDIA_CTRL_PLAY },
{ 0x10b, “Record”, MEDIA_CTRL_RECORD },
{ 0, NULL, 0 }
};

/*
Griffin PowerMate
*/
static struct media_ctrl_key mc_powermate_keys[] = {
{ BTN_0, “Button”, MEDIA_CTRL_B1 },
{ 0, NULL, 0 }
};

struct media_ctrl_key *media_ctrl_get_key(struct media_ctrl *ctrl, int code, int *index)
{
int i = 0;
struct media_ctrl_key *keys = ctrl->device->keys;

while ( keys[i].key != 0 ) {
	if (keys[i].key == code) {
		if (index != NULL)
			*index = i;
		return &keys[i];
	}
	i++;
}

return NULL;

}

void translate_contour_hid_event(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me)
{

int lv, cv;

me->type = 0;

if (ev->type == EV_REL) {
	/* First check the outer dial */
	if (ev->code == REL_WHEEL) {
		
		cv = (signed int)ev->value;
		if (cv == 1 || cv == -1 ) cv = 0;
		
			
		if ( cv == ctrl->lastshu ) return;
		ctrl->lastshu = cv;
			
		//printf("Shuttle: %d\n", cv);
		me->type  = MEDIA_CTRL_EVENT_SHUTTLE;
		me->value = cv*2;
		me->name = _shuttle_name;
		
	} else if  (ev->code == REL_DIAL) {
		
		if ( ctrl->lastval == -1 ) ctrl->lastval = ev->value;
		lv = ctrl->lastval;
		cv = ev->value;

		if ( lv == cv ) return;
			
		ctrl->lastval = cv;
		
		if (cv < 10 && lv > 0xF0) cv +=0x100;
		if (lv < 10 && cv > 0xF0) lv +=0x100;
		
		me->type  = MEDIA_CTRL_EVENT_JOG;
		me->value = cv-lv;
		me->name = _jog_name;
		
		ctrl->jogpos += me->value;
		//printf("Jog: %06ld (%d)\n", ctrl->jogpos, me->value);
 		}
	return;
} else if (ev->type == EV_KEY) {
	int index;
	struct media_ctrl_key *key = media_ctrl_get_key(ctrl, ev->code, &index);
	if ( key == NULL ) return;
	
	me->type  = MEDIA_CTRL_EVENT_KEY;
	me->code = key->code;
	me->value = ev->value;
	me->name = ( char* )key->name;
	me->index = index;
	 
	//printf("Key: %04x %02x: %s\n", ev->code, ev->value, key->name);
	
}

}

void translate_compliant(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me)
{
me->type = 0;

// printf("Translate %02x %02x\n", ev->type, ev->code );

if (ev->type == EV_REL) {
	if  (ev->code == REL_DIAL) {
		
		me->type  = MEDIA_CTRL_EVENT_JOG;
		me->value = (signed int)ev->value;
		me->name = _jog_name;
		
		ctrl->jogpos += me->value;
		//printf("Jog: %06ld (%d)\n", ctrl->jogpos, me->value);
 	}
	return;
} else if (ev->type == EV_ABS) {
	// printf("ABS\n" );
	if  ( ev->code == 0x1c || ev->code == ABS_THROTTLE ) {
		//printf("ABS_MISC\n" );
		me->type  = MEDIA_CTRL_EVENT_SHUTTLE;
		me->value = (signed int)ev->value;
		me->name = _shuttle_name;
		
		ctrl->shuttlepos = me->value;
		//printf("Shuttle: %06d (%d)\n", ctrl->shuttlepos, me->value);
	}
} else if (ev->type == EV_KEY) {
	int index;
	struct media_ctrl_key *key = media_ctrl_get_key(ctrl, ev->code, &index);
	if ( key == NULL ) return;
	
	me->type  = MEDIA_CTRL_EVENT_KEY;
	me->code = key->code;
	me->value = ev->value;
	me->name = ( char* )key->name;
	me->index = index;
	 
	//printf("Key: %04x %02x: %s\n", ev->code, ev->value, key->name);
	
}

}

struct media_ctrl_device supported_devices[] = {
{ 0x0b33, 0x0030, “Contour ShuttlePRO v2”, mc_shuttle_pro_keys, translate_contour_hid_event },
{ 0x0b33, 0x0020, “Contour ShuttleXPress”, mc_shuttle_xpress_keys, translate_contour_hid_event },
{ 0x0b33, 0x0010, “Contour ShuttlePro”, mc_shuttle_pro_keys, translate_contour_hid_event },
{ 0x0b33, 0x0011, “Contour ShuttlePro”, mc_shuttle_pro_keys, translate_contour_hid_event }, /* Hercules OEM */
{ 0x05f3, 0x0240, “Contour ShuttlePro”, mc_shuttle_pro_keys, translate_contour_hid_event },
{ 0x0760, 0x0001, “JLCooper MCS3”, mc_jlcooper_mcs3_keys, translate_compliant },
{ 0x077d, 0x0410, “Griffin PowerMate”, mc_powermate_keys, translate_compliant },
{ 0, 0, 0 }
};

void media_ctrl_translate(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me)
{
if ( ctrl->device ) ctrl->device->translate(ctrl, ev, me);
}

void media_ctrl_read_event(struct media_ctrl *ctrl, struct media_ctrl_event *me)
{
ssize_t n;
struct input_event ev;

// struct media_ctrl_event me;

if ( ctrl->fd > 0 ) {
	n = read(ctrl->fd, &ev, sizeof(ev));
} else {
	return;
}

if (n != sizeof(ev)) {
	//printf("JogShuttle::inputCallback: read: (%d) %s\n", errno, strerror(errno));
	close(ctrl->fd);
	ctrl->fd = 0;
	return;
}

if ( ctrl->device && ctrl->device->translate)
	ctrl->device->translate(ctrl, &ev, me);
else
	me->type = 0;

if ( me->type  == MEDIA_CTRL_EVENT_JOG ) {
	struct timeval timev;
	gettimeofday(&timev, NULL);
	unsigned long now = (unsigned long)timev.tv_usec + (1000000*(unsigned long)timev.tv_sec);
	if ( now < ctrl->last_jog_time + 40000 ) {
		//printf("*** Fast Jog %02d %05d ***\n", me->value, now - ctrl->last_jog_time);
		ctrl->jogrel = me->value;
		me->type = MEDIA_CTRL_EVENT_NONE;
	} else {
		me->value += ctrl->jogrel;
		ctrl->jogrel = 0;
		ctrl->last_jog_time = now;
		// printf("*** Jog %02d ***\n", me->value);
	}
}

return;

}

int probe_device(struct media_ctrl *mc)
{
short devinfo[4];
int i = 0;

if ( ioctl(mc->fd, EVIOCGID, &devinfo) ) {
	perror("evdev ioctl");
	return 0;
}

do {
	if ( supported_devices[i].vendor == devinfo[1] 
		&& supported_devices[i].product == devinfo[2] ) {
			
		mc->device = &supported_devices[i];
		//printf("Success on /dev/input/event%d: %s\n", mc->eventno, mc->device->name);
		// mc->fd = fd;
		// mc->translate = mc->device.translate_function;
		// mc = malloc(sizeof(struct media_ctrl));
		mc->jogpos  = 0;
		mc->lastval = -1;
		mc->last_jog_time = 0;
		return 1;
	} else {
		//mc->device = NULL;
	}

} while ( supported_devices[++i].vendor != 0 );
		
return 0;

}

void media_ctrl_get_device_list()
{
// TBD
}

void find_first_device(struct media_ctrl *mc)
{
char buf[256];
int fd, i;

for ( i = 0; i < 32; i++ ) {
	sprintf(buf, "/dev/input/event%d", i); 
	fd = open( buf, O_RDONLY );
	if ( fd < 0 ) {
		perror(buf);
	} else {
		mc->fd = fd;
		mc->eventno = i;
		if( probe_device(mc) ) {
			return;
		} else {		
			close(fd);
			mc->fd = -1;
		}
	}
}
return;

}

void media_ctrl_close(struct media_ctrl *mc)
{
if (mc->fd > 0)
close( mc->fd );
memset( mc, 0, sizeof( struct media_ctrl ) );
}

void media_ctrl_open(struct media_ctrl *mc)
{
find_first_device(mc);
}
-----------------END mediactrl.c-----------------------

-----------------BEGIN mediactrl.h---------------------
-/*

  • This program is free software; you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation; either version 2 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program; if not, write to the Free Software Foundation,
  • Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    */
    #ifndef _MEDIA_CTRL_H
    #define _MEDIA_CTRL_H

// just to make the code more readable
#define KEY_RELEASE 0x00
#define KEY_PRESS 0x01

// not used yet
#define MEDIA_ST_ACTIVE 0x02
#define MEDIA_ST_INACTIVE 0x01

// media ctrl event types
#define MEDIA_CTRL_EVENT_NONE 0x00
#define MEDIA_CTRL_EVENT_KEY 0x01
#define MEDIA_CTRL_EVENT_JOG 0x02
#define MEDIA_CTRL_EVENT_SHUTTLE 0x03
#define MEDIA_CTRL_EVENT_STICK 0x04

// the disconnect event - not used yet
#define MEDIA_CTRL_DISCONNECT 0x01

#define MEDIA_CTRL_SHIFT 0x01

#define MEDIA_CTRL_PLAY 0x10
#define MEDIA_CTRL_PLAY_FWD 0x10
#define MEDIA_CTRL_REVERSE 0x11
#define MEDIA_CTRL_PLAY_REV 0x11
#define MEDIA_CTRL_STOP 0x12
#define MEDIA_CTRL_PAUSE 0x13
#define MEDIA_CTRL_NEXT 0x14
#define MEDIA_CTRL_PREV 0x15
#define MEDIA_CTRL_RECORD 0x16
#define MEDIA_CTRL_FAST_FORWARD 0x17
#define MEDIA_CTRL_REWIND 0x18

#define MEDIA_CTRL_STICK_LEFT 0x20
#define MEDIA_CTRL_STICK_RIGHT 0x21
#define MEDIA_CTRL_STICK_UP 0x22
#define MEDIA_CTRL_STICK_DOWN 0x23

/* function keys, usually at top of device */
#define MEDIA_CTRL_F1 0x100
#define MEDIA_CTRL_F2 0x101
#define MEDIA_CTRL_F3 0x102
#define MEDIA_CTRL_F4 0x103
#define MEDIA_CTRL_F5 0x104
#define MEDIA_CTRL_F6 0x105
#define MEDIA_CTRL_F7 0x106
#define MEDIA_CTRL_F8 0x107
#define MEDIA_CTRL_F9 0x108
#define MEDIA_CTRL_F10 0x109
#define MEDIA_CTRL_F11 0x10a
#define MEDIA_CTRL_F12 0x10b
#define MEDIA_CTRL_F13 0x10c
#define MEDIA_CTRL_F14 0x10d
#define MEDIA_CTRL_F15 0x10e
#define MEDIA_CTRL_F16 0x10f

#define MEDIA_CTRL_B1 0x110
#define MEDIA_CTRL_B2 0x111
#define MEDIA_CTRL_B3 0x112
#define MEDIA_CTRL_B4 0x113
#define MEDIA_CTRL_B5 0x114
#define MEDIA_CTRL_B6 0x115
#define MEDIA_CTRL_B7 0x116
#define MEDIA_CTRL_B8 0x117
#define MEDIA_CTRL_B9 0x118
#define MEDIA_CTRL_B10 0x119
#define MEDIA_CTRL_B11 0x11a
#define MEDIA_CTRL_B12 0x11b
#define MEDIA_CTRL_B13 0x11c
#define MEDIA_CTRL_B14 0x11d
#define MEDIA_CTRL_B15 0x11e
#define MEDIA_CTRL_B16 0x11f

#ifdef __cplusplus
extern “C” {
#endif

struct media_ctrl_device;

struct media_ctrl_key {
int key; // internal keycode - do not use
const char *name;
int code; // eventcode
int action;
};

struct media_ctrl_event {
struct timeval time;
unsigned short type;
unsigned short code;
char *name;
int value;
unsigned short index;
};

struct media_ctrl {

int fd;
int eventno;

int status;

struct media_ctrl_device *device;

long jogpos;
int  shuttlepos;

int lastval;
int lastshu;

int jogrel; // accumulate relative values if events come too fast
unsigned long last_jog_time; // last jog event

};

struct media_ctrl_device {
int vendor;
int product;
const char *name;
struct media_ctrl_key *keys;
void (*translate)(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me);
};

void media_ctrl_open(struct media_ctrl *);
void media_ctrl_close(struct media_ctrl *);
void media_ctrl_read_event(struct media_ctrl *, struct media_ctrl_event *);

struct media_ctrl_key *media_ctrl_get_keys(struct media_ctrl *);

#ifdef __cplusplus
}
#endif

#endif

------------------END mediactrl.h--------------------

I got an email from Contour today with something called libcontour and test_lib and test_raw as well as a patch file. Not sure what it’s for but it’s probably the secret to this thing. :slight_smile: Could I email it (just .c files and a makefile) to someone that knows more than me to see if it looks any more helpfull than the two files I attached in the other response?

Boing!

Please don’t paste source code to the forum. The HTML will mangle it and it’s really not the place. You should bring this to the ardour-dev mailing list instead.

Ok. btw - pm next time on stuff like this so I would have a chance to edit it. Once you reply it’s frozen in place forever.

Of course none of this helps address the initial question but I apologize for the ‘source pasting’ none-the-less.

Any thoughts on an solution? :slight_smile:

Boing

Just send it to the mailing list where people with experience writing surfaces can look at it.