Joystick Ports

This is part of the UCSD Music 176
project, spring 2000.

Joysticks have many different designs, but all audio cards with joystick ports are at least able to deal with the original, analog joystick. You can use this to measure 4 logic values and 4 variable resistances (0-100K ohms.) See the excellent description in https://www.hut.fi/Misc/Electronics/docs/joystick/pc_joystick.html.

The hard part turns out to be getting linux to agree that you have a joystick. I've tried this with opl3, sblive, and es1370 devices and you have to do different, arbitrary-sounding things in each separate case.

In all cases, you have to make a finite resistance appear between at least the "X1" and "Y1" pins and ground; if you don't you get "device or resource busy" errors which you try the "insmod joy-analog" step below.

For OPL3, I put the lines,

options opl3 io=0x388

options opl3sa2 mss_io=0x530 irq=5 dma=0 dma2=1 mpu_io=0x388 io=0x370

in /etc/conf.modules, and typed

modprobe opl3

modprobe opl3sa2

insmod joystick

insmod joy-analog

... and it worked. For emu10k1, the appropriate /etc/conf.modules entry seems to be:

options emu10k1 joystick=0x200

and then,

modprobe emu10k1

insmod joystick

insmod joy-analog

(however, I have one machine this has never worked on.) For the es1370 (for instance the ensoniq AudioPCI), the magic line in conf.modules is

options es1370 joystick=1

Whatever your hardware, the next step is to compile and run the following program (you'll probably have to fool with the include paths):

#include "stdio.h"
#include "fcntl.h"
#include "/usr/src/linux\/include/linux/joystick.h"

int main(int argc, char **argv)
{
    struct js_event e;
    int x;
    int fd = open ("/dev/js0", O_RDONLY);
    if (fd < 0)
    {
        fprintf(stderr, "couldn't open joystick\n");
        exit(1);
    }
    while ((x = read (fd, &e, sizeof(struct js_event))) >=
        sizeof(struct js_event))
    {
        printf("time %10d type %10d n %10d value %10d\n",
            e.time, e.type, e.number, e.value);
    }
}

... and presto, your joystick appears! Once you've got this far, make a Pd extern as follows:

#include "m_imp.h"
#include "unistd.h"
#include "errno.h"
#include "string.h"
#include "fcntl.h"
#include "/usr/src/linux/include/linux/joystick.h"

typedef struct joystick
{
    t_object x_ob;
    int x_fd;
} t_joystick;

void joystick_poll(t_joystick *x)
{
    struct js_event e;
    if (x->x_fd >= 0)
    {
        int r = read (x->x_fd, &e, sizeof(e));
        if (r == sizeof(e))
        {
            t_atom at[4];
            SETFLOAT(at, e.type);
            SETFLOAT(at + 1, e.number);
            SETFLOAT(at + 2, e.value);
            SETFLOAT(at + 3, e.time);
            outlet_list(x->x_ob.ob_outlet, 0, 4, at);
        }
        else post("joystick read returned %d", r);
    }
}

void joystick_float(t_joystick *x, t_floatarg f)
{
    if (f != 0)
    {
        if (x->x_fd >= 0)
            return;
        x->x_fd = open ("/dev/js0", O_RDONLY);
        if (x->x_fd < 0)
        {
            post("/dev/js0: %s", strerror(errno));
            pd_error(x, "couldn't open joystick\n");
            return;
        }
        sys_addpollfn(x->x_fd, (t_fdpollfn)joystick_poll, x);
    }
    else
    {
        if (x->x_fd >= 0)
        {
            sys_rmpollfn(x->x_fd);
            close(x->x_fd);
            x->x_fd = -1;
        }
    }
}

void joystick_free(t_joystick *x)
{
    joystick_float(x, 0);
}

t_class *joystick_class;

t_joystick *joystick_new(void)
{
    t_joystick *x = (t_joystick *)pd_new(joystick_class);
    outlet_new(&x->x_ob, &s_list);
    x->x_fd = -1;
    return (x);
}

void joystick_setup(void)
{
    post("joystick v0.1");
    joystick_class = class_new(gensym("joystick"), (t_newmethod)joystick_new,
        (t_method)joystick_free, sizeof(t_joystick), 0, 0);
    class_addfloat(joystick_class, joystick_float);
}

and compile it with this makefile:


NAME=joystick
CSYM=joystick

current: pd_linux

pd_linux: $(NAME).pd_linux

.SUFFIXES: .pd_linux

LINUXCFLAGS = -DPD -O2 -funroll-loops -fomit-frame-pointer \
    -Wall -W -Wshadow -Wstrict-prototypes -Werror \
    -Wno-unused -Wno-parentheses -Wno-switch

LINUXINCLUDE =  -I/home/msp/pd/src -I/usr/include

.c.pd_linux:
        cc $(LINUXCFLAGS) $(LINUXINCLUDE) -o $*.o -c $*.c
        ld -export_dynamic  -shared -o $*.pd_linux $*.o -lc -lm
        strip --strip-unneeded $*.pd_linux
        rm $*.o

and test it with this patch:


#N canvas 137 82 455 306 10;
#X obj 36 64 joystick;
#X msg 36 40 1;
#X msg 67 39 0;
#X obj 36 88 print;
#X obj 18 8 joystick;
#X text 97 37 1 opens the port \, and 0 closes it.;
#X text 116 65 output: lists of 4 elements:;
#X text 179 90 with 128 added to report initial condition;
#X text 180 102 when opened.;
#X text 143 115 index: which switch or joystick motion parameter;
#X text 144 130 value: new value of the switch or potentiometer;
#X text 144 77 type: 1 for switch \, 2 for potentiometer \,;
#X text 143 145 time: time stamp from driver \, not necessarily in;
#X text 180 156 sync with Pd time;
#X text 33 156 NOTES.;
#X text 78 8 - joystick port input (linux only);
#X text 30 175 You have to figure out how to get the joystick driver loaded. Aud
io driver packages sometimes document what you need to do. Be sure to check the 
permissions and ownership of /dev/js0.;
#X text 28 222 You can use the joystick port for inputs other than a standard jo
ystick. See https://www.hut.fi/Misc/Electronics/docs/joystick/pc_joystick.html fo
r helpful information.;
#X text 27 267 Apparently any number of devices can have the joystick port open 
at once.;
#X connect 0 0 3 0;
#X connect 1 0 0 0;
#X connect 2 0 0 0;

wasn't that easy? Didn't hurt a bit.