Joysticks and javascript (fruit on the bottom)


While trolling the wide web for analog stuff, I happened upon the website of Grant Richter (he of Wiard synth fame), and there beheld this interesting analog joystick module, which seemed cool and useful. The arrival of Max 4.5 now includes the ability to develop Max/MSP objects without writing C code, and while I am dumb as a sack of hammers (at least to start with), I was lucky enough to be hanging out recently with R. Luke DuBois, producer, half of the PeRColate brain trust, muso, and (perhaps most peculiarly) friend. As befits the humble and gifted, he sat down with me one day and assisted in bringing my silly idea into the world in a form I never would have imagined: as an 8-channel panner.

Of course, my goals were so much more humble–I just wanted something like the joystick. Since, as Martin Luther points out, “It is easier to tear down than to build,” I decided to make a stripped-down Javascript object that would do what I wanted. I thought I’d share it with you.

Please note that any infelicities in the following Javascript code derive in no way from Mr. DuBois. Honest.

You’ll need to save the following source code with a name like “basic_joystick.js” and then load it using the Max jsui object. There are eight individual outputs, corresponding to the 8 dots around the border of the circle. Positioning the red joystick within the circle will simultaneously output eight control values in the range 0. – 1.0 (perfect for, say, sending parameter messages to things hosted by the Max vst~ object. The rest is up to you.


_begin javascript__

/ joystick.js
/

/ cool joystick with multiple axis outputs
/

/ vastly simplified from gregory and luke’s 6.04 cadillac panner
/
props to Luke, at the very least. May good karma attend him!

/ 8 outlets
inlets = 0;
outlets = 8;

/ global variables
var xpos = 0.;
var ypos = 0.5;

/ initialize the arrays
var amps = new Array(8);
for(i=0;i<8;i++)
{
amps[i] = 0.0;
}

/ set up jsui defaults to 3d
sketch.default3d();

/ initialize graphics
function loadbang()
{
doit(xpos, ypos);
draw();
refresh();
}

/ draw — main graphics function
function draw()
{
with (sketch)
{
/ set how the polygons are rendered
glclearcolor(0.,0.,0.,0.); /
set the clear color
glclear(); / erase the background

glcullface(“back”);

/ draw background
glcolor(0.,0.,0.,0.);
moveto(0.,0., -0.2); / move the drawing point
shapeslice(10);
sphere(0.04);
glcolor(1., 1., 1., 1.);
shapeslice(60);
circle(1.0);

/ dots around the outside (crufty, but hey)
moveto(-1.,0., -0.1); / move the drawing point
glcolor(1.,1.,1.,1.);
shapeslice(10);
sphere(0.04);
moveto(1.,0., -0.1); /
move the drawing point
sphere(0.04);
moveto(0.,1., -0.1); / move the drawing point
sphere(0.04);
moveto(0.,-1., -0.1); /
move the drawing point
sphere(0.04);
moveto(-.707107,.707107, -0.1); / move the drawing point
sphere(0.04);
moveto(.707107,.707107, -0.1); /
move the drawing point
sphere(0.04);
moveto(.707107,-.707107, -0.1); / move the drawing point
sphere(0.04);
moveto(-.707107,-.707107, -0.1); /
move the drawing point
sphere(0.04);

shapeslice(10);
/ Here’s the “tip” of the joystick
glcolor(.75,0.,.0,1.);
glclear(“depth”); /
erase depth buffer
shapeslice(20);
moveto(xpos, ypos, -0.1);
sphere(0.08);

}
}

/ bang — draw and refresh display
function bang()
{
draw();
refresh();
}

/ onresize — deal with a resized jsui box
function onresize(w,h)
{
bang(); / draw and refresh display
}
onresize.local = 1; /
make function private to prevent triggering from Max

function doit(x,y)
{

xsquare = Math.pow(x, 2);
ysquare = Math.pow(y, 2)
dist = Math.sqrt(xsquare+ysquare);
if(dist>1.000)
{
dist = 1.000;
}
theta = Math.atan2(y, x);

mathpi = Math.PI*0.25;

xpos = dist*Math.cos(theta);
ypos = dist*Math.sin(theta);
xpos1 = dist*Math.cos(theta-mathpi);
ypos1 = dist*Math.sin(theta-mathpi);

if (xpos>1.0) xpos=1.0;
if (ypos>1.0) ypos=1.0;
if (xpos1>1.0) xpos1=1.0;
if (ypos1>1.0) ypos1=1.0;

amps[0] = (ypos+1.0)*0.5;
amps[1] = (xpos1+1.0)*0.5;
amps[2] = (xpos+1.0)*0.5;
amps[3] = 1.0-(ypos1+1.0)*0.5;
amps[4] = 1.0-amps[0];
amps[5] = 1.0-amps[1];
amps[6] = 1.0-amps[2];
amps[7] = 1.0-amps[3];
for(i=0;i<8;i++)
{
outlet(i, amps[i]);
}

}
doit.local = 1;

/ onclick — deal with mouse click event
function onclick(x,y)
{
doit(sketch.screentoworld(x,y)[0], sketch.screentoworld(x,y)[1]);

bang(); / draw and refresh display
}
onclick.local = 1; / make function private to prevent triggering from Max

function ondrag(x,y)
{
doit(sketch.screentoworld(x,y)[0], sketch.screentoworld(x,y)[1]);

bang(); / draw and refresh display
}
ondrag.local = 1; / make function private to prevent triggering from Max

/ ondblclick — pass buck to onclick()
function ondblclick(x,y)
{
doit(sketch.screentoworld(x,y)[0], sketch.screentoworld(x,y)[1]);

bang(); / draw and refresh display
}
ondblclick.local = 1; /
make function private to prevent triggering from Max

function fsaa(v)
{
sketch.fsaa = v;
bang();
}