Commission Music

Commission Music
Bespoke Noise!!

Monday, 9 October 2006

HID Update

Download a new version of my HID classes.

So I think some of my original features were overzealous, so some of them have been removed. For instance, no more specifying a ControlSpec for an HIDElementExt. If you want to use a ControlSpec, you should attach the HIDElementExt (or HIDElement Group) to a CV by means of the action. Remember that CVs are really useful objects. See their help file for more information. For example of how to attach one:

element.action_({arg vendorID, productID, locID, cookie, value;

 var scaled, mycv;
     
 scaled = element.scale(value);
         
 mycv = cvDictionary.at(element.usage.asSymbol);
 mycv.input_(scaled);
});

Note the name and order of arguments, as this is a change. In this example, cvDictionary is an IdentityDictionary where CVs are stored according to the element.usage as a key. scale is a message you can pass to an element in which a value is scaled according to the min and max to be a number between 0 and 1. You can still set the min and max of an element based on what you've discovered to be the range of your HID, regardless of what lies the device may whisper in your ear about it's ranges.

The HIDDeviceExt.configure message was not really needed, so it's gone.

Here is some code to automatically generate a GUI for every HID that you have attached. Note that you will need the Conductor class for it, which is part of the Wesleyan nightly build. This GUI will give you one line for every reported piece of the device. You can then try moving everything around to see what their actual ranges are (if they are smaller than reported) and the correlation between reported names and the elements.

(

 var elems, names, cv, conductor;

 HIDDeviceServiceExt.buildDeviceList;
 
 HIDDeviceServiceExt.devices.do({ arg dev;
 
  elems = [];
  names = [];

  dev.queueDevice;

  dev.elements.do ({ arg elm;
  
   cv = CV.new.sp(0, elm.min, elm.max, 0, 'linear');
   elems = elems.add(cv);
   names = names.add(elm.usage.asSymbol);
   elm.action_({ arg vendorID, productID, locID, cookie, value;
     
    var scaled, mycv;
     
    scaled = elm.scale(value);
         
    mycv = conductor.at(elm.usage.asSymbol);
    mycv.input_(scaled);
   });

  });
 
  conductor = Conductor.make({ arg cond; 
  
   elems.do({ arg item, index;
   
    cond.put(names[index], item);
   });
   
   cond.argList = cond.argList ++ elems;
   cond.argNames = cond.argNames ++ names;
   cond.valueItems = cond.valueItems ++ names;
   cond.guiItems = cond.valueItems;
  });
  
  conductor.show;
  
 });
 
 HIDDeviceServiceExt.runEventLoop;

)

Then stop it with:

HIDDeviceServiceExt.stopEventLoop;

But what if the returned values are outside of the range? And what are the cookies? You should then generate a little report to correlate everything.

(
 HIDDeviceServiceExt.devices.do({arg dev;
  [dev.manufacturer, dev.product, dev.vendorID, dev.productID, dev.locID].postln;
  dev.elements.do({arg ele;
   [ele.type, ele.usage, ele.cookie, ele.min, ele.max, ele.minFound, ele.maxFound].postln;
  });
 });
)

You can use the minFound and maxFound data to calibrate the device.

This is my current code to configure my (crappy) joystick:

HIDDeviceServiceExt.buildDeviceList;
HIDDeviceServiceExt.queueDeviceByName('USB Joystick STD');

(

 var thisHID, simpleButtons, buttons, stick;
 
 thisHID = HIDDeviceServiceExt.deviceDict.at('USB Joystick STD');
 simpleButtons = HIDElementGroup.new;
 buttons = HIDElementGroup.new;
 stick = HIDElementGroup.new;
  
 thisHID.deviceSpec = IdentityDictionary[
   // buttons
   \trig->7, \left->9, \right->10, \down->8, \hat->11,
   // stick
   \x->12, \y->13,
   \wheel->14,
   \throttle->15
  ]; 
  
  
 simpleButtons.add(thisHID.get(\trig));
 simpleButtons.add(thisHID.get(\left));
 simpleButtons.add(thisHID.get(\right));
 simpleButtons.add(thisHID.get(\down));
  
 buttons.add(simpleButtons);
 buttons.add(thisHID.get(\hat));
  
 stick.add(thisHID.get(\x));
 stick.add(thisHID.get(\y));
  
 // make sure each element is only lsted once in the nodes list, or else it
 // will run it's action once for each time a value arrives for it
  
 thisHID.nodes = [ buttons, stick, thisHID.get(\wheel), thisHID.get(\throttle)];
  
 // When I was testing my joystick, I noticed that it lied a bit about ranges
  
 thisHID.get(\hat).min = -1;  // it gives back -12 for center, but -1 is close enough
  
 // none of the analog inputs ever went below 60
  
  
 thisHID.get(\wheel).min = 60;
 thisHID.get(\throttle).min = 60;
 // can set all stick elements at once
 stick.min = 60;
)

Setting the min and the max for each element is used for the scale message. If you don't want to bother scaling the values to be between 0 and 1 (or some other range) and you set the threshold to 0, then you can skip setting the min and the max.

Here is a silly example of using the x and y axis of my joystick to control the frequency and amplitude of a Pbind in a GUI. Note that it would work almost identically without the GUI.

(

 HIDDeviceServiceExt.runEventLoop;

 Conductor.make({arg cond, freq, amp;
 
  var joystick;
 
  freq.spec_(\freq);
  amp.spec_(\amp);
  
  joystick = HIDDeviceServiceExt.deviceDict.at('USB Joystick STD');
  
  joystick.get(\y).action_({ arg vendorID, productID, locID, cookie, value;
  
   freq.input_(joystick.get(\y).scale(value));
  });
  
  joystick.get(\x).action_({ arg vendorID, productID, locID, cookie, value;
  
   amp.input_(joystick.get(\x).scale(value));
  });
  
  cond.name_("silly demo");
  
  cond.pattern_( Pbind (
  
   \freq, freq,
   \amp, amp
  ));
  
 }).show;
  
)

Don't forget to stop the loop when you're done:

HIDDeviceServiceExt.stopEventLoop;

As you may have guessed from this EventLoop business, HIDs are polled. Their timing, therefore, may not be as fast as you'd like for gaming. They may be more suited to controlling Pbinds like in the silly example, rather than Synth objects. You will need to experiment on your own system to see what will work for you.

The HIDElementExt all have a threshold of change below which they will not call their actions. This defaults to 5%, which is a good value for my very crappy joystick. You can set your own threshold. It's a floating point number representing a percent change, so you can put it between 0 and 1. Again, you may want to experiment with this. Most HIDs have a little bit of noise and flutter where they report tiny actions where none occurred. Also, do not buy a 5€ joystick from a sketchy shop. It's range will suck and it will will flutter like crazy and it will be slow and you will end up buying a more expensive joystick anyway.

joystick.threshold_(0.01); // 1% 

Tags: , ,

No comments: