deprecate SCViewHolder-layRight
[supercollider.git] / SCClassLibrary / Common / Control / ResponseDefs.sc
blob1bb6040162c2b5dcabc63fa83e3acc0dce84486c
1 AbstractResponderFunc {
2         classvar allFuncProxies;
3         var <func, <srcID, <enabled = false, <dispatcher, <permanent = false;
4         
5         *initClass { Class.initClassTree(AbstractDispatcher); allFuncProxies = IdentitySet.new; }
6         
7         enable { 
8                 if(enabled.not, {
9                         if(permanent.not, { CmdPeriod.add(this) });
10                         dispatcher.add(this);
11                         enabled = true;
12                         allFuncProxies.add(this);
13                 });
14         } 
15         
16         disable { 
17                 if(permanent.not, { CmdPeriod.remove(this) });
18                 dispatcher.remove(this);
19                 enabled = false;
20         }
21         
22         prFunc_ {|newFunc|  
23                 func = newFunc; 
24                 this.changed(\function);
25         }
26         
27         add {|newFunc| func = func.addFunc(newFunc); this.changed(\function); }
28         
29         remove {|removeFunc| func = func.removeFunc(removeFunc); this.changed(\function); }
30         
31         gui { this.subclassResponsibility(thisMethod) }
32         
33         cmdPeriod { this.free }
34         
35         oneShot {
36                 var oneShotFunc, wrappedFunc;
37                 wrappedFunc = func;
38                 oneShotFunc = { arg ...args; this.free; wrappedFunc.value(*args); };
39                 this.prFunc_(oneShotFunc);
40         }
41         
42         permanent_{|bool| 
43                 permanent = bool;
44                 if(bool && enabled, { CmdPeriod.remove(this) }, {CmdPeriod.add(this) })
45         }
46         
47         fix { this.permanent_(true) }
48         
49         free { allFuncProxies.remove(this); this.disable }
50         
51         clear { this.prFunc_(nil) }
52         
53         *allFuncProxies { 
54                 var result;
55                 result = IdentityDictionary.new;
56                 allFuncProxies.do({|funcProxy|
57                         var key;
58                         key = funcProxy.dispatcher.typeKey;
59                         result[key] = result[key].add(funcProxy)
60                 });
61                 ^result;
62         }
63         
64         *allEnabled { 
65                 var result;
66                 result = IdentityDictionary.new;
67                 allFuncProxies.select(_.enabled).do({|funcProxy|
68                         var key;
69                         key = funcProxy.dispatcher.typeKey;
70                         result[key] = result[key].add(funcProxy)
71                 });
72                 ^result;        
73         }
74         
75         *allDisabled { 
76                 var result;
77                 result = IdentityDictionary.new;
78                 allFuncProxies.reject(_.enabled).do({|funcProxy|
79                         var key;
80                         key = funcProxy.dispatcher.typeKey;
81                         result[key] = result[key].add(funcProxy)
82                 });
83                 ^result;        
84         }
85         
88 // defines the required interface
89 AbstractDispatcher {
90         classvar <all;
91         var registered = false;
92         
93         *new { ^super.new.init; }
94         
95         init { all.add(this);}
96                 
97         *initClass { all = IdentitySet.new }
98         
99         add {|funcProxy| this.subclassResponsibility(thisMethod) } // proxies call this to add themselves to this dispatcher; should register this if needed
100         
101         remove {|funcProxy| this.subclassResponsibility(thisMethod) } // proxies call this to remove themselves from this dispatcher; should unregister if needed
102                 
103         value { this.subclassResponsibility(thisMethod) }
104         
105         valueArray {arg args; ^this.value(*args) } // needed to work in FunctionLists
106         
107         register { this.subclassResponsibility(thisMethod) } // register this dispatcher to listen for its message type
108         
109         unregister { this.subclassResponsibility(thisMethod) } // unregister this dispatcher so it no longer listens
110         
111         free { this.unregister; all.remove(this) } // I'm done
112         
113         typeKey { this.subclassResponsibility(thisMethod) } // a Symbol
114         
115         update { } // code here to update any changed state in this dispatcher's proxies, e.g. a new function; default does nothing
119 // basis for the default dispatchers
120 // uses function wrappers for matching
121 AbstractWrappingDispatcher :  AbstractDispatcher {
122         var active, <wrappedFuncs;
123         
124         init { super.init; active = IdentityDictionary.new; wrappedFuncs = IdentityDictionary.new; }
125         
126         add {|funcProxy| 
127                 var func, keys;
128                 funcProxy.addDependant(this);
129                 func = this.wrapFunc(funcProxy);
130                 wrappedFuncs[funcProxy] = func;
131                 keys = this.getKeysForFuncProxy(funcProxy);
132                 keys.do({|key| active[key] = active[key].addFunc(func) }); // support multiple keys
133                 if(registered.not, {this.register});
134         }
135         
136         remove {|funcProxy|
137                 var func, keys;
138                 funcProxy.removeDependant(this);
139                 keys = this.getKeysForFuncProxy(funcProxy);
140                 func = wrappedFuncs[funcProxy];
141                 keys.do({|key| active[key] = active[key].removeFunc(func) }); // support multiple keys
142                 wrappedFuncs[funcProxy] = nil;
143                 if(active.size == 0, {this.unregister});
144         }
145         
146                 // old Funk vs. new Funk
147         updateFuncForFuncProxy {|funcProxy|
148                 var func, oldFunc, keys;
149                 func = this.wrapFunc(funcProxy);
150                 oldFunc = wrappedFuncs[funcProxy];
151                 wrappedFuncs[funcProxy] = func;
152                 keys = this.getKeysForFuncProxy(funcProxy);
153                 keys.do({|key| active[key] = active[key].replaceFunc(oldFunc, func) }); // support multiple keys
154         }
155                 
156         wrapFunc { this.subclassResponsibility(thisMethod) }
157         
158         getKeysForFuncProxy { |funcProxy| this.subclassResponsibility(thisMethod) }
159         
160         update {|funcProxy, what| if(what == \function, { this.updateFuncForFuncProxy(funcProxy) }) }
161         
162         free { wrappedFuncs.keys.do({|funcProxy| funcProxy.removeDependant(this) }); super.free }
163         
166 // The default dispatchers below store by the 'most significant' message argument for fast lookup
167 // These are for use when more than just the 'most significant' argument needs to be matched
168 AbstractMessageMatcher {
169         var <>func;
170         
171         value { this.subclassResponsibility(thisMethod) }
172         
173         valueArray {arg args; ^this.value(*args) } // needed to work in FunctionLists
177 ///////////////////// OSC
179 OSCMessageDispatcher : AbstractWrappingDispatcher {
180         
181         wrapFunc {|funcProxy|
182                 var func, srcID, recvPort, argTemplate;
183                 func = funcProxy.func;
184                 srcID = funcProxy.srcID;
185                 recvPort = funcProxy.recvPort;
186                 argTemplate = funcProxy.argTemplate;
187                 if(argTemplate.notNil, { func = OSCArgsMatcher(argTemplate, func)});
188                 ^case(
189                         { srcID.notNil && recvPort.notNil }, { OSCFuncBothMessageMatcher(srcID, recvPort, func) },
190                         { srcID.notNil }, { OSCFuncAddrMessageMatcher(srcID, func) },
191                         { recvPort.notNil }, { OSCFuncRecvPortMessageMatcher(recvPort, func) },
192                         { func }
193                 );
194         }
195         
196         getKeysForFuncProxy {|funcProxy| ^[funcProxy.path];}
197         
198         value {|msg, time, addr, recvPort| active[msg[0]].value(msg, time, addr, recvPort);}
199         
200         register { 
201                 thisProcess.addOSCRecvFunc(this); 
202                 registered = true; 
203         }
204         
205         unregister { 
206                 thisProcess.removeOSCRecvFunc(this);
207                 registered = false;
208         }
209         
210         typeKey { ^('OSC unmatched').asSymbol }
211         
214 OSCMessagePatternDispatcher : OSCMessageDispatcher {
215         
216         value {|msg, time, addr, recvPort| 
217                 var pattern;
218                 pattern = msg[0];
219                 active.keysValuesDo({|key, func|
220                         if(key.matchOSCAddressPattern(pattern), {func.value(msg, time, addr, recvPort);});
221                 })
222         }
223         
224         typeKey { ^('OSC matched').asSymbol }
225         
228 OSCFunc : AbstractResponderFunc {
229         classvar <>defaultDispatcher, <>defaultMatchingDispatcher, traceFunc, traceRunning = false;
230         var <path, <recvPort, <argTemplate;
231         
232         *initClass {
233                 defaultDispatcher = OSCMessageDispatcher.new;
234                 defaultMatchingDispatcher = OSCMessagePatternDispatcher.new;
235                 traceFunc = {|msg, time, addr, recvPort|
236                         "OSC Message Received:\n\ttime: %\n\taddress: %\n\trecvPort: %\n\tmsg: %\n\n".postf(time, addr, recvPort, msg);
237                 }
238         }
239         
240         *new { arg func, path, srcID, recvPort, argTemplate, dispatcher;
241                 ^super.new.init(func, path, srcID, recvPort, argTemplate, dispatcher ? defaultDispatcher);
242         }
243         
244         *newMatching { arg func, path, srcID, argTemplate, recvPort;
245                 ^super.new.init(func, path, srcID, recvPort, argTemplate, defaultMatchingDispatcher);
246         }
247         
248         *trace {|bool = true| 
249                 if(bool, {
250                         if(traceRunning.not, {
251                                 thisProcess.addOSCRecvFunc(traceFunc);
252                                 CmdPeriod.add(this);
253                                 traceRunning = true;
254                         });
255                 }, {
256                         thisProcess.removeOSCRecvFunc(traceFunc);
257                         CmdPeriod.remove(this);
258                         traceRunning = false;
259                 });
260         }
261         
262         *cmdPeriod { this.trace(false) }
263         
264         init {|argfunc, argpath, argsrcID, argrecvPort, argtemplate, argdisp|
265                 path = (argpath ? path).asString;
266                 if(path[0] != $/, {path = "/" ++ path}); // demand OSC compliant paths
267                 path = path.asSymbol;
268                 srcID = argsrcID ? srcID;
269                 recvPort = argrecvPort ? recvPort;
270                 argtemplate = argtemplate.collect({|oscArg| 
271                         if(oscArg.isKindOf(String), {oscArg.asSymbol}, {oscArg}); // match Symbols not Strings
272                 });
273                 argTemplate = argtemplate ? argTemplate;
274                 func = argfunc;
275                 dispatcher = argdisp ? dispatcher;
276                 this.enable;
277                 allFuncProxies.add(this);
278         }
279         
280         printOn { arg stream; stream << this.class.name << "(" <<* [path, srcID, argTemplate] << ")" }
281         
284 OSCdef : OSCFunc {
285         classvar <all; 
286         var <key;
287         
288         *initClass {
289                 all = IdentityDictionary.new;
290         }
291         
292         *new { arg key, func, path, srcID, recvPort, argTemplate, dispatcher;
293                 var res = all.at(key);
294                 if(res.isNil) {
295                         ^super.new(func, path, srcID, recvPort, argTemplate, dispatcher).addToAll(key);
296                 } {
297                         if(func.notNil) { 
298                                 if(res.enabled, {
299                                         res.disable;
300                                         res.init(func, path, srcID, recvPort, argTemplate, dispatcher);
301                                 }, { res.init(func, path, srcID, recvPort, argTemplate, dispatcher).disable; });
302                         }
303                 }
304                 ^res
305         }
306         
307         *newMatching { arg key, func, path, srcID, argTemplate, recvPort;
308                 ^this.new(key, func, path, srcID, recvPort, argTemplate, defaultMatchingDispatcher);
309         }
310         
311         addToAll {|argkey| key = argkey; all.put(key, this) }
312         
313         free { all[key] = nil; super.free; }
314         
315         printOn { arg stream; stream << this.class.name << "(" <<* [key, path, srcID, argTemplate] << ")" }
316         
320 // if you need to test for address func gets wrapped in this
321 OSCFuncAddrMessageMatcher : AbstractMessageMatcher {
322         var addr;
323         
324         *new {|addr, func| ^super.new.init(addr, func);}
325         
326         init {|argaddr, argfunc| addr = argaddr; func = argfunc; }
327         
328         value {|msg, time, testAddr, recvPort| 
329                 if(testAddr.addr == addr.addr and: {addr.port.matchItem(testAddr.port)}, {
330                         func.value(msg, time, testAddr, recvPort)
331                 })
332         }
335 // if you need to test for recvPort func gets wrapped in this
336 OSCFuncRecvPortMessageMatcher : AbstractMessageMatcher {
337         var recvPort;
338         
339         *new {|recvPort, func| ^super.new.init(recvPort, func);}
340         
341         init {|argrecvPort, argfunc| recvPort = argrecvPort; func = argfunc; }
342         
343         value {|msg, time, addr, testRecvPort| 
344                 if(testRecvPort == recvPort, {
345                         func.value(msg, time, addr, testRecvPort)
346                 })
347         }
350 OSCFuncBothMessageMatcher : AbstractMessageMatcher {
351         var addr, recvPort;
352         
353         *new {|addr, recvPort, func| ^super.new.init(addr, recvPort, func);}
354         
355         init {|argaddr, argrecvPort, argfunc| addr = argaddr; recvPort = argrecvPort; func = argfunc; }
356         
357         value {|msg, time, testAddr, testRecvPort| 
358                 if(testAddr.addr == addr.addr and: {addr.port.matchItem(testAddr.port)} and: {testRecvPort == recvPort}, {
359                         func.value(msg, time, testAddr, testRecvPort)
360                 })
361         }
364 OSCArgsMatcher : AbstractMessageMatcher {
365         var argTemplate;
367         *new{|argTemplate, func| ^super.new.init(argTemplate, func) }
368         
369         init {|argArgTemplate, argFunc| argTemplate = argArgTemplate.asArray; func = argFunc; }
370         
371         value {|testMsg, time, addr, recvPort|Ê
372                 testMsg[1..].do({|item, i|
373                         if(argTemplate[i].matchItem(item).not, {^this});
374                 });
375                 func.value(testMsg, time, addr, recvPort)
376         }
379 ///////////////////// MIDI
381 // for \noteOn, \noteOff, \control, \polytouch
382 MIDIMessageDispatcher : AbstractWrappingDispatcher {
383         var >messageType;
384         
385         *new {|messageType| ^super.new.messageType_(messageType) }
386         
387         getKeysForFuncProxy {|funcProxy| ^(funcProxy.msgNum ? (0..127)).asArray;} // noteNum, etc.
388         
389         value {|src, chan, num, val| active[num].value(val, num, chan, src);}
390         
391         register { 
392                 MIDIIn.perform(messageType.asSetter, MIDIIn.perform(messageType.asGetter).addFunc(this)); 
393                 registered = true; 
394         }
395         
396         unregister { 
397                 MIDIIn.perform(messageType.asSetter, MIDIIn.perform(messageType.asGetter).removeFunc(this));
398                 registered = false;
399         }
400         
401         // wrapper objects based on arg type and testing requirements
402         wrapFunc {|funcProxy|
403                 var func, chan, srcID, argTemplate;
404                 func = funcProxy.func;
405                 chan = funcProxy.chan;
406                 srcID = funcProxy.srcID;
407                 argTemplate = funcProxy.argTemplate;
408                 if(argTemplate.notNil, { func = MIDIValueMatcher(argTemplate, func)});
409                 ^case(
410                         { srcID.notNil && chan.isArray }, {MIDIFuncBothCAMessageMatcher(chan, srcID, func)},
411                         { srcID.notNil && chan.notNil }, {MIDIFuncBothMessageMatcher(chan, srcID, func)},
412                         { srcID.notNil }, {MIDIFuncSrcMessageMatcher(srcID, func)},
413                         { chan.isArray }, {MIDIFuncChanArrayMessageMatcher(chan, func)},
414                         { chan.notNil }, {MIDIFuncChanMessageMatcher(chan, func)},
415                         { func }
416                 );
417         }
418         
419         typeKey { ^('MIDI ' ++ messageType).asSymbol }  
422 // for \touch, \program, \bend
423 MIDIMessageDispatcherNV : MIDIMessageDispatcher {
424         
425         getKeysForFuncProxy {|funcProxy| ^(funcProxy.chan ? (0..15)).asArray;} // chan
426         
427         value {|src, chan, val| active[chan].value(val, chan, src);}
428         
429         // wrapper objects based on arg type and testing requirements
430         wrapFunc {|funcProxy|
431                 var func, chan, srcID, argTemplate;
432                 func = funcProxy.func;
433                 chan = funcProxy.chan;
434                 srcID = funcProxy.srcID;
435                 argTemplate = funcProxy.argTemplate;
436                 if(argTemplate.notNil, { func = MIDIValueMatcher(argTemplate, func)});
437                 ^case(
438                         { srcID.notNil }, {MIDIFuncSrcMessageMatcherNV(srcID, func)},
439                         { func }
440                 );
441         }
445 MIDIFunc : AbstractResponderFunc {
446         classvar <>defaultDispatchers;
447         var <chan, <msgNum, <msgType, <argTemplate;
448         
449         *initClass {
450                 defaultDispatchers = IdentityDictionary.new;
451                 [\noteOn, \noteOff, \control, \polytouch].do({|type|
452                         defaultDispatchers[type] = MIDIMessageDispatcher(type);
453                 });
454                 [\touch, \program, \bend].do({|type|
455                         defaultDispatchers[type] = MIDIMessageDispatcherNV(type);
456                 });
457         }
458         
459         *new { arg func, msgNum, chan, msgType, srcID, argTemplate, dispatcher;
460                 ^super.new.init(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher ? defaultDispatchers[msgType]);
461         }
462         
463         *cc { arg func, ccNum, chan, srcID, argTemplate, dispatcher;
464                 ^this.new(func, ccNum, chan, \control, srcID, argTemplate, dispatcher);
465         }
466         
467         *noteOn { arg func, noteNum, chan, srcID, argTemplate, dispatcher;
468                 ^this.new(func, noteNum, chan, \noteOn, srcID, argTemplate, dispatcher);
469         }
470         
471         *noteOff { arg func, noteNum, chan, srcID, argTemplate, dispatcher;
472                 ^this.new(func, noteNum, chan, \noteOff, srcID, argTemplate, dispatcher);
473         }
474         
475         *polytouch { arg func, noteNum, chan, srcID, argTemplate, dispatcher;
476                 ^this.new(func, noteNum, chan, \polytouch, srcID, argTemplate, dispatcher);
477         }
478         
479         *touch { arg func, chan, srcID, argTemplate, dispatcher;
480                 ^this.new(func, nil, chan, \touch, srcID, argTemplate, dispatcher);
481         }
482         
483         *bend { arg func, chan, srcID, argTemplate, dispatcher;
484                 ^this.new(func, nil, chan, \bend, srcID, argTemplate, dispatcher);
485         }
486         
487         *program { arg func, chan, srcID, argTemplate, dispatcher;
488                 ^this.new(func, nil, chan, \program, srcID, argTemplate, dispatcher);
489         }
490         
491         init {|argfunc, argmsgNum, argchan, argType, argsrcID, argtempl, argdisp|
492                 msgNum = msgNum ? argmsgNum;
493                 chan = chan ? argchan;
494                 srcID = argsrcID ? srcID;
495                 func = argfunc;
496                 msgType = argType ? msgType;
497                 dispatcher = argdisp ? dispatcher;
498                 argTemplate = argtempl ? argTemplate;
499                 this.enable;
500                 allFuncProxies.add(this);
501         }
502         
503         // post pretty
504         printOn { arg stream; stream << this.class.name << "(" <<* [msgType, msgNum, chan, argTemplate] << ")" }
508 MIDIdef : MIDIFunc {
509         classvar <>all; // same as other def classes, do we need a setter really?
510         var <key;
511         
512         *initClass {
513                 all = IdentityDictionary.new;
514         }
515         
516         *new { arg key, func, msgNum, chan, msgType, srcID, argTemplate, dispatcher;
517                 var res = all.at(key);
518                 if(res.isNil) {
519                         ^super.new(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher).addToAll(key);
520                 } {
521                         if(func.notNil) { 
522                                 if(res.enabled, {
523                                         res.disable;
524                                         res.init(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher ? defaultDispatchers[msgType]);
525                                 }, { res.init(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher ? defaultDispatchers[msgType]).disable; });
526                         }
527                 }
528                 ^res
529         }
530         
531         *cc { arg key, func, ccNum, chan, srcID, argTemplate, dispatcher;
532                 ^this.new(key, func, ccNum, chan, \control, srcID, argTemplate, dispatcher);
533         }
534         
535         *noteOn { arg key, func, noteNum, chan, srcID, argTemplate, dispatcher;
536                 ^this.new(key, func, noteNum, chan, \noteOn, srcID, argTemplate, dispatcher);
537         }
538         
539         *noteOff { arg key, func, noteNum, chan, srcID, argTemplate, dispatcher;
540                 ^this.new(key, func, noteNum, chan, \noteOff, srcID, argTemplate, dispatcher);
541         }
542         
543         *polytouch { arg key, func, noteNum, chan, srcID, argTemplate, dispatcher;
544                 ^this.new(key, func, noteNum, chan, \polytouch, srcID, argTemplate, dispatcher);
545         }
546         
547         *touch { arg key, func, chan, srcID, argTemplate, dispatcher;
548                 ^this.new(key, func, nil, chan, \touch, srcID, argTemplate, dispatcher);
549         }
550         
551         *bend { arg key, func, chan, srcID, argTemplate, dispatcher;
552                 ^this.new(key, func, nil, chan, \bend, srcID, argTemplate, dispatcher);
553         }
554         
555         *program { arg key, func, chan, srcID, argTemplate, dispatcher;
556                 ^this.new(key, func, nil, chan, \program, srcID, argTemplate, dispatcher);
557         }
558         
559         addToAll {|argkey| key = argkey; all.put(key, this) }
560         
561         free { all[key] = nil; super.free; }
562         
563         // post pretty
564         printOn { arg stream; stream << this.class.name << "(" <<* [key, msgType, msgNum, chan, argTemplate] << ")" }
565         
569 // if you need to test for srcID func gets wrapped in this
570 MIDIFuncSrcMessageMatcher : AbstractMessageMatcher {
571         var srcID;
572         
573         *new {|srcID, func| ^super.new.init(srcID, func);}
574         
575         init {|argsrcID, argfunc| srcID = argsrcID; func = argfunc; }
576         
577         value {|value, num, chan, testSrc|
578                 if(srcID == testSrc, {func.value(value, num, chan, testSrc)}) 
579         }
582 // if you need to test for srcID func gets wrapped in this
583 MIDIFuncChanMessageMatcher : AbstractMessageMatcher {
584         var chan;
585         
586         *new {|chan, func| ^super.new.init(chan, func);}
587         
588         init {|argchan, argfunc| chan = argchan; func = argfunc; }
589         
590         value {|value, num, testChan, srcID|
591                 if(chan == testChan, {func.value(value, num, testChan, srcID)}) 
592         }
595 // if you need to test for chanArray func gets wrapped in this
596 MIDIFuncChanArrayMessageMatcher : AbstractMessageMatcher {
597         var chanBools;
598         
599         *new {|chanArray, func|
600                 var chanBools;
601                 // lookup bool by index fastest, so construct an Array here
602                 chanBools = Array.fill(16, {|i| chanArray.includes(i) });
603                 ^super.new.init(chanBools, func);
604         }
605         
606         init {|argchanbools, argfunc| chanBools = argchanbools; func = argfunc; }
607         
608         value {|value, num, testChan, srcID| 
609                 // lookup bool by index fastest
610                 if(chanBools[testChan], {func.value(value, num, testChan, srcID)}) 
611         }
614 // version for message types which don't pass a val
615 MIDIFuncSrcMessageMatcherNV : MIDIFuncSrcMessageMatcher {
616         
617         value {|num, chan, testSrc|
618                 if(srcID == testSrc, {func.value(num, chan, testSrc)}) 
619         }
622 // if you need to test for chan and srcID func gets wrapped in this
623 MIDIFuncBothMessageMatcher : AbstractMessageMatcher {
624         var chan, srcID;
625         
626         *new {|chan, srcID, func| ^super.new.init(chan, srcID, func);}
627         
628         init {|argchan, argsrcID, argfunc| chan = argchan; srcID = argsrcID; func = argfunc; }
629         
630         value {|value, num, testChan, testSrc| 
631                 if(srcID == testSrc and: {chan == testChan}, {func.value(value, num, testChan, testSrc)}) 
632         }       
636 // if you need to test for chanArray and srcID func gets wrapped in this
637 MIDIFuncBothCAMessageMatcher : AbstractMessageMatcher {
638         var chanBools, srcID;
639         
640         *new {|chanArray, srcID, func| 
641                 var chanBools;
642                 // lookup bool by index fastest, so construct an Array here
643                 chanBools = Array.fill(16, {|i| chanArray.includes(i) });
644                 ^super.new.init(chanBools, srcID, func);
645         }
646         
647         init {|argbools, argsrcID, argfunc| chanBools = argbools; srcID = argsrcID; func = argfunc; }
648         
649         value {|value, num, testChan, testSrc| 
650                 if(srcID == testSrc and: {chanBools[testChan]}, {func.value(value, num, testChan, testSrc)}) 
651         }       
654 // if you want to test for the actual message value, the func gets wrapped in this
655 MIDIValueMatcher : AbstractMessageMatcher {
656         var argTemplate;
658         *new{|argTemplate, func| ^super.new.init(argTemplate, func) }
659         
660         init {|argArgTemplate, argFunc| argTemplate = argArgTemplate; func = argFunc; }
661         
662         value {|...testMsg|
663                 if(argTemplate.matchItem(testMsg.first), {func.value(*testMsg)});
664         }