class library: DUGen - the server now handles audio-rate inputs correctly
[supercollider.git] / SCClassLibrary / Platform / osx / MXHID.sc
blob37de96d134c9dfe9faa2dd578e4aeaf82191626c
1 /* Wrapper for HIDDeviceService for General HID support */
2 MXHID {
3         classvar extraClasses;
4         classvar <usageMap;
5         classvar all;
6         classvar <deviceList;
7         classvar <>debug = false;
8         var <device, <slots;
9         var <>hidDeviceAction;
10         var <>action;
13         *initClass {
14                 Class.initClassTree( Event );
15                 extraClasses = Event.new;
16                 Class.initClassTree( GeneralHID );
17                 GeneralHID.add( this );
18                 this.initUsageMap;
19                 all = IdentityDictionary.new;
20         }
22         *all{
23                 ^all.copy;
24         }
26         *id { ^\osx_hid }
28         *put { arg key, object;
29                 extraClasses.put( key, object );
30         }
32         *doesNotUnderstand { arg selector ... args;
33                 ^extraClasses.perform( selector, *args );
34         }
36         /// ----------------- functions ---------------
38         *buildDeviceList{
39                 HIDDeviceService.buildDeviceList(nil,nil);
40                 //deviceList = HIDDeviceServer.deviceList;
41                 deviceList = HIDDeviceService.devices.collect{ |dev,i|
42                         [ dev, this.getInfo( dev ) ]
43                 }
44                 ^deviceList;
45         }
47         *postDevices {
48                 "HID devices at your disposal:".postln;
49                 deviceList.do{ |dev,i|
50                         "\t%:\t[\"%\"], vendor: %, product: %, locID: [%]\n".postf( i, dev[1].name, dev[1].vendor, dev[1].product, dev[1].physical );
51                 };
52         }
54         *postDevicesAndProperties {
55                 deviceList.do{ |dev,i|
56                         [ i, dev[0], dev[1].asString ].postcs;
57                         dev[0].elements.do({arg ele;
58                                 "\t".post;
59                                 [ele.type, ele.usage, ele.cookie, ele.min, ele.max, ele.ioType, ele.usagePage, ele.usageType].postln;
60                         });
61                 };
62         }
64         *getInfo{ |dev|
65                 var info;
66                 info = GeneralHIDInfo.new(
67                         dev.info.name,
68                         dev.info.bustype,
69                         dev.info.vendor,
70                         dev.info.product,
71                         dev.info.version,
72                         dev.locID
73                 );
74                 ^info;
75         }
77         *startEventLoop{ |rate|
78                 if ( rate.isNil or: (rate.size == 0),
79                         {
80                         HIDDeviceService.runEventLoop;
81                         },{
82                         HIDDeviceService.runEventLoop(rate[0]);
83                         });
84                 this.initAction;
85         }
87         *stopEventLoop{
88                 HIDDeviceService.stopEventLoop;
89         }
91         *eventLoopIsRunning{
92                 ^HIDDeviceService.eventLoopIsRunning;
93         }
95         *open { arg dev;
96                 ^super.new.init( dev );
97         }
99         *initAction{
100                 HIDDeviceService.action_({arg productID, vendorID, locID, cookie, val;
101                 //      [productID, vendorID, locID, cookie, val].postln;
102                 //      if (debug) {("debug"+[productID, vendorID, locID, cookie, val]).postln;};
103                         all.at( locID ).hidDeviceAction.value( cookie, val );
104                         try {
105                                 all.at( locID ).slots.at( cookie ).value_(val);
106                         } {
107                                 // fall thru to next action here...
108                                 if (debug) {("fall thru"+[productID, vendorID, locID, cookie, val]).postln;}
109                         }
110                 });
111         }
113         init{ |dev|
114                 hidDeviceAction = {};
115                 action = {};
117                 dev = dev[0];
118                 if ( dev.isKindOf( HIDDevice ),
119                                 {
120                                         device = dev;
121                                         device.queueDevice;
122                                         slots = IdentityDictionary.new;
123                                         all = all.put( device.locID, this );
124                                         ^GeneralHIDDevice.new( this );
125                                 },{
126                                         "not a valid device".warn;
127                                 });
128         }
130         getInfo{
131                 var info;
132                 info = GeneralHIDInfo.new(
133                         device.info.name,
134                         device.info.bustype,
135                         device.info.vendor,
136                         device.info.product,
137                         device.info.version,
138                         device.locID
139                 );
140                 ^info;
141         }
143         getSlots{
144                 var mySlots = IdentityDictionary.new;
145                 var devElements = device.elements;
146                 "From version 3.5, SuperCollider GeneralHID is using a different slot numbering scheme on OSX. Unfortunately, this breaks backwards compatibility. The advantage is that all elements will always be available.".inform;
147                 // new version, using usage page and usage type
148                 devElements.do{ |ele,i|
149                         var newSlot, key;
150                         if ( mySlots[ ele.usagePage ].isNil) {
151                                 mySlots[ ele.usagePage ] = IdentityDictionary.new;
152                         };
153                         newSlot =  MXHIDSlot.new( this, ele );
154                         slots.put( ele.cookie, newSlot);
155                                 //slots.postln;
156                         mySlots[ele.usagePage][ele.usageType] =  GeneralHIDSlot.new( ele.usagePage, ele.usageType, this, newSlot );
157                 };
158                 ^mySlots;
159                 
161                 /*
162                 // old version:
163                 // using the typemap:
164                 GeneralHIDSlot.typeMap.keysValuesDo{ |key|
165                         mySlots.put( key, IdentityDictionary.new );
166                         };
167                 devElements.do{ |ele,i|
168                         var newSlot, key;
169                         if ( this.class.usageMap[ele.usagePage].notNil,{
170                                 key = this.class.usageMap[ele.usagePage].matchAt(ele.usageType);
171                                 });
172                         //("key"+key+ele.usagePage+ele.usageType).postln;
173                         if ( key.notNil, {
174                                 newSlot =  MXHIDSlot.new( device, key, ele.usageType, ele.cookie );
175                                 slots.put( ele.cookie, newSlot);
176                                 //slots.postln;
177                                 mySlots[key][ele.usageType] =  GeneralHIDSlot.new( key, ele.usageType, device, newSlot );
178                         });
179                 };
180                 mySlots = mySlots.select(_.notEmpty);   // remove empty dicts
181                 ^mySlots;
182                 */
183         }
185         close{
186                 if ( device.notNil,
187                         {
188                                 device.dequeueDevice;
189                         });
190         }
192         isOpen{
193                 if ( device.notNil,
194                         {
195                                 ^device.isQueued;
196                         },{
197                                 ^false;
198                         }
199                 );
200         }
202         info{
203                 if ( device.notNil,
204                         {
205                                 ^device.info;
206                         },{
207                                 ^nil
208                         });
209         }
211         grab{ // not implemented on osx
212         }
214         ungrab{ // not implemented on osx
215         }
217         /*
218         *initUsageMap{
220         // the usageMap maps the usagePage and usageType to the different slot types we are using
221         // this map is not yet complete, due to time limitations. Please write to the sc-user list
222         // if you use a device not on one of the defined pages, or when stuff is missing
223                 usageMap = IdentityDictionary.new;
224                 usageMap.put( 0x01, Dictionary.new ); // Generic Desktop
225                 usageMap.at( 0x01 ).put( { |v| ( v >= 0x30 and: ( v <= 0x39 ) ); }, 0x0003 );
226                 usageMap.at( 0x01 ).put( 0x3B, 0x0002 );
227                 usageMap.at( 0x01 ).put( { |v| ( v == 0x3D or: ( v == 0x3E ) ); }, 0x0001 );
228                 usageMap.at( 0x01 ).put( { |v| ( v >= 0x40 and: ( v <= 0x46 ) ); }, 0x0003 ); // maybe 0x0002 ??
229                 usageMap.at( 0x01 ).put( 0x48, 0x0002 );
230                 usageMap.at( 0x01 ).put( { |v| ( v >= 0x81 and: ( v <= 0x93 ) ); }, 0x0001 );
231                 usageMap.at( 0x01 ).put( { |v| ( v >= 0xA0 and: ( v <= 0xA8 ) ); }, 0x0001 );
232                 usageMap.at( 0x01 ).put( { |v| ( v >= 0xB0 and: ( v <= 0xB7 ) ); }, 0x0001 );
234                 usageMap.put( 0x02, Dictionary.new ); // Simulations Controls
235                 // feel free to fill in
236                 usageMap.put( 0x03, Dictionary.new ); // VR Controls
237                 // feel free to fill in
238                 usageMap.put( 0x04, Dictionary.new ); // Sport Controls
239                 // feel free to fill in
241                 usageMap.put( 0x05, Dictionary.new ); // Game Controls
242                 usageMap.at( 0x05 ).put( { |v| ( v >= 0x20 and: ( v <= 0x29 ) ); }, 0x0003 );
243                 usageMap.at( 0x05 ).put( { |v| ( v >= 0x2A and: ( v <= 0x31 ) ); }, 0x0001 );
244                 usageMap.at( 0x05 ).put( { |v| ( v >= 0x33 and: ( v <= 0x35 ) ); }, 0x0001 ); // ?? could be wrong, as they are select type
246                 usageMap.put( 0x06, Dictionary.new ); // Generic Device Controls
247                 // feel free to fill in
249                 usageMap.put( 0x07, Dictionary.new ); // Keyboard / Keypad
250                 usageMap.at( 0x07 ).put( { |v| ( v >= 0x01 and: ( v <= 0xA4 ) ); }, 0x0001 );
251                 usageMap.at( 0x07 ).put( { |v| ( v >= 0xB0 and: ( v <= 0xDD ) ); }, 0x0001 );
252                 usageMap.at( 0x07 ).put( { |v| ( v >= 0xE0 and: ( v <= 0xE7 ) ); }, 0x0001 );
254                 usageMap.put( 0x08, Dictionary.new ); // LED
255                 usageMap.at( 0x08 ).put( { |v| ( v >= 0x01 and: ( v <= 0x39 ) ); }, 0x0011 );
256                 usageMap.at( 0x08 ).put( { |v| ( v >= 0x4B and: ( v <= 0x4D ) ); }, 0x0011 );
258                 usageMap.put( 0x09, Dictionary.new ); // Button
259                 usageMap.at( 0x09 ).put( { |v| ( v >= 0x01 and: ( v <= 0xFFFF ) ); }, 0x0001 );
261                 usageMap.put( 0x0A, Dictionary.new ); // Ordinal Page
262                 // feel free to fill in
264                 usageMap.put( 0x0B, Dictionary.new ); // Telephony Page
265                 // feel free to fill in
267                 usageMap.put( 0x0C, Dictionary.new ); // Consumer
268                 // feel free to fill in
270                 usageMap.put( 0x0D, Dictionary.new ); // Digitizer controls
271                 usageMap.at( 0x0D ).put( { |v| ( v >= 0x30 and: ( v <= 0x31 ) ); }, 0x0003 );
272                 usageMap.at( 0x0D ).put( { |v| ( v >= 0x32 and: ( v <= 0x36 ) ); }, 0x0001 );
273                 usageMap.at( 0x0D ).put( 0x38, 0x0003 );
274                 usageMap.at( 0x0D ).put( 0x3B, 0x0003 );
275                 usageMap.at( 0x0D ).put( 0x3C, 0x0001 );
276                 usageMap.at( 0x0D ).put( { |v| ( v >= 0x3D and: ( v <= 0x41 ) ); }, 0x0003 );
277                 usageMap.at( 0x0D ).put( { |v| ( v >= 0x42 and: ( v <= 0x46 ) ); }, 0x0001 );
279                 usageMap.put( 0x0F, Dictionary.new ); // Physical Interface Device (Force Feedback)
280                 // feel free to fill in
282                 usageMap.put( 0x10, Dictionary.new );   // Unicode
283                 // feel free to fill in
285                 usageMap.put( 0x14, Dictionary.new ); // Alphanumeric
286                 // feel free to fill in
288                 usageMap.put( 0x40, Dictionary.new ); // Medical instrument
289                 // feel free to fill in
291         }
292         */
296 MXHIDSlot {
297         var <device, <cookie, <value=0, <rawValue=0,  <>action;
298         var <type, <code;
299         var <element;
300         var <spec;
302         /*
303         classvar slotTypeMap;
305         *initClass {
306                 slotTypeMap = IdentityDictionary.new.addAll([
307                         0x0001 -> MXHIDKeySlot,
308                         0x0002 -> MXHIDRelSlot,
309                         0x0003 -> MXHIDAbsSlot,
310                         0x0011 -> MXHIDLedSlot
311                 ]);
312         }
313         *new { | device, evtType, evtCode, evtCookie |
314                 ^(slotTypeMap[evtType] ? this).newCopyArgs(device, evtType, evtCode, evtCookie).initSpec
315         }
316         */
318         *new{ |device, element|
319                 ^super.new.init( device, element )
320         }
322         init{ |dev,el|
323                 device = dev;
324                 element = el;
325                 this.initSpec;
326                 cookie = element.cookie;
327                 type = element.usagePage;
328                 code = element.usageType;
329         }
331         initSpec {
332                 spec = ControlSpec(element.min, element.max, \lin, 1, 0);
333         }
335         value_ { | rawval |
336                 rawValue = rawval;
337                 value = spec.unmap( value );
338                 action.value(this);
339                 device.action.value( type, code, rawValue, value );
340         }
341         next {
342                 ^this.value
343         }
347 MXHIDKeySlot : MXHIDSlot {
348         initSpec {
349                 super.initSpec;
350                 //FIXME:
351                 value = device.valueByCookie(cookie);
352         }
355 MXHIDRelSlot : MXHIDSlot {
356         var delta, <>deltaAction;
358         initSpec { }
359         value { ^value }
360         value_ { | dta |
361                 delta = dta;
362                 value = value + delta;
363                 action.value(this);
364                 deltaAction.value(this);
365                 device.action.value( type, code, dta, value );
366         }
368         delta { ^delta }
371 MXHIDLedSlot : MXHIDSlot {
373         initSpec { }
374         value { ^value }
375         value_ { | v |
376                 value = v;
377                 // FIXME: device.setLEDState( code, value );
378                 device.setValueByCookie(cookie);
379                 action.value(this);
380                 device.action.value( type, code, value, value );
381         }
384 MXHIDAbsSlot : MXHIDSlot {
385         var <info;
387         initSpec {
388                 info = device.elements.detect( { |ele| ele.cookie == cookie } );
389                 spec = ControlSpec(info.min, info.max, \lin, 1);
390                 spec.default = spec.map(0.5).asInteger;
391                 //value = info.value;
392         }
395 MXHIDAbsInfo {
396         var <value = 0, <min = 0, <max = 0, <fuzz = 0, <flat = 0;
398         printOn { | stream |
399                 stream
400                 << this.class.name << $(
401                 << "value: " << value << ", "
402                 << "min: " << min << ", "
403                 << "max: " << max << ", "
404                 << "fuzz: " << fuzz << ", "
405                 << "flat: " << flat << $)
406         }