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 { 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 { 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(); }