diff options
Diffstat (limited to 'libs/surfaces/frontier')
18 files changed, 5257 insertions, 0 deletions
diff --git a/libs/surfaces/frontier/kernel_drivers/BUILD b/libs/surfaces/frontier/kernel_drivers/BUILD new file mode 100644 index 0000000000..dc612e20bf --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/BUILD @@ -0,0 +1,10 @@ +To build, type make + +# make install and run +ir: install + rmmod tranzport + modprobe tranzport + +# make install, run, and run tests +irt: ir + diff --git a/libs/surfaces/frontier/kernel_drivers/Makefile b/libs/surfaces/frontier/kernel_drivers/Makefile new file mode 100644 index 0000000000..223fcdb6fc --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/Makefile @@ -0,0 +1,35 @@ +ifneq ($(KERNELRELEASE),) + +obj-m := tranzport.o +tranzport-objs := + +else + +KDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +MODDIR := $(DESTDIR)/lib/modules/$(shell uname -r)/kernel/sound/usb/misc +BINDIR := $(DESTDIR)/usr/local/bin + +default:: + $(MAKE) -Wall -C $(KDIR) SUBDIRS=$(PWD) modules + $(MAKE) -C tests + +install-only:: default + mkdir -p $(MODDIR) $(BINDIR) + cp tranzport.ko $(MODDIR) + $(MAKE) -C tests install + +install:: install-only + /sbin/depmod -a + +/sbin/rmmod tranzport + /sbin/modprobe tranzport + +irt:: install + tranzport_tests.sh + +clean:: + rm -f core .*.cmd *.o *.ko *.mod.c Module.symvers *.bak .\#* *~ + rm -rf .tmp_versions + $(MAKE) -C tests clean + +endif diff --git a/libs/surfaces/frontier/kernel_drivers/README b/libs/surfaces/frontier/kernel_drivers/README new file mode 100644 index 0000000000..51b4af0f2e --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/README @@ -0,0 +1,16 @@ +This directory contains the USB Tranzport Kernel driver for Linux. + +At present it accepts reads/writes of 8 byte cmds to /dev/tranzport0 to control +the lights and screen. + +Reads are possible. Wheel Compression does not currently account for button changes + +It also has some sysfs hooks that are non-functional at the moment. + +The API is closely tied to the ardour revision and WILL change. + +A sysfs interface is PERFECT for simple userspace apps to do fun things with the +lights and screen. It's fairly lousy for handling input events and very lousy +for watching the state of the shuttle wheel. + +In the end this is going to be driven by a midi layer diff --git a/libs/surfaces/frontier/kernel_drivers/doc/keycodes.html b/libs/surfaces/frontier/kernel_drivers/doc/keycodes.html new file mode 100644 index 0000000000..651517a7f2 --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/doc/keycodes.html @@ -0,0 +1,35 @@ +<HTML> +<HEAD> +<TITLE> TRANZPORT KEYCODES REFERENCE </TITLE> +</HEAD> +<BODY> +<H2> TRANZPORT KEYCODES REFERENCE </H2> + +<H3>Footswitch</H3> + +At least on every footswitch I've tried, the polarity appears to be wrong, in that the footswitch "up" position results +in 0100 being OR'd into the result. Pressing it down results in a 0, if no other keys are pressed. Releasing it results in 0100. + +Every other key when up results in 0. This odd behavior would hopefully be controllable via a command to the tranzport, +but I don't have that, so dealing with footswitch events is weird. + +So, seeing this bit enabled would be something like "HAVE_FOOTSWITCH INSTALLED", BE SMART ABOUT IT. + + +<H3>Special Key Combinations</H3> +<p> +In addition to the normal keycodes generated by the tranzport, it is possible to hit several combinations of keys and get a unique +result. Some are really weird. Perhaps the following assignments make sense: +</p><p> +<table><tr><th>PRESSING</th><th>RESULT</th><th>ASSIGNED TO</th></tr> +<tr><td>TRACKLEFT+TRACKRIGHT</td><td>TRACKLEFT+TRACKRIGHT</td><td>Master</td></tr> +<tr><td>SHIFT+TRACKLEFT+TRACKRIGHT</td><td>SHIFT+TRACKLEFT+TRACKRIGHT+UNDO</td><td>Show Bus Only Toggle</td></tr> +<tr><td>IN+OUT</td><td>IN+OUT</td><td>Zoom 100%</td></tr> +<tr><td>SHIFT+IN+OUT</td><td>SHIFT+IN+OUT+UNDO</td><td>Max Zoom</td></tr> +<tr><td>SHIFT+REW+FFW</td><td>SHIFT+REW+FFW+UNDO</td><td></td></tr> +<tr><td>RECORD+TRACKSOLO+FOOTSWITCHNOTDEPRESSED</td><td>RECORD+TRACKSOLO+BATTERY</td><td></td></tr> +<tr><td>PLAY+MUTE</td><td>PLAY+MUTE</td><td></td></tr> +</table> +</p> +</body> +</html> diff --git a/libs/surfaces/frontier/kernel_drivers/tests/Makefile b/libs/surfaces/frontier/kernel_drivers/tests/Makefile new file mode 100644 index 0000000000..534bc7da4b --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/tests/Makefile @@ -0,0 +1,23 @@ +# Some basic utilities for testing the tranzport's I/O +# eventually "tranzport" will become a flexible command +# +# + +FILES:=tranzport tranzport_lights tranzport_tests.sh +BINDIR ?= $(DESTDIR)/usr/local/bin + +all: tranzport tranzport_lights + +tranzport: tranzport.c + gcc -g -Wall -o tranzport tranzport.c + +tranzport_lights: tranzport_lights.c + gcc -g -Wall -o tranzport_lights tranzport_lights.c + +clean:: + rm -f core .*.cmd *.o *.ko *.mod.c Module.symvers *.bak .\#* *~ + rm -rf .tmp_versions tranzport tranzport_lights + +install:: + cp $(FILES) $(BINDIR) + diff --git a/libs/surfaces/frontier/kernel_drivers/tests/README b/libs/surfaces/frontier/kernel_drivers/tests/README new file mode 100644 index 0000000000..f9efd18f69 --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/tests/README @@ -0,0 +1,104 @@ +tranzport 0.1 <tranzport.sf.net> +oct 18, 2005 +arthur@artcmusic.com +--- + +The Frontier Design Tranzport(tm) (www.frontierdesign.com) is a simple +wireless USB device. It is not a MIDI device. The document on their web +site "Tranzport(tm) Native Mode Interface Description" describes the +Tranzport(tm) as if it were a MIDI device, but this is implemented by their +Windows and Macintosh software drivers. + +This code will allow you to use your Tranzport(tm) at a lower level of +abstraction. This code relies on libusb, which can be obtained from +libusb.sourceforge.net. + +To compile the program, type "make". You should end up with a executable +called "tranzport". You'll probably have to run this program as root. + +Using the program is straightforward. It will simply tell you which +buttons are being pressed and what not. If you press one of the buttons +with a light, the light will turn on. If you hold shift and press one of +the buttons with a light, the light will turn off. If you take out the +batteries to the device (or go out of range), it will tell you that the +device is offline. When you replace the batteries (or come back in +range), it should tell you it is back online. + +Once you understand how everything works, you should be able to +incorporate it into your own setup however you wish. + +This code was developed on a Linux machine, but (theoretically) it +should work on any system that is supported by libusb, since that is how +it communicates with the device. + +Here are a few more details about the device: + +There are two endpoints for communication with the device. All data +reads and writes are done in 8-byte segments. + +One endpoint is for interrupt reads. This is used to read button data +from the device. It also supplies status information for when the device +goes out of range and comes back in range, loses power and regains +power, etc. The format of the data is: + + 00 ss bb bb bb bb dd 00 (hexadecimal) + +where: + + ss - status code, 01=online ff=offline + bb - button bits + dd - data wheel, 01-3f=forward 41-7f=backward + +Please refer to the source code for a list of the button bits. + +The other endpoint is for interrupt writes. This is used to toggle the +lights on the device, and to write data to the LCD. + +There are 7 lights on the device. To turn a light on, send the following +sequence of bytes: + + 00 00 nn 01 00 00 00 00 (hexadecimal) + +where nn is the light number. + +To turn a light off: + + 00 00 nn 00 00 00 00 00 (hexadecimal) + +Here is the list of lights: + + 00 Record + 01 Track Rec + 02 Track Mute + 03 Track Solo + 04 Any Solo + 05 Loop + 06 Punch + +The size of the LCD is 20x2, and it is split into 10 cells, each cell +being 4 characters wide. The cells progress across, then down. To write +to the LCD, send the following sequence of bytes: + + 00 01 cc aa aa aa aa 00 (hexadecimal) + +where: + + cc - cell number + aa - ASCII code + +Here is a list of the cells to clarify: + + 00 row 0, column 0-3 + 01 row 0, column 4-7 + 02 row 0, column 8-11 + 03 row 0, column 12-15 + 04 row 0, column 16-19 + 05 row 1, column 0-3 + 06 row 1, column 4-7 + 07 row 1, column 8-11 + 08 row 1, column 12-15 + 09 row 1, column 16-19 + +You should refer to the "Tranzport(tm) Native Mode Interface +Description" document for a listing of the ASCII codes the LCD uses. + diff --git a/libs/surfaces/frontier/kernel_drivers/tests/tranzport.c b/libs/surfaces/frontier/kernel_drivers/tests/tranzport.c new file mode 100644 index 0000000000..2ef5b6c910 --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/tests/tranzport.c @@ -0,0 +1,375 @@ +/* + * tranzport 0.1 <tranzport.sf.net> + * oct 18, 2005 + * arthur@artcmusic.com + */ + +#include <stdarg.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <malloc.h> + +#define VENDORID 0x165b +#define PRODUCTID 0x8101 + +#define READ_ENDPOINT 0x81 +#define WRITE_ENDPOINT 0x02 + +enum { + LIGHT_RECORD = 0, + LIGHT_TRACKREC, + LIGHT_TRACKMUTE, + LIGHT_TRACKSOLO, + LIGHT_ANYSOLO, + LIGHT_LOOP, + LIGHT_PUNCH +}; + +#define BUTTONMASK_BATTERY 0x00004000 +#define BUTTONMASK_BACKLIGHT 0x00008000 +#define BUTTONMASK_TRACKLEFT 0x04000000 +#define BUTTONMASK_TRACKRIGHT 0x40000000 +#define BUTTONMASK_TRACKREC 0x00040000 +#define BUTTONMASK_TRACKMUTE 0x00400000 +#define BUTTONMASK_TRACKSOLO 0x00000400 +#define BUTTONMASK_UNDO 0x80000000 +#define BUTTONMASK_IN 0x02000000 +#define BUTTONMASK_OUT 0x20000000 +#define BUTTONMASK_PUNCH 0x00800000 +#define BUTTONMASK_LOOP 0x00080000 +#define BUTTONMASK_PREV 0x00020000 +#define BUTTONMASK_ADD 0x00200000 +#define BUTTONMASK_NEXT 0x00000200 +#define BUTTONMASK_REWIND 0x01000000 +#define BUTTONMASK_FASTFORWARD 0x10000000 +#define BUTTONMASK_STOP 0x00010000 +#define BUTTONMASK_PLAY 0x00100000 +#define BUTTONMASK_RECORD 0x00000100 +#define BUTTONMASK_SHIFT 0x08000000 + +#define STATUS_OFFLINE 0xff +#define STATUS_ONLINE 0x01 +#define STATUS_OK 0x00 + +struct tranzport_s { + int *dev; + int udev; +}; + +typedef struct tranzport_s tranzport_t; + +void log_entry(FILE *fp, char *format, va_list ap) +{ + vfprintf(fp, format, ap); + fputc('\n', fp); +} + +void log_error(char *format, ...) +{ + va_list ap; + va_start(ap, format); + log_entry(stderr, format, ap); + va_end(ap); +} + +void vlog_error(char *format, va_list ap) +{ + log_entry(stderr, format, ap); +} + +void die(char *format, ...) +{ + va_list ap; + va_start(ap, format); + vlog_error(format, ap); + va_end(ap); + exit(1); +} + +tranzport_t *open_tranzport_core() +{ + tranzport_t *z; + int val; + + z = malloc(sizeof(tranzport_t)); + if (!z) + die("not enough memory"); + memset(z, 0, sizeof(tranzport_t)); + + z->udev = open("/dev/tranzport0",O_RDWR); + if (z->udev < 1) + die("unable to open tranzport"); + + return z; +} + +tranzport_t *open_tranzport() +{ +return open_tranzport_core(); +} + +void close_tranzport(tranzport_t *z) +{ + int val; + + val = close(z->udev); + if (val < 0) + log_error("unable to release tranzport"); + + free(z); +} + +int tranzport_write_core(tranzport_t *z, uint8_t *cmd, int timeout) +{ + int val; + val = write(z->udev, cmd, 8); + if (val < 0) + return val; + if (val != 8) + return -1; + return 0; +} + +int tranzport_lcdwrite(tranzport_t *z, uint8_t cell, char *text, int timeout) +{ + uint8_t cmd[8]; + + if (cell > 9) { + return -1; + } + + cmd[0] = 0x00; + cmd[1] = 0x01; + cmd[2] = cell; + cmd[3] = text[0]; + cmd[4] = text[1]; + cmd[5] = text[2]; + cmd[6] = text[3]; + cmd[7] = 0x00; + + return tranzport_write_core(z, cmd, timeout); +} + +int tranzport_lighton(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x01; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, &cmd[0], timeout); +} + +int tranzport_lightoff(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, &cmd[0], timeout); +} + +int tranzport_read(tranzport_t *z, uint8_t *status, uint32_t *buttons, uint8_t *datawheel, int timeout) +{ + uint8_t buf[8]; + int val; + + memset(buf, 0xff, 8); + val = read(z->udev, buf, 8); + if (val < 0) { + printf("errno: %d\n",errno); + switch(errno) { + case ENOENT: ; + case ECONNRESET: ; + case ESHUTDOWN: printf("dying\n"); exit(1); break; + } + return val; + } + if (val != 8) + return -1; + + /*printf("read: %02x %02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);*/ + + *status = buf[1]; + + *buttons = 0; + *buttons |= buf[2] << 24; + *buttons |= buf[3] << 16; + *buttons |= buf[4] << 8; + *buttons |= buf[5]; + + *datawheel = buf[6]; + + return 0; +} + +void lights_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, uint8_t light) +{ + if (buttons & buttonmask) { + if (buttons & BUTTONMASK_SHIFT) { + tranzport_lightoff(z, light, 10); + } else { + tranzport_lighton(z, light, 10); + } + } +} + +void do_lights(tranzport_t *z, uint32_t buttons) +{ + lights_core(z, buttons, BUTTONMASK_RECORD, LIGHT_RECORD); + lights_core(z, buttons, BUTTONMASK_TRACKREC, LIGHT_TRACKREC); + lights_core(z, buttons, BUTTONMASK_TRACKMUTE, LIGHT_TRACKMUTE); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_TRACKSOLO); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_ANYSOLO); + lights_core(z, buttons, BUTTONMASK_PUNCH, LIGHT_PUNCH); + lights_core(z, buttons, BUTTONMASK_LOOP, LIGHT_LOOP); +} + +void buttons_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, char *str) +{ + if (buttons & buttonmask) + printf(" %s", str); +} + +void do_buttons(tranzport_t *z, uint32_t buttons, uint8_t datawheel) +{ + printf("buttons: %x ", buttons); + buttons_core(z, buttons, BUTTONMASK_BATTERY, "battery"); + buttons_core(z, buttons, BUTTONMASK_BACKLIGHT, "backlight"); + buttons_core(z, buttons, BUTTONMASK_TRACKLEFT, "trackleft"); + buttons_core(z, buttons, BUTTONMASK_TRACKRIGHT, "trackright"); + buttons_core(z, buttons, BUTTONMASK_TRACKREC, "trackrec"); + buttons_core(z, buttons, BUTTONMASK_TRACKMUTE, "trackmute"); + buttons_core(z, buttons, BUTTONMASK_TRACKSOLO, "tracksolo"); + buttons_core(z, buttons, BUTTONMASK_UNDO, "undo"); + buttons_core(z, buttons, BUTTONMASK_IN, "in"); + buttons_core(z, buttons, BUTTONMASK_OUT, "out"); + buttons_core(z, buttons, BUTTONMASK_PUNCH, "punch"); + buttons_core(z, buttons, BUTTONMASK_LOOP, "loop"); + buttons_core(z, buttons, BUTTONMASK_PREV, "prev"); + buttons_core(z, buttons, BUTTONMASK_ADD, "add"); + buttons_core(z, buttons, BUTTONMASK_NEXT, "next"); + buttons_core(z, buttons, BUTTONMASK_REWIND, "rewind"); + buttons_core(z, buttons, BUTTONMASK_FASTFORWARD, "fastforward"); + buttons_core(z, buttons, BUTTONMASK_STOP, "stop"); + buttons_core(z, buttons, BUTTONMASK_PLAY, "play"); + buttons_core(z, buttons, BUTTONMASK_RECORD, "record"); + buttons_core(z, buttons, BUTTONMASK_SHIFT, "shift"); + if (datawheel) + printf(" datawheel=%02x", datawheel); + printf("\n"); +} + +void do_lcd(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, " ", 10); + tranzport_lcdwrite(z, 1, "DISL", 10); + tranzport_lcdwrite(z, 2, "EXIA", 10); + tranzport_lcdwrite(z, 3, " FOR", 10); + tranzport_lcdwrite(z, 4, " ", 10); + + tranzport_lcdwrite(z, 5, " ", 10); + tranzport_lcdwrite(z, 6, " CUR", 10); + tranzport_lcdwrite(z, 7, "E FO", 10); + tranzport_lcdwrite(z, 8, "UND ", 10); + tranzport_lcdwrite(z, 9, " ", 10); +} + +void do_lcd2(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, "THE ", 10); + tranzport_lcdwrite(z, 1, "TRAN", 10); + tranzport_lcdwrite(z, 2, "ZPOR", 10); + tranzport_lcdwrite(z, 3, "T RO", 10); + tranzport_lcdwrite(z, 4, " KS", 10); + + tranzport_lcdwrite(z, 5, "AWES", 10); + tranzport_lcdwrite(z, 6, "OMEE", 10); + tranzport_lcdwrite(z, 7, "LEEE", 10); + tranzport_lcdwrite(z, 8, "UND ", 10); + tranzport_lcdwrite(z, 9, "GROK", 10); +} + +int lights_off(tranzport_t *z) { + static int i = 0; + int j = 0; + for(;j<2; j++,i = (i+1) % 7) { + tranzport_lightoff(z, i, 10); + } +return 0; +} + +int lights_on(tranzport_t *z) { + static int i = 0; + int j = 0; + for(;j<2; j++,i = (i+1) % 7) { + tranzport_lighton(z, i, 10); + } +return 0; +} + +int main() +{ + tranzport_t *z; + uint8_t status; + uint32_t buttons; + uint8_t datawheel; + int val; + + z = open_tranzport(); + + do_lcd(z); + + for(;;) { + + // do_lcd(z); + lights_on(z); + // do_lcd2(z); + + val = tranzport_read(z, &status, &buttons, &datawheel, 60000); + if (val < 0) + continue; + + if (status == STATUS_OFFLINE) { + printf("offline: "); + continue; + } + + if (status == STATUS_ONLINE) { + printf("online: "); + do_lcd(z); + } + + if (status == STATUS_OK) { + printf("OK: "); + do_lcd(z); + } + +// do_lights(z, buttons); + do_buttons(z, buttons, datawheel); + lights_off(z); + } + + close_tranzport(z); + + return 0; +} + diff --git a/libs/surfaces/frontier/kernel_drivers/tests/tranzport_lights.c b/libs/surfaces/frontier/kernel_drivers/tests/tranzport_lights.c new file mode 100644 index 0000000000..4096ee680d --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/tests/tranzport_lights.c @@ -0,0 +1,361 @@ +/* + * tranzport 0.1 <tranzport.sf.net> + * oct 18, 2005 + * arthur@artcmusic.com + */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <malloc.h> + +#define VENDORID 0x165b +#define PRODUCTID 0x8101 + +#define READ_ENDPOINT 0x81 +#define WRITE_ENDPOINT 0x02 + +enum { + LIGHT_RECORD = 0, + LIGHT_TRACKREC, + LIGHT_TRACKMUTE, + LIGHT_TRACKSOLO, + LIGHT_ANYSOLO, + LIGHT_LOOP, + LIGHT_PUNCH +}; + +#define BUTTONMASK_BATTERY 0x00004000 +#define BUTTONMASK_BACKLIGHT 0x00008000 +#define BUTTONMASK_TRACKLEFT 0x04000000 +#define BUTTONMASK_TRACKRIGHT 0x40000000 +#define BUTTONMASK_TRACKREC 0x00040000 +#define BUTTONMASK_TRACKMUTE 0x00400000 +#define BUTTONMASK_TRACKSOLO 0x00000400 +#define BUTTONMASK_UNDO 0x80000000 +#define BUTTONMASK_IN 0x02000000 +#define BUTTONMASK_OUT 0x20000000 +#define BUTTONMASK_PUNCH 0x00800000 +#define BUTTONMASK_LOOP 0x00080000 +#define BUTTONMASK_PREV 0x00020000 +#define BUTTONMASK_ADD 0x00200000 +#define BUTTONMASK_NEXT 0x00000200 +#define BUTTONMASK_REWIND 0x01000000 +#define BUTTONMASK_FASTFORWARD 0x10000000 +#define BUTTONMASK_STOP 0x00010000 +#define BUTTONMASK_PLAY 0x00100000 +#define BUTTONMASK_RECORD 0x00000100 +#define BUTTONMASK_SHIFT 0x08000000 + +#define STATUS_OFFLINE 0xff +#define STATUS_ONLINE 0x01 +#define STATUS_OK 0x00 + +struct tranzport_s { + int *dev; + int udev; +}; + +typedef struct tranzport_s tranzport_t; + +void log_entry(FILE *fp, char *format, va_list ap) +{ + vfprintf(fp, format, ap); + fputc('\n', fp); +} + +void log_error(char *format, ...) +{ + va_list ap; + va_start(ap, format); + log_entry(stderr, format, ap); + va_end(ap); +} + +void vlog_error(char *format, va_list ap) +{ + log_entry(stderr, format, ap); +} + +void die(char *format, ...) +{ + va_list ap; + va_start(ap, format); + vlog_error(format, ap); + va_end(ap); + exit(1); +} + +tranzport_t *open_tranzport_core() +{ + tranzport_t *z; + int val; + + z = malloc(sizeof(tranzport_t)); + if (!z) + die("not enough memory"); + memset(z, 0, sizeof(tranzport_t)); + + z->udev = open("/dev/tranzport0",O_RDWR); + if (!z->udev) + die("unable to open tranzport"); + + return z; +} + +tranzport_t *open_tranzport() +{ +return open_tranzport_core(); +} + +void close_tranzport(tranzport_t *z) +{ + int val; + + val = close(z->udev); + if (val < 0) + log_error("unable to release tranzport"); + + free(z); +} + +int tranzport_write_core(tranzport_t *z, uint8_t *cmd, int timeout) +{ + int val; + val = write(z->udev, cmd, 8); + if (val < 0) + return val; + if (val != 8) + return -1; + return 0; +} + +int tranzport_lcdwrite(tranzport_t *z, uint8_t cell, char *text, int timeout) +{ + uint8_t cmd[8]; + + if (cell > 9) { + return -1; + } + + cmd[0] = 0x00; + cmd[1] = 0x01; + cmd[2] = cell; + cmd[3] = text[0]; + cmd[4] = text[1]; + cmd[5] = text[2]; + cmd[6] = text[3]; + cmd[7] = 0x00; + + return tranzport_write_core(z, cmd, timeout); +} + +int tranzport_lighton(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x01; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, &cmd[0], timeout); +} + +int tranzport_lightoff(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, &cmd[0], timeout); +} + +int tranzport_read(tranzport_t *z, uint8_t *status, uint32_t *buttons, uint8_t *datawheel, int timeout) +{ + uint8_t buf[8]; + int val; + + memset(buf, 0xff, 8); + val = read(z->udev, buf, 8); + if (val < 0) { + // printf("errno: %d\n",errno); + return val; + } + if (val != 8) + return -1; + + /*printf("read: %02x %02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);*/ + + *status = buf[1]; + + *buttons = 0; + *buttons |= buf[2] << 24; + *buttons |= buf[3] << 16; + *buttons |= buf[4] << 8; + *buttons |= buf[5]; + + *datawheel = buf[6]; + + return 0; +} + +void lights_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, uint8_t light) +{ + if (buttons & buttonmask) { + if (buttons & BUTTONMASK_SHIFT) { + tranzport_lightoff(z, light, 1000); + } else { + tranzport_lighton(z, light, 1000); + } + } +} + +void do_lights(tranzport_t *z, uint32_t buttons) +{ + lights_core(z, buttons, BUTTONMASK_RECORD, LIGHT_RECORD); + lights_core(z, buttons, BUTTONMASK_TRACKREC, LIGHT_TRACKREC); + lights_core(z, buttons, BUTTONMASK_TRACKMUTE, LIGHT_TRACKMUTE); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_TRACKSOLO); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_ANYSOLO); + lights_core(z, buttons, BUTTONMASK_PUNCH, LIGHT_PUNCH); + lights_core(z, buttons, BUTTONMASK_LOOP, LIGHT_LOOP); +} + +void buttons_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, char *str) +{ + if (buttons & buttonmask) + printf(" %s", str); +} + +void do_buttons(tranzport_t *z, uint32_t buttons, uint8_t datawheel) +{ + printf("buttons: %x ", buttons); + buttons_core(z, buttons, BUTTONMASK_BATTERY, "battery"); + buttons_core(z, buttons, BUTTONMASK_BACKLIGHT, "backlight"); + buttons_core(z, buttons, BUTTONMASK_TRACKLEFT, "trackleft"); + buttons_core(z, buttons, BUTTONMASK_TRACKRIGHT, "trackright"); + buttons_core(z, buttons, BUTTONMASK_TRACKREC, "trackrec"); + buttons_core(z, buttons, BUTTONMASK_TRACKMUTE, "trackmute"); + buttons_core(z, buttons, BUTTONMASK_TRACKSOLO, "tracksolo"); + buttons_core(z, buttons, BUTTONMASK_UNDO, "undo"); + buttons_core(z, buttons, BUTTONMASK_IN, "in"); + buttons_core(z, buttons, BUTTONMASK_OUT, "out"); + buttons_core(z, buttons, BUTTONMASK_PUNCH, "punch"); + buttons_core(z, buttons, BUTTONMASK_LOOP, "loop"); + buttons_core(z, buttons, BUTTONMASK_PREV, "prev"); + buttons_core(z, buttons, BUTTONMASK_ADD, "add"); + buttons_core(z, buttons, BUTTONMASK_NEXT, "next"); + buttons_core(z, buttons, BUTTONMASK_REWIND, "rewind"); + buttons_core(z, buttons, BUTTONMASK_FASTFORWARD, "fastforward"); + buttons_core(z, buttons, BUTTONMASK_STOP, "stop"); + buttons_core(z, buttons, BUTTONMASK_PLAY, "play"); + buttons_core(z, buttons, BUTTONMASK_RECORD, "record"); + buttons_core(z, buttons, BUTTONMASK_SHIFT, "shift"); + if (datawheel) + printf(" datawheel=%02x", datawheel); + printf("\n"); +} + +void do_lcd(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, " ", 1000); + tranzport_lcdwrite(z, 1, "DISL", 1000); + tranzport_lcdwrite(z, 2, "EXIA", 1000); + tranzport_lcdwrite(z, 3, " FOR", 1000); + tranzport_lcdwrite(z, 4, " ", 1000); + + tranzport_lcdwrite(z, 5, " ", 1000); + tranzport_lcdwrite(z, 6, " CUR", 1000); + tranzport_lcdwrite(z, 7, "E FO", 1000); + tranzport_lcdwrite(z, 8, "UND ", 1000); + tranzport_lcdwrite(z, 9, " ", 1000); +} + +void do_lcd2(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, "THE ", 1000); + tranzport_lcdwrite(z, 1, "TRAN", 1000); + tranzport_lcdwrite(z, 2, "ZPOR", 1000); + tranzport_lcdwrite(z, 3, "T RO", 1000); + tranzport_lcdwrite(z, 4, "KS ", 1000); + + tranzport_lcdwrite(z, 5, "AWES", 1000); + tranzport_lcdwrite(z, 6, "OMEE", 1000); + tranzport_lcdwrite(z, 7, "LEEE", 1000); + tranzport_lcdwrite(z, 8, "WITH", 1000); + tranzport_lcdwrite(z, 9, "ARDO", 1000); +} + +lights_off(tranzport_t *z) { +int i; + for(i=0;i<7;i++) { + tranzport_lightoff(z, i, 1000); + } +} + +lights_on(tranzport_t *z) { +int i; + for(i=0;i<7;i++) { + tranzport_lighton(z, i, 1000); + } +} + +int main() +{ + tranzport_t *z; + uint8_t status; + uint32_t buttons; + uint8_t datawheel; + int val; + + z = open_tranzport(); + + do_lcd(z); + + for(;;) { + + do_lcd(z); + lights_on(z); + do_lcd2(z); + lights_off(z); + +// val = tranzport_read(z, &status, &buttons, &datawheel, 60000); + val = -1; + if (val < 0) + continue; + + if (status == STATUS_OFFLINE) { + printf("offline: "); + continue; + } + + if (status == STATUS_ONLINE) { + printf("online: "); + do_lcd(z); + } + + do_lights(z, buttons); + do_buttons(z, buttons, datawheel); + } + + close_tranzport(z); + + return 0; +} + diff --git a/libs/surfaces/frontier/kernel_drivers/tests/tranzport_tests.sh b/libs/surfaces/frontier/kernel_drivers/tests/tranzport_tests.sh new file mode 100755 index 0000000000..540c62fe16 --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/tests/tranzport_tests.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +echo "Testing lights" +tranzport_lights & +A=$! +sleep 30 +kill $A +echo "Testing interleaved_reads/writes" +tranzport & +A=$! +sleep 30 +kill $A + +exit 0 + +# not done yet +echo "Testing_screen" +tranzport_screen & +A=$! +sleep 30 +kill $A +echo "Testing_reads" +tranzport_read & +A=$! +sleep 30 +kill $A + diff --git a/libs/surfaces/frontier/kernel_drivers/tranzport.c b/libs/surfaces/frontier/kernel_drivers/tranzport.c new file mode 100644 index 0000000000..6893f66921 --- /dev/null +++ b/libs/surfaces/frontier/kernel_drivers/tranzport.c @@ -0,0 +1,1065 @@ +/* + * Frontier Designs Tranzport driver + * + * Copyright (C) 2007 Michael Taht (m@taht.net) + * + * Based on the usbled driver and ldusb drivers by + * + * Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005 Michael Hund <mhund@ld-didactic.de> + * + * The ldusb driver was, in turn, derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net> + * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net> + * + * 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, version 2. + * + */ + +/** + * This driver uses a ring buffer for time critical reading of + * interrupt in reports and provides read and write methods for + * raw interrupt reports. + */ + +/* Note: this currently uses a dumb ringbuffer for reads and writes. + * A more optimal driver would cache and kill off outstanding urbs that are + * now invalid, and ignore ones that already were in the queue but valid + * as we only have 17 commands for the tranzport. In particular this is + * key for getting lights to flash in time as otherwise many commands + * can be buffered up before the light change makes it to the interface. +*/ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/version.h> + +#include <asm/uaccess.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/poll.h> + +/* Define these values to match your devices */ +#define VENDOR_ID 0x165b +#define PRODUCT_ID 0x8101 + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_TRANZPORT_MINOR_BASE 0 +#else +// FIXME 176 - is the ldusb driver's minor - apply for that +#define USB_TRANZPORT_MINOR_BASE 176 +#endif + +/* table of devices that work with this driver */ +static struct usb_device_id usb_tranzport_table [] = { + { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usb_tranzport_table); +MODULE_VERSION("0.29"); +MODULE_AUTHOR("Mike Taht <m@taht.net>"); +MODULE_DESCRIPTION("Tranzport USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("Frontier Designs Control Surface"); + + +/* make this work on older kernel versions */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) + +/** + * usb_endpoint_dir_out - check if the endpoint has OUT direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type OUT, otherwise it returns false. + */ + +static inline int usb_endpoint_dir_out(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); +} + +static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + + +/** + * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type interrupt, otherwise it returns + * false. + */ +static inline int usb_endpoint_xfer_int(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT); +} + + +/** + * usb_endpoint_is_int_in - check if the endpoint is interrupt IN + * @epd: endpoint to be checked + * + * Returns true if the endpoint has interrupt transfer type and IN direction, + * otherwise it returns false. + */ + +static inline int usb_endpoint_is_int_in(const struct usb_endpoint_descriptor *epd) +{ + return (usb_endpoint_xfer_int(epd) && usb_endpoint_dir_in(epd)); +} + +/** + * usb_endpoint_is_int_out - check if the endpoint is interrupt OUT + * @epd: endpoint to be checked + * + * Returns true if the endpoint has interrupt transfer type and OUT direction, + * otherwise it returns false. + */ + +static inline int usb_endpoint_is_int_out(const struct usb_endpoint_descriptor *epd) +{ + return (usb_endpoint_xfer_int(epd) && usb_endpoint_dir_out(epd)); +} + +#endif /* older kernel versions */ + +/* These two aren't done yet */ + +#define SUPPRESS_EXTRA_ONLINE_EVENTS 0 +#define BUFFERED_WRITES 0 + +#define SUPPRESS_EXTRA_OFFLINE_EVENTS 1 +#define COMPRESS_WHEEL_EVENTS 1 +#define BUFFERED_READS 1 +#define RING_BUFFER_SIZE 1000 +#define WRITE_BUFFER_SIZE 34 +#define TRANZPORT_USB_TIMEOUT 10 + + +static int debug = 0; + +/* Use our own dbg macro */ +#define dbg_info(dev, format, arg...) do { if (debug) dev_info(dev , format , ## arg); } while (0) + +/* Module parameters */ + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* All interrupt in transfers are collected in a ring buffer to + * avoid racing conditions and get better performance of the driver. + */ + +static int ring_buffer_size = RING_BUFFER_SIZE; + +module_param(ring_buffer_size, int, S_IRUGO); +MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); + +/* The write_buffer can one day contain more than one interrupt out transfer. + */ +static int write_buffer_size = WRITE_BUFFER_SIZE; +module_param(write_buffer_size, int, S_IRUGO); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); + +/* + * Increase the interval for debugging purposes. + * or set to 1 to use the standard interval from the endpoint descriptors. + */ + +static int min_interrupt_in_interval = TRANZPORT_USB_TIMEOUT; +module_param(min_interrupt_in_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_in_interval, "Minimum interrupt in interval in ms"); + +static int min_interrupt_out_interval = TRANZPORT_USB_TIMEOUT; +module_param(min_interrupt_out_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in ms"); + +struct tranzport_cmd { + unsigned char cmd[8]; +}; + +/* Structure to hold all of our device specific stuff */ +struct usb_tranzport { + struct semaphore sem; /* locks this structure */ + struct usb_interface* intf; /* save off the usb interface pointer */ + + int open_count; /* number of times this port has been opened */ + + struct tranzport_cmd (*ring_buffer)[RING_BUFFER_SIZE]; /* just make c happy */ + unsigned int ring_head; + unsigned int ring_tail; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + unsigned char* interrupt_in_buffer; + struct usb_endpoint_descriptor* interrupt_in_endpoint; + struct urb* interrupt_in_urb; + int interrupt_in_interval; + size_t interrupt_in_endpoint_size; + int interrupt_in_running; + int interrupt_in_done; + + char* interrupt_out_buffer; + struct usb_endpoint_descriptor* interrupt_out_endpoint; + struct urb* interrupt_out_urb; + int interrupt_out_interval; + size_t interrupt_out_endpoint_size; + int interrupt_out_busy; + + /* Sysfs support - most of these are not hooked up yet */ + + int event; /* alternate interface to events */ + int wheel; /* - for negative, 0 for none, + for positive */ + int lights; + unsigned char dump_state; /* 0 if disabled 1 if enabled */ + unsigned char enable; /* 0 if disabled 1 if enabled */ + unsigned char offline; /* if the device is out of range or asleep */ + unsigned char compress_wheel; /* flag to compress wheel events */ + unsigned char LightRecord; + unsigned char LightTrackrec; + unsigned char LightTrackmute; + unsigned char LightTracksolo; + unsigned char LightAnysolo; + unsigned char LightLoop; + unsigned char LightPunch; + unsigned char last_cmd[8]; + unsigned char screen[40]; // We'll also have cells + +}; + +/* prevent races between open() and disconnect() */ +static DEFINE_MUTEX(disconnect_mutex); + +static struct usb_driver usb_tranzport_driver; + +/** + * usb_tranzport_abort_transfers + * aborts transfers and frees associated data structures + */ +static void usb_tranzport_abort_transfers(struct usb_tranzport *dev) +{ + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + if (dev->intf) + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + if (dev->intf) + usb_kill_urb(dev->interrupt_out_urb); +} + +#define show_set_light(value) \ +static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", t->value); \ +} \ +static ssize_t set_##value(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + int temp = simple_strtoul(buf, NULL, 10); \ + \ + t->value = temp; \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IWUGO | S_IRUGO, show_##value, set_##value); + +show_set_light(LightRecord); +show_set_light(LightTrackrec); +show_set_light(LightTrackmute); +show_set_light(LightTracksolo); +show_set_light(LightAnysolo); +show_set_light(LightLoop); +show_set_light(LightPunch); + +show_set_light(enable); +show_set_light(offline); +show_set_light(compress_wheel); +show_set_light(dump_state); + +#define show_set_int(value) \ +static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", t->value); \ +} \ +static ssize_t set_##value(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + int temp = simple_strtoul(buf, NULL, 10); \ + \ + t->value = temp; \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IWUGO | S_IRUGO, show_##value, set_##value); + +show_set_int(wheel); +show_set_int(event); + +#define show_set_cmd(value) \ +static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", t->value); \ +} \ +static ssize_t set_##value(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + int temp = simple_strtoul(buf, NULL, 10); \ + \ + t->value = temp; \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IWUGO | S_IRUGO, show_##value, set_##value); + + + + +/** + * usb_tranzport_delete + */ +static void usb_tranzport_delete(struct usb_tranzport *dev) +{ + usb_tranzport_abort_transfers(dev); + /* This is just too twisted to be correct */ + if(dev->intf != NULL) { + device_remove_file(&dev->intf->dev, &dev_attr_LightRecord); + device_remove_file(&dev->intf->dev, &dev_attr_LightTrackrec); + device_remove_file(&dev->intf->dev, &dev_attr_LightTrackmute); + device_remove_file(&dev->intf->dev, &dev_attr_LightTracksolo); + device_remove_file(&dev->intf->dev, &dev_attr_LightTrackmute); + device_remove_file(&dev->intf->dev, &dev_attr_LightAnysolo); + device_remove_file(&dev->intf->dev, &dev_attr_LightLoop); + device_remove_file(&dev->intf->dev, &dev_attr_LightPunch); + device_remove_file(&dev->intf->dev, &dev_attr_wheel); + device_remove_file(&dev->intf->dev, &dev_attr_enable); + device_remove_file(&dev->intf->dev, &dev_attr_event); + device_remove_file(&dev->intf->dev, &dev_attr_offline); + device_remove_file(&dev->intf->dev, &dev_attr_compress_wheel); + + device_remove_file(&dev->intf->dev, &dev_attr_dump_state); + } + + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->ring_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); +} + +/** + * usb_tranzport_interrupt_in_callback + */ + +static void usb_tranzport_interrupt_in_callback(struct urb *urb) +{ + struct usb_tranzport *dev = urb->context; + unsigned int next_ring_head; + int retval = -1; + + if (urb->status) { + if (urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN) { + goto exit; + } else { + dbg_info(&dev->intf->dev, "%s: nonzero status received: %d\n", + __FUNCTION__, urb->status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length != 8) { + dev_warn(&dev->intf->dev, + "Urb length was %d bytes!! Do something intelligent \n", urb->actual_length); + } else { + dbg_info(&dev->intf->dev, "%s: received: %02x%02x%02x%02x%02x%02x%02x%02x\n", + __FUNCTION__, dev->interrupt_in_buffer[0],dev->interrupt_in_buffer[1],dev->interrupt_in_buffer[2],dev->interrupt_in_buffer[3],dev->interrupt_in_buffer[4],dev->interrupt_in_buffer[5],dev->interrupt_in_buffer[6],dev->interrupt_in_buffer[7]); +#if SUPPRESS_EXTRA_OFFLINE_EVENTS + if(dev->offline == 2 && dev->interrupt_in_buffer[1] == 0xff) { goto resubmit; } + if(dev->offline == 1 && dev->interrupt_in_buffer[1] == 0xff) { dev->offline = 2; goto resubmit; } + +/* Always pass one offline event up the stack */ + if(dev->offline > 0 && dev->interrupt_in_buffer[1] != 0xff) { dev->offline = 0; } + if(dev->offline == 0 && dev->interrupt_in_buffer[1] == 0xff) { dev->offline = 1; } + +#endif + dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", __FUNCTION__,dev->ring_head,dev->ring_tail); + + next_ring_head = (dev->ring_head+1) % ring_buffer_size; + + if (next_ring_head != dev->ring_tail) { + memcpy(&((*dev->ring_buffer)[dev->ring_head]), dev->interrupt_in_buffer, urb->actual_length); + dev->ring_head = next_ring_head; + retval = 0; + memset(dev->interrupt_in_buffer, 0, urb->actual_length); + } else { + dev_warn(&dev->intf->dev, + "Ring buffer overflow, %d bytes dropped\n", + urb->actual_length); + memset(dev->interrupt_in_buffer, 0, urb->actual_length); + } + } + +resubmit: + /* resubmit if we're still running */ + if (dev->interrupt_in_running && dev->intf) { + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "usb_submit_urb failed (%d)\n", retval); + } + +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + +/** + * usb_tranzport_interrupt_out_callback + */ +static void usb_tranzport_interrupt_out_callback(struct urb *urb) +{ + struct usb_tranzport *dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (urb->status && !(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dbg_info(&dev->intf->dev, + "%s - nonzero write interrupt status received: %d\n", + __FUNCTION__, urb->status); + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + +/** + * usb_tranzport_open + */ +static int usb_tranzport_open(struct inode *inode, struct file *file) +{ + struct usb_tranzport *dev; + int subminor; + int retval = 0; + struct usb_interface *interface; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + mutex_lock(&disconnect_mutex); + + interface = usb_find_interface(&usb_tranzport_driver, subminor); + + if (!interface) { + err("%s - error, can't find device for minor %d\n", + __FUNCTION__, subminor); + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + dev = usb_get_intfdata(interface); + + if (!dev) { + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + /* lock this device */ + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto unlock_disconnect_exit; + } + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + + /* initialize in direction */ + dev->ring_head = 0; + dev->ring_tail = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + dev->interrupt_in_endpoint_size, + usb_tranzport_interrupt_in_callback, + dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + dev->enable = 1; + dev->offline = 0; + dev->compress_wheel = 1; + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&interface->dev, "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + + +unlock_exit: + up(&dev->sem); + +unlock_disconnect_exit: + mutex_unlock(&disconnect_mutex); + + return retval; +} + +/** + * usb_tranzport_release + */ +static int usb_tranzport_release(struct inode *inode, struct file *file) +{ + struct usb_tranzport *dev; + int retval = 0; + + dev = file->private_data; + + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto exit; + } + + if (dev->open_count != 1) { + retval = -ENODEV; + goto unlock_exit; + } + + if (dev->intf == NULL) { + /* the device was unplugged before the file was released */ + up(&dev->sem); + /* unlock here as usb_tranzport_delete frees dev */ + usb_tranzport_delete(dev); + retval = -ENODEV; + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) + wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, 2 * HZ); + usb_tranzport_abort_transfers(dev); + dev->open_count = 0; + +unlock_exit: + up(&dev->sem); + +exit: + return retval; +} + +/** + * usb_tranzport_poll + */ +static unsigned int usb_tranzport_poll(struct file *file, poll_table *wait) +{ + struct usb_tranzport *dev; + unsigned int mask = 0; + + dev = file->private_data; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (dev->ring_head != dev->ring_tail) + mask |= POLLIN | POLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +/** + * usb_tranzport_read + */ +static ssize_t usb_tranzport_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct usb_tranzport *dev; + size_t bytes_to_read; + int retval = 0; + +#if BUFFERED_READS + int c = 0; +#endif + +#if COMPRESS_WHEEL_EVENTS + signed char oldwheel; + signed char newwheel; + int cancompress = 1; + int next_tail; +#endif + +/* do I have such a thing as a null event? */ + + dev = file->private_data; + + /* verify that we actually have some data to read */ + if (count == 0) + goto exit; + + /* lock this object */ + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + while (dev->ring_head == dev->ring_tail) { + + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + // atomic_cmp_exchange(&dev->interrupt_in_done,0,0); + dev->interrupt_in_done = 0 ; /* tiny race - FIXME: make atomic? */ + retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done); + if (retval < 0) { + goto unlock_exit; + } + } + + dbg_info(&dev->intf->dev, "%s: copying to userspace: %02x%02x%02x%02x%02x%02x%02x%02x\n", + __FUNCTION__, (*dev->ring_buffer)[dev->ring_tail].cmd[0],(*dev->ring_buffer)[dev->ring_tail].cmd[1],(*dev->ring_buffer)[dev->ring_tail].cmd[2],(*dev->ring_buffer)[dev->ring_tail].cmd[3],(*dev->ring_buffer)[dev->ring_tail].cmd[4],(*dev->ring_buffer)[dev->ring_tail].cmd[5],(*dev->ring_buffer)[dev->ring_tail].cmd[6],(*dev->ring_buffer)[dev->ring_tail].cmd[7]); + +#if BUFFERED_READS + c = 0; + while((c < count) && (dev->ring_tail != dev->ring_head)) { + +/* This started off in the lower level service routine, and I moved it here. Then my brain died. Not done yet. */ +#if COMPRESS_WHEEL_EVENTS + next_tail = (dev->ring_tail+1) % ring_buffer_size; + if(dev->compress_wheel) cancompress = 1; + while(dev->ring_head != next_tail && cancompress == 1 ) { + newwheel = (*dev->ring_buffer)[next_tail].cmd[6]; + oldwheel = (*dev->ring_buffer)[dev->ring_tail].cmd[6]; + // if both are wheel events, and no buttons have changes (FIXME, do I have to check?), + // and we are the same sign, we can compress +- 7F + // FIXME: saner check for overflow! - max of +- 7F + // FIXME the math is wrong for going in reverse, actually, as the midi spec doesn't allow signed chars + + dbg_info(&dev->intf->dev, "%s: trying to compress: %02x%02x%02x%02x%02x %02x %02x %02x\n", + __FUNCTION__, (*dev->ring_buffer)[dev->ring_tail].cmd[0],(*dev->ring_buffer)[dev->ring_tail].cmd[1],(*dev->ring_buffer)[dev->ring_tail].cmd[2],(*dev->ring_buffer)[dev->ring_tail].cmd[3],(*dev->ring_buffer)[dev->ring_tail].cmd[4],(*dev->ring_buffer)[dev->ring_tail].cmd[5],(*dev->ring_buffer)[dev->ring_tail].cmd[6],(*dev->ring_buffer)[dev->ring_tail].cmd[7]); + + + if(((*dev->ring_buffer)[dev->ring_tail].cmd[6] != 0 && + (*dev->ring_buffer)[next_tail].cmd[6] != 0 ) && + ((newwheel > 0 && oldwheel > 0) || + (newwheel < 0 && oldwheel < 0)) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[2] == (*dev->ring_buffer)[next_tail].cmd[2]) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[3] == (*dev->ring_buffer)[next_tail].cmd[3]) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[4] == (*dev->ring_buffer)[next_tail].cmd[4]) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[5] == (*dev->ring_buffer)[next_tail].cmd[5])) + { + dbg_info(&dev->intf->dev, "%s: should compress: %02x%02x%02x%02x%02x%02x%02x%02x\n", + __FUNCTION__, (*dev->ring_buffer)[dev->ring_tail].cmd[0],(*dev->ring_buffer)[dev->ring_tail].cmd[1],(*dev->ring_buffer)[dev->ring_tail].cmd[2],(*dev->ring_buffer)[dev->ring_tail].cmd[3],(*dev->ring_buffer)[dev->ring_tail].cmd[4],(*dev->ring_buffer)[dev->ring_tail].cmd[5],(*dev->ring_buffer)[dev->ring_tail].cmd[6],(*dev->ring_buffer)[dev->ring_tail].cmd[7]); + + newwheel += oldwheel; + if(oldwheel > 0 && !(newwheel > 0)) { + newwheel = 0x7f; + cancompress = 0; + } + if(oldwheel < 0 && !(newwheel < 0)) { + newwheel = 0x80; + cancompress = 0; + } + + (*dev->ring_buffer)[next_tail].cmd[6] = newwheel; + dev->ring_tail = next_tail; + next_tail = (dev->ring_tail+1) % ring_buffer_size; + } else { + cancompress = 0; + } + } +#endif /* COMPRESS_WHEEL_EVENTS */ + + if (copy_to_user(&buffer[c], &(*dev->ring_buffer)[dev->ring_tail], 8)) { + retval = -EFAULT; + goto unlock_exit; + } + + dev->ring_tail = (dev->ring_tail+1) % ring_buffer_size; + c+=8; + dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", __FUNCTION__,dev->ring_head,dev->ring_tail); + } + retval = c; + +#else + if (copy_to_user(buffer, &(*dev->ring_buffer)[dev->ring_tail], 8)) { + retval = -EFAULT; + goto unlock_exit; + } + + dev->ring_tail = (dev->ring_tail+1) % ring_buffer_size; + dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", __FUNCTION__,dev->ring_head,dev->ring_tail); + + retval = 8; +#endif /* BUFFERED_READS */ + +unlock_exit: + /* unlock the device */ + up(&dev->sem); + +exit: + return retval; +} + +/** + * usb_tranzport_write + */ +static ssize_t usb_tranzport_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct usb_tranzport *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* lock this object */ + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + if (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy); + if (retval < 0) { + goto unlock_exit; + } + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min(count, write_buffer_size*dev->interrupt_out_endpoint_size); + if (bytes_to_write < count) + dev_warn(&dev->intf->dev, "Write buffer overflow, %zd bytes dropped\n",count-bytes_to_write); + + dbg_info(&dev->intf->dev, "%s: count = %zd, bytes_to_write = %zd\n", __FUNCTION__, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + if (dev->interrupt_out_endpoint == NULL) { + err("Endpoint should not be be null! \n"); + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + interface_to_usbdev(dev->intf), + usb_sndintpipe(interface_to_usbdev(dev->intf), + dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + usb_tranzport_interrupt_out_callback, + dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + err("Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + up(&dev->sem); + +exit: + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations usb_tranzport_fops = { + .owner = THIS_MODULE, + .read = usb_tranzport_read, + .write = usb_tranzport_write, + .open = usb_tranzport_open, + .release = usb_tranzport_release, + .poll = usb_tranzport_poll, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver usb_tranzport_class = { + .name = "tranzport%d", + .fops = &usb_tranzport_fops, + .minor_base = USB_TRANZPORT_MINOR_BASE, +}; + + +/** + * usb_tranzport_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int usb_tranzport_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_tranzport *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + int true_size; + int retval = -ENOMEM; + + /* allocate memory for our device state and intialize it */ + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&intf->dev, "Out of memory\n"); + goto exit; + } + init_MUTEX(&dev->sem); + dev->intf = intf; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + iface_desc = intf->cur_altsetting; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) + dev->interrupt_in_endpoint = endpoint; + + if (usb_endpoint_is_int_out(endpoint)) + dev->interrupt_out_endpoint = endpoint; + } + if (dev->interrupt_in_endpoint == NULL) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) + dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n"); + + + dev->interrupt_in_endpoint_size = le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); + + if (dev->interrupt_in_endpoint_size != 8) + dev_warn(&intf->dev, "Interrupt in endpoint size is not 8!\n"); + + if(ring_buffer_size == 0) { ring_buffer_size = RING_BUFFER_SIZE; } + true_size = min(ring_buffer_size,RING_BUFFER_SIZE); + /* FIXME - there are more usb_alloc routines for dma correctness. Needed? */ + + dev->ring_buffer = kmalloc((true_size*sizeof(struct tranzport_cmd))+8, GFP_KERNEL); + + if (!dev->ring_buffer) { + dev_err(&intf->dev, "Couldn't allocate ring_buffer of size %d\n",true_size); + goto error; + } + dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->interrupt_out_endpoint_size = dev->interrupt_out_endpoint ? le16_to_cpu(dev->interrupt_out_endpoint->wMaxPacketSize) : + udev->descriptor.bMaxPacketSize0; + + if (dev->interrupt_out_endpoint_size !=8) + dev_warn(&intf->dev, "Interrupt out endpoint size is not 8!)\n"); + + dev->interrupt_out_buffer = kmalloc(write_buffer_size*dev->interrupt_out_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + dev->interrupt_in_interval = min_interrupt_in_interval > dev->interrupt_in_endpoint->bInterval ? min_interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; + if (dev->interrupt_out_endpoint) + dev->interrupt_out_interval = min_interrupt_out_interval > dev->interrupt_out_endpoint->bInterval ? min_interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(intf, dev); + + retval = usb_register_dev(intf, &usb_tranzport_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(intf, NULL); + goto error; + } + + if((retval = device_create_file(&intf->dev, &dev_attr_LightRecord))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_LightTrackrec))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_LightTrackmute))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_LightTracksolo))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_LightAnysolo))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_LightLoop))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_LightPunch))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_wheel))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_event))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_dump_state))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_compress_wheel))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_enable))) goto error; + if((retval = device_create_file(&intf->dev, &dev_attr_offline))) goto error; + + /* let the user know what node this device is now attached to */ + dev_info(&intf->dev, "Tranzport Device #%d now attached to major %d minor %d\n", + (intf->minor - USB_TRANZPORT_MINOR_BASE), USB_MAJOR, intf->minor); + +exit: + return retval; + +error: + usb_tranzport_delete(dev); + + return retval; +} + +/** + * usb_tranzport_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void usb_tranzport_disconnect(struct usb_interface *intf) +{ + struct usb_tranzport *dev; + int minor; + + /* FIXME: The skel code calls lock_kernel here, doesn't use a mutex, needed? */ + mutex_lock(&disconnect_mutex); + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + + down(&dev->sem); + + minor = intf->minor; + + /* give back our minor */ + usb_deregister_dev(intf, &usb_tranzport_class); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + up(&dev->sem); + usb_tranzport_delete(dev); + } else { + dev->intf = NULL; + up(&dev->sem); + } + + mutex_unlock(&disconnect_mutex); + + dev_info(&intf->dev, "Tranzport Surface #%d now disconnected\n", + (minor - USB_TRANZPORT_MINOR_BASE)); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver usb_tranzport_driver = { + .name = "tranzport", + .probe = usb_tranzport_probe, + .disconnect = usb_tranzport_disconnect, + .id_table = usb_tranzport_table, +}; + +/** + * usb_tranzport_init + */ +static int __init usb_tranzport_init(void) +{ + int retval; + + /* register this driver with the USB subsystem */ + retval = usb_register(&usb_tranzport_driver); + if (retval) + err("usb_register failed for the "__FILE__" driver. Error number %d\n", retval); + + return retval; +} + +/** + * usb_tranzport_exit + */ +static void __exit usb_tranzport_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&usb_tranzport_driver); +} + +module_init(usb_tranzport_init); +module_exit(usb_tranzport_exit); + diff --git a/libs/surfaces/frontier/tests/Makefile b/libs/surfaces/frontier/tests/Makefile new file mode 100644 index 0000000000..aafb9aaa57 --- /dev/null +++ b/libs/surfaces/frontier/tests/Makefile @@ -0,0 +1,17 @@ +# Some basic utilities for testing the tranzport's I/O +# eventually "tranzport" will become a flexible command +# +# +all: tranzport tranzport_lights + +tranzport: tranzport.c + gcc -g -Wall -o tranzport tranzport.c + +tranzport_lights: tranzport_lights.c + gcc -g -Wall -o tranzport_lights tranzport_lights.c + +clean:: + rm -f core .*.cmd *.o *.ko *.mod.c Module.symvers *.bak .\#* *~ + rm -rf .tmp_versions tranzport tranzport_lights + + diff --git a/libs/surfaces/frontier/tests/README b/libs/surfaces/frontier/tests/README new file mode 100644 index 0000000000..f9efd18f69 --- /dev/null +++ b/libs/surfaces/frontier/tests/README @@ -0,0 +1,104 @@ +tranzport 0.1 <tranzport.sf.net> +oct 18, 2005 +arthur@artcmusic.com +--- + +The Frontier Design Tranzport(tm) (www.frontierdesign.com) is a simple +wireless USB device. It is not a MIDI device. The document on their web +site "Tranzport(tm) Native Mode Interface Description" describes the +Tranzport(tm) as if it were a MIDI device, but this is implemented by their +Windows and Macintosh software drivers. + +This code will allow you to use your Tranzport(tm) at a lower level of +abstraction. This code relies on libusb, which can be obtained from +libusb.sourceforge.net. + +To compile the program, type "make". You should end up with a executable +called "tranzport". You'll probably have to run this program as root. + +Using the program is straightforward. It will simply tell you which +buttons are being pressed and what not. If you press one of the buttons +with a light, the light will turn on. If you hold shift and press one of +the buttons with a light, the light will turn off. If you take out the +batteries to the device (or go out of range), it will tell you that the +device is offline. When you replace the batteries (or come back in +range), it should tell you it is back online. + +Once you understand how everything works, you should be able to +incorporate it into your own setup however you wish. + +This code was developed on a Linux machine, but (theoretically) it +should work on any system that is supported by libusb, since that is how +it communicates with the device. + +Here are a few more details about the device: + +There are two endpoints for communication with the device. All data +reads and writes are done in 8-byte segments. + +One endpoint is for interrupt reads. This is used to read button data +from the device. It also supplies status information for when the device +goes out of range and comes back in range, loses power and regains +power, etc. The format of the data is: + + 00 ss bb bb bb bb dd 00 (hexadecimal) + +where: + + ss - status code, 01=online ff=offline + bb - button bits + dd - data wheel, 01-3f=forward 41-7f=backward + +Please refer to the source code for a list of the button bits. + +The other endpoint is for interrupt writes. This is used to toggle the +lights on the device, and to write data to the LCD. + +There are 7 lights on the device. To turn a light on, send the following +sequence of bytes: + + 00 00 nn 01 00 00 00 00 (hexadecimal) + +where nn is the light number. + +To turn a light off: + + 00 00 nn 00 00 00 00 00 (hexadecimal) + +Here is the list of lights: + + 00 Record + 01 Track Rec + 02 Track Mute + 03 Track Solo + 04 Any Solo + 05 Loop + 06 Punch + +The size of the LCD is 20x2, and it is split into 10 cells, each cell +being 4 characters wide. The cells progress across, then down. To write +to the LCD, send the following sequence of bytes: + + 00 01 cc aa aa aa aa 00 (hexadecimal) + +where: + + cc - cell number + aa - ASCII code + +Here is a list of the cells to clarify: + + 00 row 0, column 0-3 + 01 row 0, column 4-7 + 02 row 0, column 8-11 + 03 row 0, column 12-15 + 04 row 0, column 16-19 + 05 row 1, column 0-3 + 06 row 1, column 4-7 + 07 row 1, column 8-11 + 08 row 1, column 12-15 + 09 row 1, column 16-19 + +You should refer to the "Tranzport(tm) Native Mode Interface +Description" document for a listing of the ASCII codes the LCD uses. + diff --git a/libs/surfaces/frontier/tests/tranzport.c b/libs/surfaces/frontier/tests/tranzport.c new file mode 100644 index 0000000000..1eeacd6578 --- /dev/null +++ b/libs/surfaces/frontier/tests/tranzport.c @@ -0,0 +1,347 @@ +/* + * tranzport 0.1 <tranzport.sf.net> + * oct 18, 2005 + * arthur@artcmusic.com + */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <usb.h> + +#define VENDORID 0x165b +#define PRODUCTID 0x8101 + +#define READ_ENDPOINT 0x81 +#define WRITE_ENDPOINT 0x02 + +enum { + LIGHT_RECORD = 0, + LIGHT_TRACKREC, + LIGHT_TRACKMUTE, + LIGHT_TRACKSOLO, + LIGHT_ANYSOLO, + LIGHT_LOOP, + LIGHT_PUNCH +}; + +#define BUTTONMASK_BATTERY 0x00004000 +#define BUTTONMASK_BACKLIGHT 0x00008000 +#define BUTTONMASK_TRACKLEFT 0x04000000 +#define BUTTONMASK_TRACKRIGHT 0x40000000 +#define BUTTONMASK_TRACKREC 0x00040000 +#define BUTTONMASK_TRACKMUTE 0x00400000 +#define BUTTONMASK_TRACKSOLO 0x00000400 +#define BUTTONMASK_UNDO 0x80000000 +#define BUTTONMASK_IN 0x02000000 +#define BUTTONMASK_OUT 0x20000000 +#define BUTTONMASK_PUNCH 0x00800000 +#define BUTTONMASK_LOOP 0x00080000 +#define BUTTONMASK_PREV 0x00020000 +#define BUTTONMASK_ADD 0x00200000 +#define BUTTONMASK_NEXT 0x00000200 +#define BUTTONMASK_REWIND 0x01000000 +#define BUTTONMASK_FASTFORWARD 0x10000000 +#define BUTTONMASK_STOP 0x00010000 +#define BUTTONMASK_PLAY 0x00100000 +#define BUTTONMASK_RECORD 0x00000100 +#define BUTTONMASK_SHIFT 0x08000000 + +#define STATUS_OFFLINE 0xff +#define STATUS_ONLINE 0x01 + +struct tranzport_s { + struct usb_device *dev; + usb_dev_handle *udev; +}; + +typedef struct tranzport_s tranzport_t; + +void log_entry(FILE *fp, char *format, va_list ap) +{ + vfprintf(fp, format, ap); + fputc('\n', fp); +} + +void log_error(char *format, ...) +{ + va_list ap; + va_start(ap, format); + log_entry(stderr, format, ap); + va_end(ap); +} + +void vlog_error(char *format, va_list ap) +{ + log_entry(stderr, format, ap); +} + +void die(char *format, ...) +{ + va_list ap; + va_start(ap, format); + vlog_error(format, ap); + va_end(ap); + exit(1); +} + +tranzport_t *open_tranzport_core(struct usb_device *dev) +{ + tranzport_t *z; + int val; + + z = malloc(sizeof(tranzport_t)); + if (!z) + die("not enough memory"); + memset(z, 0, sizeof(tranzport_t)); + + z->dev = dev; + z->udev = usb_open(z->dev); + if (!z->udev) + die("unable to open tranzport"); + + val = usb_claim_interface(z->udev, 0); + if (val < 0) + die("unable to claim tranzport"); + + return z; +} + +tranzport_t *open_tranzport() +{ + struct usb_bus *bus; + struct usb_device *dev; + + usb_init(); + usb_find_busses(); + usb_find_devices(); + + for(bus=usb_busses; bus; bus=bus->next) { + for(dev=bus->devices; dev; dev=dev->next) { + if (dev->descriptor.idVendor != VENDORID) + continue; + if (dev->descriptor.idProduct != PRODUCTID) + continue; + + return open_tranzport_core(dev); + } + } + + die("can't find tranzport"); + return 0; +} + +void close_tranzport(tranzport_t *z) +{ + int val; + + val = usb_release_interface(z->udev, 0); + if (val < 0) + log_error("unable to release tranzport"); + + val = usb_close(z->udev); + if (val < 0) + log_error("unable to close tranzport"); + + free(z); +} + +int tranzport_write_core(tranzport_t *z, uint8_t *cmd, int timeout) +{ + int val; + val = usb_interrupt_write(z->udev, WRITE_ENDPOINT, cmd, 8, timeout); + if (val < 0) + return val; + if (val != 8) + return -1; + return 0; +} + +int tranzport_lcdwrite(tranzport_t *z, uint8_t cell, char *text, int timeout) +{ + uint8_t cmd[8]; + + if (cell > 9) { + return -1; + } + + cmd[0] = 0x00; + cmd[1] = 0x01; + cmd[2] = cell; + cmd[3] = text[0]; + cmd[4] = text[1]; + cmd[5] = text[2]; + cmd[6] = text[3]; + cmd[7] = 0x00; + + return tranzport_write_core(z, cmd, timeout); +} + +int tranzport_lighton(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x01; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, cmd, timeout); +} + +int tranzport_lightoff(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, cmd, timeout); +} + +int tranzport_read(tranzport_t *z, uint8_t *status, uint32_t *buttons, uint8_t *datawheel, int timeout) +{ + uint8_t buf[8]; + int val; + + memset(buf, 0, 8); + val = usb_interrupt_read(z->udev, READ_ENDPOINT, buf, 8, timeout); + if (val < 0) + return val; + if (val != 8) + return -1; + + /*printf("read: %02x %02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);*/ + + *status = buf[1]; + + *buttons = 0; + *buttons |= buf[2] << 24; + *buttons |= buf[3] << 16; + *buttons |= buf[4] << 8; + *buttons |= buf[5]; + + *datawheel = buf[6]; + + return 0; +} + +void lights_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, uint8_t light) +{ + if (buttons & buttonmask) { + if (buttons & BUTTONMASK_SHIFT) { + tranzport_lightoff(z, light, 1000); + } else { + tranzport_lighton(z, light, 1000); + } + } +} + +void do_lights(tranzport_t *z, uint32_t buttons) +{ + lights_core(z, buttons, BUTTONMASK_RECORD, LIGHT_RECORD); + lights_core(z, buttons, BUTTONMASK_TRACKREC, LIGHT_TRACKREC); + lights_core(z, buttons, BUTTONMASK_TRACKMUTE, LIGHT_TRACKMUTE); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_TRACKSOLO); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_ANYSOLO); + lights_core(z, buttons, BUTTONMASK_PUNCH, LIGHT_PUNCH); + lights_core(z, buttons, BUTTONMASK_LOOP, LIGHT_LOOP); +} + +void buttons_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, char *str) +{ + if (buttons & buttonmask) + printf(" %s", str); +} + +void do_buttons(tranzport_t *z, uint32_t buttons, uint8_t datawheel) +{ + printf("buttons:"); + buttons_core(z, buttons, BUTTONMASK_BATTERY, "battery"); + buttons_core(z, buttons, BUTTONMASK_BACKLIGHT, "backlight"); + buttons_core(z, buttons, BUTTONMASK_TRACKLEFT, "trackleft"); + buttons_core(z, buttons, BUTTONMASK_TRACKRIGHT, "trackright"); + buttons_core(z, buttons, BUTTONMASK_TRACKREC, "trackrec"); + buttons_core(z, buttons, BUTTONMASK_TRACKMUTE, "trackmute"); + buttons_core(z, buttons, BUTTONMASK_TRACKSOLO, "tracksolo"); + buttons_core(z, buttons, BUTTONMASK_UNDO, "undo"); + buttons_core(z, buttons, BUTTONMASK_IN, "in"); + buttons_core(z, buttons, BUTTONMASK_OUT, "out"); + buttons_core(z, buttons, BUTTONMASK_PUNCH, "punch"); + buttons_core(z, buttons, BUTTONMASK_LOOP, "loop"); + buttons_core(z, buttons, BUTTONMASK_PREV, "prev"); + buttons_core(z, buttons, BUTTONMASK_ADD, "add"); + buttons_core(z, buttons, BUTTONMASK_NEXT, "next"); + buttons_core(z, buttons, BUTTONMASK_REWIND, "rewind"); + buttons_core(z, buttons, BUTTONMASK_FASTFORWARD, "fastforward"); + buttons_core(z, buttons, BUTTONMASK_STOP, "stop"); + buttons_core(z, buttons, BUTTONMASK_PLAY, "play"); + buttons_core(z, buttons, BUTTONMASK_RECORD, "record"); + buttons_core(z, buttons, BUTTONMASK_SHIFT, "shift"); + if (datawheel) + printf(" datawheel=%02x", datawheel); + printf("\n"); +} + +void do_lcd(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, " ", 1000); + tranzport_lcdwrite(z, 1, "DISL", 1000); + tranzport_lcdwrite(z, 2, "EXIA", 1000); + tranzport_lcdwrite(z, 3, " FOR", 1000); + tranzport_lcdwrite(z, 4, " ", 1000); + + tranzport_lcdwrite(z, 5, " ", 1000); + tranzport_lcdwrite(z, 6, " CUR", 1000); + tranzport_lcdwrite(z, 7, "E FO", 1000); + tranzport_lcdwrite(z, 8, "UND ", 1000); + tranzport_lcdwrite(z, 9, " ", 1000); +} + +int main() +{ + tranzport_t *z; + uint8_t status; + uint32_t buttons; + uint8_t datawheel; + int val; + + z = open_tranzport(); + + do_lcd(z); + + for(;;) { + val = tranzport_read(z, &status, &buttons, &datawheel, 60000); + if (val < 0) + continue; + + if (status == STATUS_OFFLINE) { + printf("offline\n"); + continue; + } + + if (status == STATUS_ONLINE) { + printf("online\n"); + do_lcd(z); + } + + do_lights(z, buttons); + do_buttons(z, buttons, datawheel); + } + + close_tranzport(z); + + return 0; +} + diff --git a/libs/surfaces/frontier/tests/tranzport_lights.c b/libs/surfaces/frontier/tests/tranzport_lights.c new file mode 100644 index 0000000000..28a8462d84 --- /dev/null +++ b/libs/surfaces/frontier/tests/tranzport_lights.c @@ -0,0 +1,361 @@ +/* + * tranzport 0.1 <tranzport.sf.net> + * oct 18, 2005 + * arthur@artcmusic.com + */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <malloc.h> + +#define VENDORID 0x165b +#define PRODUCTID 0x8101 + +#define READ_ENDPOINT 0x81 +#define WRITE_ENDPOINT 0x02 + +enum { + LIGHT_RECORD = 0, + LIGHT_TRACKREC, + LIGHT_TRACKMUTE, + LIGHT_TRACKSOLO, + LIGHT_ANYSOLO, + LIGHT_LOOP, + LIGHT_PUNCH +}; + +#define BUTTONMASK_BATTERY 0x00004000 +#define BUTTONMASK_BACKLIGHT 0x00008000 +#define BUTTONMASK_TRACKLEFT 0x04000000 +#define BUTTONMASK_TRACKRIGHT 0x40000000 +#define BUTTONMASK_TRACKREC 0x00040000 +#define BUTTONMASK_TRACKMUTE 0x00400000 +#define BUTTONMASK_TRACKSOLO 0x00000400 +#define BUTTONMASK_UNDO 0x80000000 +#define BUTTONMASK_IN 0x02000000 +#define BUTTONMASK_OUT 0x20000000 +#define BUTTONMASK_PUNCH 0x00800000 +#define BUTTONMASK_LOOP 0x00080000 +#define BUTTONMASK_PREV 0x00020000 +#define BUTTONMASK_ADD 0x00200000 +#define BUTTONMASK_NEXT 0x00000200 +#define BUTTONMASK_REWIND 0x01000000 +#define BUTTONMASK_FASTFORWARD 0x10000000 +#define BUTTONMASK_STOP 0x00010000 +#define BUTTONMASK_PLAY 0x00100000 +#define BUTTONMASK_RECORD 0x00000100 +#define BUTTONMASK_SHIFT 0x08000000 + +#define STATUS_OFFLINE 0xff +#define STATUS_ONLINE 0x01 +#define STATUS_OK 0x00 + +struct tranzport_s { + int *dev; + int udev; +}; + +typedef struct tranzport_s tranzport_t; + +void log_entry(FILE *fp, char *format, va_list ap) +{ + vfprintf(fp, format, ap); + fputc('\n', fp); +} + +void log_error(char *format, ...) +{ + va_list ap; + va_start(ap, format); + log_entry(stderr, format, ap); + va_end(ap); +} + +void vlog_error(char *format, va_list ap) +{ + log_entry(stderr, format, ap); +} + +void die(char *format, ...) +{ + va_list ap; + va_start(ap, format); + vlog_error(format, ap); + va_end(ap); + exit(1); +} + +tranzport_t *open_tranzport_core() +{ + tranzport_t *z; + int val; + + z = malloc(sizeof(tranzport_t)); + if (!z) + die("not enough memory"); + memset(z, 0, sizeof(tranzport_t)); + + z->udev = open("/dev/tranzport0",O_RDWR); + if (!z->udev) + die("unable to open tranzport"); + + return z; +} + +tranzport_t *open_tranzport() +{ +return open_tranzport_core(); +} + +void close_tranzport(tranzport_t *z) +{ + int val; + + val = close(z->udev); + if (val < 0) + log_error("unable to release tranzport"); + + free(z); +} + +int tranzport_write_core(tranzport_t *z, uint8_t *cmd, int timeout) +{ + int val; + val = write(z->udev, cmd, 8); + if (val < 0) + return val; + if (val != 8) + return -1; + return 0; +} + +int tranzport_lcdwrite(tranzport_t *z, uint8_t cell, char *text, int timeout) +{ + uint8_t cmd[8]; + + if (cell > 9) { + return -1; + } + + cmd[0] = 0x00; + cmd[1] = 0x01; + cmd[2] = cell; + cmd[3] = text[0]; + cmd[4] = text[1]; + cmd[5] = text[2]; + cmd[6] = text[3]; + cmd[7] = 0x00; + + return tranzport_write_core(z, cmd, timeout); +} + +int tranzport_lighton(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x01; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, &cmd[0], timeout); +} + +int tranzport_lightoff(tranzport_t *z, uint8_t light, int timeout) +{ + uint8_t cmd[8]; + + cmd[0] = 0x00; + cmd[1] = 0x00; + cmd[2] = light; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + cmd[7] = 0x00; + + return tranzport_write_core(z, &cmd[0], timeout); +} + +int tranzport_read(tranzport_t *z, uint8_t *status, uint32_t *buttons, uint8_t *datawheel, int timeout) +{ + uint8_t buf[8]; + int val; + + memset(buf, 0xff, 8); + val = read(z->udev, buf, 8); + if (val < 0) { + // printf("errno: %d\n",errno); + return val; + } + if (val != 8) + return -1; + + /*printf("read: %02x %02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);*/ + + *status = buf[1]; + + *buttons = 0; + *buttons |= buf[2] << 24; + *buttons |= buf[3] << 16; + *buttons |= buf[4] << 8; + *buttons |= buf[5]; + + *datawheel = buf[6]; + + return 0; +} + +void lights_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, uint8_t light) +{ + if (buttons & buttonmask) { + if (buttons & BUTTONMASK_SHIFT) { + tranzport_lightoff(z, light, 1000); + } else { + tranzport_lighton(z, light, 1000); + } + } +} + +void do_lights(tranzport_t *z, uint32_t buttons) +{ + lights_core(z, buttons, BUTTONMASK_RECORD, LIGHT_RECORD); + lights_core(z, buttons, BUTTONMASK_TRACKREC, LIGHT_TRACKREC); + lights_core(z, buttons, BUTTONMASK_TRACKMUTE, LIGHT_TRACKMUTE); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_TRACKSOLO); + lights_core(z, buttons, BUTTONMASK_TRACKSOLO, LIGHT_ANYSOLO); + lights_core(z, buttons, BUTTONMASK_PUNCH, LIGHT_PUNCH); + lights_core(z, buttons, BUTTONMASK_LOOP, LIGHT_LOOP); +} + +void buttons_core(tranzport_t *z, uint32_t buttons, uint32_t buttonmask, char *str) +{ + if (buttons & buttonmask) + printf(" %s", str); +} + +void do_buttons(tranzport_t *z, uint32_t buttons, uint8_t datawheel) +{ + printf("buttons: %x ", buttons); + buttons_core(z, buttons, BUTTONMASK_BATTERY, "battery"); + buttons_core(z, buttons, BUTTONMASK_BACKLIGHT, "backlight"); + buttons_core(z, buttons, BUTTONMASK_TRACKLEFT, "trackleft"); + buttons_core(z, buttons, BUTTONMASK_TRACKRIGHT, "trackright"); + buttons_core(z, buttons, BUTTONMASK_TRACKREC, "trackrec"); + buttons_core(z, buttons, BUTTONMASK_TRACKMUTE, "trackmute"); + buttons_core(z, buttons, BUTTONMASK_TRACKSOLO, "tracksolo"); + buttons_core(z, buttons, BUTTONMASK_UNDO, "undo"); + buttons_core(z, buttons, BUTTONMASK_IN, "in"); + buttons_core(z, buttons, BUTTONMASK_OUT, "out"); + buttons_core(z, buttons, BUTTONMASK_PUNCH, "punch"); + buttons_core(z, buttons, BUTTONMASK_LOOP, "loop"); + buttons_core(z, buttons, BUTTONMASK_PREV, "prev"); + buttons_core(z, buttons, BUTTONMASK_ADD, "add"); + buttons_core(z, buttons, BUTTONMASK_NEXT, "next"); + buttons_core(z, buttons, BUTTONMASK_REWIND, "rewind"); + buttons_core(z, buttons, BUTTONMASK_FASTFORWARD, "fastforward"); + buttons_core(z, buttons, BUTTONMASK_STOP, "stop"); + buttons_core(z, buttons, BUTTONMASK_PLAY, "play"); + buttons_core(z, buttons, BUTTONMASK_RECORD, "record"); + buttons_core(z, buttons, BUTTONMASK_SHIFT, "shift"); + if (datawheel) + printf(" datawheel=%02x", datawheel); + printf("\n"); +} + +void do_lcd(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, " ", 1000); + tranzport_lcdwrite(z, 1, "DISL", 1000); + tranzport_lcdwrite(z, 2, "EXIA", 1000); + tranzport_lcdwrite(z, 3, " FOR", 1000); + tranzport_lcdwrite(z, 4, " ", 1000); + + tranzport_lcdwrite(z, 5, " ", 1000); + tranzport_lcdwrite(z, 6, " CUR", 1000); + tranzport_lcdwrite(z, 7, "E FO", 1000); + tranzport_lcdwrite(z, 8, "UND ", 1000); + tranzport_lcdwrite(z, 9, " ", 1000); +} + +void do_lcd2(tranzport_t *z) +{ + tranzport_lcdwrite(z, 0, "THE ", 1000); + tranzport_lcdwrite(z, 1, "TRAN", 1000); + tranzport_lcdwrite(z, 2, "ZPOR", 1000); + tranzport_lcdwrite(z, 3, "T RO", 1000); + tranzport_lcdwrite(z, 4, " KS", 1000); + + tranzport_lcdwrite(z, 5, "AWES", 1000); + tranzport_lcdwrite(z, 6, "OMEE", 1000); + tranzport_lcdwrite(z, 7, "LEEE", 1000); + tranzport_lcdwrite(z, 8, "UND ", 1000); + tranzport_lcdwrite(z, 9, "GROK", 1000); +} + +lights_off(tranzport_t *z) { +int i; + for(i=0;i<7;i++) { + tranzport_lightoff(z, i, 1000); + } +} + +lights_on(tranzport_t *z) { +int i; + for(i=0;i<7;i++) { + tranzport_lighton(z, i, 1000); + } +} + +int main() +{ + tranzport_t *z; + uint8_t status; + uint32_t buttons; + uint8_t datawheel; + int val; + + z = open_tranzport(); + + do_lcd(z); + + for(;;) { + + do_lcd(z); + lights_on(z); + do_lcd2(z); + lights_off(z); + +// val = tranzport_read(z, &status, &buttons, &datawheel, 60000); + val = -1; + if (val < 0) + continue; + + if (status == STATUS_OFFLINE) { + printf("offline: "); + continue; + } + + if (status == STATUS_ONLINE) { + printf("online: "); + do_lcd(z); + } + + do_lights(z, buttons); + do_buttons(z, buttons, datawheel); + } + + close_tranzport(z); + + return 0; +} + diff --git a/libs/surfaces/frontier/tranzport/SConscript b/libs/surfaces/frontier/tranzport/SConscript new file mode 100644 index 0000000000..5d390f3e2f --- /dev/null +++ b/libs/surfaces/frontier/tranzport/SConscript @@ -0,0 +1,56 @@ +# -*- python -*- + +import os +import os.path +import glob + +Import('env final_prefix install_prefix final_config_prefix libraries i18n') + +tranzport = env.Copy() + +# +# this defines the version number of libardour_tranzport +# + +domain = 'ardour_tranzport' + +tranzport.Append(DOMAIN = domain, MAJOR = 1, MINOR = 0, MICRO = 0) +tranzport.Append(CXXFLAGS = "-DPACKAGE=\\\"" + domain + "\\\"") +tranzport.Append(CXXFLAGS="-DLIBSIGC_DISABLE_DEPRECATED") +tranzport.Append(PACKAGE = domain) +tranzport.Append(POTFILE = domain + '.pot') + +tranzport_files=Split(""" +interface.cc +tranzport_control_protocol.cc +""") + +tranzport.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE") +tranzport.Append(CXXFLAGS="-DDATA_DIR=\\\""+final_prefix+"/share\\\"") +tranzport.Append(CXXFLAGS="-DCONFIG_DIR=\\\""+final_config_prefix+"\\\"") +tranzport.Append(CXXFLAGS="-DLOCALEDIR=\\\""+final_prefix+"/share/locale\\\"") + +tranzport.Merge ([ + libraries['ardour'], + libraries['ardour_cp'], + libraries['sigc2'], + libraries['pbd'], + libraries['midi++2'], + libraries['xml'], + libraries['usb'], + libraries['glib2'], + libraries['glibmm2'] + ]) + +libardour_tranzport = tranzport.SharedLibrary('ardour_tranzport', tranzport_files) + +if tranzport['TRANZPORT']: + Default(libardour_tranzport) + if env['NLS']: + i18n (tranzport, tranzport_files, env) + env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2', 'surfaces'), libardour_tranzport)) + +env.Alias('tarball', env.Distribute (env['DISTTREE'], + [ 'SConscript' ] + + tranzport_files + + glob.glob('po/*.po') + glob.glob('*.h'))) diff --git a/libs/surfaces/frontier/tranzport/interface.cc b/libs/surfaces/frontier/tranzport/interface.cc new file mode 100644 index 0000000000..f6d0dc8206 --- /dev/null +++ b/libs/surfaces/frontier/tranzport/interface.cc @@ -0,0 +1,51 @@ +#include <control_protocol/control_protocol.h> +#include "tranzport_control_protocol.h" + +using namespace ARDOUR; + +ControlProtocol* +new_tranzport_protocol (ControlProtocolDescriptor* descriptor, Session* s) +{ + TranzportControlProtocol* tcp = new TranzportControlProtocol (*s); + + if (tcp->set_active (true)) { + delete tcp; + return 0; + } + + return tcp; + +} + +void +delete_tranzport_protocol (ControlProtocolDescriptor* descriptor, ControlProtocol* cp) +{ + delete cp; +} + +bool +probe_tranzport_protocol (ControlProtocolDescriptor* descriptor) +{ + return TranzportControlProtocol::probe(); +} + +static ControlProtocolDescriptor tranzport_descriptor = { + name : "Tranzport", + id : "uri://ardour.org/surfaces/tranzport:0", + ptr : 0, + module : 0, + mandatory : 0, + supports_feedback : false, + probe : probe_tranzport_protocol, + initialize : new_tranzport_protocol, + destroy : delete_tranzport_protocol +}; + + +extern "C" { +ControlProtocolDescriptor* +protocol_descriptor () { + return &tranzport_descriptor; +} +} + diff --git a/libs/surfaces/frontier/tranzport/tranzport_control_protocol.cc b/libs/surfaces/frontier/tranzport/tranzport_control_protocol.cc new file mode 100644 index 0000000000..b0c03fb71e --- /dev/null +++ b/libs/surfaces/frontier/tranzport/tranzport_control_protocol.cc @@ -0,0 +1,1950 @@ +/* + Copyright (C) 2006 Paul Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: tranzport_control_protocol.cc 1252 2006-12-29 19:13:18Z sampo $ +*/ + +/* Design notes: The tranzport is a unique device, basically a + 20 lcd gui with 22 shift keys and 8 blinking lights. + + As such it has several unique constraints. The device exerts flow control + by having a usb write fail. It is pointless to retry madly at that point, + the device is busy, and it's not going to become unbusy very quickly. + + So writes need to be either "mandatory" or "unreliable", and therein + lies the rub, as the kernel can also drop writes, and missing an + interrupt in userspace is also generally bad. + + It will be good one day, to break the gui, keyboard, and blinking light + components into separate parts, but for now, this remains monolithic. + + A more complex surface might have hundreds of lights and several displays. + + mike.taht@gmail.com + */ + +#define DEFAULT_USB_TIMEOUT 10 +#define MAX_RETRY 1 +#define MAX_TRANZPORT_INFLIGHT 4 +#define DEBUG_TRANZPORT 0 +#define HAVE_TRANZPORT_KERNEL_DRIVER 0 + +#include <iostream> +#include <algorithm> +#include <cmath> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <float.h> +#include <sys/time.h> +#include <errno.h> + +#include <pbd/pthread_utils.h> + +#include <ardour/route.h> +#include <ardour/audio_track.h> +#include <ardour/session.h> +#include <ardour/tempo.h> +#include <ardour/location.h> +#include <ardour/dB.h> + +#include "tranzport_control_protocol.h" + +using namespace ARDOUR; +using namespace std; +using namespace sigc; +using namespace PBD; + +#include "i18n.h" + +#include <pbd/abstract_ui.cc> + +BaseUI::RequestType LEDChange = BaseUI::new_request_type (); +BaseUI::RequestType Print = BaseUI::new_request_type (); +BaseUI::RequestType SetCurrentTrack = BaseUI::new_request_type (); + +/* Base Tranzport cmd strings */ + +static const uint8_t cmd_light_on[] = { 0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; +static const uint8_t cmd_light_off[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; +static const uint8_t cmd_write_screen[] = { 0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; + +static inline double +gain_to_slider_position (ARDOUR::gain_t g) +{ + if (g == 0) return 0; + return pow((6.0*log(g)/log(2.0)+192.0)/198.0, 8.0); + +} + +static inline ARDOUR::gain_t +slider_position_to_gain (double pos) +{ + /* XXX Marcus writes: this doesn't seem right to me. but i don't have a better answer ... */ + if (pos == 0.0) return 0; + return pow (2.0,(sqrt(sqrt(sqrt(pos)))*198.0-192.0)/6.0); +} + + +TranzportControlProtocol::TranzportControlProtocol (Session& s) + : ControlProtocol (s, X_("Tranzport")) +{ + /* tranzport controls one track at a time */ + + set_route_table_size (1); + timeout = 6000; // what is this for? + buttonmask = 0; + _datawheel = 0; + _device_status = STATUS_OFFLINE; + udev = 0; + current_track_id = 0; + last_where = max_frames; + wheel_mode = WheelTimeline; + wheel_shift_mode = WheelShiftGain; + wheel_increment = WheelIncrScreen; + bling_mode = BlingOff; + timerclear (&last_wheel_motion); + last_wheel_dir = 1; + last_track_gain = FLT_MAX; + display_mode = DisplayNormal; + gain_fraction = 0.0; + invalidate(); + screen_init(); + lights_init(); + print(0,0,"!!Welcome to Ardour!!"); + print(1,0,"!Peace through Music!"); +} + +void TranzportControlProtocol::light_validate (LightID light) +{ + lights_invalid[light] = 0; +} + +void TranzportControlProtocol::light_invalidate (LightID light) +{ + lights_invalid[light] = 1; +} + +void TranzportControlProtocol::lights_validate () +{ + memset (lights_invalid, 0, sizeof (lights_invalid)); +} + +void TranzportControlProtocol::lights_invalidate () +{ + memset (lights_invalid, 1, sizeof (lights_invalid)); +} + +void TranzportControlProtocol::lights_init() +{ + for (uint32_t i = 0; i < sizeof(lights_current)/sizeof(lights_current[0]); i++) { + lights_invalid[i] = lights_current[i] = + lights_pending[i] = lights_flash[i] = false; + } +} + + + +int +TranzportControlProtocol::lights_flush () +{ + if ( _device_status == STATUS_OFFLINE) { return (0); } + + // Figure out iterators one day soon + // for (LightID i = i.start(), i = i.end(); i++) { + // if (lights_pending[i] != lights_current[i] || lights_invalid[i]) { + // if (light_set(i, lights_pending[i])) { + // return i-1; + // } + // } + //} + if ((lights_pending[LightRecord] != lights_current[LightRecord]) || lights_invalid[LightRecord]) { + if (light_set(LightRecord,lights_pending[LightRecord])) { + return 1; + } + } + if ((lights_pending[LightTrackrec] != lights_current[LightTrackrec]) || lights_invalid[LightTrackrec]) { + if (light_set(LightTrackrec,lights_pending[LightTrackrec])) { + return 1; + } + } + + if ((lights_pending[LightTrackmute] != lights_current[LightTrackmute]) || lights_invalid[LightTrackmute]) { + if (light_set(LightTrackmute,lights_pending[LightTrackmute])) { + return 1; + } + } + + if ((lights_pending[LightTracksolo] != lights_current[LightTracksolo]) || lights_invalid[LightTracksolo]) { + if (light_set(LightTracksolo,lights_pending[LightTracksolo])) { + return 1; + } + } + if ((lights_pending[LightAnysolo] != lights_current[LightAnysolo]) || lights_invalid[LightAnysolo]) { + if (light_set(LightAnysolo,lights_pending[LightAnysolo])) { + return 1; + } + } + if ((lights_pending[LightLoop] != lights_current[LightLoop]) || lights_invalid[LightLoop]) { + if (light_set(LightLoop,lights_pending[LightLoop])) { + return 1; + } + } + if ((lights_pending[LightPunch] != lights_current[LightPunch]) || lights_invalid[LightPunch]) { + if (light_set(LightPunch,lights_pending[LightPunch])) { + return 1; + } + } + + return 0; +} + +// Screen specific commands + +void +TranzportControlProtocol::screen_clear () +{ + const char *blank = " "; + print(0,0,blank); + print(1,0,blank); +} + +void TranzportControlProtocol::screen_invalidate () +{ + for(int row = 0; row < 2; row++) { + for(int col = 0; col < 20; col++) { + screen_invalid[row][col] = true; + screen_current[row][col] = 0x7f; + screen_pending[row][col] = ' '; + // screen_flash[row][col] = ' '; + } + } + // memset (&screen_invalid, 1, sizeof(screen_invalid)); + // memset (&screen_current, 0x7F, sizeof (screen_current)); // fill cache with a character we otherwise never use +} + +void TranzportControlProtocol::screen_validate () +{ +} + +void TranzportControlProtocol::screen_init () +{ + screen_invalidate(); +} + +int +TranzportControlProtocol::screen_flush () +{ + int cell = 0, row, col_base, col, pending = 0; + if ( _device_status == STATUS_OFFLINE) { return (-1); } + + for (row = 0; row < 2 && pending == 0; row++) { + for (col_base = 0, col = 0; col < 20 && pending == 0; ) { + if ((screen_pending[row][col] != screen_current[row][col]) + || screen_invalid[row][col]) { + + /* something in this cell is different, so dump the cell to the device. */ + + uint8_t cmd[8]; + cmd[0] = 0x00; + cmd[1] = 0x01; + cmd[2] = cell; + cmd[3] = screen_pending[row][col_base]; + cmd[4] = screen_pending[row][col_base+1]; + cmd[5] = screen_pending[row][col_base+2]; + cmd[6] = screen_pending[row][col_base+3]; + cmd[7] = 0x00; + + if(write(cmd) != 0) { + /* try to update this cell on the next go-round */ +#if DEBUG_TRANZPORT > 4 + printf("usb screen update failed for some reason... why? \ncmd and data were %02x %02x %02x %02x %02x %02x %02x %02x\n", + cmd[0],cmd[1],cmd[2], cmd[3], cmd[4], cmd[5],cmd[6],cmd[7]); +#endif + pending += 1; + // Shouldn't need to do this + // screen_invalid[row][col_base] = screen_invalid[row][col_base+1] = + // screen_invalid[row][col_base+2] = screen_invalid[row][col_base+3] = true; + + } else { + /* successful write: copy to current cached display */ + screen_invalid[row][col_base] = screen_invalid[row][col_base+1] = + screen_invalid[row][col_base+2] = screen_invalid[row][col_base+3] = false; + memcpy (&screen_current[row][col_base], &screen_pending[row][col_base], 4); + } + + /* skip the rest of the 4 character cell since we wrote+copied it already */ + + col_base += 4; + col = col_base; + cell++; + + } else { + + col++; + + if (col && col % 4 == 0) { + cell++; + col_base += 4; + } + } + } + } + return pending; +} + + +// Tranzport specific + +void TranzportControlProtocol::invalidate() +{ + lcd_damage(); lights_invalidate(); screen_invalidate(); // one of these days lcds can be fine but screens not +} + +TranzportControlProtocol::~TranzportControlProtocol () +{ + set_active (false); +} + + +int +TranzportControlProtocol::set_active (bool yn) +{ + if (yn != _active) { + + if (yn) { + + if (open ()) { + return -1; + } + + if (pthread_create_and_store (X_("tranzport monitor"), &thread, 0, _monitor_work, this) == 0) { + _active = true; + } else { + return -1; + } + + } else { + cerr << "Begin tranzport shutdown\n"; + screen_clear (); + lcd_damage(); + lights_off (); + for(int x = 0; x < 10 && flush(); x++) { usleep(1000); } + pthread_cancel_one (thread); + cerr << "Tranzport Thread dead\n"; + close (); + _active = false; + cerr << "End tranzport shutdown\n"; + } + } + + return 0; +} + +void +TranzportControlProtocol::show_track_gain () +{ + if (route_table[0]) { + gain_t g = route_get_gain (0); + if ((g != last_track_gain) || lcd_isdamaged(0,9,8)) { + char buf[16]; + snprintf (buf, sizeof (buf), "%6.1fdB", coefficient_to_dB (route_get_effective_gain (0))); + print (0, 9, buf); + last_track_gain = g; + } + } else { + print (0, 9, " "); + } +} + +void +TranzportControlProtocol::normal_update () +{ + show_current_track (); + show_transport_time (); + show_track_gain (); + show_wheel_mode (); +} + +void +TranzportControlProtocol::next_display_mode () +{ + switch (display_mode) { + + case DisplayNormal: + enter_big_meter_mode(); + break; + + case DisplayBigMeter: + enter_normal_display_mode(); + break; + + case DisplayRecording: + enter_normal_display_mode(); + break; + + case DisplayRecordingMeter: + enter_big_meter_mode(); + break; + + case DisplayConfig: + case DisplayBling: + case DisplayBlingMeter: + enter_normal_display_mode(); + break; + } +} + +// FIXME, these 3 aren't done yet + +void +TranzportControlProtocol::enter_recording_mode () +{ + lcd_damage(); // excessive + screen_clear (); + lights_off (); + display_mode = DisplayRecording; +} + +void +TranzportControlProtocol::enter_bling_mode () +{ + lcd_damage(); + screen_clear (); + lights_off (); + display_mode = DisplayBling; +} + +void +TranzportControlProtocol::enter_config_mode () +{ + lcd_damage(); + screen_clear (); + lights_off (); + display_mode = DisplayConfig; +} + + +void +TranzportControlProtocol::enter_big_meter_mode () +{ + screen_clear (); + lcd_damage(); + lights_off (); + last_meter_fill = 0; + display_mode = DisplayBigMeter; +} + +void +TranzportControlProtocol::enter_normal_display_mode () +{ + screen_clear (); + lcd_damage(); + lights_off (); + display_mode = DisplayNormal; + // normal_update(); +} + + +float +log_meter (float db) +{ + float def = 0.0f; /* Meter deflection %age */ + + if (db < -70.0f) return 0.0f; + if (db > 6.0f) return 1.0f; + + if (db < -60.0f) { + def = (db + 70.0f) * 0.25f; + } else if (db < -50.0f) { + def = (db + 60.0f) * 0.5f + 2.5f; + } else if (db < -40.0f) { + def = (db + 50.0f) * 0.75f + 7.5f; + } else if (db < -30.0f) { + def = (db + 40.0f) * 1.5f + 15.0f; + } else if (db < -20.0f) { + def = (db + 30.0f) * 2.0f + 30.0f; + } else if (db < 6.0f) { + def = (db + 20.0f) * 2.5f + 50.0f; + } + + /* 115 is the deflection %age that would be + when db=6.0. this is an arbitrary + endpoint for our scaling. + */ + + return def/115.0f; +} + +void +TranzportControlProtocol::show_meter () +{ + // you only seem to get a route_table[0] on moving forward - bug elsewhere + if (route_table[0] == 0) { + // Principle of least surprise + print (0, 0, "No audio to meter!!!"); + print (1, 0, "Select another track"); + return; + } + + float level = route_get_peak_input_power (0, 0); + float fraction = log_meter (level); + + /* Someday add a peak bar*/ + + /* we draw using a choice of a sort of double colon-like character ("::") or a single, left-aligned ":". + the screen is 20 chars wide, so we can display 40 different levels. compute the level, + then figure out how many "::" to fill. if the answer is odd, make the last one a ":" + */ + + uint32_t fill = (uint32_t) floor (fraction * 40); + char buf[21]; + uint32_t i; + + if (fill == last_meter_fill) { + /* nothing to do */ + return; + } + + last_meter_fill = fill; + + bool add_single_level = (fill % 2 != 0); + fill /= 2; + + if (fraction > 0.98) { + light_on (LightAnysolo); + } + + /* add all full steps */ + + for (i = 0; i < fill; ++i) { + buf[i] = 0x07; /* tranzport special code for 4 quadrant LCD block */ + } + + /* add a possible half-step */ + + if (i < 20 && add_single_level) { + buf[i] = 0x03; /* tranzport special code for 2 left quadrant LCD block */ + ++i; + } + + /* fill rest with space */ + + for (; i < 20; ++i) { + buf[i] = ' '; + } + + /* print() requires this */ + + buf[21] = '\0'; + + print (0, 0, buf); + print (1, 0, buf); +} + +void +TranzportControlProtocol::show_bbt (nframes_t where) +{ + if ((where != last_where) || lcd_isdamaged(1,9,8)) { + char buf[16]; + BBT_Time bbt; + session->tempo_map().bbt_time (where, bbt); + sprintf (buf, "%03" PRIu32 "|%02" PRIu32 "|%04" PRIu32, bbt.bars,bbt.beats,bbt.ticks); + last_bars = bbt.bars; + last_beats = bbt.beats; + last_ticks = bbt.ticks; + last_where = where; + + if(last_ticks < 1960) { print (1, 9, buf); } // save a write so we can do leds + + // if displaymode is recordmode show beats but not yet + lights_pending[LightRecord] = false; + lights_pending[LightAnysolo] = false; + switch(last_beats) { + case 1: if(last_ticks < 500 || last_ticks > 1960) lights_pending[LightRecord] = true; break; + default: if(last_ticks < 250) lights_pending[LightAnysolo] = true; + } + + // update lights for tempo one day + // if (bbt_upper_info_label) { + // TempoMap::Metric m (session->tempo_map().metric_at (when)); + // sprintf (buf, "%-5.2f", m.tempo().beats_per_minute()); + // bbt_lower_info_label->set_text (buf); + // sprintf (buf, "%g|%g", m.meter().beats_per_bar(), m.meter().note_divisor()); + // bbt_upper_info_label->set_text (buf); + } + } + + +void +TranzportControlProtocol::show_transport_time () +{ + nframes_t where = session->transport_frame(); + show_bbt(where); +} + +void +TranzportControlProtocol::show_smpte (nframes_t where) +{ + if ((where != last_where) || lcd_isdamaged(1,9,10)) { + + char buf[5]; + SMPTE::Time smpte; + + session->smpte_time (where, smpte); + + if (smpte.negative) { + sprintf (buf, "-%02" PRIu32 ":", smpte.hours); + } else { + sprintf (buf, " %02" PRIu32 ":", smpte.hours); + } + print (1, 8, buf); + + sprintf (buf, "%02" PRIu32 ":", smpte.minutes); + print (1, 12, buf); + + sprintf (buf, "%02" PRIu32 ":", smpte.seconds); + print (1, 15, buf); + + sprintf (buf, "%02" PRIu32, smpte.frames); + print_noretry (1, 18, buf); + + last_where = where; + } +} + +void* +TranzportControlProtocol::_monitor_work (void* arg) +{ + return static_cast<TranzportControlProtocol*>(arg)->monitor_work (); +} + +// I note that these usb specific open, close, probe, read routines are basically +// pure boilerplate and could easily be abstracted elsewhere + +#if !HAVE_TRANZPORT_KERNEL_DRIVER + +bool +TranzportControlProtocol::probe () +{ + struct usb_bus *bus; + struct usb_device *dev; + + usb_init(); + usb_find_busses(); + usb_find_devices(); + + for (bus = usb_busses; bus; bus = bus->next) { + + for(dev = bus->devices; dev; dev = dev->next) { + if (dev->descriptor.idVendor == VENDORID && dev->descriptor.idProduct == PRODUCTID) { + return true; + } + } + } + + return false; +} + +int +TranzportControlProtocol::open () +{ + struct usb_bus *bus; + struct usb_device *dev; + + usb_init(); + usb_find_busses(); + usb_find_devices(); + + for (bus = usb_busses; bus; bus = bus->next) { + + for(dev = bus->devices; dev; dev = dev->next) { + if (dev->descriptor.idVendor != VENDORID) + continue; + if (dev->descriptor.idProduct != PRODUCTID) + continue; + return open_core (dev); + } + } + + error << _("Tranzport: no device detected") << endmsg; + return -1; +} + +int +TranzportControlProtocol::open_core (struct usb_device* dev) +{ + if (!(udev = usb_open (dev))) { + error << _("Tranzport: cannot open USB transport") << endmsg; + return -1; + } + + if (usb_claim_interface (udev, 0) < 0) { + error << _("Tranzport: cannot claim USB interface") << endmsg; + usb_close (udev); + udev = 0; + return -1; + } + + if (usb_set_configuration (udev, 1) < 0) { + cerr << _("Tranzport: cannot configure USB interface") << endmsg; + } + + return 0; +} + +int +TranzportControlProtocol::close () +{ + int ret = 0; + + if (udev == 0) { + return 0; + } + + if (usb_release_interface (udev, 0) < 0) { + error << _("Tranzport: cannot release interface") << endmsg; + ret = -1; + } + + if (usb_close (udev)) { + error << _("Tranzport: cannot close device") << endmsg; + udev = 0; + ret = 0; + } + + return ret; +} + +int TranzportControlProtocol::read(uint8_t *buf, uint32_t timeout_override) +{ + int val; + // Get smarter about handling usb errors soon. Like disconnect + // pthread_testcancel(); + val = usb_interrupt_read (udev, READ_ENDPOINT, (char *) buf, 8, 10); + // pthread_testcancel(); + return val; +} + + +int +TranzportControlProtocol::write_noretry (uint8_t* cmd, uint32_t timeout_override) +{ + int val; + if(inflight > MAX_TRANZPORT_INFLIGHT) { return (-1); } + val = usb_interrupt_write (udev, WRITE_ENDPOINT, (char*) cmd, 8, timeout_override ? timeout_override : timeout); + + if (val < 0) { +#if DEBUG_TRANZPORT + printf("usb_interrupt_write failed: %d\n", val); +#endif + return val; + } + + if (val != 8) { +#if DEBUG_TRANZPORT + printf("usb_interrupt_write failed: %d\n", val); +#endif + return -1; + } + ++inflight; + + return 0; + +} + +int +TranzportControlProtocol::write (uint8_t* cmd, uint32_t timeout_override) +{ +#if MAX_RETRY > 1 + int val; + int retry = 0; + if(inflight > MAX_TRANZPORT_INFLIGHT) { return (-1); } + + while((val = usb_interrupt_write (udev, WRITE_ENDPOINT, (char*) cmd, 8, timeout_override ? timeout_override : timeout))!=8 && retry++ < MAX_RETRY) { + printf("usb_interrupt_write failed, retrying: %d\n", val); + } + + if (retry == MAX_RETRY) { + printf("Too many retries on a tranzport write, aborting\n"); + } + + if (val < 0) { + printf("usb_interrupt_write failed: %d\n", val); + return val; + } + if (val != 8) { + printf("usb_interrupt_write failed: %d\n", val); + return -1; + } + ++inflight; + return 0; +#else + return (write_noretry(cmd,timeout_override)); +#endif + +} + +#else +#error Kernel API not defined yet for Tranzport +// Something like open(/dev/surface/tranzport/event) for reading and raw for writing) +#endif + +// We have a state "Unknown" - STOP USING SPACES FOR IT - switching to arrow character +// We have another state - no_retry. Misleading, as we still retry on the next pass +// I think it's pointless to keep no_retry and instead we should throttle writes +// We have an "displayed" screen +// We always draw into the pending screen, which could be any of several screens +// We have an active screen +// Print arg - we have +// setactive +// so someday I think we need a screen object. + +/* +screen_flash.clear(); +screen_flash.print(0,0,"Undone:"); // Someday pull the undo stack from somewhere +screen_flash.print(1,0,"Nextup:"); + +if(flash_messages && lcd.getactive() != screen_flash) lcd.setactive(screen_flash,2000); + +screen::setactive(screen_name,duration); // duration in ms +screen::getactive(); +*/ + + +int +TranzportControlProtocol::flush () +{ + int pending = 0; + if(!(pending = lights_flush())) { + pending = screen_flush(); + } + return pending; +} + +// doing these functions made me realize that screen_invalid should be lcd_isdamaged FIXME soon + +bool TranzportControlProtocol::lcd_damage() +{ + screen_invalidate(); + return true; +} + +bool TranzportControlProtocol::lcd_damage (int row, int col, int length) +{ + bool result = false; + int endcol = col+length-1; + if((endcol > 19)) { endcol = 19; } + if((row >= 0 && row < 2) && (col >=0 && col < 20)) { + for(int c = col; c < endcol; c++) { + screen_invalid[row][c] = true; + } + result = true; + } + return result; +} + +// Gotta switch to bitfields, this is collossally dumb +// Still working on the layering, arguably screen_invalid should be lcd_invalid + +bool TranzportControlProtocol::lcd_isdamaged () +{ + for(int r = 0; r < 2; r++) { + for(int c = 0; c < 20; c++) { + if(screen_invalid[r][c]) { +#if DEBUG_TRANZPORT > 5 + printf("row: %d,col: %d is damaged, should redraw it\n", r,c); +#endif + return true; + } + } + } + return false; +} + +bool TranzportControlProtocol::lcd_isdamaged (int row, int col, int length) +{ + bool result = 0; + int endcol = col+length; + if((endcol > 19)) { endcol = 19; } + if((row >= 0 && row < 2) && (col >=0 && col < 20)) { + for(int c = col; c < endcol; c++) { + if(screen_invalid[row][c]) { +#if DEBUG_TRANZPORT > 5 + printf("row: %d,col: %d is damaged, should redraw it\n", row,c); +#endif + return true; + } + } + } + return result; +} + +// lcd_clear would be a separate function for a smart display +// here it does nothing, but for the sake of completeness it should +// probably write the lcd, and while I'm on the topic it should probably +// take a row, col, length argument.... + +void +TranzportControlProtocol::lcd_clear () +{ + +} + +// These lcd commands are not universally used yet and may drop out of the api + +int +TranzportControlProtocol::lcd_flush () +{ + return 0; +} + +int +TranzportControlProtocol::lcd_write(uint8_t* cmd, uint32_t timeout_override) +{ + return write(cmd,timeout_override); +} + +void +TranzportControlProtocol::lcd_fill (uint8_t fill_char) +{ +} + +void +TranzportControlProtocol::lcd_print (int row, int col, const char* text) +{ + print(row,col,text); +} + +void TranzportControlProtocol::lcd_print_noretry (int row, int col, const char* text) +{ + print(row,col,text); +} + +// Lights are buffered + +void +TranzportControlProtocol::lights_on () +{ + lights_pending[LightRecord] = lights_pending[LightTrackrec] = + lights_pending[LightTrackmute] = lights_pending[LightTracksolo] = + lights_pending[LightAnysolo] = lights_pending[LightLoop] = + lights_pending[LightPunch] = true; +} + +void +TranzportControlProtocol::lights_off () +{ + lights_pending[LightRecord] = lights_pending[LightTrackrec] = + lights_pending[LightTrackmute] = lights_pending[LightTracksolo] = + lights_pending[LightAnysolo] = lights_pending[LightLoop] = + lights_pending[LightPunch] = false; +} + +int +TranzportControlProtocol::light_on (LightID light) +{ + lights_pending[light] = true; + return 0; +} + +int +TranzportControlProtocol::light_off (LightID light) +{ + lights_pending[light] = false; + return 0; +} + +int +TranzportControlProtocol::light_set (LightID light, bool offon) +{ + uint8_t cmd[8]; + cmd[0] = 0x00; cmd[1] = 0x00; cmd[2] = light; cmd[3] = offon; + cmd[4] = 0x00; cmd[5] = 0x00; cmd[6] = 0x00; cmd[7] = 0x00; + + if (write (cmd) == 0) { + lights_current[light] = offon; + lights_invalid[light] = false; + return 0; + } else { + return -1; + } +} + +int TranzportControlProtocol::rtpriority_set(int priority) +{ + struct sched_param rtparam; + int err; + // preallocate and memlock some stack with memlock? + char *a = (char*) alloca(4096*2); a[0] = 'a'; a[4096] = 'b'; + memset (&rtparam, 0, sizeof (rtparam)); + rtparam.sched_priority = priority; /* XXX should be relative to audio (JACK) thread */ + // Note - try SCHED_RR with a low limit + // - we don't care if we can't write everything this ms + // and it will help if we lose the device + if ((err = pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam)) != 0) { + PBD::info << string_compose (_("%1: thread not running with realtime scheduling (%2)"), name(), strerror (errno)) << endmsg; + return 1; + } + return 0; +} + +// Running with realtime privs is bad when you have problems + +int TranzportControlProtocol::rtpriority_unset(int priority) +{ + struct sched_param rtparam; + int err; + memset (&rtparam, 0, sizeof (rtparam)); + rtparam.sched_priority = priority; + if ((err = pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam)) != 0) { + PBD::info << string_compose (_("%1: can't stop realtime scheduling (%2)"), name(), strerror (errno)) << endmsg; + return 1; + } + PBD::info << string_compose (_("%1: realtime scheduling stopped (%2)"), name(), strerror (errno)) << endmsg; + return 0; +} + +// Slowly breaking this into where I can make usb processing it's own thread. + +void* +TranzportControlProtocol::monitor_work () +{ + uint8_t buf[8]; + int val = 0, pending = 0; + bool first_time = true; + uint8_t offline = 0; + + + PBD::ThreadCreated (pthread_self(), X_("Tranzport")); + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, 0); + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0); + next_track (); + rtpriority_set(); + inflight=0; + flush(); + + while (true) { + + /* bInterval for this beastie is 10ms */ + + if (_device_status == STATUS_OFFLINE) { + first_time = true; + if(offline++ == 1) { + cerr << "Transport has gone offline\n"; + } + } else { + offline = 0; // hate writing this + } + + val = read(buf); + + if (val == 8) { + process (buf); + } + +#if DEBUG_TRANZPORT > 2 + if(inflight > 1) printf("Inflight: %d\n", inflight); +#endif + + + if (_device_status != STATUS_OFFLINE) { + if (first_time) { + invalidate(); + lcd_clear (); + lights_off (); + first_time = false; + offline = 0; + pending = 3; // Give some time for the device to recover + } + /* update whatever needs updating */ + update_state (); + + /* still struggling with a good means of exerting flow control */ + // pending = flush(); + + if(pending == 0) { + pending = flush(); + } else { + if(inflight > 0) { + pending = --inflight; // we just did a whole bunch of writes so wait + } else { + pending = 0; + } + } + // pending = 0; + } + } + + return (void*) 0; +} + +int TranzportControlProtocol::lights_show_recording() +{ + // FIXME, flash recording light when recording and transport is moving + return lights_show_normal(); +} + +// gotta do bling next! + +int TranzportControlProtocol::lights_show_bling() +{ + switch (bling_mode) { + case BlingOff: break; + case BlingKit: break; // rotate rec/mute/solo/any solo back and forth + case BlingRotating: break; // switch between lights + case BlingPairs: break; // Show pairs of lights + case BlingRows: break; // light each row in sequence + case BlingFlashAll: break; // Flash everything randomly + } + return 0; +} + +int TranzportControlProtocol::lights_show_normal() +{ + /* Track only */ + + if (route_table[0]) { + boost::shared_ptr<AudioTrack> at = boost::dynamic_pointer_cast<AudioTrack> (route_table[0]); + lights_pending[LightTrackrec] = at && at->record_enabled(); + lights_pending[LightTrackmute] = route_get_muted(0); + lights_pending[LightTracksolo] = route_get_soloed(0); + } else { + lights_pending[LightTrackrec] = false; + lights_pending[LightTracksolo] = false; + lights_pending[LightTrackmute] = false; + } + + /* Global settings */ + + lights_pending[LightLoop] = session->get_play_loop(); + lights_pending[LightPunch] = Config->get_punch_in() || Config->get_punch_out(); + lights_pending[LightRecord] = session->get_record_enabled(); + lights_pending[LightAnysolo] = session->soloing(); + + return 0; +} + +int TranzportControlProtocol::lights_show_tempo() +{ + // someday soon fiddle with the lights based on the tempo + return lights_show_normal(); +} + +int +TranzportControlProtocol::update_state () +{ + /* do the text and light updates */ + + switch (display_mode) { + case DisplayBigMeter: + lights_show_tempo(); + show_meter (); + break; + + case DisplayNormal: + lights_show_normal(); + normal_update (); + break; + + case DisplayConfig: + break; + + case DisplayRecording: + lights_show_recording(); + normal_update(); + break; + + case DisplayRecordingMeter: + lights_show_recording(); + show_meter(); + break; + + case DisplayBling: + lights_show_bling(); + normal_update(); + break; + + case DisplayBlingMeter: + lights_show_bling(); + show_meter(); + break; + } + return 0; + +} + +#define TRANZPORT_BUTTON_HANDLER(callback, button_arg) if (button_changes & button_arg) { \ + if (buttonmask & button_arg) { \ + callback##_press (buttonmask&ButtonShift); } else { callback##_release (buttonmask&ButtonShift); } } + +int +TranzportControlProtocol::process (uint8_t* buf) +{ + // printf("read: %02x %02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + + uint32_t this_button_mask; + uint32_t button_changes; + + _device_status = buf[1]; + + this_button_mask = 0; + this_button_mask |= buf[2] << 24; + this_button_mask |= buf[3] << 16; + this_button_mask |= buf[4] << 8; + this_button_mask |= buf[5]; + _datawheel = buf[6]; + + button_changes = (this_button_mask ^ buttonmask); + buttonmask = this_button_mask; + + if (_datawheel) { + datawheel (); + } + + // SHIFT + STOP + PLAY for bling mode? + // if (button_changes & ButtonPlay & ButtonStop) { + // bling_mode_toggle(); + // } or something like that + + TRANZPORT_BUTTON_HANDLER(button_event_battery,ButtonBattery); + TRANZPORT_BUTTON_HANDLER(button_event_backlight,ButtonBacklight); + TRANZPORT_BUTTON_HANDLER(button_event_trackleft,ButtonTrackLeft); + TRANZPORT_BUTTON_HANDLER(button_event_trackright,ButtonTrackRight); + TRANZPORT_BUTTON_HANDLER(button_event_trackrec,ButtonTrackRec); + TRANZPORT_BUTTON_HANDLER(button_event_trackmute,ButtonTrackMute); + TRANZPORT_BUTTON_HANDLER(button_event_tracksolo,ButtonTrackSolo); + TRANZPORT_BUTTON_HANDLER(button_event_undo,ButtonUndo); + TRANZPORT_BUTTON_HANDLER(button_event_in,ButtonIn); + TRANZPORT_BUTTON_HANDLER(button_event_out,ButtonOut); + TRANZPORT_BUTTON_HANDLER(button_event_punch,ButtonPunch); + TRANZPORT_BUTTON_HANDLER(button_event_loop,ButtonLoop); + TRANZPORT_BUTTON_HANDLER(button_event_prev,ButtonPrev); + TRANZPORT_BUTTON_HANDLER(button_event_add,ButtonAdd); + TRANZPORT_BUTTON_HANDLER(button_event_next,ButtonNext); + TRANZPORT_BUTTON_HANDLER(button_event_rewind,ButtonRewind); + TRANZPORT_BUTTON_HANDLER(button_event_fastforward,ButtonFastForward); + TRANZPORT_BUTTON_HANDLER(button_event_stop,ButtonStop); + TRANZPORT_BUTTON_HANDLER(button_event_play,ButtonPlay); + TRANZPORT_BUTTON_HANDLER(button_event_record,ButtonRecord); + return 0; +} + +void +TranzportControlProtocol::show_current_track () +{ + char pad[11]; + char *v; + int len; + if (route_table[0] == 0) { + print (0, 0, "----------"); + last_track_gain = FLT_MAX; + } else { + strcpy(pad," "); + v = (char *)route_get_name (0).substr (0, 10).c_str(); + if((len = strlen(v)) > 0) { + strncpy(pad,(char *)v,len); + } + print (0, 0, pad); + } +} + +void +TranzportControlProtocol::button_event_battery_press (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_battery_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_backlight_press (bool shifted) +{ +#if DEBUG_TRANZPORT + printf("backlight pressed\n"); +#endif +} + +void +TranzportControlProtocol::button_event_backlight_release (bool shifted) +{ +#if DEBUG_TRANZPORT + printf("backlight released\n\n"); +#endif + if (shifted) { + lcd_damage(); + lcd_clear(); + last_where += 1; /* force time redisplay */ + last_track_gain = FLT_MAX; + normal_update(); // redraw_screen(); + } +} + +void +TranzportControlProtocol::button_event_trackleft_press (bool shifted) +{ + prev_track (); +} + +void +TranzportControlProtocol::button_event_trackleft_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_trackright_press (bool shifted) +{ + next_track (); +} + +void +TranzportControlProtocol::button_event_trackright_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_trackrec_press (bool shifted) +{ + if (shifted) { + toggle_all_rec_enables (); + } else { + route_set_rec_enable (0, !route_get_rec_enable (0)); + } +} + +void +TranzportControlProtocol::button_event_trackrec_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_trackmute_press (bool shifted) +{ + if (shifted) { + // Mute ALL? Something useful when a phone call comes in. Mute master? + } else { + route_set_muted (0, !route_get_muted (0)); + } +} + +void +TranzportControlProtocol::button_event_trackmute_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_tracksolo_press (bool shifted) +{ +#if DEBUG_TRANZPORT + printf("solo pressed\n"); +#endif + if (display_mode == DisplayBigMeter) { + light_off (LightAnysolo); + return; + } + + if (shifted) { + session->set_all_solo (!session->soloing()); + } else { + route_set_soloed (0, !route_get_soloed (0)); + } +} + +void +TranzportControlProtocol::button_event_tracksolo_release (bool shifted) +{ +#if DEBUG_TRANZPORT + printf("solo released\n"); +#endif +} + +void +TranzportControlProtocol::button_event_undo_press (bool shifted) +{ + if (shifted) { + redo (); // someday flash the screen with what was redone + } else { + undo (); // someday flash the screen with what was undone + } +} + +void +TranzportControlProtocol::button_event_undo_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_in_press (bool shifted) +{ + if (shifted) { + toggle_punch_in (); + } else { + ControlProtocol::ZoomIn (); /* EMIT SIGNAL */ + } +} + +void +TranzportControlProtocol::button_event_in_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_out_press (bool shifted) +{ + if (shifted) { + toggle_punch_out (); + } else { + ControlProtocol::ZoomOut (); /* EMIT SIGNAL */ + } +} + +void +TranzportControlProtocol::button_event_out_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_punch_press (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_punch_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_loop_press (bool shifted) +{ + if (shifted) { + next_wheel_shift_mode (); + } else { + loop_toggle (); + } +} + +void +TranzportControlProtocol::button_event_loop_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_prev_press (bool shifted) +{ + if (shifted) { + ControlProtocol::ZoomToSession (); /* EMIT SIGNAL */ + } else { + prev_marker (); + } +} + +void +TranzportControlProtocol::button_event_prev_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_add_press (bool shifted) +{ + add_marker (); +} + +void +TranzportControlProtocol::button_event_add_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_next_press (bool shifted) +{ + if (shifted) { + next_wheel_mode (); + } else { + next_marker (); + } +} + +void +TranzportControlProtocol::button_event_next_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_rewind_press (bool shifted) +{ + if (shifted) { + goto_start (); + } else { + rewind (); + } +} + +void +TranzportControlProtocol::button_event_rewind_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_fastforward_press (bool shifted) +{ + if (shifted) { + goto_end (); + } else { + ffwd (); + } +} + +void +TranzportControlProtocol::button_event_fastforward_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_stop_press (bool shifted) +{ + if (shifted) { + next_display_mode (); + } else { + transport_stop (); + } +} + +void +TranzportControlProtocol::button_event_stop_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_play_press (bool shifted) +{ + if (shifted) { + set_transport_speed (1.0f); + } else { + transport_play (); + } +} + +void +TranzportControlProtocol::button_event_play_release (bool shifted) +{ +} + +void +TranzportControlProtocol::button_event_record_press (bool shifted) +{ + if (shifted) { + save_state (); + } else { + rec_enable_toggle (); + } +} + +void +TranzportControlProtocol::button_event_record_release (bool shifted) +{ +} + +void button_event_mute (bool pressed, bool shifted) +{ + static int was_pressed = 0; + // if(pressed) { } +} + +void +TranzportControlProtocol::datawheel () +{ + if ((buttonmask & ButtonTrackRight) || (buttonmask & ButtonTrackLeft)) { + + /* track scrolling */ + + if (_datawheel < WheelDirectionThreshold) { + next_track (); + } else { + prev_track (); + } + + timerclear (&last_wheel_motion); + + } else if ((buttonmask & ButtonPrev) || (buttonmask & ButtonNext)) { + + if (_datawheel < WheelDirectionThreshold) { + next_marker (); + } else { + prev_marker (); + } + + timerclear (&last_wheel_motion); + + } else if (buttonmask & ButtonShift) { + + /* parameter control */ + + if (route_table[0]) { + switch (wheel_shift_mode) { + case WheelShiftGain: + if (_datawheel < WheelDirectionThreshold) { + step_gain_up (); + } else { + step_gain_down (); + } + break; + case WheelShiftPan: + if (_datawheel < WheelDirectionThreshold) { + step_pan_right (); + } else { + step_pan_left (); + } + break; + + case WheelShiftMarker: + break; + + case WheelShiftMaster: + break; + + } + } + + timerclear (&last_wheel_motion); + + } else { + + switch (wheel_mode) { + case WheelTimeline: + scroll (); + break; + + case WheelScrub: + scrub (); + break; + + case WheelShuttle: + shuttle (); + break; + } + } +} + +void +TranzportControlProtocol::scroll () +{ + float m = 1.0; + if (_datawheel < WheelDirectionThreshold) { + m = 1.0; + } else { + m = -1.0; + } + switch(wheel_increment) { + case WheelIncrScreen: ScrollTimeline (0.2*m); break; + default: break; // other modes unimplemented as yet + } +} + +void +TranzportControlProtocol::scrub () +{ + float speed; + struct timeval now; + struct timeval delta; + int dir; + + gettimeofday (&now, 0); + + if (_datawheel < WheelDirectionThreshold) { + dir = 1; + } else { + dir = -1; + } + + if (dir != last_wheel_dir) { + /* changed direction, start over */ + speed = 0.1f; + } else { + if (timerisset (&last_wheel_motion)) { + + timersub (&now, &last_wheel_motion, &delta); + + /* 10 clicks per second => speed == 1.0 */ + + speed = 100000.0f / (delta.tv_sec * 1000000 + delta.tv_usec); + + } else { + + /* start at half-speed and see where we go from there */ + + speed = 0.5f; + } + } + + last_wheel_motion = now; + last_wheel_dir = dir; + + set_transport_speed (speed * dir); +} + +void +TranzportControlProtocol::config () +{ + // FIXME +} + +void +TranzportControlProtocol::shuttle () +{ + if (_datawheel < WheelDirectionThreshold) { + if (session->transport_speed() < 0) { + session->request_transport_speed (1.0); + } else { + session->request_transport_speed (session->transport_speed() + 0.1); + } + } else { + if (session->transport_speed() > 0) { + session->request_transport_speed (-1.0); + } else { + session->request_transport_speed (session->transport_speed() - 0.1); + } + } +} + +void +TranzportControlProtocol::step_gain_up () +{ + if (buttonmask & ButtonStop) { + gain_fraction += 0.001; + } else { + gain_fraction += 0.01; + } + + if (gain_fraction > 2.0) { + gain_fraction = 2.0; + } + + route_set_gain (0, slider_position_to_gain (gain_fraction)); +} + +void +TranzportControlProtocol::step_gain_down () +{ + if (buttonmask & ButtonStop) { + gain_fraction -= 0.001; + } else { + gain_fraction -= 0.01; + } + + if (gain_fraction < 0.0) { + gain_fraction = 0.0; + } + + route_set_gain (0, slider_position_to_gain (gain_fraction)); +} + +void +TranzportControlProtocol::step_pan_right () +{ +} + +void +TranzportControlProtocol::step_pan_left () +{ +} + +void +TranzportControlProtocol::next_wheel_shift_mode () +{ + switch (wheel_shift_mode) { + case WheelShiftGain: + wheel_shift_mode = WheelShiftPan; + break; + case WheelShiftPan: + wheel_shift_mode = WheelShiftMaster; + break; + case WheelShiftMaster: + wheel_shift_mode = WheelShiftGain; + break; + case WheelShiftMarker: // Not done yet, disabled + wheel_shift_mode = WheelShiftGain; + break; + } + + show_wheel_mode (); +} + +void +TranzportControlProtocol::next_wheel_mode () +{ + switch (wheel_mode) { + case WheelTimeline: + wheel_mode = WheelScrub; + break; + case WheelScrub: + wheel_mode = WheelShuttle; + break; + case WheelShuttle: + wheel_mode = WheelTimeline; + } + + show_wheel_mode (); +} + +void +TranzportControlProtocol::next_track () +{ + ControlProtocol::next_track (current_track_id); + gain_fraction = gain_to_slider_position (route_get_effective_gain (0)); +} + +void +TranzportControlProtocol::prev_track () +{ + ControlProtocol::prev_track (current_track_id); + gain_fraction = gain_to_slider_position (route_get_effective_gain (0)); +} + +void +TranzportControlProtocol::show_wheel_mode () +{ + string text; + + switch (wheel_mode) { + case WheelTimeline: + text = "Time"; + break; + case WheelScrub: + text = "Scrb"; + break; + case WheelShuttle: + text = "Shtl"; + break; + } + + switch (wheel_shift_mode) { + case WheelShiftGain: + text += ":Gain"; + break; + + case WheelShiftPan: + text += ":Pan "; + break; + + case WheelShiftMaster: + text += ":Mstr"; + break; + + case WheelShiftMarker: + text += ":Mrkr"; + break; + } + + print (1, 0, text.c_str()); +} + +// Was going to keep state around saying to retry or not +// haven't got to it yet, still not sure it's a good idea + +void +TranzportControlProtocol::print (int row, int col, const char *text) { + print_noretry(row,col,text); +} + +void +TranzportControlProtocol::print_noretry (int row, int col, const char *text) +{ + int cell; + uint32_t left = strlen (text); + char tmp[5]; + int base_col; + + if (row < 0 || row > 1) { + return; + } + + if (col < 0 || col > 19) { + return; + } + + while (left) { + + if (col >= 0 && col < 4) { + cell = 0; + base_col = 0; + } else if (col >= 4 && col < 8) { + cell = 1; + base_col = 4; + } else if (col >= 8 && col < 12) { + cell = 2; + base_col = 8; + } else if (col >= 12 && col < 16) { + cell = 3; + base_col = 12; + } else if (col >= 16 && col < 20) { + cell = 4; + base_col = 16; + } else { + return; + } + + int offset = col % 4; + + /* copy current cell contents into tmp */ + + memcpy (tmp, &screen_pending[row][base_col], 4); + + /* overwrite with new text */ + + uint32_t tocopy = min ((4U - offset), left); + + memcpy (tmp+offset, text, tocopy); + + /* copy it back to pending */ + + memcpy (&screen_pending[row][base_col], tmp, 4); + + text += tocopy; + left -= tocopy; + col += tocopy; + } +} + +XMLNode& +TranzportControlProtocol::get_state () +{ + XMLNode* node = new XMLNode (X_("Protocol")); + node->add_property (X_("name"), _name); + return *node; +} + +int +TranzportControlProtocol::set_state (const XMLNode& node) +{ + return 0; +} + +int +TranzportControlProtocol::save (char *name) +{ + // Presently unimplemented + return 0; +} + +int +TranzportControlProtocol::load (char *name) +{ + // Presently unimplemented + return 0; +} diff --git a/libs/surfaces/frontier/tranzport/tranzport_control_protocol.h b/libs/surfaces/frontier/tranzport/tranzport_control_protocol.h new file mode 100644 index 0000000000..f13e4a3a44 --- /dev/null +++ b/libs/surfaces/frontier/tranzport/tranzport_control_protocol.h @@ -0,0 +1,320 @@ + +#ifndef ardour_tranzport_control_protocol_h +#define ardour_tranzport_control_protocol_h + +#include <vector> + +#include <sys/time.h> +#include <pthread.h> +#include <usb.h> + +#include <glibmm/thread.h> + +#include <ardour/types.h> + +#include <control_protocol/control_protocol.h> + +class TranzportControlProtocol : public ARDOUR::ControlProtocol +{ + public: + TranzportControlProtocol (ARDOUR::Session&); + virtual ~TranzportControlProtocol(); + + int set_active (bool yn); + + static bool probe (); + + XMLNode& get_state (); + int set_state (const XMLNode&); + + private: + static const int VENDORID = 0x165b; + static const int PRODUCTID = 0x8101; + static const int READ_ENDPOINT = 0x81; + static const int WRITE_ENDPOINT = 0x02; + const static int STATUS_OFFLINE = 0xff; + const static int STATUS_ONLINE = 0x01; + const static uint8_t WheelDirectionThreshold = 0x3f; + + enum LightID { + LightRecord = 0, + LightTrackrec, + LightTrackmute, + LightTracksolo, + LightAnysolo, + LightLoop, + LightPunch + }; + + enum ButtonID { + ButtonBattery = 0x00004000, + ButtonBacklight = 0x00008000, + ButtonTrackLeft = 0x04000000, + ButtonTrackRight = 0x40000000, + ButtonTrackRec = 0x00040000, + ButtonTrackMute = 0x00400000, + ButtonTrackSolo = 0x00000400, + ButtonUndo = 0x80000000, + ButtonIn = 0x02000000, + ButtonOut = 0x20000000, + ButtonPunch = 0x00800000, + ButtonLoop = 0x00080000, + ButtonPrev = 0x00020000, + ButtonAdd = 0x00200000, + ButtonNext = 0x00000200, + ButtonRewind = 0x01000000, + ButtonFastForward = 0x10000000, + ButtonStop = 0x00010000, + ButtonPlay = 0x00100000, + ButtonRecord = 0x00000100, + ButtonShift = 0x08000000 + }; + + enum WheelShiftMode { + WheelShiftGain, + WheelShiftPan, + WheelShiftMaster, + WheelShiftMarker + }; + + enum WheelMode { + WheelTimeline, + WheelScrub, + WheelShuttle + }; + + // FIXME - look at gtk2_ardour for snap settings + + enum WheelIncrement { + WheelIncrSlave, + WheelIncrScreen, + WheelIncrSample, + WheelIncrBeat, + WheelIncrBar, + WheelIncrSecond, + WheelIncrMinute + }; + + enum DisplayMode { + DisplayNormal, + DisplayRecording, + DisplayRecordingMeter, + DisplayBigMeter, + DisplayConfig, + DisplayBling, + DisplayBlingMeter + }; + + enum BlingMode { + BlingOff, + BlingKit, + BlingRotating, + BlingPairs, + BlingRows, + BlingFlashAll + }; + + pthread_t thread; + uint32_t buttonmask; + uint32_t timeout; + uint32_t inflight; + uint8_t _datawheel; + uint8_t _device_status; + uint32_t current_track_id; + WheelMode wheel_mode; + WheelShiftMode wheel_shift_mode; + DisplayMode display_mode; + BlingMode bling_mode; + WheelIncrement wheel_increment; + usb_dev_handle* udev; + + ARDOUR::gain_t gain_fraction; + + Glib::Mutex update_lock; + + bool screen_invalid[2][20]; + char screen_current[2][20]; + char screen_pending[2][20]; + char screen_flash[2][20]; + + bool lights_invalid[7]; + bool lights_current[7]; + bool lights_pending[7]; + bool lights_flash[7]; + + uint32_t last_bars; + uint32_t last_beats; + uint32_t last_ticks; + + bool last_negative; + uint32_t last_hrs; + uint32_t last_mins; + uint32_t last_secs; + uint32_t last_frames; + nframes_t last_where; + ARDOUR::gain_t last_track_gain; + uint32_t last_meter_fill; + struct timeval last_wheel_motion; + int last_wheel_dir; + + Glib::Mutex io_lock; + + int open (); + int read (uint8_t *buf,uint32_t timeout_override = 0); + int write (uint8_t* cmd, uint32_t timeout_override = 0); + int write_noretry (uint8_t* cmd, uint32_t timeout_override = 0); + int close (); + int save(char *name = "default"); + int load(char *name = "default"); + void print (int row, int col, const char* text); + void print_noretry (int row, int col, const char* text); + + int rtpriority_set(int priority = 52); + int rtpriority_unset(int priority = 0); + + int open_core (struct usb_device*); + + static void* _monitor_work (void* arg); + void* monitor_work (); + + int process (uint8_t *); + int update_state(); + void invalidate(); + int flush(); + // bool isuptodate(); // think on this. It seems futile to update more than 30/sec + + // A screen is a cache of what should be on the lcd + + void screen_init(); + void screen_validate(); + void screen_invalidate(); + int screen_flush(); + void screen_clear(); + // bool screen_isuptodate(); // think on this - + + // Commands to write to the lcd + + int lcd_init(); + bool lcd_damage(); + bool lcd_isdamaged(); + + bool lcd_damage(int row, int col = 0, int length = 20); + bool lcd_isdamaged(int row, int col = 0, int length = 20); + + int lcd_flush(); + int lcd_write(uint8_t* cmd, uint32_t timeout_override = 0); // pedantic alias for write + void lcd_fill (uint8_t fill_char); + void lcd_clear (); + void lcd_print (int row, int col, const char* text); + void lcd_print_noretry (int row, int col, const char* text); + + // Commands to write to the lights + // FIXME - on some devices lights can have intensity and colors + + void lights_init(); + void lights_validate(); + void lights_invalidate(); + void light_validate(LightID light); + void light_invalidate(LightID light); + int lights_flush(); + int lights_write(uint8_t* cmd,uint32_t timeout_override = 0); // pedantic alias to write + + // a cache of what should be lit + + void lights_off (); + void lights_on (); + int light_set(LightID, bool offon = true); + int light_on (LightID); + int light_off (LightID); + + // some modes for the lights, should probably be renamed + + int lights_show_normal(); + int lights_show_recording(); + int lights_show_tempo(); + int lights_show_bling(); + + void enter_big_meter_mode (); + void enter_normal_display_mode (); + void enter_config_mode(); + void enter_recording_mode(); + void enter_bling_mode(); + + void next_display_mode (); + void normal_update (); + + void show_current_track (); + void show_track_gain (); + void show_transport_time (); + void show_bbt (nframes_t where); + void show_smpte (nframes_t where); + void show_wheel_mode (); + void show_gain (); + void show_pan (); + void show_meter (); + + void datawheel (); + void scrub (); + void scroll (); + void shuttle (); + void config (); + + void next_wheel_mode (); + void next_wheel_shift_mode (); + + void set_current_track (ARDOUR::Route*); + void next_track (); + void prev_track (); + void step_gain_up (); + void step_gain_down (); + void step_pan_right (); + void step_pan_left (); + + + void button_event_battery_press (bool shifted); + void button_event_battery_release (bool shifted); + void button_event_backlight_press (bool shifted); + void button_event_backlight_release (bool shifted); + void button_event_trackleft_press (bool shifted); + void button_event_trackleft_release (bool shifted); + void button_event_trackright_press (bool shifted); + void button_event_trackright_release (bool shifted); + void button_event_trackrec_press (bool shifted); + void button_event_trackrec_release (bool shifted); + void button_event_trackmute_press (bool shifted); + void button_event_trackmute_release (bool shifted); + void button_event_tracksolo_press (bool shifted); + void button_event_tracksolo_release (bool shifted); + void button_event_undo_press (bool shifted); + void button_event_undo_release (bool shifted); + void button_event_in_press (bool shifted); + void button_event_in_release (bool shifted); + void button_event_out_press (bool shifted); + void button_event_out_release (bool shifted); + void button_event_punch_press (bool shifted); + void button_event_punch_release (bool shifted); + void button_event_loop_press (bool shifted); + void button_event_loop_release (bool shifted); + void button_event_prev_press (bool shifted); + void button_event_prev_release (bool shifted); + void button_event_add_press (bool shifted); + void button_event_add_release (bool shifted); + void button_event_next_press (bool shifted); + void button_event_next_release (bool shifted); + void button_event_rewind_press (bool shifted); + void button_event_rewind_release (bool shifted); + void button_event_fastforward_press (bool shifted); + void button_event_fastforward_release (bool shifted); + void button_event_stop_press (bool shifted); + void button_event_stop_release (bool shifted); + void button_event_play_press (bool shifted); + void button_event_play_release (bool shifted); + void button_event_record_press (bool shifted); + void button_event_record_release (bool shifted); + + // new api + void button_event_mute (bool pressed, bool shifted); +}; + + +#endif // ardour_tranzport_control_protocol_h |